En este artículo se presentará un ejemplo de cómo explotar una vulnerabilidad compleja partiendo del código fuente. Se realizará en primer lugar una búsqueda dentro del propio código, partiendo de algunas técnicas ya vistas, y partir de lo que se vaya encontrando se debe ir probando las correspondientes explotaciones. Para este caso se muestra un caso de la aplicación de ATutor.

Como siempre ocurre cuando tenemos acceso al código fuente, primero nos gusta echar un vistazo y tener una idea de la aplicación. ¿Cómo está organizada? ¿Podemos identificar algún estilo de codificación que pueda ayudarnos con las búsquedas de cadenas en la base de código? ¿Hay algo más que pueda ayudarnos a racionalizar y minimizar la cantidad de tiempo que necesitamos para investigar adecuadamente nuestro objetivo?

 

Pasos para realizar la explotación mediante el código

Nos dimos cuenta de que era bastante fácil identificar todas las páginas web de ATutor accesibles al público. Más concretamente, todas las páginas que no requieren autenticación contienen la siguiente línea en su código fuente:

$_user_location = ‘public’;

Es importante analizar siempre primero las porciones de código que no requieren autenticación, ya que son las más sensibles a los ataques, y cualquiera puede llegar a ellas.

Una vulnerabilidad que pueda acceder a la porción no autenticada del código nos permitirá obtener un punto de apoyo inicial en el sistema, que luego será escalado explotando otras vulnerabilidades en las secciones protegidas de la aplicación.

Con esto en mente, decidimos enumerar todas las páginas a las que podíamos acceder sin autenticación utilizando una búsqueda grep y utilizar los resultados como punto de partida para nuestro análisis.

La siguiente búsqueda grep te permitirá repetir este proceso por ti mismo:

[email protected]:~$ grep -rnw /var/www/html/ATutor -e “^.*user_location.*public.*” –color

Aunque esta búsqueda detectó algunos falsos positivos, terminamos con un subconjunto de aproximadamente 85 páginas web de ATutor. Dado el hecho de que ATutor utiliza una base de datos, se decidió empezar a buscar a buscar vulnerabilidades de inyección SQL tradicionales en estas páginas o en funciones llamadas directamente desde estas páginas.

Después de pasar algún tiempo haciéndolo, descubrimos un hallazgo potencialmente interesante. Veamos el código encontrado en /var/www/html/ATutor/mods/_standard/social/index_public.php:

14: $_user_location = ‘public’;

15:

16: define(‘AT_INCLUDE_PATH’, ‘../../../include/’);

17: require(AT_INCLUDE_PATH.’vitals.inc.php’);

18: require_once(AT_SOCIAL_INCLUDE.’constants.inc.php’);

19: require(AT_SOCIAL_INCLUDE.’friends.inc.php’);

20: require(AT_SOCIAL_INCLUDE.’classes/PrivacyControl/PrivacyObject.class.php’);

21: require(AT_SOCIAL_INCLUDE.’classes/PrivacyControl/PrivacyController.class.php’);

La variable $_user_location indica la accesibilidad pública y después de revisar los archivos de las declaraciones “require” así como el resto de index_public.php, verificamos que no hay código de autenticación. Además, accediendo a esta página web a través de un navegador confirmamos que podemos llegar a esta sección sin autenticación, mediante http://autor/ATutor/mods/_standard/social/index_public.php

Inspeccionando index_public.php, vemos comprobaciones de las variables GET p y rand_key, pero nada que parece impedirnos llegar a la primera sentencia if de la línea 38, que es donde las cosas se ponen un poco más interesantes.

23: if(isset($_POST[‘rand_key’])){

24: $rand_key = $addslashes($_POST[‘rand_key’]); //should we excape?

25: }

26: //paginator settings

27: if(isset($_GET[‘p’])){

28: $page = intval($_GET[‘p’]);

29: }

30: if (!isset($page)) {

31: $page = 1;

32: }

33: $count = (($page-1) * SOCIAL_FRIEND_SEARCH_MAX) + 1;

34: $offset = ($page-1) * SOCIAL_FRIEND_SEARCH_MAX;

35:

36:

37: //if $_GET[‘q’] is set, handle Ajax.

38: if (isset($_GET[‘q’])){

39: $query = $addslashes($_GET[‘q’]);

40:

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++;

56: }

57: echo ”;

58: }

59: exit;

60: }

El código primero comprueba si el parámetro GET q está establecido (línea 38) y si lo está, el valor que contiene es aparentemente saneado usando la función addslashes (línea 39). Inmediatamente después, nuestro valor controlado por el usuario se pasa a la función searchFriends (línea 42).

La lectura del código anterior debe hacer que te detengas por un momento. Cada vez que veamos nombres de variables nombres de variables como query o qry, o nombres de funciones que contienen la cadena search, nuestro primer instinto debería ser seguir el camino y ver a dónde nos lleva el código. Puede que no nos lleve a nada o puede que nos lleve a un código que maneje adecuadamente los datos controlados por el usuario, dejándonos sin nada con lo que trabajar.

Sin embargo, incluso en el peor de los casos, podríamos aprender cómo la aplicación maneja la entrada del usuario lo que nos puede ahorrar tiempo más adelante cuando nos encontremos con situaciones similares.

Dicho esto, vamos a seguir esta llamada a la función y ver con qué nos encontramos. Una rápida búsqueda grep como la siguiente nos ayuda a encontrar la implementación de la función searchFriends.

[email protected]:~$ grep -rnw /var/www/html/ATutor -e “function searchFriends” –color

./mods/_standard/social/lib/friends.inc.php:260:function searchFriends($name,

$searchMyFriends = false, $offset=-1){

Veamos cómo se implementa la función searchFriends() en friends.inc.php.

260: function searchFriends($name, $searchMyFriends = false, $offset=-1){

261: global $addslashes;

262: $result = array();

263: $my_friends = array();

264: $exact_match = false;

265:

266: //break the names by space, then accumulate the query

267: if (preg_match(“/^\\\\?\”(.*)\\\\?\”$/”, $name, $matches)){

268: $exact_match = true;

269: $name = $matches[1];

270: }

271: $name = $addslashes($name);

272: $sub_names = explode(‘ ‘, $name);

273: foreach($sub_names as $piece){

274: if ($piece == ”){

275: continue;

276: }

Si miramos al principio del siguiente código, podemos ver qué $addslashes aparece de nuevo, indicando que probablemente tendremos que lidiar con algún tipo de sanitización. En la línea 271, vemos que el intento de sanitización ocurre como se esperaba. Luego, en la línea 272, nuestra variable $nombre, controlada por el usuario, se descompone en un array llamado $subnombres, utilizando un espacio como separador.

278: //if there are 2 double quotes around a search phrase, then search it as

if it’s “first_name last_name”.

279: //else, match any contact in the search phrase.

280: if ($exact_match){

281: $match_piece = “= ‘$piece’ “;

282: } else {

283: //$match_piece = “LIKE ‘%$piece%’ “;

284: $match_piece = “LIKE ‘%%$piece%%’ “;

285: }

286: if(!isset($query )){

287: $query = ”;

288: }

289: $query .= “(first_name $match_piece OR second_name $match_piece OR

last_name $match_piece OR login $match_piece ) AND “;

290: }

Encontramos que, en cada iteración, la variable $piece está siendo concatenada en una cadena que contiene una palabra clave SQL LIKE (línea 284). Finalmente, nuestra variable $match_piece semicontrolada es incorporada a la consulta SQL parcial (variable $query) en la línea 289.

337: $sql = ‘SELECT * FROM ‘.TABLE_PREFIX.’members M WHERE ‘;

338: if (isset($_SESSION[‘member_id’])){

339: $sql .= ‘member_id!=’.$_SESSION[‘member_id’].’ AND ‘;

340: }

341: }

342: $sql = $sql . $query;

343: if ($offset >= 0){

344: $sql .= ” LIMIT $offset, “. SOCIAL_FRIEND_SEARCH_MAX;

345: }

346:

347: $rows_members = queryDB($sql, array());

De nuevo, recordar que la advertencia devuelta es el resultado de la directiva PHP display_errors esté activada. En un entorno de producción, esto rara vez ocurre y no se puede confiar en ello.

Sin embargo, 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’];

En el siguiente articulo se continuará explicando los siguientes pasos para obtener la información de la base de datos, aprovechando los visto hasta ahora para hacer un SQL Injection, y el correspondiente script en Python para ejecutar el exploit para este caso concreto.

 

Alejandro Rueda Romero – Ingeniero en Ciberseguridad

 

BIBLIOGRAFÍA

  • Aziz Saadaoui. (2022, 28 febrero). https://github.com/svdwi/OSWE-Labs-Poc