参数过滤如何拦截注入

wen 开源项目 28

本文目录导读:

参数过滤如何拦截注入

  1. 核心原则
  2. SQL注入拦截
  3. XSS(跨站脚本)注入拦截
  4. 命令注入拦截
  5. 其他常见注入(LDAP, XML, OS Path等)
  6. 总结:如何构建一个健壮的“参数过滤”体系

参数过滤是拦截注入攻击(如SQL注入、XSS、命令注入等)的核心手段,但需要明确一点:没有“万能”的过滤函数,有效的防御需要结合上下文(Context)进行编码或使用安全API,单纯的“过滤”字符(如黑名单)很容易被绕过。

以下是针对不同类型注入的拦截策略,从“最佳实践”到“应急过滤”进行说明。

核心原则

  1. 不要相信用户输入:这是安全开发的铁律。
  2. 首选安全API,而非过滤:这是最有效的防御。
  3. 输出编码/转义:这是针对XSS等注入的第二道防线。
  4. 最小权限原则:数据库账户、系统账户只给予必需的最小权限。

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) => { ... });

❌ 不推荐的备选方案:输入过滤(黑名单/白名单)

仅在无法使用参数化查询(如动态表名、排序字段)时,作为最后的手段使用。

  • 白名单(推荐):只允许预定义的值,只允许 ASCDESC 作为排序方式。

    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)等方式轻松绕过。

XSS(跨站脚本)注入拦截

XSS攻击是将恶意脚本注入到网页中,供其他用户执行。

✅ 最佳实践:输出编码(Output Encoding)

将数据发送到浏览器时,根据输出位置的上下文进行编码,不要仅在输入时过滤。

  • HTML Body 上下文:转换成HTML实体。<script> 变成 &lt;script&gt;

    • PHP: htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8')
    • Java: StringEscapeUtils.escapeHtml4(userInput)
    • Python (Django): 模板会自动转义 {{ user_input }}
    • JavaScript (React/Vue): 默认使用JSX/模板语法时,React会自动转义。慎用 dangerouslySetInnerHTMLv-html
  • 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):使用socketcURL库来实现Ping功能。
  • 代替exec('convert ' . $input):使用ImageMagickGD库的API。

❌ 次选方案:参数化 + 白名单

如果必须调用系统命令:

  1. 使用escapeshellarg()escapeshellcmd()
    • escapeshellarg():为单个参数添加引号并转义,最常用。exec("ls -l " . escapeshellarg($_GET['file']));
    • escapeshellcmd():对整个命令字符串进行转义,但允许指定管道 和重定向 > 等,更危险,因为整个字符串被攻击者控制的可能性更高。
  2. 严格的白名单校验:只允许特定的字符集。
    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()),然后检查目标路径是否在白名单目录之内。

如何构建一个健壮的“参数过滤”体系

不要只依赖于单个函数,应用层安全的纵深防御体系如下:

  1. 第一道防线(核心)不使用字符串拼接来构造命令或查询,始终使用:

    • SQL:参数化查询/预编译语句。
    • 系统命令:内部API或escapeshellarg()
    • HTML:输出编码。
  2. 第二道防线(纵深防御):对输入进行验证和清理(输入过滤)。

    • 类型检查$_GET['id'] 必须是整数?强制转换成 (int)
    • 长度限制$_POST['username'] 最长20个字符。
    • 格式验证:邮箱用正则 ,URL用 filter_var($url, FILTER_VALIDATE_URL)
    • 白名单:只接受已知安全的字符集([a-zA-Z0-9_ -])。
  3. 第三道防线(基础设施)

    • 数据库账户:只给予表级别的 SELECT, INSERT, UPDATE, DELETE 权限,永远不要使用 GRANT ALL 的系统库账户
    • Web应用防火墙 (WAF):作为网络层的一道额外过滤,拦截常见的攻击载荷,但不要完全依赖它。
    • 最小权限原则:Web服务器运行账户只拥有读取必要文件、写入日志的权限。

一句话总结:过滤是在无法使用安全API时的最后手段;安全API(参数化查询、输出编码)才是永恒的真理。

抱歉,评论功能暂时关闭!