Python代码注入防护
代码注入是最危险的安全漏洞之一,可能导致任意代码执行。掌握防护策略至关重要。
eval/exec 注入防护
危险示例
Python
# 绝对不要这样做!
user_input = "__import__('os').system('rm -rf /')"
result = eval(user_input) # 极危险!
# 常见攻击模式:
# __import__('os').system('恶意命令')
# open('/etc/passwd').read()
# (lambda: ().__class__.__base__.__subclasses__())()[...]
安全替代方案
Python
# 方案1:使用安全的表达式解析器
from ast import parse, literal_eval
# literal_eval 只支持常量表达式
safe_result = literal_eval("[1, 2, 3]") # 安全
# literal_eval("__import__('os')") # ValueError
# 方案2:自定义表达式处理器
class SafeCalculator:
"安全的数学表达式计算"
ALLOWED_OPERATIONS = {
'+': lambda a, b: a + b,
'-': lambda a, b: a - b,
'*': lambda a, b: a * b,
'/': lambda a, b: a / b if b != 0 else None,
'pow': lambda a, b: a ** b,
}
def calculate(self, expression):
import re
# 只允许数字和运算符
if not re.match(r'^[\d\s+\-*/.()]+$', expression):
raise ValueError("非法字符")
try:
return eval(expression, {'__builtins__': {}}, {})
except:
raise ValueError("计算错误")
calc = SafeCalculator()
print(calc.calculate("2 + 3 * 4")) # 14
受限执行环境
Python
import ast
class SafeExecutor:
"受限执行环境"
SAFE_BUILTINS = {
'abs': abs, 'min': min, 'max': max,
'len': len, 'sum': sum, 'range': range,
'list': list, 'tuple': tuple, 'dict': dict,
'str': str, 'int': int, 'float': float,
'bool': bool, 'sorted': sorted,
}
FORBIDDEN_NODES = {
ast.Import, ast.ImportFrom,
ast.Call, ast.Attribute, ast.Subscript,
}
def check_ast(self, code):
"AST 安全检查"
tree = ast.parse(code, mode='eval')
for node in ast.walk(tree):
if node.__class__ in self.FORBIDDEN_NODES:
# 允许部分安全的调用
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
if node.func.id in self.SAFE_BUILTINS:
continue
raise SecurityError(f"禁止节点: {node.__class__.__name__}")
return tree
def safe_eval(self, expression):
"安全执行表达式"
tree = self.check_ast(expression)
return eval(expression, {'__builtins__': self.SAFE_BUILTINS}, {})
executor = SafeExecutor()
print(executor.safe_eval("abs(-5) + max(1, 2)")) # 7
# executor.safe_eval("__import__('os')") # SecurityError
模板注入防护
Jinja2 安全配置
Python
from jinja2 import Environment, BaseLoader, select_autoescape
# 安全配置
env = Environment(
loader=BaseLoader(),
autoescape=select_autoescape(['html', 'xml']), # 自动转义
)
# 模板渲染
template = env.from_string("Hello {{ name }}!")
safe_output = template.render(name="<script>alert('xss')</script>")
print(safe_output) # Hello <script>alert('xss')</script>!
# 禁用危险功能
sandbox_env = Environment(
loader=BaseLoader(),
autoescape=True,
)
# 不暴露危险对象到模板上下文
safe_context = {'name': 'safe', 'items': [1, 2, 3]}
SSTI(服务端模板注入)防护
Python
# 危险模式:模板字符串拼接
dangerous_template = "Hello " + user_input # 如果 user_input 包含模板语法
# "{{config.items()}}" 可能泄露配置
# 安全方案:
# 1. 使用参数化模板
template = env.from_string("Hello {{ name }}!")
result = template.render(name=user_input) # user_input 作为数据
# 2. 禁止模板语法
def safe_text(text):
"移除模板语法"
import re
return re.sub(r'\{[{%#].*?[%#}]\}', '', text)
clean_text = safe_text(user_input)
template = env.from_string("Hello " + clean_text)
命令注入防护
subprocess 安全使用
Python
import subprocess
# 危险方式:shell=True
# subprocess.call(f"ls {user_input}", shell=True) # 危险!
# 安全方式:shell=False,参数列表
result = subprocess.run(
['ls', '-l', user_input], # 参数分离
shell=False,
capture_output=True,
text=True
)
# 验证输入
import re
def safe_filename(filename):
"验证文件名"
# 只允许安全字符
if not re.match(r'^[\w\-\.]+$', filename):
raise ValueError("非法文件名")
return filename
safe_input = safe_filename(user_input)
result = subprocess.run(['ls', safe_input], shell=False)
Python
# 安全执行外部命令
class SafeCommand:
"安全命令执行器"
ALLOWED_COMMANDS = {'ls', 'cat', 'grep', 'echo'}
def execute(self, command, args):
"安全执行"
if command not in self.ALLOWED_COMMANDS:
raise ValueError(f"禁止命令: {command}")
# 参数验证
safe_args = [self.validate_arg(arg) for arg in args]
return subprocess.run(
[command] + safe_args,
shell=False,
capture_output=True,
text=True,
timeout=10 # 限制执行时间
)
def validate_arg(self, arg):
"验证参数"
if not re.match(r'^[\w\-\.\/]+$', str(arg)):
raise ValueError(f"非法参数: {arg}")
return arg
executor = SafeCommand()
result = executor.execute('ls', ['-l', '/tmp'])
os.system 替代方案
Python
import os
# 危险:os.system
# os.system(f"ls {user_input}") # 危险!
# 安全替代:
# 1. 使用 subprocess
subprocess.run(['ls', user_input], shell=False)
# 2. 使用 shlex 分割
import shlex
cmd = "ls -l"
parts = shlex.split(cmd)
subprocess.run(parts, shell=False)
SQL 注入防护
Python
import sqlite3
# 危险方式:字符串拼接
# query = f"SELECT * FROM users WHERE name = '{user_input}'" # 危险!
# 安全方式:参数化查询
conn = sqlite3.connect('database.db')
cursor = conn.cursor()
# 使用占位符
cursor.execute(
"SELECT * FROM users WHERE name = ?",
(user_input,) # 参数作为元组
)
# 多个参数
cursor.execute(
"SELECT * FROM users WHERE name = ? AND age > ?",
(user_input, min_age)
)
Python
# SQLAlchemy 安全用法
from sqlalchemy import create_engine, text
engine = create_engine('sqlite:///database.db')
with engine.connect() as conn:
# 参数化查询
result = conn.execute(
text("SELECT * FROM users WHERE name = :name"),
{'name': user_input}
)
输入验证函数
Python
import re
from typing import Any
class InputValidator:
"输入验证器"
@staticmethod
def validate_alphanumeric(value: str, max_length: int = 100) -> str:
"验证字母数字字符串"
if not isinstance(value, str):
raise TypeError("期望字符串类型")
if len(value) > max_length:
raise ValueError(f"长度超过 {max_length}")
if not re.match(r'^[a-zA-Z0-9_\-]+$', value):
raise ValueError("包含非法字符")
return value
@staticmethod
def validate_email(email: str) -> str:
"验证邮箱格式"
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
raise ValueError("非法邮箱格式")
return email
@staticmethod
def validate_path(path: str) -> str:
"验证路径"
# 防止路径遍历
if '..' in path or path.startswith('/'):
raise ValueError("非法路径")
if not re.match(r'^[\w\-\.\/]+$', path):
raise ValueError("路径包含非法字符")
return path
@staticmethod
def validate_int(value: Any, min_val: int, max_val: int) -> int:
"验证整数范围"
try:
num = int(value)
except (TypeError, ValueError):
raise ValueError("期望整数类型")
if not min_val <= num <= max_val:
raise ValueError(f"数值必须在 {min_val} 到 {max_val} 之间")
return num
validator = InputValidator()
safe_name = validator.validate_alphanumeric(user_input)
整体防护策略
Python
class SecurityMiddleware:
"安全中间件"
def __init__(self):
self.validator = InputValidator()
def sanitize_input(self, data: dict) -> dict:
"清理输入数据"
sanitized = {}
for key, value in data.items():
# 根据键名选择验证策略
if key in ('name', 'username'):
sanitized[key] = self.validator.validate_alphanumeric(value)
elif key == 'email':
sanitized[key] = self.validator.validate_email(value)
elif key == 'path':
sanitized[key] = self.validator.validate_path(value)
elif key in ('id', 'age', 'count'):
sanitized[key] = self.validator.validate_int(value, 0, 1000000)
else:
# 默认:移除危险字符
sanitized[key] = self.sanitize_default(value)
return sanitized
def sanitize_default(self, value: str) -> str:
"默认清理"
if isinstance(value, str):
# 移除潜在危险字符
return re.sub(r'[<>&\'"\\]', '', value)
return value
# 使用
middleware = SecurityMiddleware()
safe_data = middleware.sanitize_input({
'name': 'Alice',
'email': 'alice@example.com',
'path': 'safe/path'
})
要点总结
- 禁止直接
eval/exec用户输入 - **
literal_eval**仅支持常量表达式,是安全替代 - 模板引擎启用自动转义,禁用危险上下文
- **
subprocess**使用shell=False,参数分离传递 - SQL查询使用参数化,禁止字符串拼接
📝 发现内容有误?点击此处直接编辑