MySQL本身不执行JavaScript,无法防范XSS;真正防线在应用层:入库前按字段语义做白名单校验与场景化转义,出库后依渲染上下文(HTML/JS/URL)安全输出。

MySQL 本身不执行 JavaScript,也不会直接触发 XSS;所谓“MySQL 防范 XSS”本质是防止恶意脚本通过后端拼接、未过滤的用户输入写入数据库,再在前端渲染时执行——风险不在 MySQL,而在应用层对 INSERT / UPDATE 输入的放行和对 SELECT 输出的裸用。
为什么只靠 MySQL 转义根本没用
MySQL 的 mysql_real_escape_string(已废弃)或 mysqli::escape_string 只处理 SQL 层面的引号闭合,比如把 ' 变成 \',防止注入。但它完全不管 <script></script>、onerror= 这类前端可执行内容。一旦你用 echo $row['content'] 直接输出到 HTML,浏览器照常解析执行。
- MySQL 不知道字段将来会在
<div> 里还是 <code><input value="">里渲染,无法决定该用 HTML 实体、JS 字符串转义还是 URL 编码 - 即使存库前用
htmlspecialchars()处理了,如果前端又用v-html或innerHTML渲染,照样 XSS - 多端共用同一字段时(如 API 返回给 Web、App、邮件),统一转义反而会破坏非 HTML 场景的显示
- 用户名:
preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]{2,20}$/u', $input)—— 仅允许中英文、数字、下划线,长度限制 - 富文本内容(如文章正文):用成熟库如
HTMLPurifier,配置白名单标签(p、strong、img)和属性(src仅限 http(s) 协议),禁用on*事件和javascript:协议 - 纯文本摘要(如 meta description):入库前强制
strip_tags($input)+trim()+mb_substr($input, 0, 200) - 字段名带
_html后缀(如content_html):表示已由HTMLPurifier处理过,前端可用<div>= $row['content_html'] ?></div>,但禁止用于<input value=""> - 字段名带
_text后缀(如title_text):表示纯文本,前端必须用<div>= htmlspecialchars($row['title_text'], ENT_QUOTES, 'UTF-8') ?></div> - 字段名带
_raw后缀(如user_input_raw):仅用于日志或后台审核,绝不直出到前端页面 - 后端 API 返回前,对所有可能含用户输入的字段做
htmlspecialchars()(不是json_encode()!后者只转义引号和反斜杠) - 前端拿到 JSON 后,一律用
textContent或框架的安全绑定(如 Vue 的{{ }}默认转义),禁用innerHTML、insertAdjacentHTML、eval() - 若必须支持富文本,API 应返回结构化数据(如
{ type: 'paragraph', text: '...' }),前端用白名单渲染器构造 DOM,而非拼 HTML 字符串
后端输入校验必须分场景做白名单
不能只靠“过滤掉 script 标签”这种黑名单式清洗——绕过方式太多(<scr>ipt></scr>、大小写混写、编码混淆)。真正有效的做法是:明确字段用途,按需定义合法字符集。
存储策略决定输出安全边界
存什么,决定了你后续怎么取、怎么用。不要把“是否转义”逻辑分散在每个 echo 前,而应在入库时就定好语义。
最容易被忽略的坑:JSON 接口 + 前端模板
很多人以为“我用的是 AJAX,返回 JSON,肯定安全”,结果在前端用 element.innerHTML = data.content 或 Vue 的 v-html 渲染,等于把 XSS 风险从 PHP 转嫁给了 JavaScript。
真正的防线不在 MySQL,而在每一次 INSERT 前的语义判定,和每一次 SELECT 后的上下文感知输出。字段设计阶段没想清楚“这个值最终在哪种 HTML 位置、以什么方式渲染”,后面补多少转义都容易漏。


















