Metasploit 데이터를 Httpx로?
오랜만에 Metasploit 관련 글을 쓰는 것 같습니다. 다름이 아니라 netpen이라는 plugin을 하나 찾았는데, 이를 이용하면 Metasploit으로 수집한 정보를 가지고 nuclei나 zap/burp 등 다른 도구와 파이프 라인으로 구성해서 사용하기 좋아보였습니다.
Netpen
공식 플러그인은 아니고 wdahlenburg가 만들어둔 플러그인 스크립트로 metasploit에서 수집된 정보를 host:port
형태로 콤보 리스트를 만들어줍니다. 간단한 작업이지만 막상 metasploit으로 수집하고 이를 다시 파싱하려면 약간 귀찮은데요. 이 플러그인은 이러한 점을 딱 해결해줍니다.
- https://github.com/wdahlenburg/MSF-Plugins/blob/main/netpen.rb
Add plugin
repo에서 코드를 받아서 metasploit의 plugin 디렉토리에 넣어줍니다. 코드는 #netpen code부분에 추가해두었습니다.
wget -P /opt/metasploit-framework/embedded/framework/plugins/ \
https://raw.githubusercontent.com/wdahlenburg/MSF-Plugins/main/netpen.rb
이후 metasploit에서 load
명령으로 netpen 플러그인을 로드할 수 있습니다.
msf6> load netpen
[*] Successfully loaded plugin: Network Pentest Toolset
Test
Recon for testing
테스트를 위해 임의로 몇개 도메인을 nmap으로 포트스캐닝했습니다.
msf6 > db_nmap -PN www.hahwul.com dalfox.hahwul.com testphp.vulnweb.com
이후 services 명령으로 리스트를 살펴보면 80,443 등으로 수집된 것을 볼 수 있습니다.
msf6 > services
Services
========
host port proto name state info
---- ---- ----- ---- ----- ----
44.228.249.3 80 tcp http open
44.228.249.3 443 tcp https filtered
185.199.108.153 80 tcp http open
185.199.108.153 443 tcp https open
185.199.110.153 80 tcp http open
185.199.110.153 443 tcp https open
metasploit이 pentesting 도구로써는 좋지만, 웹 취약점을 테스팅하기에는 솔직히 어려운 부분들이 좀 많습니다. 그나마 CVE나 Exploit 기반 테스팅을 쉽게할 수 있다는 장점이 있었지만, projectdiscovery의 nuclei가 나온 이후 시점부턴 솔직히 좀 매력이 떨어지긴 합니다.
어쩄던 metasaploit에서 host:port
형태로 콤보 데이터를 뽑는건 조금 번거롭습니다.
Make combo
자 그럼 netpen을 이용해서 콤보 리스트를 뽑아봅시다. help를 보면 netpen에서 지원하는 커맨드를 확인할 수 있습니다.
msf6 > help
Network Pentest Toolset Commands
================================
Command Description
------- -----------
grab_all List all services in host:port format
grab_host_port List all related hosts in host:port format based on searchable parameters
grab_web List all web related hosts in host:port format to be passed into httprobe
list_services List all open services
기호에 맞게 사용하시면 되고, 테스트로는 grab_all로 리스트를 뽑아봅시다.
msf6 > grab_all -f host-ports.txt
이후 metasploit을 종료한 후 파일을 열어보면 콤보 리스트 형태로 저장된 것을 볼 수 있습니다.
Send httpx
자 익숙한 포맷(host:port)이 나왔으니 httpx로 전달해서 웹 서비스를 식별해봅시다.
cat host_ports.txt | httpx -sc -title
Netpen code
module Msf
class Plugin::NetPen < Msf::Plugin
class NetPenDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
'Network Pentest Toolset'
end
def commands
{
'grab_web' => 'List all web related hosts in host:port format to be passed into httprobe',
'grab_all' => 'List all services in host:port format',
'grab_host_port' => 'List all related hosts in host:port format based on searchable parameters',
'list_services' => 'List all open services'
}
end
def cmd_grab_web(*_args)
results = []
# Grab all services matching 'http' type
http_services = framework.db.services.where(state: 'open').select { |s| !s.name.nil? and s.name.include? 'http' }
http_services.each do |h|
host = framework.db.hosts(id: h.host_id)[0]
ip = host.address
results << "#{ip}:#{h.port}"
end
# Grab all hosts on port 80 and 443
web_services = framework.db.services.where(state: 'open').select { |s| s.port == 80 || s.port == 443 }
web_services.each do |w|
host = framework.db.hosts(id: w.host_id)[0]
ip = host.address
results << "#{ip}:#{w.port}"
end
results.uniq!
results.each do |r|
print "#{r}\n"
end
end
def cmd_grab_all(*args)
results = []
opts = Rex::Parser::Arguments.new(
'-f' => [false, 'Optionally write host:port combos to file'],
'-h' => [false, 'Display help']
)
file = nil
help = false
opts.parse(args) do |opt, idx, _val|
case opt
when '-h'
help = true
when '-f'
file = args[idx + 1]
end
end
if help
print_line('Usage: grab_all [-f combos.txt]')
return
end
# Grab all services matching 'http' type
services = framework.db.services.where(state: 'open')
services.each do |s|
host = framework.db.hosts(id: s.host_id)[0]
ip = host.address
results << "#{ip}:#{s.port}"
end
results.uniq!
if file.nil?
results.each do |r|
print "#{r}\n"
end
else
fp = File.open(file, 'w')
results.each do |r|
fp.write("#{r}\n")
end
fp.close
end
end
def cmd_grab_host_port(*args)
opts = Rex::Parser::Arguments.new(
'-S' => [false, 'Search for a service string'],
'-p' => [false, 'Inlude specific ports in results (Ex: 80,443-445)']
)
query = nil
ports = nil
opts.parse(args) do |opt, idx, _val|
case opt
when '-S'
query = args[idx + 1]
when '-p'
ports = args[idx + 1]
end
end
if query.nil? && ports.nil?
print_line('Usage: grab_host_port [-S http] [-p 80,443]')
return
end
results = []
unless query.nil?
http_services = framework.db.services.where(state: 'open').select { |s| !s.name.nil? and s.name.include? query }
http_services.each do |h|
host = framework.db.hosts(id: h.host_id)[0]
ip = host.address
results << "#{ip}:#{h.port}"
end
end
unless ports.nil?
port_list = Rex::Socket.portspec_crack(ports)
port_services = framework.db.services.where(state: 'open').where.not(name: nil).select { |s| port_list.include? s.port }
port_services.each do |p|
host = framework.db.hosts(id: p.host_id)[0]
ip = host.address
results << "#{ip}:#{p.port}"
end
end
results.uniq!
results.each do |r|
print "#{r}\n"
end
end
def cmd_list_services(*_args)
services = framework.db.services.where(state: 'open').where.not(name: nil).pluck(:name).map { |s| s.sub('ssl/', '') }
service_dictionary = {}
services.each_with_index do |s, _i|
service_dictionary[s] = services.count(s)
end
service_dictionary = service_dictionary.sort_by { |_k, v| v }.reverse.to_h
service_dictionary.each do |k, _v|
print "#{k}\n"
end
end
end
def name
'Network Pentest Toolset'
end
def desc
'Toolset to assist with basic network pentest tools by leveraging the MSF DB'
end
def initialize(framework, opts)
super
add_console_dispatcher(NetPenDispatcher)
end
def cleanup
remove_console_dispatcher('Network Pentest Toolset')
end
end
end
References
- https://github.com/wdahlenburg/MSF-Plugins/blob/main/netpen.rb