INYECCIONES BATCH
Escrito por Carlos Montiers
¿A qué me refiero con inyecciones batch?
A texto ingresado en un script batch (archivo por lotes), que confunde al interprete de comandos, y permite hacer otra cosa, que no debería hacer (supuestamente).
Un muy buena artículo que escribió Sirdarckcat sobre el tema, afirmaba que los script batch no deberían utilizarse en entornos seguros, y concuerdo con él, por lo tanto este artículo no pretenderá demostrar lo contrario, es decir, que batch es seguro, porque no lo es, de hecho, los script batch dependen del interprete de comandos (cmd.exe), y ese intérprete de comandos lee el registro, y puede ser bloqueado, y/o ejecutar comandos cuando se abra:
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Policies\Microsoft\Windows\System]
"DisableCMD"=dword:00000001
00000001 Impide tanto la ejecución de script batch como abrir el cmd.exe.
00000002 Impide solo la ejecución de script batch.
Nota: Con un editor hexadecimal podría modificarse el archivo cmd.exe reemplazando el string Unicode "DisableCMD" con espacios, y de esta forma cmd.exe siempre podría abrirse.
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"AutoRun"="exit"
[HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor]
"AutoRun"="exit"
Aquí siempre que ejecutemos cmd.exe se ejecutará el comando exit, por lo que se saldrá. Puede evitarse que cmd.exe haga esto, eliminando estos valores en el registro o ejecutando con el parámetro /D (cmd.exe /d)
Además que si trabajamos con programas externos dentro de un script batch, podría reemplazarse ese programa que llamamos dentro del script batch, y ya estariamos haciendo otra cosa.
Bueno, las inyecciones batch puede producirse, ya sea, introduciendo comandos cuando se nos pide dentro de un script (set /p), utilizando parámetros (%1 %2 %3 %4 %5 %6 %7 %8 %9), leyendo un archivo (for /f), o suplantando un programa que fue llamado sin especificar extensión o usando call.
No pretendo afirmar que no deban evitarse las batch injections, pues a mí punto de vista, los script batch deberían utilizarse para operaciones de mantenimiento, como copiar archivos de respaldo, cosas así, automatizar tareas, pero aún así hay quienes lo "sobreexplotan" llegando a hacer programas, o incluso juegos, y es en este modo, que el evitar batch injections tiene su lado bueno qué es: evitar errores.
Ejemplos:
@echo off
:inicio
echo 1 Abrir calculadora
echo 2 Salir
set /p opcion=Ingrese opcion:
if %opcion%==1 (calc)
if %opcion%==2 (exit)
pause
goto inicio
Aqui si simplemente presionamos enter, cmd.exe terminará el script porque se produce un error al utilizar if ==1 (calc) es decir, provocamos un error de sintaxis, y nos dirá: No se esperaba (calc) en este momento. También podriamos acceder a la opción de abrir la calculadora sin presionar 1, que sería escribiendo: not 0
Entonces cmd.exe haría:
if not 0==1 (calc)
Aquí tenemos una batch injection.
Lo malo que tiene este script es que calc se especifica sin la extensión .exe, por lo que si tuviesemos un archivo llamado calc.bat en el mismo lugar donde ejecutamos nuestro script, ejecutariamos dicho archivo .bat
Podría evitarse hasta cierto modo el error de sintaxis encerrando la variable entre comillas, así:
@echo off
:inicio
echo 1 Abrir calculadora
echo 2 Salir
set /p opcion=Ingrese opcion:
if "%opcion%"=="1" (calc)
if "%opcion%==2" (exit)
pause
goto inicio
Pero si ingresaramos un comilla simple, encontraría error igualmente: if """=="1" (calc)
Se podría evitar que ingresen un texto vacio así:
if "%opcion%"=="" (echo No ingreso ninguna opcion)
Aún así podría utilizarse una comilla simple, y provocar un error, por lo que es mejor hacerlo así:
set opcion=
set /p opcion=Ingrese opcion:
if not defined opcion (echo Ingrese una opcion)
¿Qué hacemos? Eliminamos primeramente la variable opcion, y luego colocamos el set /p para que se ingrese un texto, y preguntamos si la variable opcion está definida, si se presiono solamente enter, cmd no definiría la variable y significaria que se presiono enter. Nota: si la variable tuviese un valor antes, se quedaría con dicho valor, por eso borramos la variable antes como una buena práctica, pues puede traer problemas en ciclos repetitivos, por ejemplo alguien ingreso la opcion 1, y luego al fin del script se le pregunta si desea ejecutar otra opcion, y la persona coloca que si, y vuelve al menú de opciones y presiona enter, inmediatamente volvería a hacer la opcion 1, porque fue con ese valor que quedó la variable.
Bien, vamos a ver más batch injections.
En el menú de opciones, cuando nos pregunte que ingresemos una opcion, escribiremos:
1"=="1" (mspaint.exe) else rem
y cmd hará en esta parte: if "%opcion%"=="1" (calc)
if "1"=="1" (mspaint.exe) else rem"=="1" (calc)
Y ejecutaremos el paint.
Bien borremos todo, y ahora escribamos otro script:
@echo off
:inicio
set texto=
set /p texto=Ingrese texto:
if not defined texto (goto inicio)
pause
Ahora ejecutemos el script y escribamos lo siguiente:
hola |calc
hola & calc
¿Qué sucedió?
Nada.
¿Porqué?
Porque no hemos manipulado la variable, esto nos demuestra que las batch injections ocurren cuando manipulamos la variable, es decir, cuando mostramos su valor dentro del cmd.exe. Añadamos por favor al script debajo del if not defined...
esto:
echo Usted escribio: %texto%
E ingresemos un texto cualquiera, y veremos que se nos muestra lo que ingreasamos, pero volvamos a escribir los códigos anteriores y veremos que se nos abre la calculadora de windows.
Se podría buscar una solución reemplazando los carácteres de batch injections como: | & ^ eliminandolos de la variable así:
set texto=%texto:&=%
set texto=%texto:^=%
Pero eso no es correcto, porque con este método no podemos filtrar el carácter: | y también recordemos que las batch injections se producen al manipular la variable.
También quisiera añadir, en esta parte, que cuando hagamos manipulación de variables, evitemos nombrar variables con nombre de números, ya que se confunden con los parámetros que recibe cmd.exe, y también puede traer problemas.
Ejemplo si queremos saber que la primeras tres letras de una variable son: abc, y tenemos una variable llamada 1, esto probablemente no funcionará:
set 1=abcdario
if "abc"=="%1:~0,3%" (echo Comienza con abc)
pause
Esto ocurre porque como ejecutamos el script, sin pasarle ningún argumento, cmd.exe evaluará:
if "abc"==":~0,3" (echo Comienza con abc)
Cambiemos el script de la siguiente forma, y veremos que funciona.
set palabra=abcdario
if "abc"=="%palabra:~0,3%" (echo Comienza con abc)
pause
Otro punto que quisiera decir es que el uso de call para manipular el contenido de una variable usando otras variables, también puede producir batch injections:
Ejemplo:
set frase=sa
set palabra=cama
call set palabra=%%palabra:ma=%frase%%%
¿Cómo? Si tenemos un archivo llamado set.bat, en vez de utilizar el comando set interno de dos, llamaremos con call a ese archivo por lotes. Aunque esto es poco probable, es posible.
Lo mismo sucede si hacemos:
set frase=sa
set palabra=cama
call echo %%palabra:ma=%frase%%%
y tenemos un echo.bat
Es decir, no es únicamente con set, también puede ser con cualquier otro comando interno de cmd.exe que llamemos con call, y que tengamos un archivo .bat del mismo nombre en el lugar dónde ejecutemos el script.
Una solución a call set, es utilizar setlocal enabledelayedexpansion, así:
set frase=sa
set palabra=cama
setlocal enabledelayedexpansion
set palabra=!palabra:ma=%frase%!
Otra batch injection es el carácter ascii 255, alt+255 o alt+0160 (son el mismo carácter) que produce quiebres al evaluarlo con los if, pues es un carácter "invisible" pero siempre el quiebre devolverá un errorlevel 9009, por lo que podremos identificarlo, y más adelante veremos cómo.
Uso de etiquetas.
Si tenemos un código susceptible a batch injection, podemos saltar a cualquier etiqueta, con goto.
Pienso que pudiese evitarse esto, limitando el contenido de un texto a pocos carácteres, como 5, ya que la palabra goto tiene 4 carácteres, es decir limitar, pero aún pienso que hay alguna forma de saltarse esto, aunque no lo he encontrado. Pero está buena la idea. Además que le añado su apoyo donde afirmarse, en caso que se produzca un quiebre, lee: goto inicio y no avanza hacia abajo.
@echo off
:inicio
cls
echo TEXTO.Mostrar texto
echo SALIR.Salir
set opcion=
set /p opcion=Ingrese opcion:
if not defined opcion goto inicio
if /i "%opcion:~0,5%"=="TEXTO" (goto texto) else (if /i "%opcion:~0,5%"=="salir" (goto SALIR))
goto inicio
:salir
echo Has escogido salir.
pause
exit
:texto
echo Esto es un texto.
pause
exit
Bien, colocaré otra forma de evitar las batch injections (desde cierto punto de vista) con un detector de carácteres "nocivos".
::filter.bat -- detect batch injections, and alert.
::Version: 3.1
::$author: Carlos Montiers
@echo off
set findstr="%WinDir%\system32\findstr.exe"
set find="%WinDir%\system32\find.exe"
:Login
echo The password is entrar
set pass=
set /p pass=Enter password:
if not defined pass (goto Login)
set pass | %find% """" >NUL 2>&1 && goto warning
rem identificamos las comillas
set pass | %find% " " >NUL 2>&1 && goto warning
rem identificamos los espacios
set pass | %findstr% "| & ^ > < # ' ` . ; , / \ + - ~ ! ) ( ] [ } { : ? *" >NUL 2>&1 && goto warning
rem identificamos una serie de caracteres
echo %pass% | find "=" >NUL 2>&1 && goto warning
rem identificamos el caracter =
if not [{cualquiercosa}]==[{%pass%}] (cd.) 2>NUL
if "%errorlevel%"=="9009" (goto warning)
rem Hacemos esto para identificar el carácter ascii 255, se evalua y en caso que sea cierto, se efectúa un comando que no devuelve nada como es cd.
rem Si se produjo el quiebre en el if por el alt+255 se nos devolvera un errorlevel 9009 que nos sirve para tener todo bajo control.
if not [{entrar}]==[{%pass%}] (goto:Login) 2>NUL
echo Password correcta
pause
goto :eof
:warning
echo Posible intento de batch injection
goto Login
Si se fijan no manipulo la variable para identificar los carácteres que podrían originar una batch injection, lo que hago es buscar carácteres en la salida del comando set.
Por ejemplo si yo coloco en cmd.exe
set palabra=computador
y coloco:
set palabra
set me devolverá:
palabra=computador
y es esa salida en la que buscó los carácteres. Solo manipulo la variable al final, con echo, cuando busco el carácter "=" pero lo hago al final, es decir, cuando se que la variable está limpia, y lo hago con echo, porque el comando set siempre devuelve un "="
También quiero añadir, que el problema del alt+255 podría originarse si coloco en otro orden los paréntesis. Por ejemplo, si quisiera hacer esto:
if [{cualquiercosa}]==[{%pass%}] (
echo Escribio cualquiercosa
)
if "%errorlevel%"=="9009" (goto warning)
Si colocase alt+255, veria el texto cualquiercosa porque se quebró la línea donde estaba el if, por lo que un supuesto código así, debería ser así:
if [{cualquiercosa}]==[{%pass%}] (
if "%errorlevel%"=="9009" (goto warning)
echo Escribio cualquiercosa
)
Es decir, esa evaluación del 9009 siempre debe ir debajo de otra evaluación, para que ataje por así decirlo el alt+255.
Bueno, este código que identifica los carácteres tiene un problema rebuscado, y es que utiliza el comando externo find.exe, y por ello, si tuviese en el mismo lugar donde tengo filter.bat un programa llamado find.exe, se ejecutaria este en vez del que debería ejecutarse, por este motivo, especifiqué que el find.exe y findstr.exe fuesen los ubicados en la carpeta system32 de %WinDir%\
Así ejecutaremos los programas "originales" siempre y cuando no modifiquemos las variables de entorno.
Ejemplo de la afirmación anterior son programas compilados del siguiente código en C, llamados find.exe y findstr.exe respectivamente:
int main()
{
return 1;
}
y si ejecutara el filter.bat y presiono un espacio, obtendría que la password es correcta. ¿Por qué? porque mis programas find.exe y findstr.exe resultado de la compilación del código anterior retornarian errorlevel 1, y con el espacio se produce un quiebre, y compararía el errorlevel 1 con el 9009, por lo que llegaría hasta abajo, aunque todo esto es muy rebuscado, pero quería señalarlo.
Otro punto a destacar del código anterior es que no utilizo etiquetas sobre lo que quiero mostrar. pregunto por la negación, la variable va en el segundo bloque y además encierro la variable entre carácteres que son identificados como de batch injection.
Así supongamos que no identifico carácteres de batch injection, y tampoco los encierro entre carácteres especiales como comillas u otros como [], y tengo la siguiente linea:
if %palabra%==entrar (echo Esto es un contenido exclusivo) else (echo No puedes entrar)
Si coloco not palabra haría la negacíón y vería el contenido exclusivo.
Ahora, si la misma línea la coloco así:
if not %palabra%==entrar (echo No puedes entrar) else (echo Esto es un contenido exclusivo)
Doy más protección a la línea por así decirlo, me evito que hagan la negación.
Y si a eso le añado lo de encerrarlo entre carácteres identificables como maliciosos, estoy colocando otra capa de protección.
Para concluir quisiera decir, que las batch injection no existirán, hasta que se descubran, sigo pensando que debe haber alguna forma "escondida" de incluir comandos creando macros con el doskey.
Por último, les dejo un script, donde apliqué el detector de caracteres nocivos tanto al aceptar entradas de parte del usuario como cuando lee archivos desde un for /f. Se llama LockUrl 5.1, y edita el archivo hosts, para bloquear páginas web.
http://sites.google.com/site/carlitosdll/codes-batch/lockurl.zip?attredirects=0Y también batch injections comúnes:
set|more
&echo on
&cmd^
&goto:etiqueta^
not 5
|calc.exe
Y un ejemplo suplantando un programa externo, utilizando una versión anterior del filter.bat, y con un problema debido al mal uso de call set, comente la linea corregida, antes de la línea con el problema
@echo off
::Creado por Carlos Montiers
setlocal enabledelayedexpansion
:Login
set pass=
set /p pass=Enter password:
if not defined pass (goto Login)
for %%a in ("|" "&" "^" ">" "<" "'" "`" "." ";" "," " " "/" "\" "+" "-" "~" "!" ")" "(" "]" "[" "{" "}") do (
set pass|find %%a >NUL && goto warning)
for %%a in ("if" "else" "goto" "echo" "rem" "start" "set" "doskey" "pause" "cmd" "command") do (
set pass|find %%a >NUL && goto warning)
set pass=%pass:"=_%
::echo off
if "%errorlevel%"=="9009" (goto warning)
call :job
if not [{98974876}]==[{%pass%}] (goto mal) 2>NUL
echo Muy bien.
echo Pronto, mas y mejor.
pause>NUL
exit
goto :eof
:job
set dic=0123456789abcdefghijklmn@opqrstuvwxyz
for /l %%a in (0,1,37) do (
set /a d%%a="%%a << 7"
set /a d%%a+="365%325"
)
set /a res=0
set pass=%pass:98974876=%
call :contar %pass%
set limit=
:::::::::::::::::Aqui esta el problema::::::::::::::::::::::::::::::::::::::
rem set limit=![%pass%.length]!
call set limit=%%[%pass%.length]%%
:::::::::::::::::Aqui esta el problema::::::::::::::::::::::::::::::::::::::
for /l %%b in (0,1,%limit%) do (for /l %%c in (0,1,37) do (call :res %%b %%c))
set pass=
set pass=%res%
goto :eof
:warning
echo
echo Posible intento de batch injection
echo.
goto Login
:contar
set palabra=%1
if defined palabra (call :length %palabra: =_%)
goto :eof
:length
set arg=%*
if not defined arg (goto :eof)
set word=%arg%
set /a cont=0
:loop
if not defined word (goto end)
set word=%word:~1%
set /a cont +=1
goto loop
:end
set [%arg: =_%.length]=%cont%
goto :eof
:res
set par1=%1
set par2=%2
set x=!pass:~%par1%,1%!
set z=!dic:~%par2%,1!
if "%x%"=="%z%" (call :res1 %par1% %par2%)
goto :eof
:res1
set /a sum="%2 + 3"
set num=!d%1%!
set /a mult="%num% * %sum%"
set /a res +=%mult%"
set /a res="%res%<<1"
goto :eof
:mal
echo Autentificacion incorrecta.
echo.
goto Login
La password correcta de este reto es: ... es una palabra de 5 carácteres, pero si en el mismo lugar de dónde se ejecuta crean un archivo llamado set.bat con el siguiente contenido:
set res=98974876
set limit=1) do rem (
Pueden ingresar cualquier texto y se los considerará como bueno.
La explicación aquí está:
cmd hace esto:
call set
set res=98974876
set limit=1) do rem (
for /l %%b in (0,1,1) do rem () do (for /l %%c in (0,1,37) do (call :res %%b %%c))
set pass=
set pass=98974876
goto :eof
Que estén bien, Saludos.