254 lines
9.5 KiB
Bash
Executable File
254 lines
9.5 KiB
Bash
Executable File
#!/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 |