La lista de comprobaciones básica para programar de forma segura y evitar la mayoría de vulnerabilidades del TOP10 de OWASP
Recientemente veíamos una entrada en Digital Munition en el que se muestran diferentes malas prácticas en programación vistas en aplicaciones Java EE. Estas malas prácticas se derivan en vulnerabilidades comunes, muchas de ellas en el top 10 de OWASP. La siguiente tabla sintetiza las causas principales de estas vulnerabilidades con las técnicas de mitigación correspondientes, un imprescindible para el desarrollo seguro:
1. Autenticación
Prácticas inseguras | Prácticas seguras |
Concatenación de peticiones SQL para validación de login. La concatenación de cadenas con los datos proporcionados por el usuario es siempre propensa a permitir ataques de inyección de SQL. String username=request.getParameter(“username”); String password=request.getParameter(“password”); String query = “SELECT * FROM users WHERE username = “+username+” AND password=”+password; Statement st = con. createStatement(); Results res = st.executeQuery(query); | Uso de peticiones parametrizadas o precompiladas. Para evitar inyecciones SQL se recomienda el uso de sentencias preparadas y consultas parametrizadas. String username=request.getParameter(“username”); String password=request.getParameter(“password”); String query = “SELECT * FROM users WHERE username =? ANDpassword=?“; PreparedStatement ps = con.prepareStatement(query); ps.setString(1, username); ps.setString(2, password); Results res = ps.executeQuery(); |
Falta de autenticación en el acceso a páginas/acciones internas. Muchas veces, las aplicaciones no implementan autenticación para las páginas/acciones internas, lo que da como resultado el acceso a páginas/acciones internas sin iniciar sesión/una sesión válida. | Implementar código de autenticación en todas las páginas internas haciendo uso de variables de sesión // Después de iniciar sesión con éxito. Establecer la identidad del usuario en una variable de sesión. session.setAttribute(“username”, username); // En cada solicitud, verificar si la identidad del usuario está presente en la sesión. String username = (String)session.getAttribute(“username”); if(username == NULL)){ response.sendRedirect(“ErrorPage.java”); return; } |
No restricción de los intentos fallidos de inicio de sesión. Si la aplicación no restringe los intentos de inicio de sesión inválidos, permitirá que el usuario pueda hacer un ataque de fuerza bruta y comprometa otras cuentas de usuario. | Realizar un seguimiento y restringir los intentos fallidos de inicio Mantener una lista de intentos fallidos de inicio de sesión por parte de un usuario. Establecer un límite de umbral de intentos no válidos como por ej. a 5. Bloquear temporalmente al usuario al superar el límite de umbral. |
2. Autorización
Prácticas inseguras | Prácticas seguras |
Falta de restricción de acceso al nivel de datos. Muchas veces, las aplicaciones exponen claves primarias o identificadores de registros de datos como campos ocultos a los usuarios. Y en las peticiones, las usan para identificar y procesar los registros solicitados. Sin embargo, si se confía en estas entradas sin ninguna validación, se podrían manipular para acceder a registros de datos no autorizados del sistema. String query = “SELECT * FROM accts WHERE account = ?“; PreparedStatement pstmt = connection.prepareStatement(query , … ); pstmt.setString( 1, request.getParameter(“acct”)); ResultSet results = pstmt.executeQuery( ); Aquí, la lógica anterior obtendrá cualquier registro que coincida con el valor del parámetro de solicitud: "acct". | Implementar restricción de acceso a nivel de datos. No utilizar entradas no confiables como identificadores para los registros de datos. Aplicar comprobaciones de acceso a nivel de datos para garantizar que el usuario tenga acceso al registro solicitado. Esto se hace usando restricciones de consulta como se muestra a continuación. Hay que tener en cuenta que aquí la identificación del usuario se obtiene inicialmente de la sesión iniciada por el usuario. //Obtener la identidad del usuario en la sesión String uname = (String) session.getAttribute(“username”); //Obtener el registro de datos junto con la restricción (validación de registros) String query = “SELECT * FROM accts WHERE account = ? and username = ?“; PreparedStatement pstmt = connection.prepareStatement(query , … ); pstmt.setString( 1, request.getParameter(“acct”)); pstmt.setString( 2, uname); ResultSet results = pstmt.executeQuery( ); |
Ninguna restricción de acceso basada en roles En general, las aplicaciones ocultan las características de los usuarios, dependiendo de sus funciones. Sin embargo, si la verificación de acceso basada en roles no se implementa en las páginas internas, un usuario con pocos privilegios podrá acceder a todas las funciones indicadas para otros usuarios de privilegios elevados. | Implementar restricciones de acceso basadas en roles. Aplicar controles de acceso basados en roles en todas las características de la aplicación. Esta verificación es para garantizar que el usuario tenga privilegios adecuados para acceder a las funciones solicitadas. Esto se puede hacer al obtener el rol de usuario conectado de la base de datos y verificarlo con los roles necesarios para acceder a la función solicitada. //Obtener la identidad de usuario de la sesión String uname = (String) session.getAttribute(“username”); //Obtener el rol del usuario de la base de datosString query = “SELECT role FROM users WHERE username = ?”; … ResultSet results = pstmt.executeQuery( ); if(results. next()){ String urole = results.getString(“role”); } //Validating whether the user is allowed to access the feature if (!urole.equals(“admin”)) { response.sendRedirect(“Error.jsp”); return; } |
3. Inyección SQL
Prácticas inseguras | Prácticas seguras |
Uso de entradas no confiables (como los valores obtenidos de la solicitud, base de datos, sesión, etc.) sin ninguna validación previa, para formar consultas SQL concatenadas. Si las entradas que no son de confianza se utilizan para formar consultas SQL concatenadas, se produce una vulnerabilidad de inyección de SQL. // Entrada no confiable String customerID = request.getParameter(“customer”); //Petición SQL concatenada String query = “SELECT balance FROM customer_data where customer_id = “ + customerID; Statement statement = connection.createStatement(); ResultSet results = statement.executeQuery(query); | Usar SIEMPRE peticiones SQL precompiladas o parametrizadas. //Entrada no confiable. String customerID = request.getParameter(“customerId”); //Petición SQL parametrizada String query = “SELECT balance FROM customer_data wherecustomer_id = ?“; PreparedStatement ps = conn.prepareStatement(query); ps.setString(1, customerID); ResultSet results = ps.executeQuery(); |
4. Validación de entradas
Prácticas inseguras | Prácticas seguras |
El uso de entradas no confiables (como los valores obtenidos de la solicitud, base de datos, sesión, etc.) sin ninguna validación previa, para formar comandos del sistema operativo, rutas de archivos, URL o almacenados en la base de datos. //Entrada no confiable String name = request.getParameter(“req_name”); //Salvado en la base de datos sin validación previa String query = “INSERT into user_details VALUES (?); PreparedStatement ps = con.prepareStatement(query); ps.setString(1, name); | Validar todas las entradas no confiables, principalmente los parámetros de solicitud, antes de procesarlos. A) Validación de lista blanca: se refiere a validar las entradas del usuario en función de su naturaleza (como aceptar valores alfanuméricos o solo valores numéricos, etc.) Estas validaciones también incluyen restricciones de longitud y tipo de datos. B) Validación de la lista negra: se refiere a la comprobación de la presencia de caracteres especiales o cierto conjunto de caracteres en la lista posterior en los valores de entrada. //Entrada no confiable String name = request.getParameter(“req_name”); //Validar el valor introducido antes de guardarlo en la base de datos. Pattern p = Pattern.compile(“[^A-Za-z]”); Matcher m = p.matcher(name); boolean b = m.find(); if (b != true) { String query = “INSERT into users VALUES (?); PreparedStatement ps = con.prepareStatement(query); ps.setString(1, name); } |
5. Ataques de redirección
Prácticas inseguras | Prácticas seguras |
Uso de entradas no confiables (como los valores obtenidos de la solicitud, base de datos, sesión, etc.) sin ninguna validación previa, para formar el valor de la URL de redirección. //Entrada no confiable String url = request.getParameter(“redirect_to”); //Usado para redireccionar al usuario a la URL facilitada. response.redirect(url); | Usar URLs relativas para redirección. En caso de que se requiera redireccionamiento a sitios externos, limitarlo a un conjunto de dominios específicos. A) Mantener una lista de dominios de confianza. <%! public static String m_TargetDomains[] = { “test.com”, “example.com”, “google.com” }; %> B) Validar la URL de destino presente en la solicitud comparándola con la lista predefinida de dominios de confianza. En caso de no coincidir, redirigir al usuario a una página de error. <% String url = request.getParameter(“redirect_to”); boolean validTarget = false; for(int i = 0; i < m_TargetDomains.length; i++) { if (url.equals(m_TargetDomains[i])) { validTarget = true; break; } } if (validTarget == false ) { response.sendRedirect(“Error.jsp”); } else{ response.sendRedirect(url); } %> |
6. Cross-Site Scripting
Prácticas inseguras | Prácticas seguras |
Mostrar datos en la respuesta sin ninguna codificación ni validación. Las aplicaciones, en general, recuperan las entradas de los usuarios de la solicitud y las procesan sin ninguna validación. Si dichos valores de las entradas se muestran de nuevo al usuario en cualquiera de las funciones posteriores, sin ninguna codificación, provocan una vulnerabilidad de Cross site scripting. //Untrusted input <% String fname = request.getParameter(“fname”); %> //Displaying value of the variable in the form element, without any validation or encoding Fname:<input type=”text” id=”fname” value=”<%= fname%>” /> | Codificar los datos antes de mostrarlos en las respuestas //Entrada no confiable <% String fname = request.getParameter(“fname”); //Codificar el valor a mostrar String encodedName = HTMLencode(fname); //Mostrar el valor codificado de la variable en el element del formulario. Fname: <input type=”text” id=”fname” value=”<%=encodedName %>” /> %> |
7. Cross-Site Request Forgery
Prácticas inseguras | Prácticas seguras |
Ejecutar solicitudes de cambio de estado sin ningún valor de token // Un formulario que acepta la entrada del usuario. No contiene ningún token aleatorio. <html> <body> <form name=”form1″ method=”get” action=”textInput.jsp”> Enter your name:<input type=”text” name=”yourname” /> <input type=”submit” value=”Submit”/> </form> </body></html> // Al recibir la solicitud, los valores del formulario se leen y se guardan en la base de datos. Como no hay un parámetro aleatorio en la solicitud, dichas solicitudes se pueden falsificar fácilmente desde una sesión de usuario válida. <% String name=request.getParameter(“yourname“); …. String query = “INSERT into user_details VALUES (?); PreparedStatement ps = con.prepareStatement(query); ps.setString(1, name); …. %> | Implementar el token Anti-CSRF para solicitudes de cambio de estado. Por lo tanto, dificultando la falsificación de tales solicitudes desde otras sesiones iniciadas. A) Generar un token aleatorio para cada usuario autenticado y guardarlo en la sesión del usuario. session.setAttribute(“csrfToken”, generateCSRFToken()); private String generateCSRFToken() throws NoSuchAlgorithmException { byte[] random = new byte[16]; BASE64Encoder encoder = new BASE64Encoder(); SecureRandom sr = SecureRandom.getInstance(“SHA1PRNG”); sr.nextBytes(random); return encoder.encode(random); } B) Agregar estos tokens de seguridad a las páginas de transacción, como parámetro oculto. String token = session.getAttribute(“csrfToken”); <form name=”form1″ method=”get” action=”textInput.jsp”> Enter your name:<input type=”text” name=”yourname” /> <input id=”csrftoken” type=”hidden” value=”<%=token%>” /> <input type=”submit” value=”Submit”/> </form> C) Al recibir la solicitud, validarla sobre la base de este token. Aceptar la solicitud solo si el token es válido. HttpSession session = request.getSession(); String storedToken = (String)session.getAttribute(“csrfToken“); String token = request.getParameter(“csrftoken“); //Validation check if (storedToken.equals(token)) { // procesar la petición } else { //rechazar la petición } //Invalidar la sesión hasta el logout session.invalidate(); |
8. Administración de sesiones
Prácticas inseguras | Prácticas seguras |
Mantenimiento de los mismos tokens de sesión Pre-login y Post-login Por lo general, las aplicaciones mantienen el mismo ID de sesión para las páginas de inicio de sesión y post inicio de sesión. En todos estos casos, si el atacante obtiene la ID de la sesión de inicio de sesión previa de cualquier usuario, puede ser fácilmente utilizada para secuestrar la sesión de inicio de sesión posterior de ese usuario. | Mantener diferentes tokens de sesión para las sesiones de inicio de sesión y post inicio de sesión. // Después de iniciar sesión con éxito, invalida la sesión actual e inicie una nueva sesión. Esto generará una nueva ID de sesión para el usuario después de iniciar sesión. if (login succeeds) { HttpSession userSession = req.getSession(); userSession.invalidate(); userSession = req.getSession(true); … … } |
Sin invalidación de sesión después de cerrar sesión Si la aplicación no invalida la sesión del usuario actual después de cerrar sesión, será fácilmente secuestrada. Se mostrará el acceso a las páginas internas de la aplicación, ya que la sesión del usuario permanecerá activa en el servidor. | Invalidar la sesión del usuario en el logout //En logout, invalidar la sesión <% HttpSession userSession = request.getSession();%> <% userSession.invalidate(); |
Atributos path y httpOnly de la cookie no configurados en la aplicación No implementar el atributo "httpOnly" hace que el ID de sesión almacenado en las cookies del lado del cliente se pueden leer con Javascripts. No implementar el atributo "path", hace que los navegadores envíen la sesión los valores de ID incluso para otras aplicaciones alojadas en el mismo servidor. | Implementar el uso de path y httpOnly en los atributos de la cookie //Configurar atributos adicionales para la cookie de sesión: response.addHeader(“Set-Cookie”,”JSESSIONID=”+ id + “; Path=/ |
No se establece un timeout de sesión en la aplicación. Si la aplicación no establece el período de inactividad de la sesión, las sesiones permanecerán idle en el servidor. | Configurar un timeout en la sesión //Configurar timeout de sesión en el fichero web.xml |
Via: www.hackplayers.com
La lista de comprobaciones básica para programar de forma segura y evitar la mayoría de vulnerabilidades del TOP10 de OWASP
Reviewed by Zion3R
on
6:41
Rating: