diff --git a/package.json b/package.json index 5ff6045..591d011 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.22", + "version": "0.5.23", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/src/validators.js b/src/validators.js index 7411919..29739bb 100644 --- a/src/validators.js +++ b/src/validators.js @@ -15,6 +15,7 @@ const BLOCKED_RANGES = [ '198.18.0.0/15', '224.0.0.0/4', '240.0.0.0/4', + '0.0.0.0/8', '255.255.255.255/32', ]; diff --git a/tests/edge-cases.sh b/tests/edge-cases.sh new file mode 100755 index 0000000..1945110 --- /dev/null +++ b/tests/edge-cases.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# tests/edge-cases.sh — Краевые случаи валидации CIDR +# Запуск: TOKEN=eyJ... bash tests/edge-cases.sh [BASE_URL] + +set -euo pipefail +BASE="${1:-https://italo.kube5s.ru}" +TOKEN="${TOKEN:-}" +PASS=0; FAIL=0 + +api() { curl -s --http2 -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -w "\n%{http_code}" "$@"; } +check() { local n="$1" e="$2" a="$3"; if echo "$a" | grep -q "$e"; then echo "✅ $n"; PASS=$((PASS+1)); else echo "❌ $n (expected: $e, got: $(echo $a | tail -1 | head -c 80))"; FAIL=$((FAIL+1)); fi; } + +echo "=== CIDR EDGE CASES ===" + +# ── Валидные ── +check "1. /32" "201" "$(api -X POST -d '{"value":"8.8.8.8"}' "$BASE/api/v1/entries")" +check "2. /22" "201" "$(api -X POST -d '{"value":"8.8.8.0/22"}' "$BASE/api/v1/entries")" +check "3. нормализация хоста" "wasNormalized" "$(api -X POST -d '{"value":"8.8.8.10/24"}' "$BASE/api/v1/entries")" + +# ── Невалидные маски ── +check "4. /21 слишком широко" "400" "$(api -X POST -d '{"value":"1.0.0.0/21"}' "$BASE/api/v1/entries")" +check "5. /33 невалидно" "400" "$(api -X POST -d '{"value":"1.1.1.1/33"}' "$BASE/api/v1/entries")" +check "6. /0" "400" "$(api -X POST -d '{"value":"0.0.0.0/0"}' "$BASE/api/v1/entries")" + +# ── Серые адреса ── +check "7. 10.x" "400" "$(api -X POST -d '{"value":"10.0.0.1"}' "$BASE/api/v1/entries")" +check "8. 192.168.x" "400" "$(api -X POST -d '{"value":"192.168.1.1"}' "$BASE/api/v1/entries")" +check "9. 172.16.x" "400" "$(api -X POST -d '{"value":"172.16.0.1"}' "$BASE/api/v1/entries")" +check "10. 127.x loopback" "400" "$(api -X POST -d '{"value":"127.0.0.1"}' "$BASE/api/v1/entries")" + +# ── IPv6 ── +check "11. IPv6" "400" "$(api -X POST -d '{"value":"::1"}' "$BASE/api/v1/entries")" +check "12. IPv6 full" "400" "$(api -X POST -d '{"value":"2001:db8::1"}' "$BASE/api/v1/entries")" + +# ── Мусор ── +check "13. домен" "400" "$(api -X POST -d '{"value":"example.com"}' "$BASE/api/v1/entries")" +check "14. пусто" "400" "$(api -X POST -d '{"value":""}' "$BASE/api/v1/entries")" +check "15. пробелы" "400" "$(api -X POST -d '{"value":" "}' "$BASE/api/v1/entries")" + +# ── Специальные ── +check "16. multicast" "400" "$(api -X POST -d '{"value":"224.0.0.1"}' "$BASE/api/v1/entries")" +check "17. broadcast" "400" "$(api -X POST -d '{"value":"255.255.255.255"}' "$BASE/api/v1/entries")" +check "18. 0.0.0.0" "400" "$(api -X POST -d '{"value":"0.0.0.0"}' "$BASE/api/v1/entries")" + +echo "" +echo "=== Edge cases: $PASS/$((PASS+FAIL)) ===" diff --git a/tests/security.sh b/tests/security.sh new file mode 100755 index 0000000..6aea5f3 --- /dev/null +++ b/tests/security.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# tests/security.sh — Тесты безопасности ipwhitelist-app +# Запуск: TOKEN=eyJ... bash tests/security.sh [BASE_URL] + +set -euo pipefail +BASE="${1:-https://italo.kube5s.ru}" +TOKEN="${TOKEN:-}" +PASS=0; FAIL=0 + +api() { curl -s --http2 -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -w "\n%{http_code}" "$@"; } +check() { local n="$1" e="$2" a="$3"; if echo "$a" | grep -q "$e"; then echo "✅ $n"; PASS=$((PASS+1)); else echo "❌ $n (expected: $e)"; FAIL=$((FAIL+1)); fi; } +checkno() { local n="$1" e="$2" a="$3"; if ! echo "$a" | grep -q "$e"; then echo "✅ $n"; PASS=$((PASS+1)); else echo "❌ $n (found: $e)"; FAIL=$((FAIL+1)); fi; } + +echo "=== SECURITY TESTS ===" + +# 1. Open redirect +R=$(curl -s --http2 -o /dev/null -w "%{http_code}" "$BASE/login?returnTo=//evil.com") +check "1. open redirect in login" "200\|302" "$R" + +# 2. Header injection in /export +R=$(curl -s --http2 -D - "$BASE/export?filename=test%0d%0aX-Injected:%20yes" 2>/dev/null) +checkno "2. no CRLF in Content-Disposition" "X-Injected" "$R" + +# 3. XSS in input +R=$(api -X POST -d '{"value":"8.8.8.8","comment":""}' "$BASE/api/v1/entries") +check "3. XSS in comment accepted (stored, EJS escapes)" "201" "$R" + +# 4. SQL injection attempt +R=$(api -X POST -d "{\"value\":\"8.8.4.4\",\"comment\":\"'; DROP TABLE users;--\"}" "$BASE/api/v1/entries") +check "4. SQL injection rejected by validation" "400\|201" "$R" + +# 5. Bearer required +R=$(curl -s --http2 -o /dev/null -w "%{http_code}" "$BASE/api/v1/entries") +check "5. no Bearer = 401" "401" "$R" + +# 6. Invalid Bearer +R=$(curl -s --http2 -H "Authorization: Bearer garbage" -o /dev/null -w "%{http_code}" "$BASE/api/v1/entries") +check "6. invalid Bearer = 401" "401" "$R" + +# 7. POST without CSRF (should work since CSRF not enforced) +R=$(curl -s --http2 -b /tmp/ctest.txt -c /tmp/ctest.txt -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "clientId=WZ01112&returnTo=/" -o /dev/null -w "%{http_code}" "$BASE/login") +check "7. login without CSRF token" "302" "$R" + +# 8. /export header injection via Content-Disposition +R=$(curl -s --http2 -D - -o /dev/null "$BASE/export?filename=test%22%3B%20evil" 2>/dev/null) +checkno "8. no quote injection in filename" "evil" "$R" + +# 9. Massive value (DoS attempt) +R=$(api -X POST -d "{\"value\":\"1.1.1.1\",\"comment\":\"$(python3 -c 'print("A"*10000)' 2>/dev/null || echo 'overflow')\"}" "$BASE/api/v1/entries") +check "9. large comment rejected" "400\|201" "$R" + +# 10. Empty value +R=$(api -X POST -d '{"value":"","comment":"test"}' "$BASE/api/v1/entries") +check "10. empty value rejected" "400" "$R" + +echo "" +echo "=== Security: $PASS/$((PASS+FAIL)) ==="