直接在eloquent mutator中用strip_tags()不安全,因其仅删标签而不处理onerror等js事件、危险协议;且mutator不触发于fill/update等批量操作。应统一在creating/updating事件中调用html净化器(如league/html-sanitizer)净化指定字段,并配置严格白名单。

为什么直接在 Eloquent set* mutator 里用 strip_tags() 不够安全
因为 strip_tags() 只删 HTML 标签,不处理属性里的 JavaScript 事件(如 onerror)、CSS 表达式、data: URI 协议等。用户提交 <img src="x" onerror="alert(1)">,strip_tags() 会保留 onerror,存进数据库后渲染仍可能触发 XSS。
更关键的是:mutator 是“写入前处理”,但若字段被批量赋值($model->fill($request->all()))或通过 update() 直接更新,mutator 不会被调用 —— 净化逻辑就漏掉了。
- 优先用 Laravel 自带的
Illuminate\Support\Str::clean()(Laravel 9.26+),它基于 HTML Purifier 的轻量封装,能过滤事件、危险协议和 CSS 表达式 - 旧版本或需精细控制时,推荐
league/html-sanitizer,比直接集成 HTML Purifier 更轻、API 更清晰 - 永远不要依赖前端 JS 过滤 —— 后端必须做最终校验
如何在 Eloquent 模型中统一净化指定字段(比如 content 和 bio)
别只靠 mutator。把净化逻辑抽成模型的私有方法,在 setAttribute() 中拦截关键字段,确保所有写入路径(fill、create、update、save)都经过同一道过滤。
use League\HTMLSanitizer\Sanitizer;
use League\HTMLSanitizer\SanitizerInterface;
class Post extends Model
{
protected $sanitizedAttributes = ['content', 'bio'];
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->sanitizeAttributes();
});
static::updating(function ($model) {
$model->sanitizeAttributes();
});
}
protected function sanitizeAttributes()
{
foreach ($this->sanitizedAttributes as $attribute) {
if ($this->isDirty($attribute) && !is_null($this->getAttribute($attribute))) {
$sanitizer = app(SanitizerInterface::class);
$this->attributes[$attribute] = $sanitizer->sanitize($this->attributes[$attribute]);
}
}
}
}
- 用
creating和updating事件,覆盖 ORM 主要写入时机;saved太晚,数据已落库 -
isDirty()避免对未修改字段重复处理,也防止 null 值被转成空字符串 - 别在
setAttribute()里硬编码 sanitizer 实例 —— 容易导致测试难 mock,也违反依赖注入原则
league/html-sanitizer 的配置要点:哪些标签和属性该留,哪些必须砍
默认配置偏保守(只留 p, br, strong),实际业务常需放宽。比如富文本编辑器输出的 ul, ol, blockquote 要保留,但 style 属性、onclick、javascript: href 必须禁用。
$sanitizer = new Sanitizer([
'tags' => ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'blockquote'],
'attributes' => [
'*' => ['class'], // 所有标签只允许 class 属性
'a' => ['href'],
],
'protocols' => [
'a' => ['http', 'https'],
],
]);
-
'*' => ['class']表示所有标签只保留class,其他属性(id,style,onclick)全删 -
a标签的href必须配合protocols限制协议,否则href="javascript:alert(1)"依然存活 - 不要开启
iframe或script—— 再严格的白名单也挡不住新型绕过,这类内容应交由专用服务(如 oEmbed)处理
什么时候不该用模型层净化?—— 那些容易被忽略的“绕过点”
模型层净化管不了原始 SQL 查询、raw 字段更新、DB facade 直接操作,还有队列任务里延迟处理的数据。比如一个通知邮件模板从数据库读出 content 字段后直接 echo 渲染,如果没在视图层二次转义,照样 XSS。
- 数据库迁移中用
DB::statement()批量更新字段?得手动调用 sanitizer,模型事件不触发 - API 返回 JSON 时,
content字段值仍含危险 HTML?净化必须在序列化前完成,而不是靠前端v-html—— 那等于放弃防御 - Blade 模板里用
{{ $post->content }}是自动转义的,但用{!! $post->content !!}就完全跳过 —— 这时候净化过的值也得再检查是否含残留风险
最麻烦的其实是历史数据:上线净化逻辑前已存在的脏数据,不会自动清理。上线后得跑一次 php artisan tinker 脚本批量重写字段,否则新老数据混在一起,边界 case 很难测全。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











