Files
zhuangang/topics/cs/web-security/examples/secure-api/test_secure_app.py
T

72 lines
3.0 KiB
Python

# requires: fastapi, httpx
import pytest
from fastapi.testclient import TestClient
from secure_app import (DB, app, check_fetch_url, find_orders_safe, find_orders_unsafe, render_answer_safe,
render_answer_unsafe)
client = TestClient(app, base_url="https://testserver")
def test_sql_injection_unsafe_leaks_other_tenant():
rows = find_orders_unsafe(DB, "t1", "x' OR 1=1 --")
assert ("B001", "t2") in rows # 证明漏洞真实存在
def test_sql_injection_safe():
assert find_orders_safe(DB, "t1", "x' OR 1=1 --") == []
assert find_orders_safe(DB, "t1", "A00") == [("A001", "t1"), ("A002", "t1")]
def test_xss_escaped():
evil = '<img src=x onerror="alert(document.cookie)">'
assert "<img" in render_answer_unsafe(evil)
assert "<img" not in render_answer_safe(evil) and "&lt;img" in render_answer_safe(evil)
r = client.get("/answer", params={"q": "<script>alert(1)</script>"})
assert "<script>" not in r.text and "script-src 'self'" in r.headers["content-security-policy"]
def test_csrf():
c = TestClient(app, base_url="https://testserver")
c.post("/login")
# 模拟跨站伪造:浏览器会自动带上 Cookie,但攻击页面读不到 csrf_token,无法设置请求头
assert c.post("/transfer", data={"amount": 100}).status_code == 403
token = c.cookies.get("csrf_token")
r = c.post("/transfer", data={"amount": 100}, headers={"X-CSRF-Token": token})
assert r.status_code == 200
def test_cors_allowlist():
ok = client.get("/orders", headers={"Origin": "https://app.example.com"})
assert ok.headers.get("access-control-allow-origin") == "https://app.example.com"
evil = client.get("/orders", headers={"Origin": "https://evil.example"})
assert "access-control-allow-origin" not in evil.headers
pre = client.options("/orders", headers={"Origin": "https://evil.example", "Access-Control-Request-Method": "GET"})
assert pre.status_code == 400
def fake_resolve(mapping):
return lambda host, port: [(None, None, None, "", (mapping[host], port))]
@pytest.mark.parametrize("url,ip", [
("http://169.254.169.254/latest/meta-data/", "169.254.169.254"), # 云元数据
("http://localhost:6379/", "127.0.0.1"),
("http://intranet.corp/", "10.0.0.8"),
("http://rebind.example/", "192.168.1.10"), # 公网域名解析到内网
])
def test_ssrf_blocks_internal(url, ip):
host = url.split("/")[2].split(":")[0]
with pytest.raises(ValueError):
check_fetch_url(url, resolve=fake_resolve({host: ip}))
@pytest.mark.parametrize("url", ["file:///etc/passwd", "gopher://x/", "http://user:pw@example.com/"])
def test_ssrf_blocks_schemes_and_credentials(url):
with pytest.raises(ValueError):
check_fetch_url(url, resolve=fake_resolve({"example.com": "93.184.215.14", "x": "93.184.215.14"}))
def test_ssrf_allows_public():
assert check_fetch_url("https://example.com/a", resolve=fake_resolve({"example.com": "93.184.215.14"}))