James Cao
DreamHack - crawling

DreamHack - crawling Web Challenge Write-up

Room / Challenge: crawling (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: crawling (web) Link: https://dreamhack.io/wargame/challenges/274 Level: 2 Date: 18-11-2025 Goal Leveraging the crawling service to get access to /admin page and get the flag. My Solution The web app is simple with the crawling logic, the main logic is in this code: def check_get(url): ip = lookup(urlparse(url).netloc.split(':')[0]) if ip == False or ip =='0.0.0.0': return "Not a valid URL." res=requests.get(url) if check_global(ip) == False: return "Can you access my admin page~?" for i in res.text.split('>'): if 'referer' in i: ref_host = urlparse(res.headers.get('refer')).netloc.split(':')[0] if ref_host == 'localhost': return False if ref_host == '127.0.0.1': return False res=requests.get(url) return res.text It doesn’t allow us to have ip address to 0.0.0.0, also there is a check of IP: ...

November 18, 2025 · 2 min
DreamHack - Tomcat Manager

DreamHack - Tomcat Manager Web Challenge Write-up

Room / Challenge: tomcat-manager (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: tomcat-manager (web) Link: https://dreamhack.io/wargame/challenges/248 Level: 2 Date: 17-11-2025 Goal Examining the code and leverage LFI and RCE to capture the flag. My Solution The source code has the ROOT.war besides Dockerfile and tomcat-users.xml. The tomcat-users.xml content: <?xml version="1.0" encoding="UTF-8"?> <tomcat-users xmlns="http://tomcat.apache.org/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd" version="1.0"> <role rolename="manager-gui"/> <role rolename="manager-script"/> <role rolename="manager-jmx"/> <role rolename="manager-status"/> <role rolename="admin-gui"/> <role rolename="admin-script"/> <user username="tomcat" password="[**SECRET**]" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script" /> </tomcat-users> It suggests that we have to find the password for user tomcat to get access to the code, let’s examine the source in ROOT.war. I use JD-GUI: ...

November 17, 2025 · 2 min
DreamHack - Relative Path Overwrite

DreamHack - Relative Path Overwrite Web Challenge Write-up

Room / Challenge: Relative Path Overwrite (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: Relative Path Overwrite (web) Link: https://dreamhack.io/wargame/challenges/439 Level: 2 Date: 17-11-2025 Goal Injecting XSS and leveraging Relative Path to get the flag. My Solution The app has the vuln.php where we can inject XSS Scripting: <script src="filter.js"></script> <pre id=param></pre> <script> var param_elem = document.getElementById("param"); var url = new URL(window.location.href); var param = url.searchParams.get("param"); if (typeof filter !== 'undefined') { for (var i = 0; i < filter.length; i++) { if (param.toLowerCase().includes(filter[i])) { param = "nope !!"; break; } } } param_elem.innerHTML = param; </script> However it has a blacklists in filter.js: ...

November 17, 2025 · 1 min
DreamHack - easy-login

DreamHack - easy-login Web Challenge Write-up

Room / Challenge: easy-login (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: easy-login (web) Link: https://dreamhack.io/wargame/challenges/1213 Level: 2 Date: 17-11-2025 Goal Login as admin to get the flag. My Solution This is the PHP code in index.php which validates our credentials: <?php function generatePassword($length) { $characters = '0123456789abcdef'; $charactersLength = strlen($characters); $pw = ''; for ($i = 0; $i < $length; $i++) { $pw .= $characters[random_int(0, $charactersLength - 1)]; } return $pw; } function generateOTP() { return 'P' . str_pad(strval(random_int(0, 999999)), 6, "0", STR_PAD_LEFT); } $admin_pw = generatePassword(32); $otp = generateOTP(); function login() { if (!isset($_POST['cred'])) { echo "Please login..."; return; } if (!($cred = base64_decode($_POST['cred']))) { echo "Cred error"; return; } if (!($cred = json_decode($cred, true))) { echo "Cred error"; return; } if (!(isset($cred['id']) && isset($cred['pw']) && isset($cred['otp']))) { echo "Cred error"; return; } if ($cred['id'] != 'admin') { echo "Hello," . $cred['id']; return; } if ($cred['otp'] != $GLOBALS['otp']) { echo "OTP fail"; return; } if (!strcmp($cred['pw'], $GLOBALS['admin_pw'])) { require_once('flag.php'); echo "Hello, admin! get the flag: " . $flag; return; } echo "Password fail"; return; } ?> The problem is this every new request will be compared to new password and OTP so it is impossible to guess/brute-force the creds: ...

November 17, 2025 · 2 min
DreamHack - weblog-1

DreamHack - weblog-1 Web Challenge Write-up

Room / Challenge: weblog-1 (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: weblog-1 (web) Link: https://dreamhack.io/wargame/challenges/71 Level: 2 Date: 14-11-2025 Goal Examining the source code and access.log to answer questions then get the flag. My Solution This challenge is a web challenge with a mix with forensics challenge. First question: Please enter the password for the admin account that was stolen by the attacker. In the access.log scroll down we can see a bunch of requests of blind SQL Injection: ...

November 14, 2025 · 3 min
DreamHack - Client Side Template Injection

DreamHack - Client Side Template Injection Web Challenge Write-up

Room / Challenge: Client Side Template Injection (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: Client Side Template Injection (web) Link: https://dreamhack.io/wargame/challenges/437 Level: 2 Date: 14-11-2025 Goal Bypassing CSP rules and get the flag with XSS. My Solution This challenge is similar to CSP Bypass and DOM XSS, however the CSP policy is different: @app.after_request def add_header(response): global nonce response.headers['Content-Security-Policy'] = f"default-src 'self'; img-src https://dreamhack.io; style-src 'self' 'unsafe-inline'; script-src 'nonce-{nonce}' 'unsafe-eval' https://ajax.googleapis.com; object-src 'none'" nonce = os.urandom(16).hex() return response The app accepts script from https://ajax.googleapis.com, this is a huge security vulnerability we can check it in CSP Evaluator ...

November 14, 2025 · 1 min
DreamHack - file-csp-1

DreamHack - file-csp-1 Web Challenge Write-up

Room / Challenge: file-csp-1 (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: file-csp-1 (web) Link: https://dreamhack.io/wargame/challenges/36 Level: 2 Date: 14-11-2025 Goal Crafted the correct CSP satisfying the needs to get the flag. My Solution There are 3 routes in this challenge /test, /live and /verify. The /verify is the route we need to satisfy to get the flag: @APP.route('/verify', methods=['GET', 'POST']) def verify_csp(): global CSP if request.method == 'POST': csp = request.form.get('csp') try: options = webdriver.ChromeOptions() for _ in ['headless', 'window-size=1920x1080', 'disable-gpu', 'no-sandbox', 'disable-dev-shm-usage']: options.add_argument(_) driver = webdriver.Chrome('/chromedriver', options=options) driver.implicitly_wait(3) driver.set_page_load_timeout(3) driver.get(f'http://localhost:8000/live?csp={quote(csp)}') try: a = driver.execute_script('return a()'); except: a = 'error' try: b = driver.execute_script('return b()'); except: b = 'error' try: c = driver.execute_script('return c()'); except Exception as e: c = 'error' c = e try: d = driver.execute_script('return $(document)'); except: d = 'error' if a == 'error' and b == 'error' and c == 'c' and d != 'error': return FLAG return f'Try again!, {a}, {b}, {c}, {d}' except Exception as e: return f'An error occured!, {e}' return render_template('verify.html') The /test and /live is where we can use to test our payloads. The app requirements is we have to crafted the correct CSP policy which satisfy the needs in the csp.html: ...

November 14, 2025 · 3 min
DreamHack - web-deserialize-python

DreamHack - web-deserialize-python Web Challenge Write-up

Room / Challenge: web-deserialize-python (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: web-deserialize-python (web) Link: https://dreamhack.io/wargame/challenges/40 Level: 2 Date: 13-11-2025 Goal Leveraging insecure deserialization to retrieve the flag. My Solution The vulnerability is in the /check-session route: @app.route('/check_session', methods=['GET', 'POST']) def check_session(): if request.method == 'GET': return render_template('check_session.html') elif request.method == 'POST': session = request.form.get('session', '') info = pickle.loads(base64.b64decode(session)) return render_template('check_session.html', info=info) The server will loads whatever we pass to the session data: info = pickle.loads(base64.b64decode(session)) The vulnerability here is the pickle. Therefore, we can create a malicious payload through Python: ...

November 13, 2025 · 1 min
DreamHack - baby-sqlite

DreamHack - baby-sqlite Web Challenge Write-up

Room / Challenge: baby-sqlite (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: baby-sqlite (web) Link: https://dreamhack.io/wargame/challenges/1 Level: 2 Date: 13-11-2025 Goal Leveraging SQL Injection to bypass the check and get the flag. My Solution The app has the /login route: @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template('login.html') uid = request.form.get('uid', '').lower() upw = request.form.get('upw', '').lower() level = request.form.get('level', '9').lower() sqli_filter = ['[', ']', ',', 'admin', 'select', '\'', '"', '\t', '\n', '\r', '\x08', '\x09', '\x00', '\x0b', '\x0d', ' '] for x in sqli_filter: if uid.find(x) != -1: return 'No Hack!' if upw.find(x) != -1: return 'No Hack!' if level.find(x) != -1: return 'No Hack!' with app.app_context(): conn = get_db() query = f"SELECT uid FROM users WHERE uid='{uid}' and upw='{upw}' and level={level};" try: req = conn.execute(query) result = req.fetchone() if result is not None: uid = result[0] if uid == 'admin': return FLAG except: return 'Error!' return 'Good!' So here we have to login as admin to get the flag: ...

November 13, 2025 · 2 min
DreamHack - sql injection bypass waf Advanced

DreamHack - sql injection bypass waf Advanced Web Challenge Write-up

Room / Challenge: sql injection bypass WAF Advanced (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: sql injection bypass WAF Advanced (web) Link: https://dreamhack.io/wargame/challenges/416 Level: 2 Date: 12-11-2025 Goal Bypass WAF and use SQL Injection to get the flag My Solution The app.py has this WAF check: keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/', '\n', '\r', '\t', '\x0b', '\x0c', '-', '+'] def check_WAF(data): for keyword in keywords: if keyword in data.lower(): return True return False It is harder for us to make requests that we want to do some injection. This is what it looked like when we are blocked by WAF: ...

November 12, 2025 · 2 min