Skip to content

Commit d8289f8

Browse files
committed
feat(vulnTestServer): 模块化重构并增强安全性
- 将server.py拆分为模块化结构(handlers目录) - 数据修改接口(INSERT/UPDATE)改用参数化查询防止SQL注入污染数据 - 只读查询接口保留SQL注入漏洞用于测试 - 移除堆叠查询的数据修改能力 - 添加Windows/Linux启动脚本(start.bat/start.sh) - 更新启动横幅显示接口安全级别
1 parent 9e9c958 commit d8289f8

11 files changed

Lines changed: 1338 additions & 561 deletions

File tree

src/vulnTestServer/database.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,48 @@ def init_database():
9494
)
9595
''')
9696

97+
# 创建购物车表
98+
cursor.execute('''
99+
CREATE TABLE IF NOT EXISTS cart (
100+
id INTEGER PRIMARY KEY AUTOINCREMENT,
101+
user_id INTEGER NOT NULL,
102+
product_id INTEGER NOT NULL,
103+
quantity INTEGER DEFAULT 1,
104+
session_id TEXT,
105+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
106+
FOREIGN KEY (user_id) REFERENCES users(id),
107+
FOREIGN KEY (product_id) REFERENCES products(id)
108+
)
109+
''')
110+
111+
# 创建用户反馈表
112+
cursor.execute('''
113+
CREATE TABLE IF NOT EXISTS feedback (
114+
id INTEGER PRIMARY KEY AUTOINCREMENT,
115+
user_id INTEGER,
116+
session_id TEXT,
117+
title TEXT NOT NULL,
118+
content TEXT NOT NULL,
119+
rating INTEGER DEFAULT 5,
120+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
121+
)
122+
''')
123+
124+
# 创建用户会话表
125+
cursor.execute('''
126+
CREATE TABLE IF NOT EXISTS user_sessions (
127+
id INTEGER PRIMARY KEY AUTOINCREMENT,
128+
user_id INTEGER NOT NULL,
129+
session_id TEXT NOT NULL UNIQUE,
130+
token TEXT NOT NULL,
131+
device_info TEXT,
132+
ip_address TEXT,
133+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
134+
expires_at TIMESTAMP,
135+
FOREIGN KEY (user_id) REFERENCES users(id)
136+
)
137+
''')
138+
97139
# 插入测试用户
98140
test_users = [
99141
('admin', hash_password('admin123'), 'admin@vulnshop.local', '13800000001', 'Admin Office', 99999.99, 1),
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
VulnShop API处理器模块
5+
6+
按功能划分的处理器:
7+
- user_handlers: 用户相关(登录、注册、更新、资料)
8+
- product_handlers: 商品相关(列表、搜索、详情)
9+
- order_handlers: 订单相关(创建、查询、取消)
10+
- cart_handlers: 购物车相关(添加、更新)
11+
- system_handlers: 系统相关(配置、重置、API信息、反馈)
12+
"""
13+
14+
from .user_handlers import UserHandlerMixin
15+
from .product_handlers import ProductHandlerMixin
16+
from .order_handlers import OrderHandlerMixin
17+
from .cart_handlers import CartHandlerMixin
18+
from .system_handlers import SystemHandlerMixin
19+
20+
__all__ = [
21+
'UserHandlerMixin',
22+
'ProductHandlerMixin',
23+
'OrderHandlerMixin',
24+
'CartHandlerMixin',
25+
'SystemHandlerMixin',
26+
]
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
VulnShop 基础处理器 - 公共方法和工具函数
5+
6+
提供所有处理器共享的响应发送、请求解析等公共功能
7+
"""
8+
9+
import json
10+
import os
11+
import time
12+
import xml.etree.ElementTree as ET
13+
from urllib.parse import unquote
14+
from datetime import datetime
15+
16+
17+
class BaseHandlerMixin:
18+
"""基础处理器Mixin - 提供公共方法"""
19+
20+
# 静态文件MIME类型
21+
MIME_TYPES = {
22+
'.html': 'text/html; charset=utf-8',
23+
'.css': 'text/css; charset=utf-8',
24+
'.js': 'application/javascript; charset=utf-8',
25+
'.json': 'application/json; charset=utf-8',
26+
'.png': 'image/png',
27+
'.jpg': 'image/jpeg',
28+
'.jpeg': 'image/jpeg',
29+
'.gif': 'image/gif',
30+
'.ico': 'image/x-icon',
31+
'.svg': 'image/svg+xml',
32+
}
33+
34+
def log_message(self, format, *args):
35+
"""自定义日志格式"""
36+
from config import LOG_REQUESTS, LOG_FILE
37+
if LOG_REQUESTS:
38+
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
39+
message = f"[{timestamp}] {self.address_string()} - {format % args}"
40+
print(message)
41+
try:
42+
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
43+
with open(LOG_FILE, 'a', encoding='utf-8') as f:
44+
f.write(message + '\n')
45+
except:
46+
pass
47+
48+
def send_json_response(self, data, status=200):
49+
"""发送JSON响应"""
50+
self.send_response(status)
51+
self.send_header('Content-Type', 'application/json; charset=utf-8')
52+
self.send_header('Access-Control-Allow-Origin', '*')
53+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
54+
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
55+
self.end_headers()
56+
response = json.dumps(data, ensure_ascii=False)
57+
self.wfile.write(response.encode('utf-8'))
58+
59+
def send_error_response(self, message, status=400, sql_error=None):
60+
"""发送错误响应(可能包含SQL错误信息用于演示)"""
61+
from config import DEBUG, DIFFICULTY
62+
data = {
63+
'success': False,
64+
'message': message,
65+
'timestamp': int(time.time() * 1000)
66+
}
67+
if sql_error and DEBUG:
68+
data['debug'] = {
69+
'sql_error': str(sql_error),
70+
'difficulty': DIFFICULTY
71+
}
72+
self.send_json_response(data, status)
73+
74+
def send_xml_response(self, data, status=200):
75+
"""发送XML响应"""
76+
self.send_response(status)
77+
self.send_header('Content-Type', 'application/xml; charset=utf-8')
78+
self.send_header('Access-Control-Allow-Origin', '*')
79+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
80+
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Session-Id, X-Token')
81+
self.end_headers()
82+
83+
# 将dict转换为XML
84+
def dict_to_xml(d, root_name='response'):
85+
root = ET.Element(root_name)
86+
for key, value in d.items():
87+
child = ET.SubElement(root, key)
88+
if isinstance(value, dict):
89+
for k, v in value.items():
90+
sub = ET.SubElement(child, k)
91+
sub.text = str(v) if v is not None else ''
92+
elif isinstance(value, list):
93+
for item in value:
94+
item_el = ET.SubElement(child, 'item')
95+
if isinstance(item, dict):
96+
for k, v in item.items():
97+
sub = ET.SubElement(item_el, k)
98+
sub.text = str(v) if v is not None else ''
99+
else:
100+
item_el.text = str(item)
101+
else:
102+
child.text = str(value) if value is not None else ''
103+
return ET.tostring(root, encoding='unicode')
104+
105+
xml_str = '<?xml version="1.0" encoding="UTF-8"?>\n' + dict_to_xml(data)
106+
self.wfile.write(xml_str.encode('utf-8'))
107+
108+
def send_static_file(self, filepath):
109+
"""发送静态文件"""
110+
static_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'static')
111+
full_path = os.path.normpath(os.path.join(static_dir, filepath))
112+
113+
# 防止目录遍历
114+
if not full_path.startswith(static_dir):
115+
self.send_error(403, 'Forbidden')
116+
return
117+
118+
if not os.path.exists(full_path):
119+
self.send_error(404, 'File Not Found')
120+
return
121+
122+
ext = os.path.splitext(filepath)[1].lower()
123+
mime_type = self.MIME_TYPES.get(ext, 'application/octet-stream')
124+
125+
try:
126+
with open(full_path, 'rb') as f:
127+
content = f.read()
128+
self.send_response(200)
129+
self.send_header('Content-Type', mime_type)
130+
self.send_header('Content-Length', len(content))
131+
self.end_headers()
132+
self.wfile.write(content)
133+
except Exception as e:
134+
self.send_error(500, str(e))
135+
136+
def get_post_data(self):
137+
"""获取POST请求数据,支持JSON、URL-encoded和XML格式"""
138+
content_length = int(self.headers.get('Content-Length', 0))
139+
if content_length == 0:
140+
return {}
141+
142+
post_data = self.rfile.read(content_length).decode('utf-8')
143+
content_type = self.headers.get('Content-Type', '')
144+
145+
if 'application/json' in content_type:
146+
try:
147+
return json.loads(post_data)
148+
except:
149+
return {}
150+
elif 'application/xml' in content_type or 'text/xml' in content_type:
151+
# XML解析
152+
try:
153+
root = ET.fromstring(post_data)
154+
result = {}
155+
for child in root:
156+
result[child.tag] = child.text or ''
157+
return result
158+
except:
159+
return {}
160+
else:
161+
# application/x-www-form-urlencoded
162+
result = {}
163+
for pair in post_data.split('&'):
164+
if '=' in pair:
165+
key, value = pair.split('=', 1)
166+
result[unquote(key)] = unquote(value)
167+
return result
168+
169+
def get_content_type(self):
170+
"""获取请求的Content-Type类型"""
171+
content_type = self.headers.get('Content-Type', '')
172+
if 'application/json' in content_type:
173+
return 'json'
174+
elif 'application/xml' in content_type or 'text/xml' in content_type:
175+
return 'xml'
176+
else:
177+
return 'form'
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
VulnShop 购物车处理器 - 购物车相关API
5+
6+
包含:添加购物车、更新购物车
7+
8+
注意:购物车操作涉及数据修改,使用参数化查询保护,避免SQL注入导致数据污染
9+
"""
10+
11+
import sqlite3
12+
13+
from config import DEBUG
14+
from database import get_db_connection
15+
16+
17+
class CartHandlerMixin:
18+
"""购物车相关处理器Mixin"""
19+
20+
def handle_cart_add(self, data):
21+
"""
22+
添加购物车 - URL-encoded格式 - 安全接口
23+
24+
请求体示例 (application/x-www-form-urlencoded):
25+
user_id=1&product_id=2&quantity=1&session_id=abc123&csrf_token=xyz789
26+
27+
注意:此接口使用参数化查询,不存在SQL注入漏洞
28+
(数据修改操作需保护,避免测试数据污染)
29+
"""
30+
user_id = data.get('user_id', '')
31+
product_id = data.get('product_id', '')
32+
quantity = data.get('quantity', '1')
33+
session_id = data.get('session_id', '') # 会话ID
34+
csrf_token = data.get('csrf_token', '') # CSRF token
35+
36+
if not user_id or not product_id:
37+
self.send_json_response({'success': False, 'message': 'user_id and product_id are required'}, 400)
38+
return
39+
40+
# 验证参数是否为有效数字
41+
try:
42+
user_id_int = int(user_id)
43+
product_id_int = int(product_id)
44+
quantity_int = int(quantity)
45+
except ValueError:
46+
self.send_json_response({'success': False, 'message': 'Invalid numeric parameters'}, 400)
47+
return
48+
49+
if DEBUG:
50+
print(f"[CartAdd] session_id={session_id}, csrf_token={csrf_token}")
51+
52+
conn = get_db_connection()
53+
cursor = conn.cursor()
54+
55+
try:
56+
# 使用参数化查询,安全插入数据
57+
cursor.execute('''
58+
INSERT INTO cart (user_id, product_id, quantity, session_id)
59+
VALUES (?, ?, ?, ?)
60+
''', (user_id_int, product_id_int, quantity_int, session_id))
61+
conn.commit()
62+
cart_id = cursor.lastrowid
63+
64+
self.send_json_response({
65+
'success': True,
66+
'message': 'Added to cart',
67+
'data': {
68+
'cart_id': cart_id,
69+
'session_id': session_id
70+
}
71+
})
72+
except sqlite3.Error as e:
73+
self.send_json_response({'success': False, 'message': f'Failed to add to cart: {str(e)}'}, 500)
74+
finally:
75+
conn.close()
76+
77+
def handle_cart_update(self, data):
78+
"""
79+
更新购物车 - URL-encoded格式 - 安全接口
80+
81+
请求体示例 (application/x-www-form-urlencoded):
82+
cart_id=1&quantity=3&session_id=abc123&csrf_token=xyz789
83+
84+
注意:此接口使用参数化查询,不存在SQL注入漏洞
85+
(数据修改操作需保护,避免测试数据污染)
86+
"""
87+
cart_id = data.get('cart_id', '')
88+
quantity = data.get('quantity', '')
89+
session_id = data.get('session_id', '') # 会话ID
90+
csrf_token = data.get('csrf_token', '') # CSRF token
91+
92+
if not cart_id:
93+
self.send_json_response({'success': False, 'message': 'cart_id is required'}, 400)
94+
return
95+
96+
# 验证参数是否为有效数字
97+
try:
98+
cart_id_int = int(cart_id)
99+
quantity_int = int(quantity) if quantity else 1
100+
except ValueError:
101+
self.send_json_response({'success': False, 'message': 'Invalid numeric parameters'}, 400)
102+
return
103+
104+
if DEBUG:
105+
print(f"[CartUpdate] session_id={session_id}, csrf_token={csrf_token}")
106+
107+
conn = get_db_connection()
108+
cursor = conn.cursor()
109+
110+
try:
111+
# 使用参数化查询,安全更新数据
112+
cursor.execute('UPDATE cart SET quantity = ? WHERE id = ?', (quantity_int, cart_id_int))
113+
conn.commit()
114+
115+
self.send_json_response({
116+
'success': True,
117+
'message': 'Cart updated',
118+
'data': {
119+
'cart_id': cart_id,
120+
'session_id': session_id
121+
}
122+
})
123+
except sqlite3.Error as e:
124+
self.send_json_response({'success': False, 'message': f'Failed to update cart: {str(e)}'}, 500)
125+
finally:
126+
conn.close()

0 commit comments

Comments
 (0)