De1CTF 2019 SSRF Me Writeup
This is my writeup for the SSRF Me
challenge. This challenge was part of De1CTF 2019.
We only got an URL that we should access as part of the challenge. Accessing that URL returns some python code.
Python code - server(click to expand)
We can notice some things by analysing the code:
- the ‘/’ endpoint returns the code, as we observed before
- the
/geneSign
endpoint will call the getSign function- The getSign function takes 2 arguments: param and action (both of them are strings)
- That function will also access the ‘secert_key’ variable which is randomly generated
- The function returns an MD5 hash of the string obtained by merging secert_key, param and action
- the
/geneSign
endpoint will set the action to ‘scan’ and let the user supply the value for ‘param’
- the
/De1ta
endpoint will create a Task object and return the result of its.Exec()
method- This endpoint allows the user to supply values for 3 variables
- action - by setting the ‘action’ cookie
- sign - by setting the ‘sign’ cookie
- param - by using an URL parameter named ‘param’
- It looks like the IP address of the user is also used (we can ignore this as it is used only for sandboxing purposes)
- Next, we should look at details about the Task class. It has a simple constructor that uses the values mentioned above and the Exec method. The Exec method is where the important stuff happens
- One of the first things that we notice in the Exec method is the
self.checkSign()
check. To avoid getting an error (code 500), the request should have a ‘sign’ cookie with the expected value - The expected value is a MD5 hash computed by calling the getSign method that was mentioned above. This means that we should be able to get a hash by using the
/geneSign
endpoint - The
/geneSign
endpoint has the value for ‘action’ set to ‘scan’. We should imitate that and set the action to ‘scan’ in our request too. As a result, if we chose a valid URL for ‘param’, generate a hash using the/geneSign
endpoint and then use that hash with the ‘scan’ action and the same parameter at the/De1ta
endpoint we can get a 200 (OK) response from the server - Now it is time to take a look at what the ‘scan’ action means for this program. After that check passes, we have two available actions:
- scan - takes the URL from ‘param’, open it and then put the first 50 characters from it into a file
- read - returns the content obtained by the last ‘scan’ action
- Another thing that should be mentioned here is that the check is performed using
in
. This means that the user-supplied string could contain both of these actions (e.g. “scanread” would be a valid input and it would trigger both actions) - The actions are always performed in the same order without taking into account the order of their keywords in the user-supplied string. So both “scanread” and “readscan” will produce the same result (a ‘scan’ action followed by a ‘read’ action)
- One of the first things that we notice in the Exec method is the
- This endpoint allows the user to supply values for 3 variables
- So in order to read the content of an arbitrary URL we should be able to use a ‘read’ action. However, we cannot obtain a valid hash from
/geneSign
because that will always have the action set to ‘scan’- But as I mentioned above, the order of the words ‘scan’ and ‘read’ in the string does not matter. This means that we could make the request to
/geneSign
using ‘read’ followed the the URL as the value for ‘param’. After the strings are merged this will be the same as if the action was “scanread” and the value of ‘param’ was the original URL that we wanted to access
- But as I mentioned above, the order of the words ‘scan’ and ‘read’ in the string does not matter. This means that we could make the request to
- Doing the actions mentioned above allowed me to access /etc/passwd as a PoC for this “exploit”. Howevere, as we expected, only the first 50 characters of the file were returned.
- To make things easier from here, I created a Python script that does all the required steps to read a file from the server
get_file.py (click to expand)
- Next, I tried to obtain the
./flag
file without success so I assumed that need a full path for that- For this, I tried to access
/proc/self/cmdline
to get some information about the current process (the working directory in particular) but the 50 character limit prevented me from obtaining relevant information - I also tried reading other files from the system or from /proc/self but I couldn’t get any useful information. An example of useless information that I got was that PID of the process and the fact that the server uses the uWSGI framework
- For this, I tried to access
- After a while I decided to take a step back and try going for
./flag
again but this time I tried to readflag
(without the ‘./’)- To my surprise, that worked and I obtained the flag for this challenge