CodeIgniter Security 类实战:XSS 过滤、CSRF 防护与安全工具方法深度解析
2026/9/21 18:32:33 网站建设 项目流程

CodeIgniter Security 类实战:XSS 过滤、CSRF 防护与安全工具方法深度解析

【免费下载链接】CodeIgniterOpen Source PHP Framework (originally from EllisLab)项目地址: https://gitcode.com/gh_mirrors/co/CodeIgniter

本篇指南以 CodeIgniter 框架的 Security Class(安全类)为核心,讲解如何基于 system/core/Security.php 构建安全应用:包括跨站脚本(XSS)过滤、跨站请求伪造(CSRF)防护、文件名净化、HTML 实体解码与安全随机字节生成。读者读完可掌握CI_Security全部公开方法的正确调用方式、application/config/config.php中相关配置项的取值与影响,以及底层实现原理与官方测试的验证结论。

Security Class 概览

Security Class 是 CodeIgniter 的框架核心类之一,职责是对输入数据进行安全处理,帮助开发者构建安全的应用。它封装了两大类能力:

  • 输入净化:XSS 过滤、文件名净化、实体解码等,防御恶意输入;
  • 请求防伪:CSRF Token 的生成、验证与 Cookie 管理。

在控制器中,Security 类以单例属性$this->security的形式自动加载,无需手动实例化:

$this->security->xss_clean($data);

框架同时提供了 system/helpers/security_helper.php 作为全局函数包装层,加载该辅助函数文件后可直接调用xss_clean()sanitize_filename()strip_image_tags()encode_php_tags(),它们内部都通过get_instance()->security委托给CI_Security实例执行,例如:

function xss_clean($str, $is_image = FALSE) { return get_instance()->security->xss_clean($str, $is_image); }

XSS 过滤

XSS(跨站脚本)攻击试图在应用中注入并执行 JavaScript 或其他恶意代码,用以劫持 Cookie、篡改页面或执行其他恶意操作。CodeIgniter 内置的 XSS 过滤机制会识别常见攻击技术,一旦发现被禁止的内容,就将其转换为 HTML 字符实体(character entities),使其在浏览器中变为无害文本。

基本用法:xss_clean()

通过xss_clean()方法过滤数据:

$data = $this->security->xss_clean($data);

该方法接受字符串或字符串数组作为输入(数组会递归处理每个元素),返回净化后的数据。以官方测试 tests/codeigniter/core/Security_test.php 为例,输入:

Hello, i try to <script>alert('Hack');</script> your site

过滤结果为:

Hello, i try to [removed]alert&#40;'Hack'&#41;;[removed] your site

可以看到<script>标签被整体替换为[removed]alert(中的左括号被转义为&#40;,使攻击载荷彻底失去可执行能力。

图片上传安全检测:is_image 参数

xss_clean()的可选第二参数$is_image专用于上传文件的 XSS 检测。当设为TRUE时,方法不再返回改写后的字符串,而是返回布尔值:图片安全返回TRUE,若包含浏览器可能尝试执行的恶意信息则返回FALSE

if ($this->security->xss_clean($file, TRUE) === FALSE) { // file failed the XSS test }

从源码 system/core/Security.php 可以看出其判定原理:处理完成后将净化结果与转换前的字符串快照做全等比较,若内容在过滤过程中发生了任何改动,说明其中存在被清除的恶意代码,即判定为不安全。测试用例 test_xss_clean_image_invalid 验证了<img src=javascript:alert(...)>这类注入会被拦截并返回FALSE

另外,图片模式下对 PHP 标签的转义策略也不同:普通文本会同时转义<??>,而图片因常含 PHP 短标签,只转义长标签<?php(源码见 system/core/Security.php)。

重要xss_clean()不适合用于过滤 HTML 属性值!对属性值请改用框架提供的全局函数html_escape(),以避免破坏属性结构与引入二次注入风险。

底层过滤流程(源码级)

从 system/core/Security.php 可以看到,xss_clean()的执行链相当严密,依次执行:

  1. 数组递归:若输入是数组,逐个元素递归过滤;
  2. 移除不可见字符:调用remove_invisible_characters()清除控制字符与十六进制编码字符;
  3. URL 解码:用rawurldecode()循环解码(保留加号语义),拆穿%77%77%77之类的编码伪装,同时用_urldecodespaces()处理被空格打断的百分号编码;
  4. 实体转 ASCII:仅在标签内部把字符实体解码为可识别的 ASCII,使后续黑名单匹配可靠;
  5. Tab 转空格:防止ja\tvascript这类用制表符拆词的绕过;
  6. 黑白名单替换_do_never_allowed()依次应用$_never_allowed_str(如document.cookie.innerHTML<!--<%等字符串替换)与$_never_allowed_regex(如javascript\s*:vbscript\s*:data:...base64...等正则替换为[removed]);
  7. PHP 标签转义<??>转为实体;
  8. 压缩拆散单词:用_compact_exploded_words()j a v a s c r i p t这类被空格拆散的关键词重新拼回,便于后续规则命中;
  9. JS 链接与图片清理_js_link_removal()_js_img_removal()负责清掉href=/src=中的javascript:window..cookie等危险内容;
  10. 净化 naughty HTML_sanitize_naughty_html()alertiframeobjectsvgstyle等危险标签整体转义,对on\w+styleformaction等"邪恶属性"替换为xss=removed
  11. 危险函数转义:将eval(...)alert(...)system(...)等函数调用的括号转为&#40;/&#41;,并同样处理eval`...`这类模板字符串形式的"tag functions";
  12. 最终清理:再次执行_do_never_allowed()兜底。

该过滤器基于 Bitflux 的 XSS 防护思路并参考了 ha.ckers.org 的 XSS 漏洞清单编写,源码注释也坦承"没有任何过滤器是 100% 万无一失的",因此建议只用于数据提交阶段,而非一般性的运行时处理。

与 Input 类配合使用

过滤器的典型使用场景是与 Input 类结合。查看 system/core/Input.php 可知,get()post()cookie()server()input_stream()等方法都支持第二参数$xss_clean,传入TRUE时数据会先经$this->security->xss_clean($value)净化再返回:

$this->input->post('some_data', TRUE); // 取 POST 值并做 XSS 过滤 $this->input->get('page', TRUE); // 取 GET 值并做 XSS 过滤

相关辅助方法

CI_Security还提供两个与 XSS 配套的方法:

  • xss_hash():生成并缓存一个 32 位十六进制随机串,源码 system/core/Security.php 显示它由get_random_bytes(16)生成,失败时退化为md5(uniqid(mt_rand(), TRUE))。它在内部用于 URL 中 GET 参数的保护标记(_decode_entity()),一般不直接在业务中使用;
  • strip_image_tags($str):从字符串中提取<img>标签的src值,返回图片 URL。在security_helper.php中同样有同名全局函数。

跨站请求伪造(CSRF)防护

CSRF 攻击利用浏览器自动携带 Cookie 的特性,诱导已登录用户在不知情的情况下向目标站点提交伪造请求。CodeIgniter 通过同步令牌模式防护:为每次会话生成随机 Token,写入 Cookie 并注入表单,提交时校验两者一致。

启用与核心配置

application/config/config.php中修改以下配置即可启用(默认关闭):

$config['csrf_protection'] = TRUE;

配置文件 application/config/config.php 中定义了整套 CSRF 参数及其默认值:

配置项默认值说明
csrf_protectionFALSE是否启用 CSRF 防护,接受用户数据时强烈建议开启
csrf_token_name'csrf_test_name'隐藏表单字段名(Token 名称)
csrf_cookie_name'csrf_cookie_name'存放 Token 的 Cookie 名称
csrf_expire7200Token 过期时间(秒),默认 2 小时
csrf_regenerateTRUE每次提交后是否重新生成 Token
csrf_exclude_urisarray()跳过 CSRF 校验的 URI 白名单

从构造函数(system/core/Security.php)可以看到,当csrf_protectionTRUE且非 CLI 环境时,Security 类会自动读取上述配置、生成 hash 并立即执行csrf_verify()。注意:csrf_cookie_name还会拼接cookie_prefix前缀。

表单自动注入:form_open()

如果使用表单辅助函数,form_open()会自动在表单内插入 CSRF 隐藏字段,无需手工编写。查看 system/helpers/form_helper.php 的实现:当csrf_protection开启、表单 action 指向本站base_url()且非 GET 方法时,自动追加隐藏输入框。

值得一提的是,该实现还内建了针对BREACH 攻击的防护:用get_random_bytes(1)生成一个随机的"白噪声",以随机数量的空格包裹 Token 输入框(负值前缀、正值后缀),使压缩类攻击难以从响应长度中提取 Token 信息。

手动构建表单与 AJAX 请求

不使用form_open()时,可通过get_csrf_token_name()get_csrf_hash()手动获取字段名和值:

$csrf = array( 'name' => $this->security->get_csrf_token_name(), 'hash' => $this->security->get_csrf_hash() ); ... <input type="hidden" name="<?=$csrf['name'];?>" value="<?=$csrf['hash'];?>" />

这两个方法同样适用于发送合法的 AJAX POST 请求:在 JavaScript 中读取 Token 名与哈希,随请求体一起提交,即可通过服务端校验。

Token 再生策略:csrf_regenerate

Token 可以每次提交后重新生成(默认行为),也可以在 CSRF Cookie 生命周期内保持不变

$config['csrf_regenerate'] = TRUE;

默认的每次再生提供更严格的安全保障,但可能带来可用性问题——旧 Token 立即失效,导致前进/后退导航、多标签页/多窗口、异步请求等场景下校验失败。若你的应用对这些场景敏感,可将其设为FALSE换取体验;csrf_expire仍会控制 Token 的最终过期时间。

白名单排除:csrf_exclude_uris

某些 URI 需要豁免 CSRF 校验,典型场景是接收外部 POST 内容(如第三方回调)的 API 端点

$config['csrf_exclude_uris'] = array('api/person/add');

该配置支持大小写不敏感的正则表达式

$config['csrf_exclude_uris'] = array( 'api/record/[0-9]+', 'api/title/[a-z]+' );

源码 system/core/Security.php 显示匹配逻辑为preg_match('#^'.$excluded.'$#i'.(UTF8_ENABLED ? 'u' : ''), $uri->uri_string())——即以^$锚定整段 URI 字符串,并附加i(忽略大小写)修饰符,UTF-8 环境下再加u修饰符。

验证流程(源码级)

csrf_verify()(system/core/Security.php)的完整流程如下:

  1. 非 POST 请求:不校验,直接调用csrf_set_cookie()设置 Cookie 后返回;
  2. 白名单检查:若当前 URI 命中csrf_exclude_uris,跳过校验直接返回;
  3. Token 比对:校验$_POST[token_name]$_COOKIE[cookie_name]同时存在且均为字符串,并用hash_equals()恒定时间比较(防止时序侧信道攻击);
  4. 清理:无论结果如何,先从$_POST中 unset 掉 Token,避免污染后续业务数据;
  5. 再生:若csrf_regenerateTRUE,清除旧 Cookie 并重置内部 hash;
  6. 重新下发_csrf_set_hash()生成/复用 hash,csrf_set_cookie()重新写入 Cookie;
  7. 失败处理:校验不通过则调用csrf_show_error(),通过show_error('The action you have requested is not allowed.', 403)返回 403 错误。

_csrf_set_hash()(system/core/Security.php)还有一个细节:若 Cookie 中已存在格式合法的 32 位十六进制 hash(preg_match('#^[0-9a-f]{32}$#iS')),会直接复用而不是重新生成——因为页面可能内嵌子页面,每次加载都重新生成会导致校验失败。新 hash 则由get_random_bytes(16)的二进制结果经bin2hex()得到。

Cookie 的写入(csrf_set_cookie(),system/core/Security.php)遵循以下规则:

  • 过期时间为time() + $csrf_expire
  • cookie_secureTRUE但当前不是 HTTPS 连接,拒绝下发并返回FALSE
  • PHP 7.3+ 使用setcookie()的数组参数形式,否则手工构造Set-Cookie头;
  • Cookie 的pathdomainsecurehttponly均取自对应的 cookie 配置项;
  • SameSite属性被硬编码为Strict,进一步缓解 CSRF 风险。

文件名净化:sanitize_filename()

sanitize_filename()用于清理用户输入的文件名,防止目录遍历(directory traversal)攻击及其他安全威胁,特别适合处理用户上传/提交的文件名:

$filename = $this->security->sanitize_filename($this->input->post('filename'));

默认情况下会剥离所有路径成分/./均被移除),只保留纯文件名。若业务允许用户输入包含相对路径(如file/in/some/approved/folder.txt),可将第二参数设为TRUE

$filename = $this->security->sanitize_filename($this->input->post('filename'), TRUE);

其实现(system/core/Security.php)基于公开属性$filename_bad_chars黑名单——包含../<!----><>、引号、&$#、花括号、%20%22%3c%253c等双重编码变体(见 system/core/Security.php),处理过程为:先移除不可见字符,再循环执行str_replace直至字符串不再变化,最后stripslashes()去除转义。官方测试 test_sanitize_filename 验证了输入./<!--foo-->会输出安全的foo

HTML 实体解码:entity_decode()

entity_decode()与 PHP 原生html_entity_decode()ENT_COMPAT模式下行为类似,额外优势是能识别不带分号结尾的 HTML 实体——部分浏览器允许省略分号并仍能正确解析,而html_entity_decode()不会转换这类实体(源码注释见 system/core/Security.php)。

$decoded = $this->security->entity_decode($encoded);

第二参数$charset指定输入字符串字符集,留空时使用配置的$config['charset'](默认'UTF-8')。实现(system/core/Security.php)通过get_html_translation_table()建立实体映射表,循环处理字母实体、数字实体与 UTF-16 双字节实体,直至字符串不再变化。官方测试 test_entity_decode 覆盖了&lt;div&gt;&colon;&NewLine;等实体的解码,并确认&foo(非实体)不会被误转换。

安全随机字节:get_random_bytes()

get_random_bytes($length)是获取安全随机字节的便捷方法,用于生成 CSRF 与 XSS Token。文档描述其候选来源依次为mcrypt_create_iv()/dev/urandomopenssl_random_pseudo_bytes();结合当前仓库源码 system/core/Security.php 可以确认,实际优先级为:

  1. PHP 7+ 内置的random_bytes()(最优先,失败则直接返回FALSE,不做降级);
  2. mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)
  3. 读取/dev/urandom(用stream_set_chunk_size()限制熵消耗);
  4. openssl_random_pseudo_bytes($length)
  5. 全部不可用时返回FALSE

方法会先校验$length为非空且为数字字符串,否则返回FALSE(测试 test_get_random_bytes 验证了非法长度返回FALSE)。需要注意:输出并不保证密码学安全,它只是"尽力而为"的最佳尝试——但源码中调用方(如_csrf_set_hash()xss_hash())会在返回FALSE时退化为md5(uniqid(mt_rand(), TRUE)),兼顾了极端环境下的可用性。

类参考:CI_Security 公开方法一览

方法签名返回值用途
xss_clean()xss_clean($str[, $is_image = FALSE])mixed清除输入数据中的 XSS 漏洞;$is_imageTRUE时对图片内容做安全检测,安全返回TRUE,检测到恶意数据返回FALSE不可用于过滤 HTML 属性值,请改用html_escape()
sanitize_filename()sanitize_filename($str[, $relative_path = FALSE])string净化文件名,防御目录遍历;$relative_pathTRUE时保留目录结构
get_csrf_token_name()get_csrf_token_name()string返回 CSRF Token 名称(即$config['csrf_token_name']
get_csrf_hash()get_csrf_hash()string返回 CSRF 哈希值,与get_csrf_token_name()配合用于手工构建表单或发送 AJAX POST 请求
entity_decode()entity_decode($str[, $charset = NULL])string类似html_entity_decode()ENT_COMPAT模式,但能识别无分号结尾的实体;$charset留空时使用$config['charset']
get_random_bytes()get_random_bytes($length)string获取随机字节流,失败返回FALSE;用于生成 CSRF 与 XSS Token,输出不保证密码学安全

此外,CI_Security还公开了csrf_verify()csrf_set_cookie()csrf_show_error()xss_hash()strip_image_tags()等可覆写方法(前两者适合在自定义扩展类中覆写以调整校验与下发逻辑)。

测试验证

仓库在 tests/codeigniter/core/Security_test.php 中为 Security 类提供了完整的单元测试覆盖,可在tests/目录下通过 PHPUnit 运行验证:

  • XSS 过滤test_xss_clean验证脚本标签与函数调用的净化;test_xss_clean_string_array验证数组递归处理;test_xss_clean_entity_double_encoded验证双重编码实体的拆解;test_xss_clean_sanitize_naughty_html_tagstest_xss_clean_sanitize_naughty_html_attributes覆盖on*属性、fscommandseekSegmentTime等边界场景;
  • CSRF 校验test_csrf_verify(GET 通过)、test_csrf_verify_invalid(POST 无 Token 抛出 403 异常)、test_csrf_verify_valid(POST 携带正确 Token 通过)、test_csrf_set_hash(空 Cookie 名时仍能生成 hash);
  • 其他方法test_entity_decodetest_sanitize_filenametest_strip_image_tagstest_get_random_bytestest_xss_hash

这些测试既是对框架行为的权威定义,也是理解各类方法边界条件的绝佳教材。

小结

Security Class 是 CodeIgniter 输入安全防线中的关键一环:xss_clean()以多阶段、多策略的深度净化机制拦截 XSS 载荷;CSRF 组件以"Cookie + 隐藏字段 +hash_equals恒定时间比对 + 白名单 + SameSite=Strict"的组合提供开箱即用的防伪造能力;sanitize_filename()entity_decode()get_random_bytes()则分别覆盖文件名、实体与随机数等安全细节。结合实际项目,建议遵循文档与源码中反复强调的三条原则:接受用户数据时开启 CSRF 防护、对提交数据使用 XSS 过滤、对 HTML 属性值一律使用html_escape()而非xss_clean()

【免费下载链接】CodeIgniterOpen Source PHP Framework (originally from EllisLab)项目地址: https://gitcode.com/gh_mirrors/co/CodeIgniter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询