0Pricing
Linux Command Line & Bash Scripting Mastery · 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 Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery, actualiza a CoddyKit PRO. El curso de Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery?

No se requiere experiencia previa. Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery?

Sí. Cada lección de Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery