在JavaScript中如何取消转义HTML实体?

293

我有一些JavaScript代码,用它来与XML-RPC后端进行通信。 XML-RPC返回以下格式的字符串:

<img src='myimage.jpg'>

然而,当我使用 JavaScript 将字符串插入 HTML 时,它们会直接渲染为字符串。我看不到图像,我只看到字面的字符串:

<img src='myimage.jpg'>

我猜测HTML在XML-RPC通道上被转义了。

我该如何在JavaScript中取消转义字符串?我尝试了这个页面上的技术,但没有成功:http://paulschreiber.com/blog/2008/09/20/javascript-how-to-unescape-html-entities/

还有哪些方法可以诊断问题?


这篇文章中包含的庞大函数似乎运行良好:http://blogs.msdn.com/b/aoakley/archive/2003/11/12/49645.aspx 我认为这不是最聪明的解决方案,但它能够工作。 - mati
2
作为包含HTML实体的字符串与escapeURI编码字符串不同,因此这些函数无法使用。 - Marcel Korpel
2
@Matias 注意,自2003年编写该函数以来,HTML(例如通过HTML 5规范)已添加了新的命名实体-例如,它无法识别“𝕫”。这是一个不断发展的规范问题;因此,您应选择一个实际正在维护的工具来解决它。 - Mark Amery
可能是如何使用jQuery解码HTML实体?的重复问题。 - lucascaro
我刚刚意识到很容易将这个问题与编码HTML实体混淆。我刚刚意识到我在这个问题上不小心发布了一个错误的答案!不过我已经删除了它。 - shreyasm-dev
34个回答

-2

function decodeHTMLContent(htmlText) {
  var txt = document.createElement("span");
  txt.innerHTML = htmlText;
  return txt.innerText;
}

var result = decodeHTMLContent('One &amp; two &amp; three');
console.log(result);


这个答案比多年前给出的textarea答案更好在哪里? - Dan Dascalescu
这将会带来安全问题。你可以随意添加一个<img>并运行任意JS,没有任何阻止措施。请勿在生产环境中(或者如果其他人会使用它的话,在业余项目中)使用此代码或类似代码。 - Radvylf Programs

-2
var encodedStr = 'hello &amp; world';

var parser = new DOMParser;
var dom = parser.parseFromString(
    '<!doctype html><body>' + encodedStr,
    'text/html');
var decodedString = dom.body.textContent;

console.log(decodedString);

2
@Wladimir Palant(AdBlock Plus的作者)已经在4年前给出了DOMParser的答案。你发帖前有没有看过之前的回答? - Dan Dascalescu

-3

当前得票最高的答案有一个缺点,即从字符串中删除HTML。如果这不是您想要的(当然也不是问题的一部分),那么我建议使用正则表达式查找HTML实体(/&[^;]*;/gmi),然后遍历匹配项并将它们转换。

function decodeHTMLEntities(str) {
  if (typeof str !== 'string') {
    return false;
  }
  var element = document.createElement('div');
  return str.replace(/&[^;]*;/gmi, entity => {
    entity = entity.replace(/</gm, '&lt;');
    element.innerHTML = entity;
    return element.textContent;
  });
}

var encoded_str = `<b>&#8593; &#67;&#65;&#78;'&#84;&nbsp;&#72;&#65;&#67;&#75;&nbsp;&#77;&#69;,&nbsp;&#66;&#82;&#79;</b>`;
var decoded_str = decodeHTMLEntities(encoded_str);

console.log(decoded_str);

关于XSS攻击:

虽然innerHTML不会在<script>标签中执行代码,但是可能会在on*事件属性中运行代码,因此仅使用上述正则表达式可能会被用户传入以下字符串利用:

&<img src='asdfa' error='alert(`doin\' me a hack`)' />;

因此,在将任何<字符放入隐藏的div元素之前,有必要将它们转换为&lt;

另外,为了覆盖所有可能性,我要注意一下:在全局范围内定义的函数可以被控制台中重新定义的函数覆盖,因此重要的是要使用const定义此函数或将其放在非全局范围内。

注意:以下示例中尝试的漏洞会使堆栈片段编辑器混淆,因为它进行预处理,所以您需要在浏览器的控制台中运行它或在自己的文件中查看结果。

var tests = [
  "here's a spade: &spades;!",
  "&<script>alert('hackerman')</script>;",
  "&<img src='asdfa' error='alert(`doin\' me a hack`)' />;",
  "<b>&#8593; &#67;&#65;&#78;'&#84;&nbsp;&#72;&#65;&#67;&#75;&nbsp;&#77;&#69;,&nbsp;&#66;&#82;&#79;</b>"
];

var decoded = tests.map(decodeHTMLEntities).join("\n");
console.log(decoded);

结果是:

here's a spade: ♠!
&<script>alert('hackerman')</script>;
&<img src='asdfa' error='alert(`doin' me a hack`)' />;
<b>↑ CAN'T HACK ME, BRO</b>

这种方法无法防止XSS攻击。你特别提到了“得票最高的答案”,但是你的回答未能处理该答案中概述的简单XSS攻击。仅仅拒绝<SCRIPT是一种非常简单的修复尝试,它甚至不能抵御最基本的规避尝试,例如< SCRIPT,更不用说指定onerroronload属性了。 - user229044
实际上这里有很多问题。它甚至无法拒绝<script>标签。当输入像&lt;script这样的内容时,你的entity变量从未包含完整的字符串<script,它只包含&lt;。即使正则表达式被修复以包括所有后续标记,你仍将比较"&LT;SCRIPT<SCRIPT - user229044
@user229044 - 当我在Firefox控制台上运行代码时,我得到的结果与您所说的不同。它在我尝试的任何在线cde编辑器上都无法工作,因为它们进行了预处理,但是当您按原样运行代码(decodeHTMLEntities('&lt;script&gt;'))时,它会生成<script>。检查单词“script”的目的是防止执行任何攻击,例如decodeHTMLEntities('&<script>alert('test')</script>;'),因为据我所知,没有包含单词“script”的html字符代码。 - I wrestled a bear once.
我还应该注意到,我已经在一个每天有数千个新独立访客的网站上使用了这段代码约一周时间,并且没有出现任何问题。 - I wrestled a bear once.
1
XSS 在使用 innerHTML 时是有可能发生的,可以参考得票最高的答案中的 XSS 负载示例:document.createElement('div').innerHTML=(\<img src="asdf" onerror="alert('no')" />`)`。 - user229044
显示剩余2条评论

-8

有一个变量,其效率与最高效答案的80%相当。

请参见基准测试:https://jsperf.com/decode-html12345678/1

performance test

console.log(decodeEntities('test: &gt'));

function decodeEntities(str) {
  // this prevents any overhead from creating the object each time
  const el = decodeEntities.element || document.createElement('textarea')

  // strip script/html tags
  el.innerHTML = str
    .replace(/<script[^>]*>([\S\s]*?)<\/script>/gmi, '')
    .replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, '');

  return el.value;
}

如果您需要保留标签,则删除两个 .replace(...) 调用(如果您不需要脚本,则可以保留第一个)。

11
恭喜你成功用虚假的清洗逻辑掩盖了漏洞,而这只是为了在实践中并不重要的性能优化。尝试在Firefox中调用 decodeEntities("</textarea '><img src=x onerror=alert(1) \">") 。请停止尝试使用正则表达式来清洗 HTML 代码。 - Wladimir Palant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接