# Guía 04.8 - Solución # 🔑 Solucionario: Evaluación Segundo Parcial - "PetHealth" ## FASE 1: DDL (Estructura) — [6 Puntos] ### Ejercicio 1: Depuración del Esquema (4 Puntos) El estudiante debe identificar y corregir: 1. **Orden de Precedencia:** No se puede crear `Mascota` antes que `Propietario` porque la llave foránea fallará. 2. **Sintaxis de Cita:** Faltaba una coma tras `AUTO_INCREMENT` y la definición de la `FOREIGN KEY` requería paréntesis. **Código Corregido:** ```MYSQL CREATE DATABASE IF NOT EXISTS veterinaria_db; USE veterinaria_db; -- Primero la tabla "madre" CREATE TABLE Propietario ( id_propietario INT AUTO_INCREMENT PRIMARY KEY, nombre VARCHAR(100) NOT NULL, correo VARCHAR(100) ) ENGINE=InnoDB; -- Luego la tabla "hija" CREATE TABLE Mascota ( id_mascota INT AUTO_INCREMENT PRIMARY KEY, nombre VARCHAR(50) NOT NULL, especie VARCHAR(30), edad INT, -- Se asume para el Ejercicio 2 id_propietario INT, CONSTRAINT fk_mascota_propietario FOREIGN KEY (id_propietario) REFERENCES Propietario(id_propietario) ON UPDATE CASCADE ON DELETE RESTRICT ) ENGINE=InnoDB; CREATE TABLE Cita ( id_cita INT PRIMARY KEY AUTO_INCREMENT, -- Coma corregida fecha_cita DATETIME NOT NULL, motivo TEXT, id_mascota INT, FOREIGN KEY (id_mascota) REFERENCES Mascota(id_mascota) -- Paréntesis corregidos ) ENGINE=InnoDB; ``` ### Ejercicio 2: Restricciones de Integridad (2 Puntos) ```SQL -- Restricción UNIQUE para el correo ALTER TABLE Propietario ADD CONSTRAINT unq_correo UNIQUE (correo); -- Restricción CHECK para la edad (MariaDB 10.2+) ALTER TABLE Mascota ADD CONSTRAINT chk_edad CHECK (edad >= 0); ``` ## FASE 2: DML (Datos) — [5 Puntos] ### Ejercicio 3: Diagnóstico de Inserción (3 Puntos) - **Registro A:** **Éxito**. Inserción normal de propietario. - **Registro B:** **Fallo**. Violación de restricción `UNIQUE` en la columna `correo` (ya existe el correo de Ana Rojas). - **Registro C:** **Fallo**. Violación de Integridad Referencial (`FOREIGN KEY`). El `id_propietario` 99 no existe en la tabla `Propietario`. ### Ejercicio 4: Mantenimiento (2 Puntos) ``` SQL UPDATE Cita SET motivo = 'Consulta General de Prevención' WHERE DATE(fecha_cita) = '2025-04-14'; ``` ## FASE 3: SELECT (Consultas) — [7 Puntos] ### Ejercicio 5: JOINs Múltiples (4 Puntos) **Instrucción:** Completa la siguiente consulta para obtener un listado que muestre: **Nombre del Propietario**, **Nombre de la Mascota** y **Fecha de su Cita**. Usa alias para las tablas. ``` SQL SELECT p.nombre AS Dueño, m.nombre AS Paciente, c.fecha_cita FROM Propietario p JOIN Mascota m ON p.id_propietario = m.id_propietario JOIN Cita c ON m.id_mascota = c.id_mascota; ``` ### Ejercicio 6: Agrupación y HAVING (3 Puntos) **Instrucción:** "Queremos saber cuántas mascotas tiene cada propietario, pero solo mostrar a aquellos que tienen más de 2 mascotas". Completa la instrucción con `GROUP BY` y `HAVING`. ```SQL SELECT p.nombre, COUNT(m.id_mascota) AS total_mascotas FROM Propietario p JOIN Mascota m ON p.id_propietario = m.id_propietario GROUP BY p.id_propietario, p.nombre HAVING total_mascotas > 2; ``` --- ## FASE 4: PHP/PDO (Seguridad) — [2 Puntos] ### Ejercicio 7: Depuración de Conexión y Seguridad El código original era vulnerable porque concatenaba variables directamente en el String de SQL, permitiendo ataques donde un usuario podría escribir `; DROP TABLE Mascota;` en la URL. **Codigo inseguro** ``` php // CÓDIGO INSEGURO $id = $_GET['id']; $sql = "SELECT * FROM Mascota WHERE id_mascota = " . $id; $resultado = $pdo->query($sql); ``` **Solución con Sentencias Preparadas:** ``` php // Uso de placeholders (?) o por nombre (:id) $id = $_GET['id']; $sql = "SELECT * FROM Mascota WHERE id_mascota = :id"; $stmt = $pdo->prepare($sql); $stmt->execute(['id' => $id]); $resultado = $stmt->fetch(); ``` Laboratorio de inyeción SQL: Este laboratorio práctico está diseñado para que los estudiantes de la clínica **"PetHealth"** experimenten cómo un parámetro aparentemente inofensivo en la URL puede convertirse en una puerta de entrada para un desastre en la base de datos. # 🧪 Laboratorio de Hacking Ético: El Expediente Expuesto (Caso Veterinaria) En este escenario, simularemos un sistema donde un médico veterinario consulta el historial de una mascota ingresando su ID en el sistema. ## Paso 1: Crear el escenario vulnerable Crea un archivo en tu servidor LAMP llamado `ver_mascota_inseguro.php`. Este código representa una práctica de programación deficiente al confiar en la entrada del usuario sin procesarla. ``` PHP <?php // ver_mascota_inseguro.php require 'config.php'; // El ID se recibe desde la URL (ej. ?id=1) $id = $_GET['id']; // PELIGRO: El dato del usuario se pega directamente en la sentencia SQL $sql = "SELECT nombre, especie, id_propietario FROM Mascota WHERE id_mascota = " . $id; echo "<h3>Consulta generada en el servidor:</h3><code>$sql</code><br><hr>"; try { // Ejecución directa vulnerable $resultado = $pdo->query($sql); $mascota = $resultado->fetch(); if ($mascota) { echo "🐾 Mascota: " . $mascota['nombre'] . "<br>"; echo "🧬 Especie: " . $mascota['especie'] . "<br>"; echo "👤 ID Propietario: " . $mascota['id_propietario'] . "<br>"; } else { echo "No se encontró la mascota."; } } catch (PDOException $e) { echo "Error técnico: " . $e->getMessage(); } ?> ``` --- ## Paso 2: El Ataque de "Bypass" de Lógica Un atacante no solo quiere ver una mascota; quiere extraer información de **todos** los clientes o incluso saltarse restricciones de privacidad. **Instrucción:** Prueba estas dos URLs en tu navegador y observa la diferencia en la "Consulta generada": 1. **Búsqueda Normal:** `http://localhost/ver_mascota_inseguro.php?id=1` 2. **Inyección de Lógica (Ver todo):** `http://localhost/ver_mascota_inseguro.php?id=1 OR 1=1` ### 🧐 ¿Qué está ocurriendo? - Al escribir `OR 1=1`, el atacante añade una condición que siempre es verdadera. - Como la base de datos evalúa la consulta completa, el `WHERE` deja de filtrar por una mascota específica y devuelve el primer registro que encuentre, o incluso podría usarse para contar cuántas filas hay en la tabla. - **Riesgo Mayor:** En sistemas más complejos, un atacante podría usar `UNION SELECT` para extraer nombres y contraseñas de la tabla de usuarios. ## Paso 3: Implementar la Defensa (PDO Prepared Statements) Para corregir esto, debemos separar la **lógica de la consulta** de los **datos del usuario**. Modifica tu archivo (o crea `ver_mascota_seguro.php`) con el siguiente estándar profesional: ``` PHP <?php // ver_mascota_seguro.php require 'config.php'; $id = $_GET['id']; // 1. La plantilla SQL usa un marcador de posición (:id) $sql = "SELECT nombre, especie, id_propietario FROM Mascota WHERE id_mascota = :id"; // 2. Preparamos la sentencia (El servidor ya sabe qué hacer, solo espera el dato) $stmt = $pdo->prepare($sql); // 3. Vinculamos el dato de forma segura y ejecutamos $stmt->execute(['id' => $id]); $mascota = $stmt->fetch(); if ($mascota) { echo "<h3>Expediente Seguro:</h3>"; echo "🐾 Mascota: " . htmlspecialchars($mascota['nombre']) . "<br>"; echo "🧬 Especie: " . htmlspecialchars($mascota['especie']) . "<br>"; } else { echo "ID de mascota no válido."; } ?> ``` ## 🛡️ ¿Por qué esta versión es inmune? Al usar `prepare()` y `execute()`, el flujo cambia radicalmente: - **Tratamiento de Datos:** Si el atacante intenta enviar `1 OR 1=1`, el sistema buscará una mascota cuyo ID sea literalmente la cadena de texto `"1 OR 1=1"`. - **Falla de Búsqueda:** Como no existe ninguna mascota con un ID que contenga palabras como "OR", la consulta simplemente no devolverá nada, protegiendo el resto de la base de datos. - **Integridad:** El motor de la base de datos nunca llegará a ejecutar el código `OR 1=1` como una instrucción lógica, sino que lo verá como un simple valor de búsqueda fallido. ### Resumen de Aprendizaje para el Parcial: 1. **Vulnerabilidad:** Concatenar variables (`. $id`) en SQL es una invitación al desastre. 2. **Riesgo:** Pérdida de datos, acceso no autorizado y ejecución de comandos maliciosos. 3. **Defensa:** Las sentencias preparadas de PDO son la herramienta estándar en aplicaciones modernas para garantizar la seguridad del backend. cd /var/www/html (ir a carpeta de servidor) mkdir veterinaria (creamos carpeta del nuevo proyecto) cd veterinaria (acceder a carpeta nueva ) cp /var/www/html/zapateria/config.php . (copiamos el archivo config de zapateria al proyecto veterinaria) ls -l (muestra alchivos existentes en la carpeta)