
Task 11 - Persistence
Local File Inclusion+Unsafe YAML Deserialization leads to Remote Code Execution
Category: Web
Difficulty: Medium
Description:
After the notorious malware strike on the Virelia Water Control Facility, phantom alerts and erratic sensor readings plague a system that was supposed to be fully remediated.
As a Black Echo red-team specialist, you must penetrate the compromised portal, unravel its hidden persistence mechanism, and neutralise the backdoor before it can be reactivated. You can access the portal via: http://MACHINE_IP:8080/
Methodology:
From the challenge description we know that port 8080 holds what we need - So, no need to do port scanning!
Let's break it down - one step at a time.
Step 1: Use the app as intended
Spawn the target machine by clicking on the Start Machine and wait until the machine is fully up(it should take no longer than 3-4 minutes).
Now access the target web app by visiting http://<your-machine-ip>:8080 as stated in the challenge description. - (Remember to replace <your-machine-ip> with the actual machine IP.)
Spend some time in understanding the app logic and what is its intended use.
Step 2: Information Gathering
After poking around for some time, we can clearly see that there are four main sections available on the target portal as follows: 1. Dashboard - Includes a couple of internal IPs 2. Config Updater - (Potentially) allows uploading new YAML config 3. Logs - Includes some debug info and file paths. 4. Telemetry - For viewing internal OT sensor data in the form of a chart.
Step 2: Information Analysis:
Out of the four discovered sections only two of them stand out as potential entry-points for our attack because:
1. Config Updater consists of a HTML form which (apparently) allows us to update the portal's YAML config file. Maybe we can upload a RCE exploit or other potentially dangerous files? But unfortunately, Upon submitting the form we get a 403 Access Denied error so we will leave it as is for now.
2. Logs section shows us some debug logs which reveal:
A password-like string:
secr3tTM192d390Full Path to the app's source code:
/opt/hmi/app.pyFull path to another Python file:
/opt/hmi/updater.py(Probably, the source code for YAML config updater).Examining the URL for Logs section reveals that a query parameter named
nameis being used to read the logs like:http://<your-machine-ip>:8080/logs/view?name=debug.log- Maybe we can try path traversal?
Step 3: LFI Exploitation:
We start by testing the
nameparameter for LFI in the Logs section.If we change the value of
nameparameter fromdebug.logtonothing.logit displays theLog Not Foundwith status code404- That was expected, nah?Now we try to change the value to an obsolete path like the one we discovered earlier(in step 2)
/opt/hmi/app.py. It threw another error:No logs foundwith a400 Bad Requeststatus code. Did you notice the difference in response? I did! This means the app is treatingnothing.logand/opt/hmi/app.pydifferently but why?Maybe coz of the extension? Let's try changing the
nameparam's value to/opt/hmi/app.logbut it again threw the same400 Bad Requesterror. Perhaps, the app is giving the error due the forward slash (/) in the value?Let's try changing the value to
\opt\hmi\app.py(now with backslash) and we get our initial404 Not Founderror back. This means the app is using some kind of black-list but after some trial and error we realize that we only get the400 Bad Requesterror code when using the forward slash in thenameparameter's value and not for any other symbol including%Let's try to bypass the blacklist by URL encoding the file path as we are allowed to use the
%symbol. Try changing the value to%2fopt%2fhmi%2fapp.pyand boom! We get access to full app's source code. Similarly, we can change the value to%2fopt%2fhmi%2fupdate.pyto getConfig Updater's source code.
Step 4: Reviewing The Source Code:
Upon reviewing the source code of
app.pywe can clearly see that the app gives a status code when a slash\is found in thenameparameter's value.The request to
/config/updateis handled by a function which checks for: 1.X-FTWheader with a hard-coded secret value(which we found in step 2 as well) 2.Content-typeheader with a value ofapplication/x-yaml3. and then passes therequest bodyas an argument to another function defined inupdate.pyfile.Upon viewing the source code of
update.pywe notice that the updater function takes our payload from the request body and tries to parse it as YAML usingyaml.UnsafeLoaderYAML loader.The config updater function in
update.pyalso writes the output of the YAML parsing to a log file namedloader.log.
Step 5: RCE Exploitation
If we do a bit of research on the YAML loader being used we come to know that it's prone to
Unsafe YAML Deserializationattacks leading toRemote Code ExecutionLet's try uploading a basic YAML deserialization to RCE payload:
The HTTP request to upload our payload will look like this:
After sending the request, visit
http://<your-machine-ip>:8080/logs/view?name=loader.log. At the bottom we can see the output of the YAML deserializer but it seems like the output of command is notreturnedback due to the nature ofsubprocess.PopenLet's try to get reverse shell using this payload:
First setup a listener on our attack machine and then send the request above with the updated payload like:
We should soon receive a reverse shell on our listener(if everything was setup correctly). We can list the files using
lscommand then usecat *.txtcommand to print the flag
Flag: THM{Sn34ky_B4ckd0or_23144}
That's all for today!
I also write on Medium and regularly share my experiences/tips about Bug Bounty, and CTFs, on LinkedIn. Feel free to reach out if you need any help!
Last updated