0Pricing
DevOps Bootcamp · Lección

Prevención de inyección de comandos y argumentos

Entrecomille, valide y pase en arrays las entradas no confiables para eliminar la separación de palabras y la inyección basada en eval.

Prevención de inyección de comandos y argumentos es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DevOps Bootcamp, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DevOps Bootcamp incluye 4 lecciones en total.

Por qué se producen los ataques de inyección en Bash

Bash es un potente lenguaje de unión: pasa texto directamente al kernel, a otros programas y a sub-shells. Esa potencia se convierte en una vulnerabilidad en cuanto una entrada no fiable llega a un comando sin validación ni comillas.

Dos causas fundamentales provocan prácticamente todas las inyecciones en Bash:

  • División de palabras: las variables sin comillas se dividen por los espacios en blanco (IFS), transformando un valor lógico en varios tokens del shell.
  • Expansión de glob: caracteres como *, ? y [ son expandidos por el shell antes incluso de que se ejecute el comando.

Un atacante que controle un nombre de archivo, un nombre de usuario, un parámetro de URL o una variable de entorno puede aprovechar ambos mecanismos para ejecutar comandos arbitrarios, leer archivos o escalar privilegios.

En esta lección se muestra exactamente cómo aparecen estas vulnerabilidades y, lo que es más importante, cómo eliminarlas mediante el uso correcto de comillas, la validación de entradas y el paso de argumentos mediante arrays.

División de palabras: la amenaza silenciosa

Cuando Bash encuentra una variable sin comillas, divide su valor usando cualquier carácter incluido en $IFS (por defecto: espacio, tabulador y salto de línea). Lo que parece un solo argumento se convierte en varios.

Ejecute el script siguiente y observe cómo un nombre de archivo con un espacio se convierte en dos argumentos independientes para rm.

#!/usr/bin/env bash
# Dangerous: unquoted variable
FILE='important file.txt'

# Create the file so the demo is self-contained
touch "$FILE"

echo "Files before:"
ls

# BUG: rm sees TWO arguments: 'important' and 'file.txt'
# If 'important' does not exist, rm prints an error but continues.
rm $FILE   # <-- unquoted, word-split happens here

echo "Files after (unquoted rm):"
ls

Ponga siempre comillas: la primera regla de un Bash defensivo

La defensa más sencilla y eficaz contra la división de palabras consiste en usar siempre comillas dobles en las expansiones de variables.

  • "$var": se expande exactamente a un token, conservando los espacios, tabuladores y saltos de línea.
  • 'literal': comillas simples; no realizan ninguna expansión y son útiles para cadenas fijas.
  • No use nunca $var sin comillas, a menos que necesite explícitamente la división de palabras y la expansión de glob.

El script siguiente muestra la versión segura del ejemplo anterior.

#!/usr/bin/env bash
set -euo pipefail

FILE='important file.txt'
touch "$FILE"

echo 'Files before:'
ls

# SAFE: double-quotes keep the filename as one token
rm "$FILE"

echo 'Files after (quoted rm):'
ls

Inyección mediante glob: cuando * se convierte en un arma

Las variables sin comillas también están sujetas a la expansión de nombres de ruta (glob). Si la entrada controlada por el usuario contiene * o ?, Bash la expande comparándola con el sistema de archivos antes de ejecutar el comando.

Un vector de ataque clásico: un formulario web establece PATTERN=* y el script ejecuta cp $PATTERN /tmp/leak/, copiando todos los archivos del directorio actual.

La solución es idéntica: poner la variable entre comillas dobles. Una variable "$PATTERN" entre comillas se pasa literalmente; el shell nunca realiza la expansión de glob sobre ella.

#!/usr/bin/env bash
set -euo pipefail

# Simulate attacker-supplied input
PATTERN='*'

mkdir -p /tmp/safe_demo_src /tmp/safe_demo_dst
touch /tmp/safe_demo_src/secret1.txt /tmp/safe_demo_src/secret2.txt

cd /tmp/safe_demo_src

# UNSAFE: glob expands, copies every file
# cp $PATTERN /tmp/safe_demo_dst/

# SAFE: pattern is treated as a literal filename
cp "$PATTERN" /tmp/safe_demo_dst/ 2>&1 || echo 'No file named literally "*" — attack neutralised'

rm -rf /tmp/safe_demo_src /tmp/safe_demo_dst

Inyección de argumentos mediante parámetros posicionales sin comillas

Los scripts que aceptan argumentos del proceso que los invoca son objetivos principales de inyección. Cada parámetro posicional ($1, $2, ...) debe llevar comillas en todos los lugares donde se utilice.

Un patrón especialmente peligroso consiste en pasar $@ o $* sin comillas a otro comando:

  • "$@": expande cada parámetro posicional como una palabra independiente y con sus propias comillas. Use siempre esta forma.
  • $@ o $* sin comillas: están sujetos a la división de palabras y a la expansión de glob.
  • "$*": une todos los parámetros en una sola palabra (rara vez es lo que necesita).
#!/usr/bin/env bash
set -euo pipefail

# Safe wrapper: forward all arguments quoted
grep_wrapper() {
    local pattern="$1"
    shift
    # "$@" preserves each file argument as one token
    grep -rn "$pattern" "$@"
}

# Usage: ./script 'error msg' /var/log/syslog '/path with spaces/app.log'
echo 'Searching current script for "safe":'
grep_wrapper 'safe' "$0"

Inyección de comandos mediante eval y entradas no validadas

eval vuelve a analizar su argumento como código del shell. Cualquier dato no fiable que llegue a eval puede ejecutar comandos arbitrarios.

Patrones peligrosos habituales:

  • eval "$user_input"
  • eval echo \$$var (búsqueda indirecta de variables)
  • Pasar datos del usuario mediante bash -c "$input"

Regla: no pase nunca entradas no fiables a eval ni a bash -c. Use alternativas seguras de Bash:

  • Expansión indirecta: ${!varname} en lugar de eval echo \$$varname
  • Arrays asociativos para búsquedas dinámicas de clave-valor
  • Funciones en lugar de cadenas de comandos generadas
#!/usr/bin/env bash
set -euo pipefail

# Simulated attacker-supplied variable name
VARNAME='PATH; echo INJECTED'

# UNSAFE: eval lets attacker run 'echo INJECTED'
# eval "echo \$$VARNAME"

# SAFE: indirect expansion only resolves valid variable names
# First validate that VARNAME is a legal identifier
if [[ "$VARNAME" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
    echo "Value: ${!VARNAME}"
else
    echo "ERROR: invalid variable name: '$VARNAME'" >&2
    exit 1
fi

Validación de entradas: listas de permitidos en lugar de listas de bloqueados

Rechazar caracteres conocidos como peligrosos (una lista de bloqueados) es frágil: los atacantes encuentran codificaciones o caracteres que usted ha olvidado. En su lugar, use una lista de permitidos: acepte únicamente los caracteres que sabe que son seguros.

Estrategias de listas de permitidos en Bash:

  • Coincidencia con una expresión regular: [[ "$input" =~ ^[A-Za-z0-9_-]+$ ]]
  • Coincidencia con un patrón: case "$input" in [A-Za-z0-9]*) ... ;; esac
  • Comprobación de enumeración: compare con un conjunto fijo de valores válidos

Valide en el límite, tan pronto como la entrada entre en el script, antes de que llegue a cualquier comando.

#!/usr/bin/env bash
set -euo pipefail

validate_username() {
    local name="$1"
    # Allowlist: only lowercase letters, digits, underscore, hyphen; 1-32 chars
    if [[ ! "$name" =~ ^[a-z0-9_-]{1,32}$ ]]; then
        echo "ERROR: invalid username '${name}'" >&2
        return 1
    fi
    echo "Username accepted: $name"
}

validate_username 'alice'          # OK
validate_username 'bob_smith-2'    # OK
validate_username 'root; rm -rf /' # REJECTED
validate_username '../etc/passwd'   # REJECTED

Uso de arrays para pasar argumentos de forma segura

Cuando necesite construir un comando dinámicamente —añadiendo flags de forma condicional o recorriendo entradas— use un array de Bash en lugar de concatenar cadenas.

La concatenación de cadenas elimina toda la estructura; un array de Bash conserva cada argumento como un elemento independiente, sin que el shell vuelva a analizarlo.

  • Declarar: args=()
  • Añadir: args+=(--flag "$value")
  • Ejecutar: command "${args[@]}"

"${args[@]}" expande cada elemento como una palabra independiente y con sus propias comillas, exactamente igual que "$@".

#!/usr/bin/env bash
set -euo pipefail

# Build a find command safely with an array
BASEDIR='/tmp'
USER_PATTERN='*.log'   # could come from user input (validate first!)
MAX_DAYS=7

cmd=(find "$BASEDIR" -type f -name "$USER_PATTERN" -mtime "+${MAX_DAYS}")

# Optionally add -delete only when requested
DELETE=false
if [[ "$DELETE" == 'true' ]]; then
    cmd+=(-delete)
fi

echo "Running: ${cmd[*]}"
"${cmd[@]}"

El separador --: protección contra la inyección de flags

Incluso un argumento entre comillas correctamente puede interpretarse como un flag de opción si comienza por -. Considere rm "$file" cuando file='-rf .': las comillas protegen contra la división de palabras, pero rm sigue interpretando -rf como flags.

La convención POSIX -- indica el fin de las opciones a la mayoría de las utilidades GNU/BSD. Todo lo que aparece después de -- se trata como un argumento posicional, nunca como un flag.

#!/usr/bin/env bash
set -euo pipefail

# Simulate a dangerous filename supplied by the user
FILENAME='-rf /tmp/safe_demo_target'

mkdir -p /tmp/safe_demo_target
touch /tmp/safe_demo_target/keep_me.txt

echo 'Files before:'
ls /tmp/safe_demo_target/

# UNSAFE (flag injection — do NOT uncomment in real systems):
# rm "$FILENAME"

# SAFE: -- ends option processing; filename is treated literally
rm -- "$FILENAME" 2>&1 || echo "No such file (attack neutralised): $FILENAME"

echo 'Files after:'
ls /tmp/safe_demo_target/
rm -rf /tmp/safe_demo_target

Sanitización de entradas para SQL y herramientas externas

Cuando los scripts de Bash invocan clientes de bases de datos (psql, mysql), curl con URL proporcionadas por el usuario u otras herramientas similares, se aplican dos capas adicionales:

  • Consultas parametrizadas: no interpole nunca datos del usuario en cadenas SQL. Pase los valores mediante -v en psql o --data-urlencode en curl.
  • Separe los datos del código: use printf con una cadena de formato literal; no permita nunca que la entrada del usuario sea la cadena de formato.

El ejemplo siguiente consulta PostgreSQL de forma segura y mantiene el valor proporcionado por el usuario completamente fuera del texto SQL.

#!/usr/bin/env bash
set -euo pipefail

# Validate first: only allow alphanumeric usernames
USERNAME="${1:-alice}"
if [[ ! "$USERNAME" =~ ^[a-z0-9_]{1,32}$ ]]; then
    echo 'ERROR: invalid username' >&2
    exit 1
fi

# UNSAFE:
# psql -c "SELECT * FROM users WHERE name = '$USERNAME';"

# SAFE: pass value as a psql variable, never inside the SQL text
# psql -v username="$USERNAME" -c 'SELECT * FROM users WHERE name = :username;'

# Demonstrate the principle with printf (safe format string usage)
printf 'Query would use username: %s\n' "$USERNAME"

Lista de comprobación de seguridad: cómo aplicarlo todo

Un script de Bash seguro y listo para producción combina todas las técnicas de esta lección en una defensa coherente y por capas. Esta es una plantilla reforzada mínima pero completa:

  • set -euo pipefail: salir en caso de error, tratar las variables no definidas como errores y propagar los fallos de los pipes.
  • Validar al inicio: use una lista de permitidos para cada entrada externa antes de que llegue a cualquier comando.
  • Poner comillas en todo: "$var", "$@", "${array[@]}"; sin excepciones, a menos que necesite la división de palabras.
  • Usar arrays para construir comandos dinámicamente.
  • Prefijar los argumentos con -- al pasar nombres de archivo o cadenas proporcionados por el usuario.
  • No usar nunca eval con datos no fiables; prefiera ${!var} para búsquedas indirectas.
  • Restringir los permisos: ejecute los scripts con los privilegios mínimos necesarios; evite sudo dentro de scripts que acepten entradas del usuario.
#!/usr/bin/env bash
# Hardened template — safe argument injection prevention
set -euo pipefail
IFS=$'\n\t'

#--- 1. Validate inputs at the boundary ---
SEARCH_DIR="${1:-}"
PATTERN="${2:-}"

[[ -z "$SEARCH_DIR" || -z "$PATTERN" ]] && { echo 'Usage: script <dir> <pattern>' >&2; exit 1; }
[[ ! "$SEARCH_DIR" =~ ^[A-Za-z0-9/_.-]+$ ]] && { echo 'ERROR: unsafe directory path' >&2; exit 1; }
[[ ! "$PATTERN" =~ ^[A-Za-z0-9._-]+$ ]]    && { echo 'ERROR: unsafe pattern'        >&2; exit 1; }

#--- 2. Build command with an array ---
cmd=(find -- "$SEARCH_DIR" -type f -name "$PATTERN")

#--- 3. Execute — no string interpolation, no eval ---
echo "Executing: ${cmd[*]}"
"${cmd[@]}"

Comprobación de conocimientos: uso de comillas y prevención de inyecciones

Compruebe su comprensión de los conceptos clave de esta lección.

Resumen de la lección: prevención de inyecciones de comandos y argumentos

Ha estudiado el conjunto completo de herramientas defensivas para gestionar entradas de Bash de forma segura:

  • La división de palabras y la expansión de glob son los mecanismos fundamentales que convierten las variables inseguras en vectores de inyección.
  • Ponga comillas dobles en todas las variables ("$var", "$@", "${arr[@]}") para bloquear ambas amenazas.
  • Use "$@" —nunca $@ ni $* sin comillas— al reenviar argumentos.
  • Prefije los nombres de archivo proporcionados por el usuario con -- para evitar la inyección de flags.
  • Use una lista de permitidos para todas las entradas externas mediante una expresión regular ([[ $v =~ ^pattern$ ]]) antes de que lleguen a cualquier comando.
  • Construya comandos dinámicos con arrays (cmd+=() → "${cmd[@]}"), nunca mediante concatenación de cadenas.
  • Elimine eval y bash -c "$input"; use ${!varname} para una expansión indirecta segura.
  • Abra siempre los scripts con set -euo pipefail y IFS=$'\n\t' como configuración básica reforzada.

Estas prácticas, aplicadas de forma coherente desde la primera línea de cada script, reducen casi a cero la superficie de ataque de Bash frente a vulnerabilidades de la clase de inyección.

Preguntas frecuentes

¿La lección «Prevención de inyección de comandos y argumentos» es gratis?

Sí — el texto completo de «Prevención de inyección de comandos y argumentos» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DevOps Bootcamp, actualiza a CoddyKit PRO. El curso de DevOps Bootcamp incluye 4 lecciones en total.

¿Qué aprenderé en «Prevención de inyección de comandos y argumentos»?

Entrecomille, valide y pase en arrays las entradas no confiables para eliminar la separación de palabras y la inyección basada en eval. Practicas DevOps Bootcamp con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar DevOps Bootcamp?

No se requiere experiencia previa. DevOps Bootcamp en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.

¿Cuánto tiempo toma la lección «Prevención de inyección de comandos y argumentos»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de DevOps Bootcamp?

Sí. Cada lección de DevOps Bootcamp incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Prevención de inyección de comandos y argumentos
  2. Gestión segura de secretos e higiene del entorno
  3. Ejecución con mínimos privilegios y disciplina con sudo
  4. Análisis estático y auditoría con ShellCheck
← Volver a DevOps Bootcamp