【问题标题】:JavaScript Canvas toDataURL security considerationsJavaScript Canvas toDataURL 安全注意事项
【发布时间】:2020-03-17 14:10:36
【问题描述】:

我很好奇是否有人可以告诉我在用户提供的图像上使用 toDataURL 有多安全。基本思想是用户 A 将在其浏览器中上传图像并将其转换为 URL,然后忽略其间的步骤,最终用户 B(以及其他用户)将检索将转换的 URL 格式返回图片并显示在用户 B 的浏览器中。

所以我的问题围绕着是否有人可以滥用系统将代码注入用户 B 的浏览器,或者以其他方式造成严重破坏。一般来说,在使用 toDataURL 然后再将其转换回来时,有哪些安全注意事项?

我知道跨源图像会污染画布,它不允许任何涉及数据的方法,但我不知道这是多少一揽子解决方案。我读过一些浏览器没有此限制,而其他浏览器(甚至同一浏览器的其他版本)根据跨源图像的内容以不同方式实现此限制。

到目前为止我在研究中的发现:

  • this question 答案指向一篇很棒的文章,从将上传的图像存储在服务器上的角度来看。
  • this question 答案指出了一种有趣的方式来隐藏我以前从未见过的图像中的脚本,但我不确定如果我不尝试从该图像中提取脚本并运行它会产生什么漏洞它。
  • this link 详细说明了浏览器选择限制对跨源图像的图像数据的访问的一个重要原因。我一直认为它只是为了防止恶意图像,但现在意识到它还可以防止更多。

从一个用户通过上传图像(不会保持上传状态而是转换为数据 url)攻击另一个用户的角度来看,以上都没有充分接近它,另一个用户稍后会下载和查看(使用 img src 设置为数据 url,而不是恶意用户的原始上传)。 2 接近回答我的问题,但据我了解,如果恶意用户也将一些脚本注入查看用户的浏览器,详细方法将无法正常工作。

解决这个问题是我想做的一个示例,包括文件上传/转换为数据 url 以及示例数据 url 以尝试导入(此示例 url 可以安全导入并且很小所以它可以快速导入):

window.onload = function() {
    document.getElementById("convert").onclick = convert;
    document.getElementById("import").onclick = importF;

    let imageLoader = document.getElementById("imageLoader");
    let canvas = document.getElementById("imageCanvas");
    let ctx = canvas.getContext("2d");

    imageLoader.addEventListener('change', e => {
      let reader = new FileReader();

      reader.onload = (ee) => {
          loadImage("imageCanvas", ee.target.result);
      }

      reader.readAsDataURL(e.target.files[0]);  
    }, false);
};

function loadImage(id, src) {
  let canvas = document.getElementById(id);
  let ctx = canvas.getContext("2d");
  let img = new Image();
  
  img.onload = () => {
      canvas.width = img.width;
      canvas.height = img.height;
      ctx.drawImage(img, 0, 0);
  }
  
  img.src = src;
}

function convert() {
  let canvas = document.getElementById("imageCanvas");
  console.log(canvas.toDataURL());
}

function importF() {
  let imageImport = document.getElementById("imageImport");
  let url = imageImport.value;
  loadImage("imageCanvas", url);
}
<label>Upload Image:</label>
<input type="file" id="imageLoader" name="imageLoader"/>
<br/>

<label>Import Image:</label>
<input type="text" id="imageImport" name="imageImport"/>
<br/>

<label>Sample URL:</label>
<code style="user-select: all;"> data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAApUlEQVQ4T2NkQALyKu5GjP8Z01kZ/1n/+s+kjSyHi80Ik1BQdvf4z8C4nYPx/z819t9M+py/GRj+MzAwgFTgocEGyCl75DEyMEz04f3OEC34lUGY+R8xloPVMMopeTgzMjLsMeb8xdAu8YFojTCFjPJKblNlWf+lTpV5z8rBCHIraYBRQ9Xtoi3XL70S0U+k6YSqZpRX9vgfK/CVIVbw66gBIzcMAHB4Ryt6jeYXAAAAAElFTkSuQmCC </code>
<br/>

<button id="import"> Import from URL </button>
<button id="convert"> Convert to URL </button>
<br/>

<canvas id="imageCanvas"></canvas>

【问题讨论】:

  • 我会说浏览器坏了,如果这真的是一个问题,需要修复,只要你在图像标签中使用数据 url,只有。因为否则显然我可以制作图像来创建恶意网站。对于 SVG(可能包含脚本)作为图像标签中的数据 url,它们将在“沙箱”中执行。
  • 很高兴了解沙盒,让我感觉更安全。我同意任何问题都可能更多地出在浏览器上而不是我身上,但听到其他人也有同样的感觉会很有帮助。
  • 当然,在您将其内联到您的页面之前,请确保它是一个有效的 URL!否则像这样的未转义的“URL”将是一个问题:data:image/png;base64,iVBORw"&gt;&lt;script&gt;console.log("Hello")&lt;/script&gt;&lt;img src="
  • 是的,不用担心,从我的问题中提供的 sn-p 可以看出,我没有计划将其内联到我的页面中。感谢所有有用的信息:)
  • @Sebastian svg in an img 不会执行任何脚本,甚至不会被沙盒化。

标签: javascript html security canvas


【解决方案1】:

这里似乎有些混乱,考虑到您的链接具有多大的误导性,我可以理解。

受污染的画布

"Tainting the canvas" 是一种安全操作,可阻止 .toDataURL() 和任何其他导出方法,例如 .toBlob().captureStream() 或 2D 上下文的 .getImageData()
只有少数情况下会执行此操作:

  • 跨源资源:这是网络上最常见的。 站点 A 在画布上绘制了一个资源,例如来自 站点 B 的图像。如果 Site B 没有告诉浏览器它允许 Site A 通过传递适当的 Allow-Origin读取此内容/em> 标头,则浏览器必须“污染”画布。
    这只保护资源。在这种情况下,站点 A 并没有真正的安全措施。

  • 信息泄露:这更像是一个例外,但它仍然是一回事。浏览器可能会自行决定某些操作可能会泄露有关其用户的隐私信息。例如,最常见的情况是在画布上绘制包含 的 SVG 图像时“污染”画布。由于这个标签可以呈现 HTML,它也可以泄露例如访问过的链接。浏览器应该对这些资源进行匿名处理,但是,Safari 仍然会污染任何此类 SVG 图像,Chrome buggily 仍然会污染从 blob: URI 提供的资源,IE 确实会污染任何 SVG 图像(不仅),并且在使用某些外部对象filter时,都曾在某些时候污染了画布。

  • 信息泄露二:在读取画布生成的位图时,还有一个浏览器无法解决的问题。当要求执行相同的绘图操作时,每个硬件和软件都会产生略有不同的结果。这可以用于finger-print当前浏览器。一些浏览器扩展也会因此阻止这些方法,或使其返回虚拟结果。

现在,这些都不能真正防止恶意图像。


嵌入恶意代码的图片

可以嵌入恶意代码的图像通常是利用图像解析器和渲染器中的漏洞。我不认为任何最新的此类解析器或渲染器仍然容易受到此类攻击,但即使有一个可以被 Web 浏览器使用,但当它被绘制到画布上时,已经为时已晚。 污染画布不会保护任何东西。

您可能听说过的一件事是stegosploit。这包括隐藏图像中的恶意代码,但那里的 HTML 画布用于解码该恶意代码。因此,如果您没有用于提取和执行嵌入的恶意脚本的脚本,则风险不大,实际上,如果您确实重新导出它,这些嵌入的数据很有可能会丢失。


将内容上传到服务器的固有风险

将任何内容上传到您的服务器时存在很多风险。我怎么强调都不过分,但请仔细阅读OWASP recommendations


上传data: URL 时的特殊风险

data: URL 是XSS attacks 的一个很好的向量。实际上,您很可能会直接使用该data: URL 构建HTML 代码。如果您没有应用正确的清理步骤,您很可能会加载攻击者的脚本而不是图像:

const dataURIFromServer = `data:image/png,"' onerror="alert('nasty script ran')"`;

const makeImgHTML = ( uri ) => `<img src="${uri}">`;

document.getElementById('container').innerHTML = makeImgHTML(dataURIFromServer);
&lt;div id="container"&gt;&lt;/div&gt;

关于data: 网址的最后一句话

data: URL 是一种将数据存储在 URL 中的方法,以便无需服务器即可直接传递数据。
data: URL 存储到服务器会适得其反。
为了表示二进制数据,这些数据需要用 base64 编码,以便所有不安全的字符仍然可以在大多数编码中表示。此操作将导致原始数据大小增加约 34%,您将不得不将其存储为 String,这对于大多数数据库来说并不方便。

真的,data: URL 来自另一个时代。您想使用它的情况很少。大多数你想用data: URL 做的事情,你应该用一个Blob 和一个blob: URL 来做。例如,将您的图像作为 Blob 直接上传到您的服务器。如果需要导出其内容,请使用 canvas .toBlob() 方法。如果您想展示用户挑选的图像,请使用img.src = URL.createObjectURL(file)



TL;DR

- 在您的场景中,toDataURL() 本身不会造成任何风险,也不会阻止任何风险。
- 使用众所周知的技术来清理用户的上传内容(永远不要相信他们,记住他们甚至可能不会使用您的 UI 与您的服务器通信)。
- 避免使用 data: URL。 它们效率低下。

【讨论】:

  • 感谢您提供的所有信息,尤其是 OWASP 链接,这是一个非常有趣的阅读以及有关 blob over data 的信息。我会考虑使用 blob,但很可能我会在没有服务器的情况下直接从一个用户传递到另一个用户,所以我必须看看两者中哪一个最适合我的需要。再次感谢!
  • 数据 URL 不一定是低效的:Base64 编码的它们压缩得非常好,您可以保存带有请求和响应标头的完整 HTTP 请求。对于小图像,它们实际上比执行额外请求更有效,对于数据 url 中的 SVG,它们几乎可以以相同的大小进行编码,并且您仍然可以获得沙盒效果:codepen.io/tigt/post/optimizing-svgs-in-data-uris
  • @Sebastian 你在说什么 HTTP 请求?我们只能将 dataURLs 与 blob URLs 进行比较。其中任何一个都没有 HTTP 请求。是的,它们的效率很低。如果从压缩或未压缩的服务器获取文件,则必须获取文件大小的 30%,因为无论如何您的二进制图像也已压缩,那么每次将其设置为 src 时,您都必须将该大字符串存储在新的 DOMString 中元素(DOMStrings 以 UTF-16 编码,因此实际大小加倍)。
  • @Kaiido - 这是关于两个用户、两个浏览器和一个从一个会话上传并在另一个会话中下载的图像。您不能在此处使用 blob URL,因为它们只能在一个 JavaScript 会话中使用。所以要么有数据 URL,要么有“真实” URL。内联(gzip-compressed fetched)数据 url(base64 编码)可以小于对二进制资源的第二次请求。数据 URL 本身并不是低效的。仅在某些情况下,其中之一是本地会话。
  • @Sebastian 所以你说的是OP'case?然后将该数据上传给其他用户,无论如何完成,您都需要两个 HTTP 请求,并且上传 + 下载二进制文件总是比上传和下载其 base64 表示更有效。例如,我刚刚测试过,一个 2000 x 2000 像素的 png 图像通过电线作为 base64 约为 21MB,而二进制文件约为 15MB。但这还不是全部,既然您获取了那个大字符串,您仍然需要将其以 UTF-16 格式存储在 DOM 中。 40MB。现在浏览器必须将其解码回二进制文件才能读取。 => 55MB 与 15MB 内存。
猜你喜欢
  • 2016-04-26
  • 1970-01-01
  • 2014-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多