Skip to content

Lab 04: Static Analysis & Code Inspection

Field Details
Course SCIA-425 โ€” Software Assurance and Quality
Week 5
Difficulty โญโญ Intermediate
Estimated Time 80 minutes
Topic Static Analysis and Code Inspection
Prerequisites Docker installed and running
Deliverables bandit_report.json, semgrep_report.json, findings_analysis.md, fixed secure_app.py

Overview

Static analysis examines source code without executing it, finding security vulnerabilities, code quality defects, and policy violations at zero runtime cost. In this lab you will run Bandit and Semgrep on a deliberately vulnerable Python web application, analyze and triage their findings, then produce a remediated version that passes both tools with zero High-severity findings.

All tools run inside Docker โ€” no local installation needed.


Setup โ€” The Vulnerable Application

Create vulnerable_app.py:

# vulnerable_app.py โ€” INTENTIONALLY INSECURE โ€” FOR LAB USE ONLY
import sqlite3
import subprocess
import hashlib
import pickle
import os
from flask import Flask, request, jsonify

app = Flask(__name__)
SECRET_KEY = "hardcoded_secret_key_12345"  # CWE-798
DB_PATH = "/tmp/users.db"

def get_db():
    return sqlite3.connect(DB_PATH)

@app.route("/login", methods=["POST"])
def login():
    username = request.form["username"]
    password = request.form["password"]
    # SQL injection: CWE-89
    conn = get_db()
    query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
    result = conn.execute(query).fetchone()
    if result:
        return jsonify({"status": "ok", "user": result[0]})
    return jsonify({"status": "fail"}), 401

@app.route("/search")
def search():
    term = request.args.get("q", "")
    # Command injection: CWE-78
    output = subprocess.check_output(f"grep -r {term} /var/log/app/", shell=True)
    return output.decode()

@app.route("/hash")
def make_hash():
    data = request.args.get("data", "")
    # Weak hash: CWE-327
    return hashlib.md5(data.encode()).hexdigest()

@app.route("/load_session")
def load_session():
    session_data = request.cookies.get("session", "")
    # Insecure deserialization: CWE-502
    obj = pickle.loads(bytes.fromhex(session_data))
    return str(obj)

@app.route("/ping")
def ping():
    host = request.args.get("host", "localhost")
    # OS command injection: CWE-78
    result = os.popen(f"ping -c 1 {host}").read()
    return result

@app.route("/admin")
def admin():
    # Missing authentication: CWE-306
    # Returns all users
    conn = get_db()
    users = conn.execute("SELECT username, password FROM users").fetchall()
    return jsonify(users)

if __name__ == "__main__":
    app.run(debug=True)  # Debug mode in production: CWE-94

Part A โ€” Run Bandit (25 pts)

Bandit is a Python-specific SAST tool that checks for common security issues.

# Run bandit in Docker, save JSON report
docker run --rm \
  -v "$PWD":/code \
  -w /code \
  python:3.11-slim \
  bash -c "pip install bandit -q && bandit -r vulnerable_app.py -f json -o bandit_report.json -ll && echo DONE"

# View summary
docker run --rm -v "$PWD":/code -w /code python:3.11-slim \
  bash -c "pip install bandit -q && bandit -r vulnerable_app.py -ll"

Analysis tasks for findings_analysis.md:

  1. How many HIGH severity issues did Bandit find?
  2. For each HIGH finding, record:
  3. Bandit test ID (e.g., B608)
  4. CWE reference
  5. Exact line number
  6. Why this is exploitable (1โ€“2 sentences)
  7. Were there any False Positives? Justify.

Part B โ€” Run Semgrep (25 pts)

Semgrep finds both language-specific and OWASP patterns across many languages.

# Run semgrep with Python security ruleset
docker run --rm \
  -v "$PWD":/src \
  returntocorp/semgrep \
  semgrep --config "p/python" --config "p/owasp-top-ten" \
  --json --output /src/semgrep_report.json \
  /src/vulnerable_app.py

# Human-readable summary
docker run --rm \
  -v "$PWD":/src \
  returntocorp/semgrep \
  semgrep --config "p/python" --config "p/owasp-top-ten" \
  /src/vulnerable_app.py

Analysis tasks (add to findings_analysis.md):

  1. How many Semgrep rules matched?
  2. Which OWASP Top 10 categories are represented?
  3. Did Semgrep find anything Bandit missed? (Compare the two reports)
  4. Did Bandit find anything Semgrep missed?

Part C โ€” Findings Triage (20 pts)

In findings_analysis.md, create a consolidated findings table merging both tools:

Finding ID Tool Severity CWE Line Vulnerability Type Exploitability Priority
F-01 Bandit HIGH CWE-89 24 SQL Injection Critical โ€” direct string interpolation in query P1
...

Triage rules: - P1: Directly exploitable, HIGH severity from either tool - P2: Exploitable under specific conditions, MEDIUM severity - P3: Defense-in-depth or informational (LOW)

Expected: at least 6 distinct vulnerabilities mapped.


Part D โ€” Remediate the Code (30 pts)

Create secure_app.py โ€” a fixed version of vulnerable_app.py. Fix all P1 findings and as many P2 as possible.

Required fixes: - SQL injection โ†’ parameterized queries - Command injection โ†’ remove shell=True, use argument lists, allowlist validation - Hardcoded secret โ†’ read from os.environ - Weak hash โ†’ use hashlib.sha256 or bcrypt for passwords - Insecure deserialization โ†’ replace pickle with json - Missing auth on /admin โ†’ add API key check from environment variable - Debug mode โ†’ app.run(debug=False) or use env variable

After fixing, verify both tools pass:

# Bandit must report zero HIGH severity
docker run --rm -v "$PWD":/code -w /code python:3.11-slim \
  bash -c "pip install bandit -q && bandit -r secure_app.py -ll"

# Semgrep must show zero ERROR-level matches
docker run --rm -v "$PWD":/src returntocorp/semgrep \
  semgrep --config "p/python" --config "p/owasp-top-ten" /src/secure_app.py

Screenshot both tool outputs on secure_app.py showing clean (or reduced) results.


Part E โ€” Additional Challenge (bonus 10 pts)

Write a custom Semgrep rule custom_rule.yaml that detects the specific pattern of using os.popen() with user-controlled input:

rules:
  - id: os-popen-user-input
    pattern: os.popen(... + request.args.get(...) + ...)
    message: "os.popen with user input enables OS command injection (CWE-78)"
    languages: [python]
    severity: ERROR

Test it:

docker run --rm -v "$PWD":/src returntocorp/semgrep \
  semgrep --config /src/custom_rule.yaml /src/vulnerable_app.py


Submission Checklist

  • [ ] vulnerable_app.py โ€” original (do not modify)
  • [ ] bandit_report.json โ€” Bandit output
  • [ ] semgrep_report.json โ€” Semgrep output
  • [ ] findings_analysis.md โ€” triage table + analysis (Parts A, B, C)
  • [ ] secure_app.py โ€” remediated version
  • [ ] Screenshots: Bandit + Semgrep on secure_app.py

Grading

Component Points
Part A โ€” Bandit analysis, findings correctly described 25
Part B โ€” Semgrep analysis, OWASP mapping, tool comparison 25
Part C โ€” Consolidated triage table (6+ findings, correct priorities) 20
Part D โ€” secure_app.py (all P1 fixed, tools show improvement) 30
Part E โ€” Custom Semgrep rule (bonus) +10
Total 100