本文目录导读:

参数过滤是拦截注入攻击(如SQL注入、XSS、命令注入等)的核心手段,但需要明确一点:没有“万能”的过滤函数,有效的防御需要结合上下文(Context)进行编码或使用安全API,单纯的“过滤”字符(如黑名单)很容易被绕过。
以下是针对不同类型注入的拦截策略,从“最佳实践”到“应急过滤”进行说明。
核心原则
- 不要相信用户输入:这是安全开发的铁律。
- 首选安全API,而非过滤:这是最有效的防御。
- 输出编码/转义:这是针对XSS等注入的第二道防线。
- 最小权限原则:数据库账户、系统账户只给予必需的最小权限。
SQL注入拦截
这是最常见的注入类型,最佳的防御方式是完全避免拼接SQL语句。
✅ 最佳实践:参数化查询(Prepared Statements)
这是拦截SQL注入的黄金标准,它有效是因为SQL语句的结构(语法)和数据(参数)被明确分开,数据库会安全地处理参数,绝不会将其视为可执行的代码。
-
PHP (PDO):
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email'); $stmt->execute(['email' => $_POST['email']]); $user = $stmt->fetch(); -
Java (JDBC):
String sql = "SELECT * FROM users WHERE email = ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, request.getParameter("email")); ResultSet rs = pstmt.executeQuery(); -
Python (MySQLdb/psycopg2):
cursor.execute("SELECT * FROM users WHERE email = %s", (request.form['email'],)) -
Node.js (mysql2):
connection.execute('SELECT * FROM users WHERE email = ?', [req.body.email], (err, results) => { ... });
❌ 不推荐的备选方案:输入过滤(黑名单/白名单)
仅在无法使用参数化查询(如动态表名、排序字段)时,作为最后的手段使用。
-
白名单(推荐):只允许预定义的值,只允许
ASC或DESC作为排序方式。def get_sort_order(user_input): allowed = {'asc': 'ASC', 'desc': 'DESC'} return allowed.get(user_input.lower(), 'ASC') # 默认ASC -
黑名单(不推荐):过滤 , , , ,
union,select等关键字。- 风险极高:攻击者可以使用编码(URL编码、Unicode)、注释()、大小写混合(
SeLeCt)、利用数据库特性(into outfile,INTO dumpfile)等方式轻松绕过。
- 风险极高:攻击者可以使用编码(URL编码、Unicode)、注释()、大小写混合(
XSS(跨站脚本)注入拦截
XSS攻击是将恶意脚本注入到网页中,供其他用户执行。
✅ 最佳实践:输出编码(Output Encoding)
在将数据发送到浏览器时,根据输出位置的上下文进行编码,不要仅在输入时过滤。
-
HTML Body 上下文:转换成HTML实体。
<script>变成<script>。- PHP:
htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8') - Java:
StringEscapeUtils.escapeHtml4(userInput) - Python (Django): 模板会自动转义
{{ user_input }} - JavaScript (React/Vue): 默认使用JSX/模板语法时,React会自动转义。慎用
dangerouslySetInnerHTML或v-html。
- PHP:
-
HTML Attribute 上下文:除了转义,还必须对属性值进行引号包裹。
<input value="<?php echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8'); ?>" />
-
JavaScript 上下文:使用
JSON.stringify()或encodeURIComponent()。永远不要将用户输入直接拼接到<script>标签内部。 -
URL 上下文:使用
urlencode()或encodeURI()。
✅ 另一重要实践:Content Security Policy (CSP)
这是一个HTTP响应头,告诉浏览器只允许加载指定来源的脚本、样式等资源,即使攻击者成功注入了一段 <script> 标签,如果其来源不在CSP白名单中,浏览器也会拒绝执行。
命令注入拦截
当应用程序调用系统命令(如 exec(), system(), shell_exec())时发生。
✅ 最佳实践:避免调用系统命令
最好的方式是使用编程语言内置的库函数,而不是直接调用 shell。
- 代替
exec('ping ' . $ip):使用socket或cURL库来实现Ping功能。 - 代替
exec('convert ' . $input):使用ImageMagick或GD库的API。
❌ 次选方案:参数化 + 白名单
如果必须调用系统命令:
- 使用
escapeshellarg()或escapeshellcmd():escapeshellarg():为单个参数添加引号并转义,最常用。exec("ls -l " . escapeshellarg($_GET['file']));escapeshellcmd():对整个命令字符串进行转义,但允许指定管道 和重定向>等,更危险,因为整个字符串被攻击者控制的可能性更高。
- 严格的白名单校验:只允许特定的字符集。
if (preg_match('/^[a-zA-Z0-9_\-]+$/', $userInput)) { exec('ls -l ' . escapeshellarg($userInput), $output); } else { // 拒绝执行 }
其他常见注入(LDAP, XML, OS Path等)
原理和策略一致:使用对应上下文的安全库。
- LDAP注入:使用
ldap_escape()函数或使用安全的LDAP库(如Spring LDAP的Filter类)。 - XML注入/XXE:禁用外部实体解析(
libxml_disable_entity_loader(true)),使用安全的XML解析器(如defusedxml库)。 - 路径遍历:规范化路径(如
realpath()),然后检查目标路径是否在白名单目录之内。
如何构建一个健壮的“参数过滤”体系
不要只依赖于单个函数,应用层安全的纵深防御体系如下:
-
第一道防线(核心):不使用字符串拼接来构造命令或查询,始终使用:
- SQL:参数化查询/预编译语句。
- 系统命令:内部API或
escapeshellarg()。 - HTML:输出编码。
-
第二道防线(纵深防御):对输入进行验证和清理(输入过滤)。
- 类型检查:
$_GET['id']必须是整数?强制转换成(int)。 - 长度限制:
$_POST['username']最长20个字符。 - 格式验证:邮箱用正则 ,URL用
filter_var($url, FILTER_VALIDATE_URL)。 - 白名单:只接受已知安全的字符集(
[a-zA-Z0-9_ -])。
- 类型检查:
-
第三道防线(基础设施):
- 数据库账户:只给予表级别的
SELECT,INSERT,UPDATE,DELETE权限,永远不要使用GRANT ALL的系统库账户。 - Web应用防火墙 (WAF):作为网络层的一道额外过滤,拦截常见的攻击载荷,但不要完全依赖它。
- 最小权限原则:Web服务器运行账户只拥有读取必要文件、写入日志的权限。
- 数据库账户:只给予表级别的
一句话总结:过滤是在无法使用安全API时的最后手段;安全API(参数化查询、输出编码)才是永恒的真理。