Skip to content

Commit 3352a04

Browse files
committed
fix: repair SSRF integration test
- Replace sys.exit() with proper pytest assertions - Add stdout/stderr capture on server start failure - Add proper test assertions for 403 responses - Add timeout to requests.get() for faster failure - Add process cleanup with kill fallback
1 parent 53a5d09 commit 3352a04

1 file changed

Lines changed: 39 additions & 38 deletions

File tree

tests/test_ssrf.py

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,78 @@
22
import time
33
import requests
44
import sys
5-
import os
5+
import pytest
66

77
SERVER_PORT = 5000
88
BASE_URL = f"http://localhost:{SERVER_PORT}"
99

10+
1011
def wait_for_server():
1112
retries = 30
1213
while retries > 0:
1314
try:
1415
# The root path 404s, but connection refused means it's not up
15-
requests.get(BASE_URL)
16+
requests.get(BASE_URL, timeout=1)
1617
return True
17-
except requests.exceptions.ConnectionError:
18+
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
1819
time.sleep(0.5)
1920
retries -= 1
2021
return False
2122

23+
2224
def test_ssrf():
2325
print("Starting server...")
24-
# Start server in background
25-
# We use sys.executable to ensure we use the same python interpreter
26-
process = subprocess.Popen([sys.executable, "server.py"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
26+
# Start server in background with different port
27+
env = {**subprocess.os.environ, 'PORT': str(SERVER_PORT)}
28+
process = subprocess.Popen(
29+
[sys.executable, "-c", f"import server; server.app.run(host='localhost', port={SERVER_PORT})"],
30+
stdout=subprocess.PIPE,
31+
stderr=subprocess.PIPE,
32+
)
2733

2834
try:
2935
if not wait_for_server():
30-
print("Server failed to start")
31-
sys.exit(1)
36+
stdout, stderr = process.communicate()
37+
print(f"Server failed to start. stdout: {stdout.decode()}, stderr: {stderr.decode()}")
38+
pytest.fail("Server failed to start within timeout")
3239

3340
print("Server started. Running tests...")
3441

35-
# Test 1: Proxy to google.com (Valid)
36-
# We might not have internet access in some sandboxes, but let's assume we do or handle it.
37-
# If we don't have internet, this might fail with connection error, but the status code won't be 400 from our validation.
38-
print("Testing valid external URL...")
39-
try:
40-
resp = requests.get(f"{BASE_URL}/api/proxy/http://example.com")
41-
print(f"External URL Status: {resp.status_code}")
42-
# It might be 200 or 500 depending on network, but we are looking for behavior.
43-
except Exception as e:
44-
print(f"External URL Request failed: {e}")
45-
46-
# Test 2: SSRF to localhost (Vulnerability)
47-
# We try to proxy to the server itself.
48-
# Since the server has no root route, accessing http://127.0.0.1:5000/ should return 404.
49-
# If the proxy works, it will return that 404 (or 500 if recursive blowup).
50-
# If blocked, it should return 400 (or whatever we decide).
42+
# Test 1: SSRF to localhost (should be blocked)
5143
print("Testing SSRF to localhost...")
5244
target_url = f"http://127.0.0.1:{SERVER_PORT}/"
5345
proxy_url = f"{BASE_URL}/api/proxy/{target_url}"
5446

5547
resp = requests.get(proxy_url)
5648
print(f"SSRF Status: {resp.status_code}")
49+
print(f"SSRF Response: {resp.text}")
5750

58-
# In the vulnerable state, we expect the code to execute the request.
59-
# Since 127.0.0.1:5000/ returns 404, the proxy will return 404.
60-
if resp.status_code == 404:
61-
print("VULNERABILITY CONFIRMED: Proxied request to localhost (received 404 from internal).")
62-
elif resp.status_code == 200:
63-
print("VULNERABILITY CONFIRMED: Proxied request to localhost (received 200).")
64-
elif resp.status_code == 500:
65-
# 500 could mean it tried and failed (e.g. max retries), which still means it tried.
66-
print("VULNERABILITY CONFIRMED: Proxied request to localhost (received 500).")
67-
elif resp.status_code == 400 or resp.status_code == 403:
68-
print("SECURE: Request to localhost blocked.")
69-
else:
70-
print(f"Unexpected status: {resp.status_code}")
51+
# Should return 403 (Forbidden) for blocked requests
52+
assert resp.status_code == 403, f"Expected 403 for localhost, got {resp.status_code}"
53+
assert "forbidden" in resp.text.lower() or "private" in resp.text.lower(), "Expected blocking error message"
54+
55+
# Test 2: SSRF to private IP (should be blocked)
56+
print("Testing SSRF to private IP...")
57+
resp = requests.get(f"{BASE_URL}/api/proxy/http://192.168.1.1/")
58+
print(f"Private IP Status: {resp.status_code}")
59+
assert resp.status_code == 403, f"Expected 403 for private IP, got {resp.status_code}"
60+
61+
# Test 3: Valid external URL format (will fail to connect, but shouldn't be blocked by SSRF)
62+
print("Testing valid external URL format...")
63+
resp = requests.get(f"{BASE_URL}/api/proxy/http://example.com/")
64+
print(f"External URL Status: {resp.status_code}")
65+
# Should NOT return 403 - the SSRF check should pass
66+
assert resp.status_code != 403, "External URL should not be blocked by SSRF check"
7167

7268
finally:
7369
print("Stopping server...")
7470
process.terminate()
75-
process.wait()
71+
try:
72+
process.wait(timeout=5)
73+
except subprocess.TimeoutExpired:
74+
process.kill()
75+
process.wait()
76+
7677

7778
if __name__ == "__main__":
7879
test_ssrf()

0 commit comments

Comments
 (0)