Introducción
La búsqueda de una pequeña tienda de artículos de exterior debe devolver solo productos publicados cuyo nombre coincida exactamente. Un error al construir la consulta permite cambiar esa regla; incluso un apóstrofo normal rompe la búsqueda. Observarás solicitudes reales, repararás una pequeña función Python y comprobarás la seguridad y los resultados útiles.
El entorno incluye Workstation, App Server y Shop Web. Solo necesitas manejo básico del terminal y de las pestañas. Aquí se explican SQL, la sintaxis Python necesaria y los controles del editor. Los datos son sintéticos; el servicio HTTP y el análisis de SQLite son reales.
Realiza una búsqueda normal de productos
Selecciona la pestaña Shop Web en la interfaz del laboratorio para comenzar este paso.
En este paso usarás la tienda y relacionarás un resultado con su fila en la base de datos. Ayudas a una pequeña tienda de artículos de exterior a reparar su búsqueda. Esta debe devolver únicamente productos publicados cuyo nombre coincida exactamente con la entrada. Todos los productos y precios son datos sintéticos de formación.
Una tabla guarda registros en filas y los describe mediante columnas con nombre. Esta es la tabla products preparada; listed vale 1 para un producto público y 0 para un prototipo interno. Los precios son unidades monetarias enteras de formación.
| id | name | price | listed |
|---|---|---|---|
| 1 | Trail Mug | 18 | 1 |
| 2 | Camper's Cup | 22 | 1 |
| 3 | Prototype Mug | 99 | 0 |
| 4 | ORANGE Bowl | 12 | 1 |
Cambia a la pestaña Shop Web. Introduce Trail Mug en Product name y selecciona Search. rows debe contener [1, "Trail Mug", 18], con error en null (sin error). La página envía una solicitud a la aplicación Python, que pide a SQLite los registros coincidentes y devuelve el resultado. La página, el código y la base son partes distintas de esta ruta.

Una consulta es una pregunta a la base de datos. La traza didáctica local muestra el SQL ejecutado por SQLite:
SELECT id, name, price FROM products WHERE listed = 1 AND name = 'Trail Mug' ORDER BY id
SELECT indica las columnas devueltas y FROM la tabla. WHERE restringe las filas. AND exige ambas condiciones: producto publicado y nombre exacto. Las comillas simples delimitan un valor de texto SQL. ORDER BY id proporciona un orden estable. La página no devuelve la columna listed, pero la consulta sigue usándola como filtro.
El terminal Workstation es el cliente para enviar solicitudes. App Server abre el directorio de la aplicación para la reparación posterior. Son sesiones independientes del mismo equipo de formación, no máquinas distintas. La página muestra esta relación y procede de la aplicación en ejecución.
En la pestaña Workstation, ejecuta la misma solicitud:
Cambia a la pestaña Workstation antes de ejecutar el siguiente comando.
python3 probe.py "Trail Mug"
python3 ejecuta el cliente preparado probe.py. Su único argumento es el texto buscado. Las comillas dobles mantienen el nombre con espacios como un argumento; el shell retira esas comillas exteriores. El cliente codifica el valor de forma segura en una solicitud HTTP, el formato usado por la aplicación web. 127.0.0.1 en el cliente significa este equipo de formación.
Lee rows, trace y error en la salida JSON. JSON es la respuesta estructurada: los arrays usan corchetes y null indica ausencia de error. Si la página no conecta, cambia a la pestaña Shop Web y actualiza la página; un error de conexión difiere de una búsqueda correcta sin resultados. Las comprobaciones leen el resultado real de la solicitud, por lo que no necesitas un informe.
Observa cómo la entrada cambia la consulta
Selecciona la pestaña Workstation en la interfaz del laboratorio para comenzar este paso.
En este paso observarás una prueba acotada de inyección SQL contra esta tienda de formación. La inyección SQL ocurre cuando la entrada modifica la estructura de la consulta en vez de seguir siendo texto. Utiliza estas pruebas solo en el laboratorio preparado.
Cambia a la pestaña App Server. Lee la función con cat, que imprime el archivo sin editarlo:
Cambia a la pestaña App Server antes de ejecutar el siguiente comando.
cat query.py
def search(db, text): define la función. db es la conexión abierta y text el valor recibido. Las líneas sangradas forman el cuerpo. sql = asigna una cadena Python a una variable. Las comillas dobles delimitan esa cadena; las simples interiores delimitan texto SQL. + text + concatena, es decir, une directamente el valor del usuario al SQL. db.execute(sql) pide ejecutarlo; fetchall() recoge las filas y return las devuelve.
En la pestaña Workstation, envía exactamente este valor, incluido el espacio después de los dos guiones:
Cambia a la pestaña Workstation antes de ejecutar el siguiente comando.
python3 probe.py "' OR 1=1 -- "
Las comillas dobles exteriores pertenecen al shell. La comilla simple inicial entra en SQLite y cierra el valor vacío de la consulta concatenada. OR permite cualquiera de las condiciones; 1=1 siempre es verdadero. -- inicia un comentario y se ignoran la comilla final y el orden originales. La traza contiene:
SELECT id, name, price FROM products WHERE listed = 1 AND name = '' OR 1=1 -- ' ORDER BY id
Verás las cuatro filas, incluida [3, "Prototype Mug", 99]. Ese prototipo no está publicado. La solicitud cruzó el límite del negocio aunque solo leyó datos. Una respuesta HTTP correcta no demuestra que la búsqueda sea segura.
Prueba ahora un nombre legítimo con apóstrofo:
python3 probe.py "Camper's Cup"
La aplicación vulnerable devuelve rows: [], trace vacío y error con near "s": syntax error. SQLite no pudo ejecutar el SQL mal formado, así que no hay consulta completada que trazar. Es un fallo esperado; el cliente termina normalmente para poder continuar.
Un apóstrofo es un dato válido. Eliminarlo cambiaría el nombre solicitado; bloquear todas las entradas que lo contienen rechazaría clientes. Bloquear palabras como OR también dañaría ORANGE Bowl. La reparación debe conservar el valor y separarlo de la estructura SQL.
Vincula el valor de búsqueda
Selecciona la pestaña App Server en la interfaz del laboratorio para comenzar este paso.
En este paso repararás la función conservando columnas, coincidencia exacta, restricción de publicación y orden. Cambia a la pestaña App Server.
Una consulta parametrizada separa estructura SQL y valores. En la API SQLite de Python, ? marca la posición de un valor. Pásalo por separado a execute; no pongas comillas al marcador ni concatenes el valor al SQL. Por ejemplo, db.execute("SELECT ?", (text,)) devuelve el texto como dato, sin interpretarlo como SQL.
(text,) es una tupla de un elemento, una colección ordenada de parámetros. La coma final la convierte en tupla; (text) solo es el valor entre paréntesis. Los paréntesis exteriores de execute(sql, (text,)) llaman a una función con dos argumentos: la plantilla SQL y la tupla. El controlador vincula el valor al marcador.
Abre el archivo en el editor del terminal:
Cambia a la pestaña App Server antes de ejecutar el siguiente comando.
nano query.py
Usa las flechas para moverte, Backspace/Delete para borrar y escribe para editar. Sustituye las dos líneas sangradas que empiezan por sql = y return para obtener esta función exacta:
def search(db, text):
sql = "SELECT id, name, price FROM products WHERE listed = 1 AND name = ? ORDER BY id"
return db.execute(sql, (text,)).fetchall()
Mantén cuatro espacios antes de cada línea del cuerpo y ninguno antes de def. Python agrupa el cuerpo por sangría. Guarda con Ctrl+O, pulsa Enter para confirmar query.py y sal con Ctrl+X. Puedes conservar el comentario original.
La aplicación lee y compila query.py en cada solicitud. Basta guardar; no hay que reiniciar. Un búfer sin guardar no tiene efecto. Un error de escritura o sangría aparecerá en error en la próxima respuesta. Reabre el archivo, corrige, guarda y repite la solicitud.
En la pestaña Workstation, repite los dos valores:
Cambia a la pestaña Workstation antes de ejecutar el siguiente comando.
python3 probe.py "Camper's Cup"
python3 probe.py "' OR 1=1 -- "
El producto real devuelve ahora [2, "Camper's Cup", 22]; la prueba especial devuelve rows: []. Ambos tienen error: null. No existe un producto con ese nombre especial exacto en esta base.
La traza puede mostrar Camper''s Cup o todo el texto especial entre comillas. SQLite muestra SQL expandido después de vincular, con valores escapados, no la plantilla original. Ver valores en la traza no significa que Python los concatenara: consulta query.py para ver la plantilla y la tupla separada. No sustituyas los parámetros por escape manual.
Conserva la búsqueda del negocio
Selecciona la pestaña Workstation en la interfaz del laboratorio para comenzar este paso.
En este paso comprobarás seguridad y funcionalidad normal juntas. Desactivar la búsqueda o fijar una respuesta vacía podría ocultar el prototipo, pero impediría encontrar productos.
En la pestaña Workstation, envía otra vez el nombre normal:
Cambia a la pestaña Workstation antes de ejecutar el siguiente comando.
python3 probe.py "Trail Mug"
Debes seguir recibiendo [1, "Trail Mug", 18]. Comprueba ahora que una palabra válida con OR no se rechaza:
python3 probe.py "ORANGE Bowl"
Espera [4, "ORANGE Bowl", 12]. Busca el prototipo no publicado por su nombre normal:
python3 probe.py "Prototype Mug"
Espera rows: [] y error: null. La vinculación evita la inyección; la restricción intacta listed = 1 sigue controlando los productos públicos. Eliminarla durante la reparación sería inseguro.
En la pestaña Shop Web, introduce Camper's Cup, selecciona Search y confirma su fila. Repite con ' OR 1=1 -- y comprueba un resultado vacío sin error. El navegador y el cliente ejercitan la misma función.

Ejecuta la comprobación del paso. Hace solicitudes de solo lectura y ejecuta tu función sin modificar con filas sintéticas adicionales en una base desechable en memoria. Comprueba coincidencia exacta, puntuación, reglas de publicación y nombres nuevos sin editar la base activa. Incluso un nombre parecido a SQL debe seguir siendo un dato buscable si existe exactamente y está publicado.
Si falla, vuelve a la pestaña App Server, abre query.py y compara marcador, coma de la tupla, sangría y restricción del negocio con la reparación. Guarda y repite. Conserva la base, el cliente y el servidor; cambiar registros o devolver respuestas fijas no repara la consulta.
Resumen
Seguiste una búsqueda desde la página hasta una consulta SQLite real, observaste cómo la entrada cambiaba su estructura y reparaste la llamada Python con un marcador y una tupla de un valor. Comprobaste nombres normales, apóstrofos, la prueba acotada y la regla de publicación intacta. El desafío de pedidos aplica la misma separación a otro objeto del negocio.


