A05 Injection
Cuando los datos y las instrucciones viajan en la misma cadena, quien controla los datos controla la instrucción. La cura es mantenerlos separados, en cualquier lenguaje.
Qué es
La injection ocurre cuando una aplicación arma un comando o consulta concatenando entrada no confiable, y un intérprete (un motor SQL, un shell, un servidor LDAP, un template engine, un navegador) no puede distinguir dónde terminan los datos y dónde empieza la instrucción.
El A05 del :2025 cubre la injection de SQL, NoSQL, comandos del SO, LDAP y plantillas, y el (XSS), que es injection en el intérprete de HTML y JavaScript del navegador.
Cómo funciona el ataque
Una consulta de login se arma como "SELECT * FROM users WHERE name = '" + name + "' AND pass = '" + pass + "'". Ingresar admin' -- como nombre convierte el resto de la consulta en un comentario y la verificación de la contraseña desaparece. Con un UNION el atacante lee otras tablas; con consultas apiladas, escribe en ellas.
- : leer, modificar o borrar datos; a veces llegar al sistema operativo.
- Injection de comandos del SO: la entrada llega a un shell (; cat /etc/passwd, $(...), acentos graves).
- XSS: la entrada llega a la página sin escape y ejecuta script en la sesión de otro usuario.
- La injection de NoSQL y de plantillas sigue el mismo patrón con otra sintaxis.
Código vulnerable
Concatenación de cadenas en SQL y en un comando de shell.
def find_user(db, name):
query = "SELECT id, email FROM users WHERE name = '" + name + "'"
return db.execute(query).fetchall()
def ping(host):
return os.popen("ping -c 1 " + host).read() # host = "x; cat /etc/passwd"const rows = await db.query(`SELECT id, email FROM users WHERE name = '${req.query.name}'`);
exec(`ping -c 1 ${req.query.host}`, cb);String sql = "SELECT id, email FROM users WHERE name = '" + name + "'";
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);La corrección
Parametriza: envía la consulta y los valores por separado, para que la base de datos nunca interprete el valor como SQL. Para los comandos, evita el shell por completo y pasa un arreglo de argumentos. Para HTML, apóyate en un template engine que haga escape por defecto. La validación (listas de permitidos de formato y longitud) es una segunda capa, no la corrección.
def find_user(db, name):
return db.execute("SELECT id, email FROM users WHERE name = ?", (name,)).fetchall()
HOST_RE = re.compile(r"^[A-Za-z0-9.-]{1,253}$")
def ping(host):
if not HOST_RE.fullmatch(host):
raise ValueError("invalid host")
return subprocess.run(["ping", "-c", "1", host], capture_output=True, text=True).stdoutconst { rows } = await db.query("SELECT id, email FROM users WHERE name = $1", [req.query.name]);
// sin shell: los argumentos se pasan como arreglo
execFile("ping", ["-c", "1", host], cb);String sql = "SELECT id, email FROM users WHERE name = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
}rows, err := db.QueryContext(ctx, "SELECT id, email FROM users WHERE name = $1", name)<p>Hello, {{ user_name }}</p> {# con escape #}
<p>Hello, {{ user_name | safe }}</p> {# PELIGROSO: no hagas esto con la entrada del usuario #}Demuestra la corrección
Escribe el ataque como una prueba. Envía los payloads clásicos y verifica que se traten como datos simples: la consulta no devuelve nada en lugar de todo, el shell nunca se invoca y los caracteres de script vuelven con escape. Conserva estas pruebas para siempre; documentan la amenaza.
PAYLOADS = ["' OR '1'='1", "admin' --", "'; DROP TABLE users; --", "x' UNION SELECT password,1 FROM users --"]
@pytest.mark.parametrize("payload", PAYLOADS)
def test_sql_payloads_are_just_data(db, payload):
assert find_user(db, payload) == [] # ninguna fila tiene ese nombre literal
assert db.execute("SELECT count(*) FROM users").fetchone()[0] > 0 # la tabla sigue existiendo
def test_ping_rejects_shell_metacharacters():
with pytest.raises(ValueError):
ping("example.com; cat /etc/passwd")
def test_html_is_escaped(client):
body = client.get("/hello?name=<script>alert(1)</script>").text
assert "<script>" not in body and "<script>" in body# detectar SQL concatenado y shell=True en la revisión, en cualquier commit
semgrep --config p/owasp-top-ten --error src/Checklist
- Consultas parametrizadas o un ORM confiable en cada llamada a la base de datos; nada de SQL armado con cadenas.
- Nunca pases entrada a un shell. Si debes ejecutar un programa, usa un arreglo de argumentos y una lista de permitidos.
- Haz escape según el contexto en la salida (HTML, atributo, URL, JavaScript); mantén activado el auto-escape; agrega una Content-Security-Policy.
- Valida la entrada con una lista de permitidos (tipo, longitud, formato) como .
- Ejecuta la cuenta de la base de datos con privilegio mínimo: sin DROP, sin acceso a archivos, sin admin.
- Escanea en el CI ( para el patrón, para el comportamiento en runtime) y conserva los payloads de ataque como pruebas de regresión.
conexiones en el mapa
- A05 Injectionpuede eludirA01 Broken Access Control
actualizado el 14/10/2026 · fuentes para profundizar · reportar un error
en otros dominios
- Pruebas de seguridad de web y APIoffseccómo probar lo que el otro enseña a corregir
- SASTappseces el patrón que el análisis estático busca en el código
- Ataques L7: qué buscarsecopsvisto desde afuera: las firmas en el borde
pon a prueba lo que entendiste
Cargando preguntas…