全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-16 8 分钟 ✍️ juanwangdev

输出编码与转义

输出编码防止XSS攻击,根据输出上下文选择正确的编码方式。

编码原理

输出编码核心原则:根据输出位置选择对应编码方式

输出上下文编码方式示例
HTML内容HTML实体编码<&lt;
HTML属性HTML属性编码"&quot;
JavaScriptJavaScript转义"\"
URLURL编码空格 → %20
CSSCSS转义特殊字符转义

HTML编码

HTML内容编码

JavaScript
function escapeHtml(str) {
  const htmlEntities = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '/': '&#x2F;'
  };

  return String(str).replace(/[&<>"'/]/g, c => htmlEntities[c]);
}

// 使用示例
const userInput = '<script>alert("xss")</script>';
const safe = escapeHtml(userInput);
// 输出: &lt;script&gt;alert(&quot;xss&quot;)&lt;&#x2F;script&gt;

HTML属性编码

JavaScript
function escapeHtmlAttribute(str) {
  // 属性值必须用引号包裹
  return escapeHtml(str);
}

// 安全使用
// <div data-value="${escapeHtmlAttribute(value)}">

重要:HTML属性值必须用引号包裹,否则编码无效。

HTML
<!-- 危险:无引号属性 -->
<div data-value=${userInput}>

<!-- 安全:引号包裹 -->
<div data-value="${escapeHtmlAttribute(userInput)}">

安全HTML生成

JavaScript
// 使用DOM API自动编码
function safeSetText(element, text) {
  element.textContent = text; // 自动编码
}

function safeSetAttribute(element, attr, value) {
  element.setAttribute(attr, value); // 浏览器自动编码属性
}

// 示例
const div = document.getElementById('output');
safeSetText(div, userInput); // 安全

JavaScript编码

字符串字面量转义

JavaScript
function escapeJsString(str) {
  const jsEscapes = {
    '\\': '\\\\',
    '"': '\\"',
    "'": "\\'",
    '\n': '\\n',
    '\r': '\\r',
    '\t': '\\t',
    '<': '\\x3C',  // 防止</script>攻击
    '>': '\\x3E',
    '&': '\\x26'
  };

  return String(str).replace(/[\\'" \n\r\t<>]/g, c => jsEscapes[c] || c);
}

// 使用示例
// <script>var msg = "${escapeJsString(userInput)}";</script>

JSON编码

JavaScript
// 安全方式:使用JSON.stringify
function safeJsonEncode(value) {
  return JSON.stringify(value);
}

// 使用示例
// <script>
//   var config = ${JSON.stringify(userConfig)};
// </script>

DOM中的JavaScript

JavaScript
// 危险:直接拼接
// <button onclick="handleClick('${userInput}')">

// 安全:使用data属性
// <button data-action="${escapeHtmlAttribute(action)}"
//         data-param="${escapeHtmlAttribute(param)}"
//         onclick="handleClick(this.dataset)">

function handleClick(dataset) {
  const { action, param } = dataset;
  // 安全使用
}

URL编码

URL组件编码

JavaScript
// 完整编码(用于URL参数值)
function encodeUrlParam(value) {
  return encodeURIComponent(value);
}

// 解码
function decodeUrlParam(value) {
  return decodeURIComponent(value);
}

// 使用示例
const url = `/search?q=${encodeURIComponent(query)}`;
const redirect = `https://example.com/callback?next=${encodeURIComponent(path)}`;

URL路径编码

JavaScript
// 路径编码(保留/符号)
function encodeUrlPath(path) {
  return encodeURI(path);
}

// 示例
const path = '/users/' + encodeURI(username);

安全URL验证

JavaScript
function safeUrl(url, allowedOrigins = []) {
  try {
    const parsed = new URL(url, window.location.origin);

    // 只允许特定协议
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      throw new Error('仅允许HTTP/HTTPS协议');
    }

    // 白名单域名检查(防止开放重定向)
    if (allowedOrigins.length > 0) {
      if (!allowedOrigins.includes(parsed.origin)) {
        throw new Error('不允许的域名');
      }
    }

    return parsed.href;
  } catch (error) {
    throw new Error('无效URL');
  }
}

// 使用示例
const allowedOrigins = ['https://example.com', 'https://api.example.com'];
const safe = safeUrl(userInput, allowedOrigins);

CSS编码

CSS字符串转义

JavaScript
function escapeCssString(str) {
  return String(str).replace(/[<>"'\\]/g, c => {
    return '\\' + c.charCodeAt(0).toString(16) + ' ';
  });
}

// 使用示例
// <style>
//   .user-class::after { content: "${escapeCssString(userContent)}"; }
// </style>

CSS选择器转义

JavaScript
function escapeCssSelector(str) {
  // CSS.escape是标准API
  if (typeof CSS !== 'undefined' && CSS.escape) {
    return CSS.escape(str);
  }

  // Polyfill
  return String(str).replace(/([^\w-])/g, (match, c) => {
    return '\\' + c;
  });
}

// 使用示例
const userClass = escapeCssSelector(userInput);
document.querySelector(`.${userClass}`);

各上下文编码对照

JavaScript
const encoders = {
  // HTML上下文
  htmlContent: escapeHtml,
  htmlAttribute: escapeHtml,

  // JavaScript上下文
  jsString: escapeJsString,
  jsData: (v) => JSON.stringify(v),

  // URL上下文
  urlParam: encodeURIComponent,
  urlPath: encodeURI,

  // CSS上下文
  cssString: escapeCssString,
  cssSelector: (v) => CSS ? CSS.escape(v) : v
};

// 根据上下文选择编码器
function encode(value, context) {
  const encoder = encoders[context];
  if (!encoder) {
    throw new Error(`未知上下文: ${context}`);
  }
  return encoder(value);
}

模板引擎集成

安全模板函数

JavaScript
function safeHtml(strings, ...values) {
  let result = strings[0];

  for (let i = 0; i < values.length; i++) {
    // 自动编码
    result += escapeHtml(values[i]) + strings[i + 1];
  }

  return result;
}

// 使用示例
const name = '<script>alert(1)</script>';
const output = safeHtml`<div>Hello, ${name}!</div>`;
// <div>Hello, &lt;script&gt;alert(1)&lt;/script&gt;!</div>

React自动转义

jsx
// React默认转义
function UserGreeting({ name }) {
  return <div>Hello, {name}</div>; // 自动HTML编码
}

// 危险:dangerouslySetInnerHTML绕过保护
function UserContent({ html }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />; // 不安全
}

// 安全:先净化再渲染
function SafeHtml({ html }) {
  const clean = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

编码时机

text
输入 → 验证 → 存储 → 读取 → 输出编码 → 显示

核心原则:存储原始数据,输出时编码。不要在存储前编码,会导致二次编码问题。

要点总结

上下文编码函数关键字符
HTML内容escapeHtml()< > & " '
HTML属性escapeHtml()同上(必须引号包裹)
JS字符串escapeJsString()\ " ' < > &
JS数据JSON.stringify()自动处理
URL参数encodeURIComponent()特殊字符
URL路径encodeURI()保留/
CSSCSS.escape()选择器特殊字符

最佳实践

  • 根据输出位置选择编码方式
  • 使用框架内置转义功能
  • 存储原始数据,输出时编码
  • 复杂场景使用DOMPurify等库
  • 避免内联事件处理器

📝 发现内容有误?点击此处直接编辑

← 上一篇 输入验证与净化
下一篇 → MVC/MVVM架构
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库