Ir al contenido principal

Lección 8 · Seguridad

Permisos por rol

Estar logueado no es lo mismo que poder hacer todo. En la tienda hay vendedores y administradores: los dos entran, pero el vendedor carga ventas y el administrador, además, cambia precios y ve reportes.

El rol se guarda en la sesión al iniciar sesión, y cada acción del área privada lo comprueba:

if ($_SESSION['rol'] !== 'administrador') {
    http_response_code(403);
    exit('No tenés permiso para esta acción');
}

La comprobación va en el servidor, siempre. Esconder un botón en el HTML no protege nada: cualquiera puede pedir la dirección a mano. El botón oculto es comodidad; el permiso en el servidor es seguridad.

Contraseñas

La contraseña no se guarda: se guarda su hash, que es irreversible. Al crear el usuario se hashea; al iniciar sesión se compara con password_verify():

$hash = password_hash($clave, PASSWORD_DEFAULT);

if (password_verify($clave, $fila['clave'])) {
    // la contraseña es correcta
}

Nunca MD5 ni SHA1: son rápidos y sirven para verificar archivos, no contraseñas. bcrypt (lo que usa PASSWORD_DEFAULT) está hecho para ser lento, y eso frena a quien prueba combinaciones.

Inyección SQL

Si el valor del usuario se pega en el texto del SQL, puede traer SQL adentro. Con ' OR '1'='1 en un campo de login, una consulta mal armada devuelve todos los usuarios y deja entrar a cualquiera.

// VULNERABLE
$sql = "SELECT * FROM usuarios WHERE usuario = '$usuario'";

// SEGURO
$consulta = $pdo->prepare('SELECT * FROM usuarios WHERE usuario = ?');
$consulta->execute([$usuario]);

La consulta preparada separa el código del valor: el valor nunca se interpreta como SQL. Es la única forma correcta de armar consultas con datos de afuera.

XSS

XSS (cross-site scripting) es inyectar HTML o JavaScript en una página que otros van a ver. Si un comentario con <script> se guarda y se muestra tal cual, se ejecuta en el navegador de cada visitante.

La defensa es escapar al mostrar:

echo htmlspecialchars($comentario, ENT_QUOTES, 'UTF-8');

Se escapa al mostrar, no al guardar: guardar entidades ensucia la base y rompe cualquier otro uso del dato. Y se escapa todo lo que venga de afuera, incluido lo que salió de la propia base de datos.

CSRF

CSRF (cross-site request forgery) es que otra página haga que el usuario envíe un formulario sin darse cuenta: la sesión ya está abierta, así que el servidor lo acepta. La defensa es un token que solo conoce el sitio:

$token = bin2hex(random_bytes(32));
$_SESSION['csrf'] = $token;

// en el formulario
echo '<input type="hidden" name="csrf" value="' . $token . '">';

// al recibir
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
    http_response_code(419);
    exit('Token inválido');
}

El token se genera por sesión, viaja en un campo oculto y se compara con hash_equals(), que tarda lo mismo acierte o falle y no filtra información por tiempos.

Resumen

  • Permisos: el rol se comprueba en el servidor, no escondiendo botones.
  • Contraseñas: password_hash y password_verify; nunca MD5 ni SHA1.
  • Inyección SQL: siempre consultas preparadas; el valor viaja aparte.
  • XSS: htmlspecialchars al mostrar todo lo que venga de afuera.
  • CSRF: token por sesión en cada formulario, comparado con hash_equals.

En la última lección se junta todo: la tienda completa, con login, catálogo, ventas y reportes, funcionando en XAMPP.

Autoevaluación de la lección 8

Licencia. El material de esta página es elaboración propia de Prof Mauro Bobyk —a partir de las fuentes que detalla el pie del sitio— y se publica bajo una licencia Creative Commons BY-NC-SA 4.0.