saltar al contenido
volver al mapa
técnica
etapa: code
3 min de lectura

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.

sqlmap (on systems you own)SemgrepCodeQLOWASP ZAPBurp SuiteORM / query builders

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.

users.pypython
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"
users.jsjavascript
const rows = await db.query(`SELECT id, email FROM users WHERE name = '${req.query.name}'`);
exec(`ping -c 1 ${req.query.host}`, cb);
UserDao.javajava
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.

users.pypython
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).stdout
users.jsjavascript
const { 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);
UserDao.javajava
String sql = "SELECT id, email FROM users WHERE name = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setString(1, name);
    ResultSet rs = ps.executeQuery();
}
users.gogo
rows, err := db.QueryContext(ctx, "SELECT id, email FROM users WHERE name = $1", name)
XSS: haz escape en la salida (el autoescape de Jinja2 viene activado; nunca marques la entrada del usuario como safe)html
<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.

test_injection.py (pytest)python
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 "&lt;script&gt;" in body
static-check.shbash
# 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.
OWASP Top 10La lista de referencia más conocida de riesgos de seguridad en aplicaciones web (A01 control de acceso roto, A02 fallas criptográficas, A03 inyección...), revisada cada pocos años por la comunidad OWASP. Es un mapa educativo de categorías de riesgo, no un checklist de herramientas ni una certificación.ver en el glosario →XSScross-site scriptingInyección en una página web: la entrada no confiable se renderiza como HTML o script en el navegador de otro usuario. Haz escape en la salida según el contexto y agrega una Content-Security-Policy.ver en el glosario →SQL injectionSQLiLa entrada concatenada en una cadena SQL se interpreta como parte de la consulta. Las consultas parametrizadas envían los valores por separado y eliminan el problema.ver en el glosario →Defensa en profundidadCapas independientes de protección, bajo la premisa de que cualquiera puede fallar: validación + parametrización + WAF + monitoreo.ver en el glosario →SASTAnálisis estático: examina el código sin ejecutarlo. Encuentra problemas por análisis de flujo, no por adivinación.ver en el glosario →DASTPrueba dinámica: ataca la aplicación en ejecución, desde afuera, en caja negra.ver en el glosario →

conexiones en el mapa

actualizado el 14/10/2026 · fuentes para profundizar · reportar un error

en otros dominios

pon a prueba lo que entendiste

Cargando preguntas…