diff --git a/loop/plan.md b/loop/plan.md index dfad8a7c..bb039237 100644 --- a/loop/plan.md +++ b/loop/plan.md @@ -364,7 +364,7 @@ #### Sprint 30: Security Penetration Testing (Week 1-4) -- [ ] **PENTEST-01** — Run automated penetration test suite. Execute `scripts/verify-pentest-fixes.sh` and `scripts/test-security.sh`. Add new tests: SQL injection (even though no SQL -- test RPC params), command injection (test all params that touch shell), auth bypass attempts, session fixation, privilege escalation via container escape. **Acceptance**: All pen tests pass. +- [x] **PENTEST-01** — Run automated penetration test suite. Execute `scripts/verify-pentest-fixes.sh` and `scripts/test-security.sh`. Add new tests: SQL injection (even though no SQL -- test RPC params), command injection (test all params that touch shell), auth bypass attempts, session fixation, privilege escalation via container escape. **Acceptance**: All pen tests pass. - [ ] **PENTEST-02** — Conduct manual security review of all RPC endpoints. Review each of the 80+ RPC endpoints in `core/archipelago/src/api/rpc/mod.rs` for: input validation, authorization checks, information disclosure, timing attacks on auth endpoints. Document findings. **Acceptance**: All endpoints reviewed; critical issues fixed. diff --git a/scripts/test-security.sh b/scripts/test-security.sh index 659701be..648ba558 100755 --- a/scripts/test-security.sh +++ b/scripts/test-security.sh @@ -1,12 +1,15 @@ #!/bin/bash -set -euo pipefail +set -uo pipefail # SEC-201: Security penetration test covering key attack vectors. -# Covers: auth bypass, session management, input validation, path traversal, SSRF. +# Covers: auth bypass, session management, input validation, path traversal, +# SSRF, command injection, session fixation, container escape. +# Runs all tests directly against the backend HTTP API (no SSH needed for curl). +HOST="${1:-192.168.1.228}" +PASSWORD="${2:-password123}" +BACKEND="http://$HOST:5678" SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}" -TARGET="archipelago@192.168.1.228" -SSH_CMD="ssh -i $SSH_KEY -o StrictHostKeyChecking=no $TARGET" -PASSWORD="password123" +SSH_CMD="ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o ConnectTimeout=10 archipelago@$HOST" PASS=0 FAIL=0 @@ -16,21 +19,33 @@ log() { echo -e "\033[1;34m[SEC]\033[0m $*"; } pass() { echo -e "\033[1;32m[PASS]\033[0m $*"; PASS=$((PASS + 1)); RESULTS+=("PASS: $*"); } fail() { echo -e "\033[1;31m[FAIL]\033[0m $*"; FAIL=$((FAIL + 1)); RESULTS+=("FAIL: $*"); } -rpc_raw() { - local cookie="${1:-}" method="$2" params="${3:-{}}" - local cookie_header="" - [ -n "$cookie" ] && cookie_header="-H 'Cookie: session=$cookie'" - $SSH_CMD "curl -s http://localhost:5678/rpc/v1 \ +SESSION="" +CSRF="" + +# Login and extract session + CSRF token +get_auth() { + local login_out + login_out=$(curl -sv "$BACKEND/rpc/v1" \ -X POST -H 'Content-Type: application/json' \ - $cookie_header \ - -d '{\"method\":\"$method\",\"params\":$params}' 2>/dev/null" + -d "{\"method\":\"auth.login\",\"params\":{\"password\":\"$PASSWORD\"}}" 2>&1 || true) + SESSION=$(echo "$login_out" | grep -i "set-cookie.*session=" | sed 's/.*session=//;s/;.*//' | head -1) + CSRF=$(echo "$login_out" | grep -i "set-cookie.*csrf_token=" | sed 's/.*csrf_token=//;s/;.*//' | head -1) } -get_session() { - $SSH_CMD "curl -s -c - http://localhost:5678/rpc/v1 \ - -X POST -H 'Content-Type: application/json' \ - -d '{\"method\":\"auth.login\",\"params\":{\"password\":\"$PASSWORD\"}}' 2>/dev/null \ - | grep session | awk '{print \$NF}'" +rpc_raw() { + local method="$1" params="${2:-{}}" + curl -s --max-time 10 -X POST "$BACKEND/rpc/v1" \ + -H 'Content-Type: application/json' \ + -d "{\"method\":\"$method\",\"params\":$params}" 2>/dev/null || echo "" +} + +rpc_auth() { + local method="$1" params="${2:-{}}" + curl -s --max-time 10 -X POST "$BACKEND/rpc/v1" \ + -H 'Content-Type: application/json' \ + -H "Cookie: session=$SESSION; csrf_token=$CSRF" \ + -H "X-CSRF-Token: $CSRF" \ + -d "{\"method\":\"$method\",\"params\":$params}" 2>/dev/null || echo "" } main() { @@ -40,8 +55,8 @@ main() { # 1. Authentication bypass — unauthenticated access to protected endpoints log "1. Auth bypass — calling protected RPC without session..." local result - result=$(rpc_raw "" "container-list") - if echo "$result" | grep -q '"code":401\|Unauthorized'; then + result=$(rpc_raw "container-list") + if echo "$result" | grep -qi '"code":401\|unauthorized'; then pass "Protected endpoints reject unauthenticated requests" else fail "container-list accessible without authentication" @@ -49,8 +64,9 @@ main() { # 2. Auth bypass — invalid session token log "2. Auth bypass — invalid session token..." - result=$(rpc_raw "fake-session-token-12345" "container-list") - if echo "$result" | grep -q '"code":401\|Unauthorized'; then + SESSION="fake-session-token-12345" CSRF="fake-csrf" + result=$(rpc_auth "container-list") + if echo "$result" | grep -qi '"code":401\|unauthorized\|"code":403'; then pass "Invalid session tokens are rejected" else fail "Invalid session token accepted" @@ -58,18 +74,155 @@ main() { # 3. Auth bypass — wrong password log "3. Auth bypass — wrong password..." - result=$(rpc_raw "" "auth.login" '{"password":"wrongpassword"}') + result=$(curl -s --max-time 10 -X POST "$BACKEND/rpc/v1" \ + -H 'Content-Type: application/json' \ + -d '{"method":"auth.login","params":{"password":"wrongpassword"}}' 2>/dev/null || echo "") if echo "$result" | grep -q '"error"'; then pass "Wrong password correctly rejected" else fail "Wrong password accepted" fi - # 4. Rate limiting — multiple failed logins - log "4. Rate limiting — rapid failed logins..." + # Get valid session for further tests + log "Getting valid session..." + get_auth + if [ ${#SESSION} -lt 10 ]; then + log "WARNING: Could not get valid session (len=${#SESSION})" + fi + echo "" + + # 5. Input validation — SQL injection attempt in RPC params + log "5. Input validation — SQL injection in params..." + result=$(rpc_auth "identity.get" "{\"id\":\"1; DROP TABLE identities; --\"}") + if echo "$result" | grep -qi "drop table\|sql\|syntax error"; then + fail "Possible SQL injection vulnerability" + else + pass "SQL injection attempt handled safely" + fi + + # 6. Input validation — XSS in params + log "6. Input validation — XSS in params..." + result=$(rpc_auth "identity.create" "{\"name\":\"\",\"purpose\":\"personal\"}") + if echo "$result" | grep -q '","purpose":"personal"}') - if echo "$result" | grep -q '