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('Hack');[removed] your site可以看到<script>标签被整体替换为[removed],alert(中的左括号被转义为(,使攻击载荷彻底失去可执行能力。
图片上传安全检测: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()的执行链相当严密,依次执行:
- 数组递归:若输入是数组,逐个元素递归过滤;
- 移除不可见字符:调用
remove_invisible_characters()清除控制字符与十六进制编码字符; - URL 解码:用
rawurldecode()循环解码(保留加号语义),拆穿%77%77%77之类的编码伪装,同时用_urldecodespaces()处理被空格打断的百分号编码; - 实体转 ASCII:仅在标签内部把字符实体解码为可识别的 ASCII,使后续黑名单匹配可靠;
- Tab 转空格:防止
ja\tvascript这类用制表符拆词的绕过; - 黑白名单替换:
_do_never_allowed()依次应用$_never_allowed_str(如document.cookie、.innerHTML、<!--、<%等字符串替换)与$_never_allowed_regex(如javascript\s*:、vbscript\s*:、data:...base64...等正则替换为[removed]); - PHP 标签转义:
<?、?>转为实体; - 压缩拆散单词:用
_compact_exploded_words()将j a v a s c r i p t这类被空格拆散的关键词重新拼回,便于后续规则命中; - JS 链接与图片清理:
_js_link_removal()与_js_img_removal()负责清掉href=/src=中的javascript:、window.、.cookie等危险内容; - 净化 naughty HTML:
_sanitize_naughty_html()对alert、iframe、object、svg、style等危险标签整体转义,对on\w+、style、formaction等"邪恶属性"替换为xss=removed; - 危险函数转义:将
eval(...)、alert(...)、system(...)等函数调用的括号转为(/),并同样处理eval`...`这类模板字符串形式的"tag functions"; - 最终清理:再次执行
_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_protection | FALSE | 是否启用 CSRF 防护,接受用户数据时强烈建议开启 |
csrf_token_name | 'csrf_test_name' | 隐藏表单字段名(Token 名称) |
csrf_cookie_name | 'csrf_cookie_name' | 存放 Token 的 Cookie 名称 |
csrf_expire | 7200 | Token 过期时间(秒),默认 2 小时 |
csrf_regenerate | TRUE | 每次提交后是否重新生成 Token |
csrf_exclude_uris | array() | 跳过 CSRF 校验的 URI 白名单 |
从构造函数(system/core/Security.php)可以看到,当csrf_protection为TRUE且非 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)的完整流程如下:
- 非 POST 请求:不校验,直接调用
csrf_set_cookie()设置 Cookie 后返回; - 白名单检查:若当前 URI 命中
csrf_exclude_uris,跳过校验直接返回; - Token 比对:校验
$_POST[token_name]与$_COOKIE[cookie_name]同时存在且均为字符串,并用hash_equals()做恒定时间比较(防止时序侧信道攻击); - 清理:无论结果如何,先从
$_POST中 unset 掉 Token,避免污染后续业务数据; - 再生:若
csrf_regenerate为TRUE,清除旧 Cookie 并重置内部 hash; - 重新下发:
_csrf_set_hash()生成/复用 hash,csrf_set_cookie()重新写入 Cookie; - 失败处理:校验不通过则调用
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_secure为TRUE但当前不是 HTTPS 连接,拒绝下发并返回FALSE; - PHP 7.3+ 使用
setcookie()的数组参数形式,否则手工构造Set-Cookie头; - Cookie 的
path、domain、secure、httponly均取自对应的 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 覆盖了<div>、:、
等实体的解码,并确认&foo(非实体)不会被误转换。
安全随机字节:get_random_bytes()
get_random_bytes($length)是获取安全随机字节的便捷方法,用于生成 CSRF 与 XSS Token。文档描述其候选来源依次为mcrypt_create_iv()、/dev/urandom、openssl_random_pseudo_bytes();结合当前仓库源码 system/core/Security.php 可以确认,实际优先级为:
- PHP 7+ 内置的
random_bytes()(最优先,失败则直接返回FALSE,不做降级); mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);- 读取
/dev/urandom(用stream_set_chunk_size()限制熵消耗); openssl_random_pseudo_bytes($length);- 全部不可用时返回
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_image为TRUE时对图片内容做安全检测,安全返回TRUE,检测到恶意数据返回FALSE。不可用于过滤 HTML 属性值,请改用html_escape() |
sanitize_filename() | sanitize_filename($str[, $relative_path = FALSE]) | string | 净化文件名,防御目录遍历;$relative_path为TRUE时保留目录结构 |
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_tags与test_xss_clean_sanitize_naughty_html_attributes覆盖on*属性、fscommand、seekSegmentTime等边界场景; - 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_decode、test_sanitize_filename、test_strip_image_tags、test_get_random_bytes、test_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),仅供参考