![]() |
| Ethical Hacking |
🛡️ Ethical Hacking — Day 17
SQL Injection Deep Dive: Detection, Confirmation & Secure Code Review
⚠️ LEARNING PURPOSE ONLY: Today's practical work is strictly for your local OWASP Juice Shop lab or another system you explicitly own or have permission to test. Do not apply these techniques to public websites, production databases, or other people's accounts.
Welcome to Day 17.
Yesterday you learned the fundamentals of SQL Injection. Today we'll move from "What is SQLi?" to:
How can I systematically determine whether an input is influencing a database query, without blindly attacking the application?
OWASP's current guidance identifies parameterized queries/prepared statements as the primary defense and recommends least privilege and appropriate allow-list validation as additional defenses. (OWASP Cheat Sheet Series)
🎯 Today's Objectives
You'll learn:
Baseline vs abnormal responses
SQLi indicators
Error-based analysis
Boolean-based concepts
Response comparison
Query parameters vs body parameters
Burp Repeater methodology
URL encoding
SQLi code review
Java/JDBC secure coding
Spring/JPA secure coding
Laravel/PHP secure coding
PostgreSQL/MySQL parameterization
Linux practical commands
Windows PowerShell practical commands
macOS practical commands
How to document a finding
How developers remediate SQL Injection
1. The SQL Injection Mental Model
Keep this model in your head:
USER INPUT
│
▼
WEB APPLICATION
│
▼
SQL QUERY
│
▼
DATABASE
│
▼
DATABASE RESULT
│
▼
HTTP RESPONSEYour job as a security learner is to understand:
Where does my input go?
↓
Does it remain data?
↓
Or can it influence SQL structure?2. Normal Application Behavior
Suppose a search endpoint receives:
q=laptopThe normal workflow might be:
Browser
↓
q=laptop
↓
Application
↓
Database query
↓
Matching products
↓
JSON responseRecord this before testing anything unusual.
This is your:
Baseline
3. What Is a Baseline?
A baseline is the application's normal behavior.
Example:
| Input | Status | Response | Length |
|---|---|---|---|
laptop | 200 | Normal results | 2,340 |
Then you can compare:
Normal
VS
TestWithout a baseline, it's difficult to determine whether something actually changed.
4. SQLi Indicators
Possible indicators include:
SQL/database error messages
Unexpected HTTP 500 responses
Significant response-length changes
Unexpected result counts
Different behavior for logically different inputs
Database-related information appearing in responses
But remember:
An indicator is not proof.
For example:
500 Internal Server Errorcould simply be an ordinary application bug.
OWASP's secure-code-review guidance also recommends preventing sensitive information from being disclosed through error responses. (OWASP Cheat Sheet Series)
5. Start Your Lab
Linux
docker psWindows
docker psmacOS
docker psIf Juice Shop is stopped:
docker start <container-id>Then verify:
Linux/macOS
curl -I http://127.0.0.1:3000Windows
curl.exe -I http://127.0.0.1:30006. Start Burp
Open:
Burp Suite
↓
Proxy
↓
Open BrowserVisit:
http://127.0.0.1:3000Keep your Burp scope limited to:
127.0.0.1:30007. Find a Search Request
Use Juice Shop normally.
Search for:
appleThen open:
Proxy
↓
HTTP historyFind the request generated by the search.
Your version may use a route similar to:
GET /rest/products/search?q=apple HTTP/1.1
Host: 127.0.0.1:3000Do not assume the endpoint.
Find the actual request in your installation.
8. Send It to Repeater
Right-click:
Send to RepeaterThen:
RepeaterSend the original request.
Record:
Method:
Endpoint:
Parameter:
Status:
Length:
Content-Type:9. Establish Your Baseline
For example:
Parameter:
q
Normal value:
apple
Status:
200
Length:
______
Result:
Normal product resultsNow you have something to compare against.
🧪 Practical Exercise 1
Ordinary Input Comparison
Test several normal values.
For example:
apple
test
phone
laptopRecord:
| Input | Status | Length | Results |
|---|---|---|---|
apple | ___ | ___ | ___ |
test | ___ | ___ | ___ |
phone | ___ | ___ | ___ |
laptop | ___ | ___ | ___ |
You're learning the application's normal behavior.
10. Special-Character Test
Inside your local lab, test a single quote:
'So conceptually:
q='Observe:
Status
Response length
Error
Response bodyDon't immediately assume SQLi.
11. Why Does a Quote Matter?
Suppose the developer wrote:
SELECT *
FROM products
WHERE name = 'USER_INPUT';The quotes define a SQL string.
If user input is concatenated directly into the query, a quote can potentially interfere with that structure.
This is why special characters can be useful diagnostic inputs.
12. Compare
Create:
| Test | Status | Length | Observation |
|---|---|---|---|
apple | 200 | ___ | Normal |
' | ___ | ___ | ___ |
Possible results:
Case A
Both return normal responsesInteresting, but not conclusive.
Case B
Quote causes validation errorCould indicate input validation.
Case C
Quote causes database-related errorWorth investigating.
Case D
Quote causes 500Interesting, but still not proof.
13. Error-Based Analysis
Suppose:
Normal:
200 OK
Quote:
500 Internal Server ErrorNow inspect the response carefully.
Look for:
SQL syntax
Database
SQLException
PostgreSQL
MySQL
SQLite
ORM
Stack traceIf sensitive internal details appear, document them—but don't exploit further.
14. Don't Over-Test
Suppose you discover:
q='causes a database error.
Don't immediately:
❌ Dump tables
❌ Extract passwords
❌ Extract customer data
❌ Modify database records
❌ Delete dataInstead:
Detect
↓
Confirm minimally
↓
Document
↓
Recommend fixProfessional testing is not about collecting the maximum amount of data.
15. Boolean-Based Concept
Now let's understand a more advanced idea.
Imagine an application evaluates:
Condition Aand:
Condition BIf the database evaluates them differently and the application's response changes accordingly, that behavior can indicate that input is influencing the query logic.
Conceptually:
Input
↓
SQL condition
↓
Database
├── TRUE
│ ↓
│ Response A
│
└── FALSE
↓
Response BThis is the basic idea behind blind/inferential SQL Injection.
OWASP classifies blind SQLi as an inferential technique where information is inferred from application/database behavior rather than directly returned. (OWASP Cheat Sheet Series)
16. Response Comparison
This is the skill I want you to develop.
Compare:
Status
Length
Headers
Body
Number of results
Error messages
Response timeExample:
| Test | Status | Length | Results |
|---|---|---|---|
| Baseline | 200 | 2,300 | 5 |
| Test A | 200 | 2,300 | 5 |
| Test B | 200 | 4,100 | 20 |
A significant difference deserves investigation.
17. But Differences Can Have Many Causes
A response change doesn't necessarily mean SQL Injection.
It could be:
Different search result
Caching
Application logic
Input validation
Database state
Random data
Authentication stateTherefore:
Evidence must be interpreted in context.
18. Query Parameter vs Body Parameter
SQL Injection can occur through different input locations.
Query parameter
GET /search?q=appleForm body
POST /search
q=appleJSON body
{
"q": "apple"
}HTTP header
Some application architectures may use request headers as inputs.
The important question is always:
Does untrusted input reach a database query unsafely?
19. JSON Example
Suppose your local lab produces:
POST /api/search HTTP/1.1
Content-Type: application/json
{
"search": "apple"
}The input is:
searchYou would analyze it the same way:
Normal
↓
Baseline
↓
Controlled test
↓
Compare20. URL Encoding
The browser may encode special characters.
For example:
'can appear as:
%27So you might see:
?q=%27rather than:
?q='This is why understanding HTTP encoding matters.
🧪 Practical Exercise 2 — Encoding
Test your local endpoint with:
q=appleThen:
q=%27Compare the results.
Record:
Raw input:
'
Encoded input:
%27
Same response?
Yes / No
Difference:
____________21. curl — Linux
Use your actual endpoint discovered in Burp.
For example:
curl -i "http://127.0.0.1:3000/rest/products/search?q=apple"Verbose:
curl -v "http://127.0.0.1:3000/rest/products/search?q=apple"Encoded character:
curl -i "http://127.0.0.1:3000/rest/products/search?q=%27"Only use the endpoint if it exists in your local Juice Shop instance.
22. curl — macOS
curl -i "http://127.0.0.1:3000/rest/products/search?q=apple"Verbose:
curl -v "http://127.0.0.1:3000/rest/products/search?q=apple"Encoded input:
curl -i "http://127.0.0.1:3000/rest/products/search?q=%27"23. curl — Windows
PowerShell:
curl.exe -i "http://127.0.0.1:3000/rest/products/search?q=apple"Verbose:
curl.exe -v "http://127.0.0.1:3000/rest/products/search?q=apple"Encoded:
curl.exe -i "http://127.0.0.1:3000/rest/products/search?q=%27"24. curl Through Burp
You can also send your local request through Burp.
Linux/macOS
curl -x http://127.0.0.1:8080 \
"http://127.0.0.1:3000/"Windows
curl.exe -x http://127.0.0.1:8080 `
"http://127.0.0.1:3000/"Flow:
curl
↓
Burp :8080
↓
Juice Shop :3000This is useful for understanding how command-line clients interact with your proxy.
25. Code Review — The Most Valuable Skill
You can often identify SQL Injection faster from source code than by testing.
Look for:
User Input
↓
String Concatenation
↓
SQL ExecutionFor example:
String sql =
"SELECT * FROM users WHERE email = '" + email + "'";🚨 This deserves review.
26. Safe Java Version
Use:
String sql =
"SELECT * FROM users WHERE email = ?";
PreparedStatement ps =
connection.prepareStatement(sql);
ps.setString(1, email);
ResultSet rs =
ps.executeQuery();The parameter is bound separately.
OWASP recommends this approach because parameterized queries keep SQL code and data separate. (OWASP Cheat Sheet Series)
27. Spring Boot/JPA
Since you work with Spring Boot, avoid dynamically constructing JPQL/HQL with user input.
❌ Unsafe pattern
String jpql =
"FROM User u WHERE u.email = '" + email + "'";
entityManager.createQuery(jpql);✅ Better
String jpql =
"FROM User u WHERE u.email = :email";
entityManager
.createQuery(jpql, User.class)
.setParameter("email", email);Or use a repository method:
Optional<User> findByEmail(String email);28. Laravel
❌ Unsafe
$sql = "SELECT *
FROM users
WHERE email = '$email'";✅ Parameterized
$sql = "SELECT *
FROM users
WHERE email = ?";
DB::select($sql, [$email]);✅ Query Builder
User::where('email', $email)->first();29. PostgreSQL
Use parameter placeholders supported by your database driver.
Conceptually:
SELECT *
FROM users
WHERE email = $1;Then:
$1 = user-provided emailThe important part isn't the placeholder syntax itself.
The important principle is:
SQL code ≠ User data30. MySQL
For drivers that use ? placeholders:
SELECT *
FROM users
WHERE email = ?;Bind the value separately.
Don't construct:
"... WHERE email = '" + email + "'"31. What About ORM?
ORMs such as:
Hibernate
JPA
Eloquent
SQLAlchemycan make secure database access easier.
But:
ORM does not automatically make every query safe.
Raw SQL and dynamic query construction can still introduce injection vulnerabilities.
OWASP explicitly notes that ORM/HQL usage can still be unsafe when dynamic query construction is used. (OWASP Cheat Sheet Series)
32. Dynamic ORDER BY
Here's an important special case.
Suppose the application lets the user select:
Sort:
Price
Name
DateYou generally cannot parameterize a SQL column identifier in exactly the same way as a normal value.
Instead, use an allow-list.
Example:
String column;
switch (sort) {
case "price":
column = "price";
break;
case "name":
column = "name";
break;
default:
throw new IllegalArgumentException();
}Then construct the query from known safe values.
OWASP recommends allow-list validation or redesign for SQL elements such as table/column names and sort directions where bind variables aren't applicable. (OWASP Cheat Sheet Series)
33. Why Escaping Isn't the Primary Defense
You may hear:
"Just escape the quotes."
Don't rely on this as your main defense.
OWASP classifies escaping all user input as strongly discouraged compared with parameterization because it is fragile and context/database dependent. (OWASP Cheat Sheet Series)
Prefer:
1. Parameterization
2. Safe stored procedures where appropriate
3. Allow-list validation where necessary
4. Least privilege34. Least-Privilege Database Account
Suppose your application only needs to read product data.
Its database account shouldn't automatically have:
DROP
ALTER
CREATE USER
GRANT
SUPERUSERpermissions.
Instead, give it only what it needs.
OWASP recommends least privilege as an additional defense so that even if a vulnerability exists, its impact is reduced. (OWASP Cheat Sheet Series)
35. Error Handling
Bad:
500
PostgreSQL error:
relation "users" does not exist
at /var/www/app/database/...Better:
500
Something went wrong.Detailed technical information should go to protected server-side logs rather than the user.
36. SQLi Testing Methodology
Memorize:
SCOPE
↓
Identify Input
↓
Baseline
↓
Harmless Variation
↓
Observe
↓
Compare Responses
↓
Investigate Signal
↓
Confirm Minimally
↓
Document
↓
Recommend FixThis is much better than:
"Try random payloads until something breaks."🧪 Day 17 Main Lab
Mission
Investigate one input parameter in your local Juice Shop installation.
Step 1
Start Juice Shop:
docker psStep 2
Open:
http://127.0.0.1:3000Step 3
Start Burp.
Step 4
Browse the application normally.
Step 5
Find an input that reaches the server.
Step 6
Capture it in:
Proxy → HTTP historyStep 7
Send it to:
RepeaterStep 8
Record baseline.
Step 9
Change one value.
Step 10
Test a harmless special character.
Step 11
Compare:
Status
Length
Body
Headers
ErrorsStep 12
Document your conclusion.
📊 Day 17 Investigation Sheet
╔══════════════════════════════════╗
║ DAY 17 SQLi LAB ║
╚══════════════════════════════════╝
Target:
127.0.0.1:3000
Feature:
________________________
Endpoint:
________________________
HTTP Method:
________________________
Parameter:
________________________
Normal Input:
________________________
Baseline Status:
________________________
Baseline Length:
________________________
Test Input:
________________________
Test Status:
________________________
Test Length:
________________________
Response Difference:
________________________
Database Error?
Yes / No
SQLi Indicator?
Yes / No / Unclear
Evidence:
________________________
Conclusion:
________________________
Recommended Fix:
________________________🔍 Day 17 Evidence Standard
Don't write:
"I found SQL Injection."
unless you actually have sufficient evidence.
Instead use:
Weak evidence
"A special character produced a different response."
Better evidence
"A special character consistently produces a database-related error while normal input produces a normal response."
Stronger conclusion
"Controlled testing demonstrates that user input changes database query behavior in the authorized lab."
Your report should distinguish:
Observationfrom:
Conclusion🧪 Practical Exercise — Code Review
Create these two files locally.
unsafe.java
String email = request.getParameter("email");
String sql =
"SELECT * FROM users WHERE email = '" + email + "'";
Statement statement =
connection.createStatement();
ResultSet result =
statement.executeQuery(sql);Identify the problem.
safe.java
String email = request.getParameter("email");
String sql =
"SELECT * FROM users WHERE email = ?";
PreparedStatement ps =
connection.prepareStatement(sql);
ps.setString(1, email);
ResultSet result =
ps.executeQuery();Identify why the second version is safer.
🧠 Day 17 Key Concept
Compare these:
❌ USER INPUT
↓
STRING CONCATENATION
↓
SQL
↓
DATABASEwith:
✅ SQL TEMPLATE
+
PARAMETER
↓
PREPARED STATEMENT
↓
DATABASEThat separation is the heart of SQL Injection prevention.
📝 Day 17 Assignment
Answer these:
1.
What is a baseline?
2.
Why should you establish a baseline before testing?
3.
Does a 500 response prove SQL Injection?
4.
Name five things you can compare between two HTTP responses.
5.
Why can URL encoding matter during testing?
6.
What is the difference between query parameters and JSON body parameters?
7.
What is error-based SQL Injection?
8.
What is blind/inferential SQL Injection?
9.
Why should SQL Injection findings be confirmed minimally?
10.
What is the primary defense against SQL Injection?
11.
Why isn't escaping user input the preferred primary defense?
12.
Why should a database account have minimum privileges?
13.
Can an ORM still contain SQL Injection vulnerabilities?
14.
How should dynamic sort fields be handled safely?
15.
Why should detailed database errors generally not be shown to users?
🏆 Day 17 Challenge
Create a SQL Injection Detection Flowchart:
User Input
↓
Baseline
↓
Controlled Variation
↓
Response Changed?
/ \
NO YES
│ │
Continue Investigate
↓
Database Indicator?
/ \
NO YES
│ │
Document Confirm
↓
Minimal Evidence
↓
Report
↓
FixThen write a one-paragraph report for your local lab.
🔐 Day 17 Ethical Rule
LEARNING PURPOSE ONLY: If your test produces unexpected database behavior, stop at the minimum evidence needed to understand the issue. Do not turn a controlled SQLi test into database extraction, credential harvesting, data modification, or destructive activity.
Professional security testing is:
Permission
↓
Scope
↓
Minimal Testing
↓
Evidence
↓
Report
↓
Remediation📚 Official References
✅ Day 17 Summary
Today you learned:
Baseline methodology
SQLi indicators
Error-based analysis
Boolean/blind SQLi concepts
Response comparison
Query/body parameters
URL encoding
Burp Repeater methodology
SQLi code review
Java/JDBC prevention
Spring/JPA prevention
Laravel/PHP prevention
PostgreSQL/MySQL parameterization
Dynamic SQL risks
Allow-list validation
Least privilege
Secure error handling
Linux commands
Windows commands
macOS commands
Professional vulnerability documentation
🔑 Most Important Lesson
Don't learn SQL Injection by memorizing payloads. Learn to trace data from HTTP input → application code → SQL query → database → response, then use parameterization to keep data separate from SQL code.
🔜 Day 18 — Cross-Site Scripting (XSS) Fundamentals
Tomorrow we'll move from server-side SQL Injection to a major client-side/web-output vulnerability: XSS.
We'll cover:
What XSS is
Reflected XSS
Stored XSS
DOM-based XSS
HTML/JavaScript contexts
Input vs output encoding
Browser security model
Burp Suite testing
Safe Juice Shop exercises
XSS prevention
Content Security Policy
JavaScript security basics
Linux, Windows & macOS practical sessions
🔐 LEARN → PRACTICE IN YOUR LAB → UNDERSTAND → FIX → SECURE.

No comments:
Post a Comment