= htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?>
这是典型用法。 如果数据进入属性: "> 查看 同样需要 HTML 属性上下文的编码。 但这里还存在一个容易被忽略的问题:编码并不能证明 URL 本身就是安全的。 例如: javascript:alert(1) 即使进行了 HTML 编码,浏览器仍然可能把它当成 href 中的 JavaScript URL。 因此 URL 不应该只是: echo htmlspecialchars($url, ENT_QUOTES, 'UTF-8'); 还需要根据业务规则验证允许的协议,例如只允许 https、http 等符合要求的 scheme,再进行 HTML 属性编码。 这就是“上下文安全”的概念:不是所有地方都可以简单套同一个函数。 JavaScript里面不要直接套 htmlspecialchars 这是另外一个非常重要的区别。 例如下面这种代码: 不能因为用了 htmlspecialchars() 就认为 JavaScript 注入问题已经彻底解决。 因为这里的数据最终进入的是 JavaScript 字符串上下文,而不是普通 HTML 文本。 更合适的方式通常是让 PHP 把数据编码成合法的 JSON,再交给 JavaScript: 这样处理的思路是“针对 JavaScript 上下文进行编码”,而不是把 HTML 编码函数到处乱用。 同样的原则也适用于 CSS、URL、JSON、HTML 属性等场景:先判断数据最终进入什么解析器,再选择对应的编码方式。 不要把 strip_tags 当成 XSS 防护 有些 PHP 项目会这样处理: $text = strip_tags($_POST['content']); 然后认为这样就安全了。 这种思路并不可靠。 strip_tags() 的主要作用是去掉 HTML/XML 标签,它不是完整的 HTML Sanitizer,也不能替代上下文相关的输出编码。 如果业务要求“用户只能提交纯文本”,更直接的方案通常是保存原始文本,然后在 HTML 输出时使用: htmlspecialchars( $text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); 如果业务要求“允许用户使用部分 HTML”,则应该使用专门的 HTML Sanitization 机制,并通过允许列表控制标签、属性和 URL scheme。 两种业务需求不能混在一起。 ENT_SUBSTITUTE 对生产环境很有价值 现代 PHP 代码中可以考虑: ENT_QUOTES | ENT_SUBSTITUTE ENT_SUBSTITUTE 的作用是在输入字符串包含无效的字符序列时,用替代字符处理,而不是因为非法编码导致整个转换过程出现异常结果。 因此在 UTF-8 网站中,常见的标准写法可以统一成: htmlspecialchars( $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); 当然,这并不意味着任何地方都机械调用这一行。安全编码仍然必须结合实际输出上下文。 CSP是第二道防线,不是htmlspecialchars的替代品 Content Security Policy,也就是 CSP,可以通过 HTTP 响应头限制浏览器允许执行哪些脚本、允许从哪些来源加载资源,从而降低 XSS 成功后的影响范围。 例如网站可以配置类似: Content-Security-Policy: default-src 'self'; script-src 'self' 但 CSP 和输出编码解决的问题并不相同。 htmlspecialchars() 是在数据进入 HTML 解析上下文之前进行编码;CSP 是浏览器端进一步限制资源和脚本执行行为。 一个网站不能因为配置了 CSP 就停止正确的输出编码,也不能因为所有输出都调用 htmlspecialchars() 就认为不需要 CSP。 生产环境还应该根据网站实际使用的第三方脚本、CDN、分析工具、广告系统等逐步收紧 CSP,而不是直接复制一个严格策略后导致正常功能全部失效。 最容易犯的错误不是不会用,而是用错地方 PHP XSS 防护最麻烦的地方,从来不是记住 htmlspecialchars() 这个函数,而是判断数据最终进入什么上下文。 如果数据输出到 HTML 正文,可以使用: echo htmlspecialchars( $text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); 如果数据进入 HTML 属性,同样需要按照 HTML 属性上下文编码。 如果数据进入 JavaScript,则应该使用适合 JavaScript 数据传输的编码方式,例如 json_encode()。 如果数据进入 URL,则除了 HTML 属性编码,还应该验证协议和 URL 格式。 如果数据是允许用户编辑的富文本 HTML,则应该使用 HTML Sanitizer,而不是简单地调用 htmlspecialchars()。 如果数据进入 SQL,则使用 PDO prepared statement,而不是靠 htmlspecialchars()。 如果数据最终进入 HTTP Header,则又是另一套规则。 这也是为什么安全开发不能简单归结为“所有用户输入都 htmlspecialchars 一下”。安全边界真正建立在“根据输出上下文进行正确编码、验证和限制”上。 对于一个 PHP 网站来说,一套比较稳妥的原则是:数据库保存业务数据,SQL 使用参数化查询,HTML 输出使用上下文正确的编码,富文本使用白名单 Sanitizer,JavaScript 数据采用适合 JavaScript 的序列化方式,URL进行协议验证,同时通过 CSP、Cookie 安全属性、CSRF 防护、权限控制和日志监控形成多层防御。 htmlspecialchars() 很重要,但它只是 PHP Web 安全体系中的一个环节。开发人员如果把它当成万能过滤器,反而容易在 JavaScript、URL、富文本和数据库等边界位置留下新的漏洞。对于实际生产系统,最值得建立的习惯不是“看到用户输入就过滤”,而是每一次输出时都先问清楚:这份数据现在要交给谁解析?是 HTML、属性、JavaScript、URL,还是 SQL?只有确定了解析上下文,才能选择正确的安全机制。PHP htmlspecialchars 怎么使用?防止网页输出出现安全问题
图片说明:示意图 图片来源:Public Domain(公有领域)
在 PHP 网站开发中,htmlspecialchars() 是最常见的 HTML 输出编码函数之一。它的作用并不是“过滤危险代码”,也不是判断用户输入是否合法,而是把某些具有 HTML 语义的特殊字符转换成浏览器不会当成标签或属性语法执行的实体,从而降低跨站脚本攻击(XSS)的风险。
这一区分非常重要。用户提交的数据本身并不一定是危险的,危险往往发生在开发者把这些数据放进 HTML、HTML 属性、JavaScript、URL 或其他解释环境时。例如用户输入:
如果 PHP 直接把这段内容输出到 HTML 页面:
echo $username;
浏览器可能把它当成真正的 HTML 元素解析。正确的 HTML 文本输出方式则应该是:
echo htmlspecialchars($username, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
此时浏览器看到的是经过编码后的文本,而不是可以直接解析执行的 HTML 标签。
htmlspecialchars 到底转换了什么
最常见的几个字符包括:
& → &
< → <
> → >
" → "
' → '
例如:
$name = '';
echo htmlspecialchars(
$name,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
页面最终显示的是:
但这些字符已经不再具有 HTML 标签的语义。
这里需要理解一个关键概念:htmlspecialchars() 做的是“输出编码”,不是“清洗 HTML”。
如果网站允许用户提交真正的 HTML,例如文章编辑器、评论系统中的富文本内容,那么直接使用 htmlspecialchars() 会把所有 HTML 标签变成普通文本。此时如果业务确实需要允许部分 HTML,就应该使用专门的 HTML Sanitizer,根据允许的标签、属性和协议进行白名单过滤,而不是试图靠 htmlspecialchars() 同时完成“允许 HTML”和“防 XSS”两个互相冲突的任务。
ENT_COMPAT、ENT_QUOTES 到底有什么区别
很多旧教程对 quote_style 的解释已经不太适合现代 PHP 开发。
ENT_COMPAT 会转义双引号,但不会转义单引号;ENT_QUOTES 则同时转义双引号和单引号。
例如:
$value = '"test" \'hello\'';
echo htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
双引号会被编码,但单引号不会。
如果输出位置是 HTML 属性,尤其是开发人员无法严格保证属性始终使用双引号、或者代码需要统一处理各种属性输入,更稳妥的习惯是:
htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
例如:
这里最重要的不是“哪个参数最安全”这么简单,而是必须根据输出上下文进行编码。HTML正文、HTML属性、JavaScript字符串、CSS和URL并不是同一个解析环境,不能认为调用一次 htmlspecialchars() 就可以覆盖所有 XSS 场景。
PHP 现代项目不要再把 ISO-8859-1 当成中文网站的默认编码
原文把 PHP 的默认编码描述成 ISO-8859-1,这个说法需要纠正。
现代 PHP 中,htmlspecialchars() 的 encoding 参数默认值已经是 UTF-8。但实际项目仍然建议显式指定编码,因为这样代码的意图更加明确,也可以避免不同运行环境、旧项目代码或编码处理方式造成误判。
对于现代中文网站,通常直接写:
htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
即可。
而且需要注意,中文并不需要因为使用 htmlspecialchars() 就变成数字实体。
例如:
echo htmlspecialchars('你好', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
正常情况下,浏览器仍然可以直接显示:
你好
并不会因为调用 htmlspecialchars() 就自动把中文转换成类似 你 的形式。
htmlspecialchars() 主要处理的是 HTML 中具有特殊语义的字符,而不是把所有 Unicode 字符转换成数字实体。
不要在数据库里提前 htmlspecialchars
这是 PHP 项目中一个非常常见的设计错误。
假设用户提交:
Tom & Jerry
很多开发者会在保存数据库之前执行:
$name = htmlspecialchars($_POST['name']);
然后把处理后的字符串存进数据库。
这种做法通常并不好。
数据库应该尽量保存业务数据的原始语义,而 HTML 编码应该根据最终输出环境进行。
例如:
$name = $_POST['name'];
$stmt = $pdo->prepare(
"INSERT INTO users (name) VALUES (:name)"
);
$stmt->execute([
':name' => $name
]);
以后输出到 HTML 时:
echo htmlspecialchars(
$name,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
这样同一份数据库数据以后还可以用于 JSON、邮件、API、CSV或者其他输出场景,而不会把某一种输出格式的编码污染到数据库里。
这也是为什么“存储时编码一次,输出时再编码”通常比“看到用户输入就统一 htmlspecialchars”更合理。
htmlspecialchars 和 PDO::prepare 解决的是两个不同的问题
原文把数据库输出与 PDO::quote() 混在了一起,这需要特别纠正。
SQL 注入和 XSS 是不同攻击面。
当数据进入 SQL 查询时,需要使用参数化查询:
$stmt = $pdo->prepare(
"SELECT * FROM users WHERE username = :username"
);
$stmt->execute([
':username' => $username
]);
这是为了防止 SQL 注入。
而从数据库取出数据以后,如果准备把它放入 HTML:
echo htmlspecialchars(
$row['username'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
这是为了防止 HTML/XSS 问题。
两者不能互相替代。
PDO::quote() 也不是 HTML XSS 防护函数。它解决的是 SQL 字符串字面量的引用问题,而且现代 PHP 项目通常应该优先使用 PDO prepared statements,而不是把各种数据拼接到 SQL 字符串里。
可以把整个过程理解成:
用户输入
↓
业务处理
↓
参数化 SQL
↓
数据库
↓
读取数据
↓
根据输出上下文进行编码
↓
HTML / JSON / JavaScript / URL
这里每一个环节解决的问题不同。
HTML正文和HTML属性必须分别考虑
如果数据出现在普通 HTML 文本中:
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP