07-25: CCTV Manager
Writeup for CCTV Manager (Web) - YesWeHack Dojo (2025) π
Description
During a pentest, we discovered a rare custom Linux distro running a CCTV management program that seems to be stuck in a boot process. If we can upload a custom firmware, we should be able to get a remote code execution (RCE) on the CCTV. We leave the rest to you.
Solution
In this writeup, we'll review the latest YesWeHack Dojo challenge, created by Brumens π
Follow me on Twitter and LinkedIn (and everywhere else πͺ) for more hacking content! π₯°
Source code review
First thing to note is the server imports yaml and jinja2, this should make us think about deserialization and/or SSTI vulnerabilities. Starting with the main function, we can see:
A token is generated based on the current time
The script takes 2 parameters from the user;
yamlandtokenThe generated token is compared to the user-supplied one
If the tokens match:
The user-supplied YAML config is loaded
A firmware object is created, using the result of the previous operation
An
update()function is called on the firmware object
Finally, the template is rendered
We'll want to know how the token is generated, since this is the first hurdle to overcome.
It makes a 16-char hex string using the seed (current time epoch). Obviously the current time is predictable, so this is a weak implementation, we'll bare this in mind for later.
Finally, on the server-side we have the Firmware class, this is the object that will be created from our YAML config (if we get the token right).
So, where is the flag? We need to also check the challenge setup code, and will find the flag is in an environment variable.
I'll not go through the rest of the code, but note that a model variable is rendered but not actually defined anywhere in the challenge.
Testing functionality
As usual, we start by testing the basic functionality and see how it works. Enter some random parameters in the two form fields and we [obviously] get an authorisation error because "meow" isn't a valid token, or even in the correct format π±
We need to overcome this before we proceed, since the YAML config is not loaded at all without a valid token!
Exploit 1: Weak PRNG
To test this I checked the HTTP request in burp and saw the POST params are token and yaml. We want to get the server time (may not be in sync with ours) so we can do that by making a request and retrieving the date from the response header.
Then, we generate a token using the same process as the server. There can be some timing issues here so I used a script that would run 5 times, incrementing the time by 1 second until it matches. Note that there is some rate-limiting so we add a slight delay between attempts.
Run the script and we see the token will match.
Exploit 2: YAML Deserialization
Now we need to exploit the YAML deserialization. I wasted an embarrassing amount of time on this stage because I assumed we needed to populate the model variable with the result (reading flag ENV VAR), as this is what's displayed on the page. Turns out the solution was much simpler than that, we can just import os.system and then cat $FLAG.
Note that the server code requires a firmware element.
We also need a version element, and we'll replace the update function with the code we want to execute.
Putting that all together, I used a YAML payload like:
Tying this altogether, we will generate our token and then submit the RCE payload with the following PoC. It can also be a good idea to add a proxy option so that you can review request/response in burp suite for debugging purposes.
We run the exploit script and receive the flag π
π©Flag: FLAG{M4lware_F1rmw4r3_N0t_F0und}
Remediation
Never use
yaml.load()on untrusted input, useyaml.safe_load()instead to prevent code execution.Don't allow user-supplied YAML to populate fields that get passed into executable functions or object methods.
Remove dangerous loaders (
yaml.Loader,yaml.FullLoader) from any deserialisation path.Avoid predictable token generation (
random.seed(time)), use cryptographically secure randomness likesecrets.token_hex().Sanitise or restrict access to
os.environ, and never expose sensitive data via global template variables.
Summary (TLDR)
Weak PRNG let us guess a time-based token which unlocks YAML deserialisation. We can then replace the update function with an RCE payload (os.system("cat $FLAG")) to dump the flag in the response.
Last updated