Cómo evitar que los hackers vulneren su código Java: inyecciones SQL y XSS bajo el microscopio
\n\nImagine: ha escrito la aplicación web perfecta en Java. Interfaz bonita, lógica rápida, arquitectura limpia. La sube al servidor, se prepara un té y... una hora después recibe un mensaje de que la base de datos ha sido eliminada y en la página principal luce un atrevido ASCII-art de alguien. ¿Le suena familiar? Lamentablemente, para muchos desarrolladores, sí.
\n\nLas dos vulnerabilidades más comunes y peligrosas de las aplicaciones web — inyecciones SQL y cross-site scripting (XSS) — cada año quitan el sueño a los administradores de miles de sitios. Y Java, a pesar de su rigor, no es una excepción. Pero hay una buena noticia: protegerse de ellas es posible. E incluso no es muy complicado.
\n\nEn este artículo analizaremos cómo exactamente los atacantes atacan las aplicaciones Java y, lo más importante, cómo establecer un punto de control fiable utilizando herramientas probadas y buenos hábitos de codificación. Abróchense los cinturones: habrá mucho código, trucos y mínimo de relleno.
\n\nInyecciones SQL: cuando el usuario se convierte en programador
\n\n¿Qué es y por qué funciona?
\n\nLa inyección SQL es una técnica de ataque en la que un atacante «inyecta» código SQL malicioso en una consulta a través de la entrada del usuario. Si la aplicación concatena cadenas sin pensar, el atacante puede acceder a datos ajenos, eliminar tablas o incluso tomar el control del servidor de base de datos.
\n\nEjemplo clásico de mal código en Java:
\n\nString userInput = request.getParameter("username");\nString query = "SELECT * FROM users WHERE username = '" + userInput + "'";\nStatement stmt = connection.createStatement();\nResultSet rs = stmt.executeQuery(query);\n\n¿Qué sucede si el usuario ingresa: admin' OR '1'='1? La consulta se convierte en:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'\n\nLa condición '1'='1' siempre es verdadera. Felicidades: el atacante acaba de iniciar sesión como administrador, sin siquiera conocer la contraseña. Y si ingresa '; DROP TABLE users; -- — todo perdido.
Cómo protegerse: PreparedStatement — su escudo
\n\nLa regla número uno en seguridad de aplicaciones Java: nunca concatenar datos de usuario con consultas SQL. Utilice consultas parametrizadas mediante PreparedStatement.
Así es como debería verse el código seguro:
\n\nString userInput = request.getParameter("username");\nString query = "SELECT * FROM users WHERE username = ?";\nPreparedStatement pstmt = connection.prepareStatement(query);\npstmt.setString(1, userInput);\nResultSet rs = pstmt.executeQuery();\n\n¿Qué ocurrió aquí? La base de datos recibe la consulta y los datos por separado. Incluso si userInput contiene código SQL malicioso, se interpretará como una cadena normal, no como un comando. PreparedStatement escapa automáticamente los caracteres especiales. Esta es la primera y más importante línea de defensa.
Medidas adicionales: procedimientos almacenados y validación
\n\nAdemás de PreparedStatement, conviene utilizar:
- \n
- Procedimientos almacenados — llamar a un procedimiento mediante
CallableStatementtambién separa el código de los datos. \n - Listas blancas — si es necesario insertar el nombre de una tabla o columna (lo cual es raro), nunca lo tome de la entrada del usuario. Use un enum o constantes estrictamente definidas. \n
- Validación de entrada — verifique que un email realmente parezca un email y que un número sea un número. Pero recuerde: la validación es una capa adicional, no un sustituto de
PreparedStatement. \n
Ejemplo de trabajo seguro con nombres de tabla dinámicos:
\n\npublic enum TableName {\n USERS, PRODUCTS, ORDERS\n}\n\npublic void queryTable(TableName table) {\n String que