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):"
lsPonga 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
$varsin 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):'
lsInyecció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_dstInyecció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 deeval 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
fiValidació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' # REJECTEDUso 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_targetSanitizació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
-venpsqlo--data-urlencodeencurl. - Separe los datos del código: use
printfcon 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
evalcon datos no fiables; prefiera${!var}para búsquedas indirectas. - Restringir los permisos: ejecute los scripts con los privilegios mínimos necesarios; evite
sudodentro 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
evalybash -c "$input"; use${!varname}para una expansión indirecta segura. - Abra siempre los scripts con
set -euo pipefailyIFS=$'\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
- Prevención de inyección de comandos y argumentos
- Gestión segura de secretos e higiene del entorno
- Ejecución con mínimos privilegios y disciplina con sudo
- Análisis estático y auditoría con ShellCheck