TALLER DE EXPLOTACIÓN MEDIATE CAJA BLANCA. PARTE 2

AUTOR: ALEJANDRO RUEDA

En este artículo se seguirá el ejemplo del caso anterior, para poder llegar a completar la explotación de una vulnerabilidad compleja partiendo del código fuente. Se fabricará un script en Python para facilitar las tareas de explotación a lo largo del código.

 

PASOS PARA REALIZAR LA EXPLOTACIÓN

Se hará uso del siguiente script programado en Python para realizar los pasos que quedan en la explotación:

En el anterior articulo se finalizo el taller en el siguiente tramo vulnerable del código, desde donde vamos a continuar. sEel siguiente ejemplo de vulnerabilidad nos el error nos señala el archivo que ya conocemos (friends.inc.php), así que veamos ver qué es exactamente lo que se rompe. Si echamos un vistazo a la línea 350, encontramos lo siguiente:

  • 347: $rows_members = queryDB($sql, array());
  • 348:
  • 349: //Get all members out
  • 350: foreach($rows_members as $row){
  • 351: $this_id = $row[‘member_id’];

La línea 350 utiliza la variable $row_members, que debe ser rellenada con los resultados de la ejecutada en la línea 347. Esto indica que la consulta puede estar rota.  Como hemos habilitado el registro de consultas del registro de consultas de MySQL, podemos investigar el archivo de registro. Cuando lo hacemos, vemos lo siguiente:

  • alex@atutor:~$ sudo tail –f /var/log/mysql/mysql.log
  • 776 Query SELECT customized FROM AT_themes WHERE dir_name = ‘default’
  • 776 Query SELECT customized FROM AT_themes WHERE dir_name = ‘default’
  • 776 Query SELECT * FROM AT_courses ORDER BY title
  • 776 Query SELECT dir_name, privilege, admin_privilege, status, cron_interval, cron_last_run FROM AT_modules WHERE status=2
  • 776 Query SELECT L.* FROM AT_language_text L, AT_language_pages P WHERE
  • L.language_code=»en» AND L.term=P.term AND
  • P.page=»/mods/_standard/social/index_public.php» ORDER BY L.variable ASC
  • 776 Query SELECT L.* FROM AT_language_text L WHERE L.language_code=»en» AND
  • L.term=»test» ORDER BY variable ASC LIMIT 1
  • 776 Query INSERT IGNORE INTO AT_language_pages (`term`, `page`) VALUES («test», «/mods/_standard/social/index_public.php»)
  • 776 Query SELECT * FROM AT_modules WHERE dir_name =’_core/services’ && status =’2′
  • 776 Query SELECT * FROM AT_members M WHERE (first_name LIKE ‘%AAAA’%’ OR second_name LIKE ‘%AAAA’%’ OR last_name LIKE ‘%AAAA’%’ OR login LIKE ‘%AAAA’%’ )
  • 776 Quit

En el listado anterior muestra que la parte de comillas simples de nuestro payload no fue escapada correctamente por la aplicación. Como resultado, deberíamos estar tratando con una vulnerabilidad de inyección SQL . Además, a partir de la consulta registrada, parece que tenemos no sólo uno, sino cuatro puntos de inyección diferentes.

Mientras seguimos probando la inyección enviando dos comillas simples (no una sola comilla doble), podemos cerrar la consulta SQL que está bajo nuestro control. Esto se puede comprobar por el hecho de que no se encuentran errores en la respuesta (el siguiente listado) ni en el archivo de registro de MySQL:

alex@kali:~/atutor$ python poc1.py atutor «AAAA»»

Response Headers:

{‘Content-Length’: ’20’, ‘Content-Encoding’: ‘gzip’, ‘Set-Cookie’:

‘ATutorID=38m1u0lvr8jatcnfb3382c7mk7; path=/ATutor/,

ATutorID=98urnfikmqo7s5m4gog1dh6sj0; path=/ATutor/,

ATutorID=98urnfikmqo7s5m4gog1dh6sj0; path=/ATutor/’, ‘Vary’: ‘Accept-Encoding’, ‘Keep-

Alive’: ‘timeout=5, max=100’, ‘Server’: ‘Apache/2.4.10 (Debian)’, ‘Connection’: ‘Keep-

Alive’, ‘Date’: ‘Tue, 24 Apr 2018 17:09:39 GMT’, ‘Content-Type’: ‘text/html;

charset=utf-8′}

Response Content:

No errors found

kali@kali:~/atutor$

Comprobando el archivo de registro, observamos que la consulta vulnerable está ahora bien formada.

40925 Query SELECT customized FROM AT_themes WHERE dir_name = ‘default’

40925 Query SELECT customized FROM AT_themes WHERE dir_name = ‘default’

40925 Query SELECT * FROM AT_courses ORDER BY title

40925 Query SELECT dir_name, privilege, admin_privilege, status,

cron_interval, cron_last_run FROM AT_modules WHERE status=2

40925 Query SELECT L.* FROM AT_language_text L, AT_language_pages P WHERE

L.language_code=»en» AND L.term=P.term AND

P.page=»/mods/_standard/social/index_public.php» ORDER BY L.variable ASC

40925 Query SELECT L.* FROM AT_language_text L WHERE L.language_code=»en» AND

L.term=»test» ORDER BY variable ASC LIMIT 1

40925 Query INSERT IGNORE INTO AT_language_pages (`term`, `page`) VALUES

(«test», «/mods/_standard/social/index_public.php»)

40925 Query SELECT * FROM AT_modules WHERE dir_name =’_core/services’ &&

status =’2′

40925 Query SELECT * FROM AT_members M WHERE (first_name LIKE ‘%AAAA»%’

OR second_name LIKE ‘%AAAA»%’ OR last_name LIKE ‘%AAAA»%’ OR login LIKE ‘%AAAA»%’

)

40925 Quit

Si usted ha tenido una exposición previa a las inyecciones SQL utilizando consultas UNION, puede pensar que esta es una oportunidad perfecta para utilizarlas y recuperar directamente datos arbitrarios de la base de datos de ATutor. Desde una perspectiva de muy alto nivel, ese enfoque se vería así:

SELECT * FROM AT_members M WHERE (first_name LIKE ‘%INJECTION_HERE’) UNION ALL SELECT

1,1,1,1,…….#

Aunque ciertamente es posible utilizar las consultas UNION, lamentablemente no nos son útiles en este caso. Específicamente, si miramos el código de index_public.php, podemos ver que los resultados de la consulta vulnerable no se muestran al usuario. En cambio, en la línea 48, el conjunto de resultados de la consulta se utiliza en un bucle foreach que pasa el $member_id recuperado a la función printSocialName. Los resultados de esta llamada a la función se muestran al usuario final utilizando la función PHP echo:

41: //retrieve a list of friends by the search

42: $search_result = searchFriends($query);

43:

44:

45: if (!empty($search_result)){

46: echo ‘

‘._AT(‘suggestions’).’:
‘;

47: $counter = 0;

48: foreach($search_result as $member_id=>$member_array){

49: //display 10 suggestions

50: if ($counter > 10){

51: break;

52: }

53:

54: echo ‘

onclick=»document.getElementById(\’search_friends\’).value=\».printSocialName($member

_id, false).’\’;

document.getElementById(\’search_friends_form\’).submit();»>’.printSocialName($member_

id, false).’


‘;

55: $counter++;

En otras palabras, los resultados de la inyección que inyectamos no se reflejan directamente en nosotros, por lo que una consulta de UNION tradicional no será útil aquí.

Podemos comprobarlo siguiendo esta ruta de ejecución de código:

555: /**

556: * Print social name, with AT_print and profile link

557: * @param int member id

558: * @param link will return a hyperlink when set to true

559: * return the name to be printed.

560: */

561: function printSocialName($id, $link=true){

562: if(!isset($str)){

563: $str = »;

564: }

565: $str .= AT_print(get_display_name($id), ‘members.full_name’);

566: if ($link) {

567: return getProfileLink($id, $str);

568: }

569: return $str;

570: }

La función printSocialName pasa el valor $member_id ($id en la línea 565) a la función get_display_name definida en vital_funcs.inc.php. Esta función se muestra en el listado siguiente:

299: if (substr($id, 0, 2) == ‘g_’ || substr($id, 0, 2) == ‘G_’){

300: $sql = «SELECT name FROM %sguests WHERE guest_id=’%d'»;

301: $row = queryDB($sql, array(TABLE_PREFIX, $id), TRUE);

302: return _AT($display_name_formats[$_config[‘display_name_format’]], »,

$row[‘name’], », »);

303: }else{

304: $sql = «SELECT login, first_name, second_name, last_name FROM %smembers

WHERE member_id=’%d'»;

305: $row = queryDB($sql, array(TABLE_PREFIX, $id), TRUE);

306: return _AT($display_name_formats[$_config[‘display_name_format’]],

$row[‘login’], $row[‘first_name’], $row[‘second_name’], $row[‘last_name’]);

307: }

En la línea 304 del listado anterior, podemos ver que get_display_name prepara y ejecuta la consulta final utilizando el parámetro $member_id pasado. Los resultados de la consulta se devuelven al que llama a la consulta.

Esta lógica de ejecución nos impide utilizar cualquier UNION en la consulta original vulnerable y convierte esta inyección SQL en una inyección ciega clásica. A diferencia de las vulnerabilidades de inyección SQL muy básicas, que permiten al atacante recuperar los datos deseados directamente a través de la página web renderizada, las inyecciones SQL ciegas nos obligan a inferir los datos que que buscamos, ya que nunca se devuelven en el conjunto de resultados de la consulta original.

Esto puede ocurrir por muchas razones, como la lógica de la aplicación web que intercepta los resultados de la consulta y los prepara para de consulta y para su visualización basándose en un conjunto de reglas, o páginas de gestión de errores cuyo contenido nunca cambia, independientemente de lo que haya provocado el error.

 

BIBLIOGRAFÍA

  • Ng Wai Foong, J. (2021, 21 noviembre).Static vs Dynamic in Application Security Testing – Towards Data Science. https://towardsdatascience.com/static-vs-dynamic-in-application-security-testing-Aziz Saadaoui. (2022, 28 febrero). https://github.com/svdwi/OSWE-Labs-Poc

 

Alejandro Rueda Romero – Ingeniero en Ciberseguridad