处理用户上传的图片时如何防止“内容嗅探”类型漏洞?

4

问题:

我在开发一个内部工具,允许用户上传图片,然后将这些图片显示给他们和其他人。

这是一个Java/Spring应用程序。我只需要关心IE11和Firefox v38+(Chrome v43+也可以)。

在开发完功能后,似乎用户可以创建一个文本文件,像这样:

 <script>alert("malicious code here!")</script>

将其保存为"maliciousImage.jpg"并上传。

稍后,当该图像在图像标签内显示时:

 <img src="blah?imgName=foobar" id="someImageID">

actualImage.jpg可以正常显示,而maliciousImage.jpg则显示为破碎的链接 - 最重要的是没有恶意内容被解释!

但是如果用户右键点击这个破碎的链接并点击“查看图片”...就会出现问题。

浏览器进行了“内容嗅探”,这对我来说是一个新概念,它检测到“maliciousImage.jpg”实际上是一个文本文件,并很好地将其呈现为HTML,没有任何犹豫。任何脚本标记都将传递给JavaScript解释器,正如您所想象的那样,我们不希望发生这种情况。

我到目前为止尝试过的方法

简而言之,我想到的防止浏览器进行内容嗅探的所有响应头组合。我在stack overflow等网站以及其他文档中找到的所有答案都暗示着设置content-type标题应该可以防止大多数浏览器对内容进行嗅探,并且设置X-content选项应该可以防止某些版本的IE。

我正在将x-content-type-options设置为no sniff,并设置响应内容类型。我阅读的文档使我相信,这应该停止内容嗅探。

response.setHeader("X-Content-Type-Options", "nosniff"); 
response.setContentType("image/jpg");

我正在拦截响应,这些标头存在,但似乎对恶意内容的处理没有影响...
我还试图在上传图片时检测哪些是恶意的和哪些不是,但我很快就发现这非常不容易...
最终目标:
自然而然地,任何输出都比在明文中执行文本文件作为HTML/javascript要好,但将任何恶意HTML显示为转义/CDATA的纯文本是理想的...虽然可能有些不切实际。

1
你永远不能信任来自客户端的数据。在服务器端,你应该检查文件类型、MIME类型,并最好使用一些图像库来获取图像尺寸或类似的东西,以确保客户端上传了一个图像,这真的无法在客户端上完成,也不能通过设置几个标头来实现。 - adeneo
@adeneo 我们已经检查了文件类型,他们正在上传文本文件并将其保存为 .jpg 文件。您建议使用哪个库来更彻底地检查这个问题? - Paul
然后您可以检查 MIME 类型,我认为 Spring 框架有一些内置的图像验证功能,例如 image.getContentType().equals(type) 等。 - adeneo
3个回答

1

我最终解决了这个问题,但忘记回答我的问题:

步骤1:阻止无效图片

为了快速解决问题,我简单地添加了一些相当直接的代码,在上传和提供服务之前使用imageio库检查图像是否实际上是图像:

import javax.imageio.ImageIO;

//...... 

Image img = attBO.getImage(imgId);
            
InputStream x = new ByteArrayInputStream(img.getData());
BufferedImage s;
try {
    s = ImageIO.read(x);
    s.getWidth();
} catch (Exception e) {
    throw new myCustomException("Invalid image");
}

现在,起初我希望这能解决我的问题 - 但实际上并没有那么简单,反而使生成有效载荷更加困难。
虽然这将被阻止:
 <script>alert("malicious code here!")</script>

很有可能生成一个有效的图像,也是一个XSS负载 - 只需要更多的努力...

步骤2:框架的愚蠢

事实证明,我从未接触过整个后处理工作流程,它会执行一些操作,例如将令牌附加到响应正文中,并使用其他框架用CSS、标头、页脚等装饰响应。

这意味着,尽管控制器明确返回image/png,但它被抓取并放置(作为字节)后处理正在获取该字节流,并将其包装在标题和页脚中,以形成完全合格的“视图” - 这个视图总是具有“content-type”text/html,因此从未正确显示。

这个问题的关键是,我的控制器直接以RESTful方式返回图像,而框架的其余部分是构建为处理控制器返回完整的视图。

因此,我不得不通过这个工作流程,并为我的代码中返回与RESTful方式不同的内容的控制器创建异常。

例如,在site-mesh中,只需排除即可(一旦理解了问题,就像往常一样简单的修复...):

<decorators defaultdir="/WEB-INF/decorators">
<excludes>
    <pattern>*blah.ctl*</pattern>
</excludes>
<decorator name="foo" page="myDecorator.jsp">
    <pattern>*</pattern>
</decorator>

然后是其他定制的调用拦截器。

第三步:内容协商

现在,我终于到了只提供图像字节码且未指定或明确生成评论的阶段。

Spring 的一个特性叫做“内容协商”。它试图将请求的“接受”头与其手头上的“消息转换器”协调,以产生这样的响应。

由于 Spring 默认没有消息转换器来生成 image/png 响应,因此它会回退到 text/html,并且我仍然看到问题。

如果我使用的是 Spring 4,我可以简单地添加注释:

@Produces("image/png")

给我的控制器 - 简单修复...

步骤4:遗留依赖

但是因为我只有Spring 3.0.5(无法升级),所以我不得不尝试其他方法。

我尝试注册新的消息转换器,但那很麻烦,或者添加一个新的后置方法拦截器来简单地将内容类型更改回“image/png” - 但那是一个hacky的头痛。

最终,我只是在控制器中公开了请求/响应,并直接将我的图像写入响应正文 - 完全规避了Spring的内容协商。

......最后,我的图像作为图像被提供并显示为图像 - 没有注入的代码被执行!


0

听起来很奇怪,因为它在其他地方完美运行。您确定响应中存在X-Content-Type-Options标头吗?

这是我以前建立的演示,其中有一个文件,它是有效的html、gif和javascript。正如您所看到的,它首先作为HTML加载,然后作为图像和脚本(执行)加载:
http://research.insecurelabs.org/content-sniffing/gifjs.html

但是,如果使用“X-Content-Type-Options:nosniff”标头加载它,则脚本不再执行:
http://research.insecurelabs.org/content-sniffing/nosniff/gifjs.html

顺便说一句,在FF / IE中图像可以正确呈现,但在Chrome中无法呈现。

这是一个演示,我尝试了您描述的内容:
http://research.insecurelabs.org/content-sniffing/stackexchange.html

第一张图片没有使用nosniff,第二张图片使用了nosniff,看起来它的作用是预期的。当使用“查看图像”打开第二张图片时,脚本不会运行。

编辑:
Firefox似乎不支持X-Content-Type-Options:nosniff

因此,您还应该向图像添加“Content-disposition:attachment; filename = image.gif”或类似内容。如果通过图像标签加载图像,则图像将正常加载,但是如果直接打开URL,则会强制下载而不是直接在浏览器中显示图像。

例如:http://research.insecurelabs.org/content-sniffing/attachment/


我正在使用代理在客户端查看我的请求/响应原始数据,并且标头存在且正确形成...但在您的第一对示例中,两个链接都会导致显示alert("1")...所以我认为我有一个不同于我想象的问题...这可能是我测试的浏览器的某种配置问题吗? - Paul
听起来是这样的。你用的是哪个浏览器?哪个版本?有安装任何扩展或插件吗? - Erlend
嗯,看起来Firefox不支持X-Content-Type-Options:nosniff。 - Erlend

-1

adeneo说得很对。你应该使用任何你想用的图像库来检查上传的文件是否是其所声称类型的有效文件。客户端发送的任何内容都可能被操纵。


你能更具体一些吗?你会推荐哪个图像库? - Paul

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