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 1
Test for XPath injectionObservationI noticed the challenge hint explicitly called out XPath and the login page accepted free-form text input, which suggested the backend was embedding user-supplied strings directly into an XPath expression and could be broken with a mismatched quote.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: anythingbashcurl -X POST http://<server>/ --data "name=' or 'x'='x&password=pass"What didn't work first
Tried: Try a SQL injection payload like ' OR 1=1 - in the username field
SQL comment syntax (--) has no equivalent in XPath, so the trailing condition is never silenced. The backend XPath parser sees the literal - characters as part of the string and the query either errors or fails to match. XPath tautologies must close with a balanced string like or 'x'='x rather than a comment.
Tried: Submit a double-quote instead of a single quote to test injection: " or "x"="x
XPath string literals can be delimited by either single or double quotes, but the delimiter must match what the application used in its query template. If the server-side query wraps the input in single quotes, injecting double quotes does not break out of the string literal and the server returns a normal login failure with no injection effect. Observe the error or difference in response when using a single quote to identify the correct 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. The payload
' or '*'='xturns the query into:/users/user[name='' or *='x' and password='pass']The
*matches any node, and thecontains(.)function tests whether the current node contains a substring. These are the building blocks of an XPath injection exploit.Step 2
Brute-force the flag character by characterObservationI noticed the server returned a distinct 'right path' success message only when the injected payload matched, confirming a boolean oracle, which suggested I could use XPath's contains() function to extract 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(., 'picoCTF{h') would also work for prefix extraction, but contains() succeeds here because the script always grows from the known picoCTF{ left anchor, making the substring unique in practice. The real danger is switching to contains() without the left anchor - searching for a bare character like 'a' matches anywhere in any node's text and gives false positives for characters that appear in other fields like 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 uses a case-sensitive check such as 'right path' and the server response varies (for example 'Right Path' or a slightly different phrase), the oracle always returns false and the flag never grows. Printing r.text for the first few requests confirms the exact success string before automating the loop.
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.
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.