Tags: python rce
Rating:
Copied from original writeup at [https://www.pmnh.site/post/ctf-deadsec-2023-trailblazer/](https://www.pmnh.site/post/ctf-deadsec-2023-trailblazer/)
## Summary
One of the things that I love about CTFs is when they provide challenges that don't require knowledge of weird language quirks or obscure exploits or (ugh) guesswork but instead just a clear head and some common sense. Kudos to the designer of the [DeadSec 2023 CTF](https://www.deadsec.xyz/) Trailblazer challenge, which offered exactly this type of problem.
## Recon
The Trailblazer challenge provided exactly one page to the site and no source code was provided. Visiting the home page of the site provided the following text content:
```
[0-9 a-z A-Z / " \+ , ( ) . # \[ \] =]
```
Visiting any other page of the site would result in the following `404` page:
![404 page](https://www.pmnh.site/images/ctf-2023-deadsec-trailblazer/404_page.png)
Interesting to note that the image appearing on the page is generated from the endpoint `/images/now` and appears to contain a timestamp.
One other observation is that the server is running the (Python) [waitress framework](https://github.com/Pylons/waitress), which we can see from the server headers:
```
Server: waitress
```
Now that we have a sense for the server and related software, let's solve this challenge!
## Analysis
The endpoint `now`, combined with the contents of the generated image, should be familiar to anyone who is at all familiar with the Python language, as being the default format of a `datetime` object, and the Python library function [`datetime.now`](https://docs.python.org/3/library/datetime.html#datetime.datetime.now) can be used to return the current timestamp:
```
Python 3.8.10 (default, Mar 13 2023, 10:26:41)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> str(datetime.datetime.now())
'2023-05-21 12:26:57.831648'
```
We note that this matches what we see in the generated image. This allows us to conclude that the solution path here is a sort of RCE which will lead to us changing the contents of the image. We can easily verify this by picking some other class-level functions in the `datetime` class such as `utcnow` or `today`, which will generate the same image.
## Leading to a Solution
So at this point we know:
* The image is being generated by Python code probably `eval`'ed from a string like this: `datetime.**last-path-segment**()`
* Certain characters are not allowed in the path segment (we assume this from the list of characters shown on the home page)
* Our goal is to read the content of `flag.txt` (this was later provided as a hint although I solved the challenge prior to this hint being available)
Let's see if we can chain a simple method call first, since the injection point ends with parens we know that the last part of our injection has to be a function that takes no parameters. So the following works:
```
/images/now().toordinal
```
This results in an image with the following content `738861`. So we've confirmed we can chain function calls as we had hoped, and the contents of the image will reflect the return value of the last function call (`toordinal` is a function on a `datetime` object as documented [here](https://docs.python.org/3/library/datetime.html#datetime.datetime.toordinal)).
If you don't want further spoilers, you can safely stop here and try to build the exploit chain yourself :grin:
## Reading a File
Finally, I had to come up with a way to read the content of the flag file and ensure the contents of the file fed into the method chain, since we can't inject carriage returns and other control structures due to the character set limitations. Also, we can't use a typical `__globals__` type injection because the `_` character is prohibited.
The path I took was to inject a Python [lambda function](https://www.w3schools.com/python/python_lambda.asp), which allows for arbitrary / simple inline code to be used to process typically an iterator such as an array or string. Typically these are used to perform some sort of processing on the input i.e. a transformation, but in this case we're just using it as a vehicle to inject arbitrary code.
Lambda functions can be used in many Python library functions, but [`map`](https://docs.python.org/3/library/functions.html#map) seemed like a logcal choice. Since `map` requires an iterable parameter, and we are starting from a `datetime` object, I decided to figure out how to get a `string` from the `datetime` and then pass the `string` to the `map` function. I built up the payload like so:
```
/images/now().strftime(%22aaa%22).title --> AAA
```
Remember we still have to end the injection with a parameterless function invocation, there are many on `string`. `strftime` on the `datetime` class was useful because it allows us to provide any arbitrary `string` as output. We pass this lambda result to `strftime` to get the string value added into the method chain. We iterate on a dummy array `[1]` so that the lambda function is executed exactly once:
```
/images/now().strftime(str(map(lambda a: a, [1]))).title --> '<Map Object At 0X7Fd1789Fd9A0>'
```
Oops! From this we can see our basic premise works, but we need to convert the `map` object (with a single element) to a printable string so we can see the result in the image output, we do this by wrapping it with the `str(list(...))` built-in functions:
```
/images/now().strftime(str(list(map(lambda a: a, [1])))).title --> '[1]`
```
Now we simply put an `open('flag.txt').read(100)` in the lambda and we should have our flag:
```
/images/now().strftime(str(list(map(lambda a: open("flag.txt").read(100), [1])))).title
```
And we see the flag is (partially) revealed!
![Partial Flag](https://www.pmnh.site/images/ctf-2023-deadsec-trailblazer/flag_in_image.png)
Further work was required to see the whole flag, this was made a little more painful because the font used in the image did not clearly indicate uppercase and lowercase letters. We'll leave this as an exercise to the reader, try reproducing this in your local Python CLI and see how you might iterate through the characters :smile:
Overall a super fun challenge that required no brute force or guesswork but just putting the pieces together. Thanks DeadSec!