James Cao
DreamHack - Not-only

DreamHack - Not-only Web Challenge Write-up

Room / Challenge: Not-only (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: Not-only (web) Link: https://dreamhack.io/wargame/challenges/1619 Level: 2 Date: 21-11-2025 Goal Find the correct user and brute-force the password to get the flag. My Solution Based on the description: Find a user with admin rights! The user's password is a flag. The password format is a string containing numbers, uppercase and lowercase letters, and special characters. { } How many admin users are there? The flag format is DH{} We have some details. We have to: ...

November 21, 2025 · 2 min
DreamHack - crack crack crack it

DreamHack - [wargame.kr] crack crack crack it Web Challenge Write-up

Room / Challenge: [wargame.kr] crack crack crack it (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: [wargame.kr] crack crack crack it (web) Link: https://dreamhack.io/wargame/challenges/330 Level: 2 Date: 21-11-2025 Goal Brute-force the password and get the flag. My Solution Based on the description: oops, i forgot my password!! somebody help me T_T (i remember that my password begin with 'G4HeulB' and the other chars is composed of number, lower alphabet only..) notice: this .htaccess file is different each other IP Address. authentication have to be same ip plz, We can create our password generator with Python and use John-the-ripper to brute-force the password: ...

November 21, 2025 · 1 min
DreamHack - Hello, go!

DreamHack - Hello, Go! Web Challenge Write-up

Room / Challenge: Hello, go! (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: Hello, go! (web) Link: https://dreamhack.io/wargame/challenges/1999 Level: 2 Date: 20-11-2025 Goal Leveraging SSTI in Go template and get the flag. My Solution The app.go has a vulnerable code in how it renders our input: t, err := template.New("page").Parse( fmt.Sprintf(` <html> <body> <h1>Hello, %s!</h1> </body> </html>`, name)) The server gets our input and render it to the template, then it executes: ...

November 20, 2025 · 1 min
DreamHack - development-env

DreamHack - development-env Web Challenge Write-up

Room / Challenge: development-env (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: development-env (web) Link: https://dreamhack.io/wargame/challenges/783 Level: 2 Date: 20-11-2025 Goal The development environment source code is deployed in production, leverage this to get the flag. My Solution The source code has 2 main routes /validate, /: app.get('/', async (req, res) => { try { let token = req.cookies.auth || ''; const payloadData = await cryptolib.readJWT(token, 'FAKE_KEY'); if (payloadData) { userflag = payloadData['uid'] == 'admin' ? flag : 'You are not admin'; res.render('main', { username: payloadData['uid'], flag: userflag }); } else { res.render('login'); } } catch (e) { if (isDevelopmentEnv) { res.json(JSON.parse(parsetrace(e, { sources: true }).json())); } else { res.json({ message: 'error' }); } } }); app.post('/validate', async (req, res) => { try { let contentType = req.header('Content-Type').split(';')[0]; if ( ['multipart/form-data', 'application/x-www-form-urlencoded'].indexOf(contentType) === -1 ) { throw new Error('content type not supported'); } else { let bodyKeys = Object.keys(req.body); if (bodyKeys.indexOf('id') === -1 || bodyKeys.indexOf('pw') === -1) { throw new Error('missing required parameter'); } else { if ( typeof database[req.body['id']] !== 'undefined' && database[req.body['id']] === req.body['pw'] ) { if ( req.get('User-Agent').indexOf('MSIE') > -1 || req.get('User-Agent').indexOf('Trident') > -1 ) throw new Error('IE is not supported'); jwt = await cryptolib.generateJWT(req.body['id'], 'FAKE_KEY'); res.cookie('auth', jwt, { maxAge: 30000, }).send("<script>alert('success');document.location.href='/'</script>"); } else { res.json({ message: 'error', detail: 'invalid id or password' }); } } } } catch (e) { if (isDevelopmentEnv) { res.status(500).json({ message: 'devError', detail: JSON.parse(parsetrace(e, { sources: true }).json()), }); } else { res.json({ message: 'error', detail: e }); } } }); Based on the description, it is a hint that the development source code is published to production. We can find that vulnerable code: ...

November 20, 2025 · 3 min
DreamHack - Are you admin?

DreamHack - Are you admin? Web Challenge Write-up

Room / Challenge: are you admin? (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: are you admin? (web) Link: https://dreamhack.io/wargame/challenges/1922 Level: 2 Date: 19-11-2025 Goal XSS Scripting takes in place to get the flag. My Solution The app is vulnerable to XSS Scripting: @app.route("/intro", methods=["GET"]) def intro(): name = request.args.get("name") detail = request.args.get("detail") return render_template("intro.html", name=name, detail=detail) @app.route("/report", methods=["GET", "POST"]) def report(): if request.method == "POST": path = request.form.get("path") if not path: return render_template("report.html", msg="fail") else: parsed_path = urlparse(path) params = parse_qs(parsed_path.query) name = params.get("name", [None])[0] detail = params.get("detail", [None])[0] if access_page(name, detail): return render_template("report.html", message="Success") else: return render_template("report.html", message="fail") else: return render_template("report.html") @app.route("/whoami", methods=["GET"]) def whoami(): user_info = "" authorization = request.headers.get('Authorization') if authorization: user_info = b64decode(authorization.split('Basic ')[1].encode()).decode() else: user_info = "guest:guest" id = user_info.split(":")[0] password = user_info.split(":")[1] if ((id == 'admin') and (password == '[**REDACTED**]')): message = FLAG return render_template('whoami.html',id=id, message=message) else: message = "You are guest" return render_template('whoami.html',id=id, message=message) There are 3 routes in the app, /intro is where we use to inject Javascript, /report is to report the URL to the bot, /whoami is where we can use to make the bot to get the flag for us. ...

November 19, 2025 · 2 min
DreamHack - Relative Path Overwrite Advanced

DreamHack - Relative Path Overwrite Advanced Web Challenge Write-up

Room / Challenge: Relative Path Overwrite Advanced (Web) Metadata Author: jameskaois CTF: DreamHack Challenge: Relative Path Overwrite Advanced (web) Link: https://dreamhack.io/wargame/challenges/440 Level: 2 Date: 18-11-2025 Goal Leveraging Relative Path Overwrite and XSS Scripting to force the bot sending the flag to you. 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') { param = "nope !!"; } else { for (var i = 0; i < filter.length; i++) { if (param.toLowerCase().includes(filter[i])) { param = "nope !!"; break; } } } param_elem.innerHTML = param; </script> However it needs the filter to not undefined in order to run our param, currently filter is undefined by default: Here is the progress we have to do to exploit the app, first we have to set a filter=[] for the vuln page to prevent the nope !! of filter === 'undefined', then force the bot to make a request to our server with the document.cookie. ...

November 18, 2025 · 2 min
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