PyCGI: From Nginx Path-Traversal to RCE; bi0s CTF 2022
Table of Contents
This year I contributed 2 challenges to bi0s CTF (formerly InCTF Internationals).
In this post, we discuss the intended solution to PyCGI challenge. The source code of this challenge can be downloaded from here.
#
Analysis
We are provided with 2 attachments for this challenge:
- Dockerfile
- Nginx.conf
Looking into the Nginx configuration, we can find that there is a potential path traversal in the /static
endpoint. You can read more about this here.
|
|
Upon opening the challenge, we are presented with a directory indexing.
Accessing the cgi-bin
directory requires authentication.
Inside the templates folder we have an index.html
file that discloses a filename - search_currency.py
.
#
Exploit
##
Exploiting File Read
The path traversal can be exploited with:
|
|
With this, we can retrieve and inspect some known files from the server.
##
Gaining access to the service
On exploiting the Nginx path traversal, we can read docker-entrypoint.sh
.
docker-entrypoint.sh
is a shell script that is typically used as the entrypoint for a Docker container. The purpose of the entrypoint script is to set up the environment and perform any necessary tasks before the main command or application is run.
The script does the following:
- It moves the file
flag.txt
to a new random location. - The htpasswd command is used to create user named
admin
in the/etc/.htpasswd
file with a specified password. This is used to protect the directory. - Spawns Nginx and FCGI.
The password specified in the htpasswd command is a non-printable character.
|
|
We can use this to access the service.
#
Understanding the service
We can use the path traversal vulnerability to read the contents of search_currency.py
.
|
|
|
|
This script reads data from the CSV file and filters it based on a currency_code
parameter, and returns the filtered data in the form of an HTML table in the HTTP response.
##
Code Injection ?
Upon looking closely, we find that the currency_code
is directly passed into df.query
. This appears similar to an SQL Injection.
If we take a look at the implementation of DataFrame.query
we can see that it uses DataFrame.eval
internally. DataFrame.eval
is considered dangerous if user-controllable input is passed, as stated in the documentation.
##
Exploiting DataFrame.query
From the documentation, it is clear that it is possible to refer to variables in the environment by prefixing them with an ‘@’ character like @a + b
Example:
|
|
With this, it is now possible to access any global variable, and even invoke functions.
|
|
##
Escalating to RCE
There could be multiple ways of gaining an RCE at this point. Here we look into two approaches:
###
Using Pandas
Pandas is a huge library supporting various functions. We could directly use this to gain an RCE. One possible solution is to use pandas.read_pickle.
Example:
We prepare a pickled payload using the below code:
|
|
The output.exploit
file can be then hosted on a server. The below payload can be used to trigger pickle deserialization on the server, thus running our exploit script.
|
|
|
|
###
Using Chains
|
|
#
Flag
|
|