今天我们来讨论下Amazon AWS java SDK的一个拒绝服务漏洞。这个官方的AWS SDK常被Java开发者用于整合一系列的AWS服务,包括像Amazon S3中整合Amazon APIs用于存储和索引文件等。其中1.8.0-1.10.34版本的官方AWS Java SDK已被确认受到影响,而最新版1.10.36 SDK已经修复了这个漏洞。
这个漏洞可以利用AWS Java SDK操纵存储在Amazon S3上的文件来实现对web服务的攻击。攻击者可以上传文件到S3存储中让web服务使用SDK执行,执行时可能会出现死循环从而导致拒绝服务。
鉴于AWS Java SDK是Amazon默认提供给Java开发者使用的,这个漏洞有着很广的影响面。例如:Nuxeo 一个非常受欢迎的开源框架–使用AWS S3 SDK存储文件的内容管理系统。大量使用Nuxeo框架的商业应用都会受到这个漏洞的影响。
我们已经将这个漏洞私下提交给了Amazon AWS安全团队。Amazon迅速修复了这个漏洞并于上周在Maven Central发布了新版本的SDK。具体修改的代码可以查看这里。
概述
SdkDigestInputStream
类的skip
方法。标准中规定其应该返回略过的字节数,但在一些特殊情况下其会返回-1
。skip
方法是2014年6月21日作为另一个问题的修复方案加入到类中的。在此之前SdkDigestInputStream
并没有重载skip
方法。
分析
如果我们查阅Java官方文档关于InputStream
类中skip
方法的描述,文档说skip
方法应该返回实际略过的字节数。如果没有略过或者输入是负数,那么skip
方法会返回0。不管任何情况下skip
方法都不应该返回一个负数。
看SdkDigestInputStream.java的第75行(来自之前的提交https://github.com/aws/aws-sdk-java/commit/257dd4908cf99ec1982feed247fd2253589c222a#diff-97412a92b5fccaea1893be646257fa3b),skip
方法可能返回-1
:
返回的这个负值可能会导致其他接收skip
方法返回值的方法出错,因为这些方法从来没考虑过接收一个负值。例如:看下popular Apache Commons Compress library中的IOUtils
类。它通过调用skip
方法处理底层的InputStream
来实现自己skip
方法。
这个方法使用了while
循环,其中skip
方法的返回值用于结束循环。
while (numToSkip > 0) { long skipped = input.skip(numToSkip); if (skipped == 0) { break; } numToSkip -= skipped; }
当Amazon S3存储使用SDk读取底层input
数据流时,skip
方法可能会返回-1
。这个返回值存储在skipped
变量中。因此,循环结尾处的numToSkip -= skipped
变成了numToSkip -= -1
。这样变量numToSkip
会不停的增长最终变成死循环。
实例
攻击场景出现于Amazon S3存储文件使用AWS SDK处理时。Apache Commons Compress library
是一个Java中非常常用的处理归档的库。
对于使用Amazon S3的Web服务来说.tar
是最容易遭受拒绝服务攻击的。攻击者只需创建一个带有额外填充数据(空字节)的归档上传到网站。然后,AWS S3 SDK会读取文件并将inputStream
传递给TarArchiveInputStream
类。为了略过额外的填充数据会调用skipRecordPadding
方法:
这个方法在第335行调用skip
方法,之后程序就会进入一个死循环从而导致拒绝服务。
修复
一个新版本的AWS SDK已经发布在Maven Central,我们建议立刻升级到这个版本。假如你不能升级的话,那么可以进行如下修复。
如果你直接调用了skip
方法,那么你可以增加一个判断确保其返回值>=0
。另外,如果需要你可以你可以复制inputStream
到一个字节数组中,然后在把数组传递给需要调用的应用。例如,使用Apache Commons Compress library
处理.tar
时可以使用如下代码:
// Assuming that input is the inputStream obtained from the AWS S3 SDKByteArrayOutputStream baos = new ByteArrayOutputStream(); IOUtils.copy(input, baos); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); TarArchiveInputStream tarStream = new TarArchiveInputStream(bais);
* 原文链接:blog.srcclr,译者/xiaix,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)