悠悠楠杉
如何构建安全的JavaScript转义函数防御XSS攻击
一、XSS攻击的本质与防御层次
去年某社交平台因未过滤用户输入导致大规模XSS蠕虫传播,造成数百万用户数据泄露。这再次印证了前端安全防护的重要性。XSS攻击的本质是攻击者通过注入恶意脚本破坏页面DOM结构,传统防御存在三大盲区:
- HTML上下文:未转义的尖括号会生成新节点
- 属性上下文:未处理引号导致属性逃逸
- JavaScript上下文:字符串拼接直接执行代码
javascript
// 典型漏洞示例
document.getElementById('output').innerHTML =
'<a href="' + userInput + '">点击</a>';
二、上下文感知的转义体系
2.1 HTML实体编码
采用双重编码策略应对不同场景:
javascript
function escapeHTML(str) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML
.replace(/"/g, '"')
.replace(/'/g, ''');
}
注意:此实现避免使用正则替换,利用DOM API自动处理所有特殊字符
2.2 属性值编码
针对HTML/URL属性需要差异化处理:
javascript
function escapeAttribute(str) {
return escapeHTML(str)
.replace(/%/g, '%25') // URL编码保留
.replace(/\+/g, '%2B');
}
2.3 JavaScript字符串编码
采用JSON序列化+截断防御:
javascript
function escapeJS(str) {
return JSON.stringify(str.toString())
.slice(1, -1)
.replace(/\//g, '\\/');
}
三、进阶防御方案
3.1 基于白名单的DOM净化
javascript
const ALLOWED_TAGS = { a: ['href', 'title'], span: [] };
function sanitizeHTML(html) {
const doc = new DOMParser().parseFromString(html, 'text/html');
const nodes = doc.body.childNodes;
return Array.from(nodes).map(node => {
if (!ALLOWED_TAGS[node.tagName]) return '';
const attrs = Array.from(node.attributes)
.filter(attr => ALLOWED_TAGS[node.tagName].includes(attr.name))
.map(attr => ` ${attr.name}="${escapeAttribute(attr.value)}"`);
return `<${node.tagName}${attrs.join('')}>${sanitizeHTML(node.innerHTML)}</${node.tagName}>`;
}).join('');
}
3.2 CSP策略兜底
即使转义失败,内容安全策略可作为最后防线:
http
Content-Security-Policy:
default-src 'self';
script-src 'unsafe-inline' 'unsafe-eval';
style-src 'self' https://cdn.example.com;
四、实战检验方案
通过单元测试验证防御效果:javascript
describe('XSS防御测试', () => {
it('应阻断脚本注入', () => {
const malicious = '';
expect(escapeHTML(malicious)).not.toContain('-->