#!/bin/bash # SQL Injection Test Suite for C-Relay # Comprehensive testing of SQL injection vulnerabilities across all filter types set -e # Configuration RELAY_HOST="127.0.0.1" RELAY_PORT="8888" TEST_TIMEOUT=10 # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Test counters TOTAL_TESTS=0 PASSED_TESTS=0 FAILED_TESTS=0 # Function to send WebSocket message and check for SQL injection success test_sql_injection() { local description="$1" local message="$2" TOTAL_TESTS=$((TOTAL_TESTS + 1)) echo -n "Testing $description... " # Send message via websocat and capture response local response response=$(echo "$message" | timeout 2 websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT') # Check if the response indicates successful query execution (which would be bad) # Look for signs that SQL injection worked (like database errors or unexpected results) if [[ "$response" == "TIMEOUT" ]]; then echo -e "${YELLOW}UNCERTAIN${NC} - Connection timeout (may indicate crash)" FAILED_TESTS=$((FAILED_TESTS + 1)) return 1 elif [[ -z "$response" ]]; then # Empty response - relay silently rejected malformed input echo -e "${GREEN}PASSED${NC} - SQL injection blocked (silently rejected)" PASSED_TESTS=$((PASSED_TESTS + 1)) return 0 elif [[ "$response" == *"NOTICE"* ]] && [[ "$response" == *"error:"* ]]; then # Relay properly rejected the input with a NOTICE error message echo -e "${GREEN}PASSED${NC} - SQL injection blocked (rejected with error)" PASSED_TESTS=$((PASSED_TESTS + 1)) return 0 elif [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"COUNT"* ]] || [[ "$response" == *"EVENT"* ]]; then # Query completed normally - this is expected for properly sanitized input echo -e "${GREEN}PASSED${NC} - SQL injection blocked (query sanitized)" PASSED_TESTS=$((PASSED_TESTS + 1)) return 0 elif [[ "$response" == *"SQL"* ]] || [[ "$response" == *"syntax"* ]]; then # Database error leaked - potential vulnerability echo -e "${RED}FAILED${NC} - SQL error leaked: $response" FAILED_TESTS=$((FAILED_TESTS + 1)) return 1 else # Unknown response echo -e "${YELLOW}UNCERTAIN${NC} - Unexpected response: $response" FAILED_TESTS=$((FAILED_TESTS + 1)) return 1 fi } # Function to test valid message (should work normally) test_valid_query() { local description="$1" local message="$2" TOTAL_TESTS=$((TOTAL_TESTS + 1)) echo -n "Testing $description... " local response response=$(echo "$message" | timeout 2 websocat ws://$RELAY_HOST:$RELAY_PORT 2>/dev/null | head -1 || echo 'TIMEOUT') if [[ "$response" == *"EOSE"* ]] || [[ "$response" == *"EVENT"* ]]; then echo -e "${GREEN}PASSED${NC} - Valid query works" PASSED_TESTS=$((PASSED_TESTS + 1)) return 0 else echo -e "${RED}FAILED${NC} - Valid query failed: $response" FAILED_TESTS=$((FAILED_TESTS + 1)) return 1 fi } echo "==========================================" echo "C-Relay SQL Injection Test Suite" echo "==========================================" echo "Testing against relay at ws://$RELAY_HOST:$RELAY_PORT" echo # Test basic connectivity first echo "=== Basic Connectivity Test ===" test_valid_query "Basic connectivity" '["REQ","basic_test",{}]' echo # SQL Injection Payloads SQL_PAYLOADS=( # Classic SQL injection "'; DROP TABLE events; --" "1' OR '1'='1" "1; SELECT * FROM sqlite_master; --" "' OR 1=1 --" "admin'--" "1' UNION SELECT password FROM users --" # Time-based blind SQL injection "1' AND SLEEP(5) --" "1' AND 1=1 WAITFOR DELAY '0:0:5' --" # Error-based SQL injection "1' AND 1=CAST((SELECT version()) AS INT) --" "1' AND 1=CONVERT(INT, (SELECT @@VERSION)) --" # Union-based injection "' UNION SELECT NULL,NULL,NULL --" "' UNION SELECT 1,2,3 --" "' UNION ALL SELECT NULL,NULL,NULL --" # Stacked queries "'; SELECT * FROM events; --" "'; DELETE FROM events; --" "'; UPDATE events SET content='hacked' WHERE 1=1; --" # Comment injection "/*" "*/" "/**/" "--" "#" # Hex encoded injection "0x53514C5F494E4A454354494F4E" # SQL_INJECTION in hex # Base64 encoded injection "J1NSTCBJTkpFQ1RJT04gLS0=" # 'SQL INJECTION -- in base64 # Nested injection "'))); DROP TABLE events; --" "')) UNION SELECT NULL; --" # Boolean-based blind injection "' AND 1=1 --" "' AND 1=2 --" "' AND (SELECT COUNT(*) FROM events) > 0 --" # Out-of-band injection (if supported) "'; EXEC master..xp_cmdshell 'net user' --" "'; DECLARE @host varchar(1024); SELECT @host=(SELECT TOP 1 master..sys.fn_varbintohexstr(password_hash) FROM sys.sql_logins WHERE name='sa'); --" ) echo "=== Authors Filter SQL Injection Tests ===" for payload in "${SQL_PAYLOADS[@]}"; do test_sql_injection "Authors filter with payload: $payload" "[\"REQ\",\"sql_test_authors_$RANDOM\",{\"authors\":[\"$payload\"]}]" done echo echo "=== IDs Filter SQL Injection Tests ===" for payload in "${SQL_PAYLOADS[@]}"; do test_sql_injection "IDs filter with payload: $payload" "[\"REQ\",\"sql_test_ids_$RANDOM\",{\"ids\":[\"$payload\"]}]" done echo echo "=== Kinds Filter SQL Injection Tests ===" # Test numeric kinds with SQL injection attempts (these will fail JSON parsing, which is expected) test_sql_injection "Kinds filter with string injection" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[\"1' OR '1'='1\"]}]" test_sql_injection "Kinds filter with negative value" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[-1]}]" test_sql_injection "Kinds filter with very large value" "[\"REQ\",\"sql_test_kinds_$RANDOM\",{\"kinds\":[999999999]}]" echo echo "=== Search Filter SQL Injection Tests ===" for payload in "${SQL_PAYLOADS[@]}"; do test_sql_injection "Search filter with payload: $payload" "[\"REQ\",\"sql_test_search_$RANDOM\",{\"search\":\"$payload\"}]" done echo echo "=== Tag Filter SQL Injection Tests ===" TAG_PREFIXES=("#e" "#p" "#t" "#r" "#d") for prefix in "${TAG_PREFIXES[@]}"; do for payload in "${SQL_PAYLOADS[@]}"; do test_sql_injection "$prefix tag filter with payload: $payload" "[\"REQ\",\"sql_test_tag_$RANDOM\",{\"$prefix\":[\"$payload\"]}]" done done echo echo "=== Timestamp Filter SQL Injection Tests ===" # Test since/until parameters test_sql_injection "Since parameter injection" "[\"REQ\",\"sql_test_since_$RANDOM\",{\"since\":\"1' OR '1'='1\"}]" test_sql_injection "Until parameter injection" "[\"REQ\",\"sql_test_until_$RANDOM\",{\"until\":\"1; DROP TABLE events; --\"}]" echo echo "=== Limit Parameter SQL Injection Tests ===" test_sql_injection "Limit parameter injection" "[\"REQ\",\"sql_test_limit_$RANDOM\",{\"limit\":\"1' OR '1'='1\"}]" test_sql_injection "Limit with UNION" "[\"REQ\",\"sql_test_limit_$RANDOM\",{\"limit\":\"0 UNION SELECT password FROM users\"}]" echo echo "=== Complex Multi-Filter SQL Injection Tests ===" # Test combinations that might bypass validation test_sql_injection "Multi-filter with authors injection" "[\"REQ\",\"sql_test_multi_$RANDOM\",{\"authors\":[\"admin'--\"],\"kinds\":[1],\"search\":\"anything\"}]" test_sql_injection "Multi-filter with search injection" "[\"REQ\",\"sql_test_multi_$RANDOM\",{\"authors\":[\"valid\"],\"search\":\"'; DROP TABLE events; --\"}]" test_sql_injection "Multi-filter with tag injection" "[\"REQ\",\"sql_test_multi_$RANDOM\",{\"#e\":[\"'; SELECT * FROM sqlite_master; --\"],\"limit\":10}]" echo echo "=== COUNT Message SQL Injection Tests ===" # Test COUNT messages which might have different code paths for payload in "${SQL_PAYLOADS[@]}"; do test_sql_injection "COUNT with authors payload: $payload" "[\"COUNT\",\"sql_count_authors_$RANDOM\",{\"authors\":[\"$payload\"]}]" test_sql_injection "COUNT with search payload: $payload" "[\"COUNT\",\"sql_count_search_$RANDOM\",{\"search\":\"$payload\"}]" done echo echo "=== Edge Case SQL Injection Tests ===" # Test edge cases that might bypass validation test_sql_injection "Empty string injection" "[\"REQ\",\"sql_edge_$RANDOM\",{\"authors\":[\"\"]}]" test_sql_injection "Null byte injection" "[\"REQ\",\"sql_edge_$RANDOM\",{\"authors\":[\"admin\\x00' OR '1'='1\"]}]" test_sql_injection "Unicode injection" "[\"REQ\",\"sql_edge_$RANDOM\",{\"authors\":[\"admin' OR '1'='1' -- 💣\"]}]" test_sql_injection "Very long injection payload" "[\"REQ\",\"sql_edge_$RANDOM\",{\"search\":\"$(printf 'a%.0s' {1..1000})' OR '1'='1\"}]" echo echo "=== Subscription ID SQL Injection Tests ===" # Test if subscription IDs can be used for injection test_sql_injection "Subscription ID injection" "[\"REQ\",\"'; DROP TABLE subscriptions; --\",{}]" test_sql_injection "Subscription ID with quotes" "[\"REQ\",\"sub\"'; SELECT * FROM events; --\",{}]" echo echo "=== CLOSE Message SQL Injection Tests ===" # Test CLOSE messages test_sql_injection "CLOSE with injection" "[\"CLOSE\",\"'; DROP TABLE subscriptions; --\"]" echo echo "=== Test Results ===" echo "Total tests: $TOTAL_TESTS" echo -e "Passed: ${GREEN}$PASSED_TESTS${NC}" echo -e "Failed: ${RED}$FAILED_TESTS${NC}" if [[ $FAILED_TESTS -eq 0 ]]; then echo -e "${GREEN}✓ All SQL injection tests passed!${NC}" echo "The relay appears to be protected against SQL injection attacks." exit 0 else echo -e "${RED}✗ SQL injection vulnerabilities detected!${NC}" echo "The relay may be vulnerable to SQL injection attacks." echo "Failed tests: $FAILED_TESTS" exit 1 fi