Secure Coding Part 6 : Command Injection — Attack (Python) — os.system
Lets see in action how a command injection payload lands on a server and how it gets executed :
✅Legit Flow :
- The user enters a safe input like
127.0.0.1in the frontend. - The frontend sends this input via an API call to the middleware.
- The middleware constructs a system command:
ping 127.0.0.14. The server executes the ping command and returns the result safely to the frontend.
✅Malicious Flow :
- The attacker enters malicious input:
127.0.0.1; ls -la2. The middleware constructs:
ping 127.0.0.1; ls -la3. The server blindly executes this command.
4. The attacker gets a directory listing of the server (proof of RCE).
Get Siddiquimohammad’s stories in your inbox
Join Medium for free to get updates from this writer.
5. Sensitive files like config.php and .htaccess become exposed.
✅The Culprit : Vulnerable backend code
import os
from flask import Flask, request
app = Flask(__name__)
@app.route('/ping', methods=['GET'])
def ping_host():
# 1️⃣ ❌ Take user input directly (no validation)
host = request.args.get('host')
# 2️⃣ ❌ Construct the command with string concatenation (DANGEROUS)
command = "ping -c 4" + host
# 3️⃣ ❌ Execute the command using os.popen (vulnerable to injection)
output = os.popen(command).read()
# 4️⃣ Check if '4 received' appears in ping output (basic check)
if "4 received" in output or "4 packets received" in output:
return "Reachable"
else:
return "Unreachable"
if __name__ == "__main__":
app.run(debug=True)Why is this code vulnerable?
host = request.args.get('host')→ Takes unsanitized user input.
command = "ping -c 4 " + host→ Concatenates input directly into a system command.
os.popen(command).read()→ Executes the entire string on the OS shell.Impact: An attacker can send
127.0.0.1; ls -la→ the system runsls -la, exposing files.
✅Fix : Secure version
import subprocess
from flask import Flask, request
import re
app = Flask(__name__)
@app.route('/ping', methods=['GET'])
def ping_host():
# 1️⃣ Get user input
host = request.args.get('host')
# 2️⃣ ✅ Validate input (only digits and dots allowed)
if not re.match(r'^[0-9\.]+$', host):
return "Invalid input!", 400
# 3️⃣ ✅ Run the ping command safely
result = subprocess.run(
["ping", "-c", "1", host], # only 1 ping to make it faster
capture_output=True,
text=True
)
# 4️⃣ ✅ Check return code and respond with simple message
if result.returncode == 0:
return "Reachable"
else:
return "Unreachable"
if __name__ == "__main__":
app.run(debug=True)What makes this code secure?
==>> Input Validation :
if not re.match(r'^[0-9\.]+$', host): return "Invalid input!", 400- Only allows digits and dots (
127.0.0.1) — no;,|,&&.
==>> No Shell Involvement :
subprocess.run(["ping", "-c", "4", host], ...)- Uses
subprocess.runwith a list (not a string) — prevents shell injection. - Example: Instead of building
ping 127.0.0.1; ls, it safely runs the binarypingwith arguments.







