Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ labs/lab7/results/
labs/lab8/results/
labs/lab9/falco/logs/
labs/lab10/work/dd/
labs/lab10/imports/import-*.json
labs/lab11/logs/
labs/lab11/reverse-proxy/certs/
labs/lab12/results/
Expand Down
24 changes: 16 additions & 8 deletions labs/lab10.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ In this lab you will practice:
**You should have from Labs 4-9** (regenerate if missing):
- Lab 4: `juice-shop.cdx.json`, `grype-from-sbom.json`, `trivy.json`
- Lab 5: `auth-report.json` (ZAP), `semgrep.json`
- Lab 6: `checkov-terraform/results_json.json`, `kics/results.json`
- Lab 6: `checkov-terraform/results_json.json`, `kics-ansible/results.json`, `kics-pulumi/results.json`
- Lab 7: `trivy-image.json`, `trivy-k8s.json`
- Lab 8: `verify-original.json` (Cosign verify output)
- Lab 9: `falco/logs/falco.log` (custom alerts)
Expand Down Expand Up @@ -58,8 +58,9 @@ mkdir -p labs/lab10/work
```

> **Plumbing provided** (in `labs/lab10/imports/`):
> - Import script that maps the Lab 4-9 file paths to DefectDojo scan-type names
> - Sample environment variables file
> - `run-imports.sh` — imports every Lab 4-7 report into DefectDojo (paths resolved
> from the repo root; portable to stock macOS bash 3.2)
> - `env.sample` — the environment variables the script reads (`DD_URL`, `DD_TOKEN`, …)

---

Expand Down Expand Up @@ -132,15 +133,15 @@ echo "Engagement: $ENGAGEMENT_ID"

### 10.4: Import scan files

For each prior lab, run:
For each prior lab, run (from the repo root):

```bash
# Template — repeat for each scan type
curl -X POST "$DD_URL/api/v2/import-scan/" \
-H "Authorization: Token $DD_TOKEN" \
-F "scan_type=Trivy Scan" \
-F "engagement=$ENGAGEMENT_ID" \
-F "file=@../../lab7/results/trivy-image.json"
-F "file=@labs/lab7/results/trivy-image.json"
```

Scan-type names to use:
Expand All @@ -152,12 +153,18 @@ Scan-type names to use:
| 5 | `semgrep.json` | `Semgrep JSON Report` |
| 5 | `auth-report.json` | `ZAP Scan` |
| 6 | `checkov-terraform/results_json.json` | `Checkov Scan` |
| 6 | `kics/results.json` | `KICS Scan` |
| 6 | `kics-ansible/results.json` | `KICS Scan` |
| 6 | `kics-pulumi/results.json` | `KICS Scan` |
| 7 | `trivy-image.json` | `Trivy Scan` |
| 7 | `trivy-k8s.json` | `Trivy Operator Scan` |
| 9 | `falco/logs/falco.log` | (custom-format — skip if not supported, document instead) |

Use the importer script in `labs/lab10/imports/import-all.sh` to automate.
To automate all of the above, just run the importer — it reuses the `DD_URL` + `DD_TOKEN`
you exported in 10.2 (see `labs/lab10/imports/env.sample` for every variable it reads):

```bash
bash labs/lab10/imports/run-imports.sh
```

### 10.5: Verify import + dedup

Expand Down Expand Up @@ -197,7 +204,8 @@ curl -s -H "Authorization: Token $DD_TOKEN" \
| 5 | Semgrep JSON Report | semgrep.json | <n> |
| 5 | ZAP Scan | auth-report.json | <n> |
| 6 | Checkov Scan | results_json.json | <n> |
| 6 | KICS Scan | results.json | <n> |
| 6 | KICS Scan | kics-ansible/results.json | <n> |
| 6 | KICS Scan | kics-pulumi/results.json | <n> |
| 7 | Trivy Scan (image) | trivy-image.json | <n> |
| 7 | Trivy Operator Scan | trivy-k8s.json | <n> |
| **Total raw imports** | | | <SUM> |
Expand Down
14 changes: 14 additions & 0 deletions labs/lab10/imports/env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Lab 10 — DefectDojo importer environment
# Copy these into your shell (or `source labs/lab10/imports/env.sample`) before
# running run-imports.sh.

# DefectDojo base URL — the importer derives the API base ($DD_URL/api/v2).
export DD_URL="http://localhost:8080"

# API token: DefectDojo UI -> Profile -> API v2 Key.
export DD_TOKEN="paste-your-token-here"

# Optional overrides (defaults shown — match step 10.3):
# export DD_PRODUCT_TYPE="Engineering"
# export DD_PRODUCT="OWASP Juice Shop"
# export DD_ENGAGEMENT="Course Semester Run"
172 changes: 85 additions & 87 deletions labs/lab10/imports/run-imports.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail

# Batch import helper for Lab 10
# - Auto-detects scan_type names from your Dojo instance
# - Imports whichever files exist among ZAP, Semgrep, Trivy, Nuclei (and optional Grype)
# Batch import helper for Lab 10.
# Imports every Lab 4-7 scan report that exists into DefectDojo, auto-detecting
# the scan_type names from your instance (with sane fallbacks if discovery fails).
#
# Usage:
# export DD_API="http://localhost:8080/api/v2"
# export DD_TOKEN="<your_api_token>"
# # Optional overrides (defaults shown)
# export DD_PRODUCT_TYPE="${DD_PRODUCT_TYPE:-Engineering}"
# export DD_PRODUCT="${DD_PRODUCT:-Juice Shop}"
# export DD_ENGAGEMENT="${DD_ENGAGEMENT:-Labs Security Testing}"
# export DD_URL="http://localhost:8080" # base URL (same as lab10.md step 10.2)
# export DD_TOKEN="<your_api_token>" # Profile -> API v2 Key in the UI
# bash labs/lab10/imports/run-imports.sh
#
# Optional overrides (defaults shown):
# DD_API="$DD_URL/api/v2"
# DD_PRODUCT_TYPE="Engineering"
# DD_PRODUCT="OWASP Juice Shop"
# DD_ENGAGEMENT="Course Semester Run"
#
# File paths are resolved relative to the repo root, so the script runs from any
# directory. Portable to bash 3.2 (stock macOS) — no mapfile/readarray.

here_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
out_dir="$here_dir"
repo_root="$(cd "$here_dir/../../.." && pwd)" # labs/lab10/imports -> repo root

require_env() {
local name="$1"
Expand All @@ -25,12 +31,13 @@ require_env() {
fi
}

require_env DD_API
require_env DD_TOKEN

DD_URL="${DD_URL:-http://localhost:8080}"
DD_API="${DD_API:-$DD_URL/api/v2}"
DD_PRODUCT_TYPE="${DD_PRODUCT_TYPE:-Engineering}"
DD_PRODUCT="${DD_PRODUCT:-Juice Shop}"
DD_ENGAGEMENT="${DD_ENGAGEMENT:-Labs Security Testing}"
DD_PRODUCT="${DD_PRODUCT:-OWASP Juice Shop}"
DD_ENGAGEMENT="${DD_ENGAGEMENT:-Course Semester Run}"

echo "Using context:"
echo " DD_API=$DD_API"
Expand All @@ -40,95 +47,86 @@ echo " DD_ENGAGEMENT=$DD_ENGAGEMENT"

have_jq=true
command -v jq >/dev/null 2>&1 || have_jq=false
if ! $have_jq; then
echo "WARN: jq not found; falling back to defaults for scan_type names." >&2
fi

# Discover scan type names from your instance if jq is available
SCAN_ZAP="${SCAN_ZAP:-}"
SCAN_SEMGREP="${SCAN_SEMGREP:-}"
SCAN_TRIVY="${SCAN_TRIVY:-}"
SCAN_NUCLEI="${SCAN_NUCLEI:-}"
$have_jq || echo "WARN: jq not found; using default scan_type names." >&2

# Discover scan_type names from the instance. Fallbacks keep the script working
# even if discovery fails (no jq, instance not up, auth error).
types=()
if $have_jq; then
echo "Discovering importer names from /test_types/ ..."
mapfile -t types < <(curl -sS -H "Authorization: Token $DD_TOKEN" "$DD_API/test_types/?limit=2000" | jq -r '.results[].name')
choose_type() {
local pat="$1"
local fallback="$2"
local val=""
while IFS= read -r name; do
[[ -n "$name" ]] && types+=("$name")
done < <(curl -sS -H "Authorization: Token $DD_TOKEN" \
"$DD_API/test_types/?limit=2000" 2>/dev/null | jq -r '.results[].name' 2>/dev/null)
fi

choose_type() {
local pat="$1" fallback="$2" t
if [[ ${#types[@]} -gt 0 ]]; then
for t in "${types[@]}"; do
if [[ "$t" =~ $pat ]]; then val="$t"; break; fi
if [[ "$t" =~ $pat ]]; then echo "$t"; return; fi
done
if [[ -z "$val" ]]; then val="$fallback"; fi
echo "$val"
}
SCAN_ZAP="${SCAN_ZAP:-$(choose_type '^ZAP' 'ZAP Scan')}"
SCAN_SEMGREP="${SCAN_SEMGREP:-$(choose_type '^Semgrep' 'Semgrep JSON Report')}"
SCAN_TRIVY="${SCAN_TRIVY:-$(choose_type '^Trivy' 'Trivy Scan')}"
SCAN_NUCLEI="${SCAN_NUCLEI:-$(choose_type '^Nuclei' 'Nuclei Scan')}"
# Grype importer (commonly named "Anchore Grype")
if [[ -z "${SCAN_GRYPE:-}" ]]; then
SCAN_GRYPE=$(printf '%s\n' "${types[@]}" | grep -i '^Anchore Grype' | head -n1)
if [[ -z "$SCAN_GRYPE" ]]; then
SCAN_GRYPE=$(printf '%s\n' "${types[@]}" | grep -i 'Grype' | head -n1)
fi
fi
else
SCAN_ZAP="${SCAN_ZAP:-ZAP Scan}"
SCAN_SEMGREP="${SCAN_SEMGREP:-Semgrep JSON Report}"
SCAN_TRIVY="${SCAN_TRIVY:-Trivy Scan}"
SCAN_NUCLEI="${SCAN_NUCLEI:-Nuclei Scan}"
fi
SCAN_GRYPE="${SCAN_GRYPE:-Anchore Grype}"
echo "$fallback"
}

SCAN_GRYPE="$(choose_type '^Anchore Grype' 'Anchore Grype')"
SCAN_TRIVY="$(choose_type '^Trivy Scan$' 'Trivy Scan')"
SCAN_TRIVY_OP="$(choose_type '^Trivy Operator' 'Trivy Operator Scan')"
SCAN_SEMGREP="$(choose_type '^Semgrep' 'Semgrep JSON Report')"
SCAN_ZAP="$(choose_type '^ZAP' 'ZAP Scan')"
SCAN_CHECKOV="$(choose_type '^Checkov' 'Checkov Scan')"
SCAN_KICS="$(choose_type '^KICS' 'KICS Scan')"

echo "Importer names:"
echo " ZAP = $SCAN_ZAP"
echo " Semgrep = $SCAN_SEMGREP"
echo " Trivy = $SCAN_TRIVY"
echo " Nuclei = $SCAN_NUCLEI"
echo " Grype = $SCAN_GRYPE"
echo " Grype = $SCAN_GRYPE"
echo " Trivy = $SCAN_TRIVY"
echo " Trivy Operator = $SCAN_TRIVY_OP"
echo " Semgrep = $SCAN_SEMGREP"
echo " ZAP = $SCAN_ZAP"
echo " Checkov = $SCAN_CHECKOV"
echo " KICS = $SCAN_KICS"

import_scan() {
local scan_type="$1"; shift
local file="$1"; shift
local scan_type="$1" file="$2"
local rel="${file#"$repo_root"/}"
if [[ ! -f "$file" ]]; then
echo "SKIP: $scan_type file not found: $file"
echo "SKIP: $scan_type file not found: $rel"
return 0
fi
local base out
base="$(basename "$file")"
out="$out_dir/import-${base//[^A-Za-z0-9_.-]/_}.json"
echo "Importing $scan_type from $file"
curl -sS -X POST "$DD_API/import-scan/" \
-H "Authorization: Token $DD_TOKEN" \
-F "scan_type=$scan_type" \
-F "file=@$file" \
-F "product_type_name=$DD_PRODUCT_TYPE" \
-F "product_name=$DD_PRODUCT" \
-F "engagement_name=$DD_ENGAGEMENT" \
-F "auto_create_context=true" \
-F "minimum_severity=Info" \
-F "close_old_findings=false" \
-F "push_to_jira=false" \
| tee "$out"
local tag base out
tag="$(basename "$(dirname "$file")")"; tag="${tag//[^A-Za-z0-9_.-]/_}"
base="$(basename "$file")"; base="${base//[^A-Za-z0-9_.-]/_}"
out="$out_dir/import-${tag}-${base}"
echo "Importing $scan_type from $rel"
if ! curl -sS -X POST "$DD_API/import-scan/" \
-H "Authorization: Token $DD_TOKEN" \
-F "scan_type=$scan_type" \
-F "file=@$file" \
-F "product_type_name=$DD_PRODUCT_TYPE" \
-F "product_name=$DD_PRODUCT" \
-F "engagement_name=$DD_ENGAGEMENT" \
-F "auto_create_context=true" \
-F "minimum_severity=Info" \
-F "close_old_findings=false" \
-F "push_to_jira=false" \
| tee "$out" >/dev/null; then
echo " WARN: import request failed for $scan_type ($base)" >&2
fi
}

# Candidate paths per tool
zap_file="labs/lab5/zap/zap-report-noauth.json"
semgrep_file="labs/lab5/semgrep/semgrep-results.json"
trivy_file="labs/lab4/trivy/trivy-vuln-detailed.json"
nuclei_file="labs/lab5/nuclei/nuclei-results.json"

# Grype
grype_file="labs/lab4/syft/grype-vuln-results.json"

import_scan "$SCAN_ZAP" "$zap_file"
import_scan "$SCAN_SEMGREP" "$semgrep_file"
import_scan "$SCAN_TRIVY" "$trivy_file"
import_scan "$SCAN_NUCLEI" "$nuclei_file"

# Grype
import_scan "$SCAN_GRYPE" "$grype_file"
# Lab 4 — SCA (SBOM-derived)
import_scan "$SCAN_GRYPE" "$repo_root/labs/lab4/grype-from-sbom.json"
import_scan "$SCAN_TRIVY" "$repo_root/labs/lab4/trivy.json"
# Lab 5 — DAST + SAST
import_scan "$SCAN_SEMGREP" "$repo_root/labs/lab5/results/semgrep.json"
import_scan "$SCAN_ZAP" "$repo_root/labs/lab5/results/auth-report.json"
# Lab 6 — IaC
import_scan "$SCAN_CHECKOV" "$repo_root/labs/lab6/results/checkov-terraform/results_json.json"
import_scan "$SCAN_KICS" "$repo_root/labs/lab6/results/kics-ansible/results.json"
import_scan "$SCAN_KICS" "$repo_root/labs/lab6/results/kics-pulumi/results.json"
# Lab 7 — Container image + K8s
import_scan "$SCAN_TRIVY" "$repo_root/labs/lab7/results/trivy-image.json"
import_scan "$SCAN_TRIVY_OP" "$repo_root/labs/lab7/results/trivy-k8s.json"

echo "Done. Import responses saved under $out_dir"
6 changes: 4 additions & 2 deletions labs/lab11/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ services:
depends_on:
- juice
ports:
- "8080:8080" # HTTP (will redirect to HTTPS)
- "8443:8443" # HTTPS
- "80:80" # HTTP (redirects to HTTPS)
- "443:443" # HTTPS
# On rootless Docker or if 80/443 are taken, remap (e.g. "8080:80","8443:443")
# and adjust the verification URLs accordingly.
volumes:
- ./reverse-proxy/nginx.conf:/etc/nginx/nginx.conf:ro
- ./reverse-proxy/certs:/etc/nginx/certs:ro
Expand Down
10 changes: 5 additions & 5 deletions labs/lab11/reverse-proxy/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ http {

# HTTP server (redirect to HTTPS)
server {
listen 8080;
listen [::]:8080;
listen 80;
listen [::]:80;
server_name _;

# Core headers (also on redirects)
Expand All @@ -73,13 +73,13 @@ http {
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" always;

return 308 https://$host:8443$request_uri;
return 308 https://$host$request_uri;
}

# HTTPS server
server {
listen 8443 ssl;
listen [::]:8443 ssl;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name _;

Expand Down
5 changes: 4 additions & 1 deletion labs/lab5.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ docker run --rm --network lab5-net \

```bash
# The provided zap-auth.yaml drives the Automation Framework
# _JAVA_OPTIONS caps ZAP's JVM heap; without it the active scan OOM-kills the container
docker run --rm --network lab5-net \
-e _JAVA_OPTIONS="-Xmx512m" \
-v "$(pwd)/labs/lab5:/zap/wrk" \
ghcr.io/zaproxy/zaproxy:stable \
zap.sh -cmd -autorun /zap/wrk/scripts/zap-auth.yaml -port 8090
Expand Down Expand Up @@ -369,7 +371,8 @@ PR checklist body:
<details>
<summary>⚠️ Common Pitfalls</summary>

- 🚨 **`zap-auth.yaml` "context not found"** — the YAML hardcodes Juice Shop running at `juice-shop:3000` (Docker network internal name). If you renamed the container or didn't put both on the same `lab5-net` network, ZAP can't reach it.
- 🚨 **`zap-auth.yaml` "context not found"** — the YAML targets `juice-shop:3000` (Docker network internal name). If you renamed the container or didn't attach both to the same `lab5-net` network, ZAP can't reach it. The container name in `docker run --name` must match exactly.
- 🚨 **Active scan dies silently (CPU 100% → container exit)** — ZAP's active scan is memory-hungry. The `_JAVA_OPTIONS="-Xmx512m"` flag in step 5.3 caps the JVM heap; omitting it lets ZAP consume all available RAM until the container is OOM-killed with no error message.
- 🚨 **Auth scan finds 0 alerts** — the `loginRequestBody` in `zap-auth.yaml` ships with the default Juice Shop admin creds (`admin@juice-sh.op` / `admin123`). If you changed them, auth scan logs in as anonymous and only sees unauth surface.
- 🚨 **`zap.sh -port 8090` instead of default 8080** — added in the plumbing because the previous lab version conflicted with users running things on 8080. Don't change it unless you also change the YAML.
- 🚨 **Semgrep `Parse error: ...`** — Juice Shop's TS sources occasionally hit edge cases. Add `--exclude='**/test/**'` to skip test fixtures if a single parse error blocks the whole scan.
Expand Down
Loading