RSS Feed
更好更安全的互联网
  • WordPress 5.0 RCE 详细分析

    2019-02-27
    作者:LoRexxar'@知道创宇404实验室
    时间:2019年2月22日
    2月20号,RIPS团队在官网公开了一篇WordPress 5.0.0 Remote Code Execution,CVE编号CVE-2019-6977,文章中主要提到在author权限账号下,可以通过修改Post Meta变量覆盖、目录穿越写文件、模板包含3个漏洞构成一个RCE漏洞。

    但在原文中,作者只大致描述了漏洞原理,其中大量的漏洞细节被省略,甚至部分的利用和后端服务器也有相对应的关系,所以在复现的过程中遇到了各种问题,我们花了大量的时间分析代码,最终终于完全还原了该漏洞,其中部分关键利用点用了和原文有些许差异的利用方式(原文说的太含糊其辞,无法复现)。在下面的分析中,我会尽量按照复现过程中的思考方式及流程,以便读者理解。

    感谢在复现、分析过程中一起的小伙伴@Badcode,帮助我修改了很多错误的@Venenof7、@sysorem,给我提供了很多帮助:>

    漏洞要求

    在反复斟酌漏洞条件之后,我们最终把漏洞要求约束为

    影响包括windows、linux、mac在内的服务端,后端图片处理库为gd/imagick都受到影响,只不过利用难度有所差异。

    其中,原文提到只影响release 5.0.0版,但现在官网上可以下载的5.0.0已经修复该漏洞。实际在WordPress 5.1-alpha-44280更新后未更新的4.9.9~5.0.0的WordPress都受到该漏洞影响。

    漏洞复现

    下面的复现流程包含部分独家利用以及部分与原文不符的利用方式,后面的详情会解释原因。

    传图片

    改信息

    保留该数据包,并添加POST

    裁剪

    同理保留改数据包,并将POST改为下面的操作,其中nonce以及id不变

    触发需要的裁剪

    图片已经过去了

    包含,我们选择上传一个test.txt,然后再次修改信息,如前面

    点击查看附件页面,如果图片被裁剪之后仍保留敏感代码,则命令执行成功。

    详细分析

    下面我们详细分析一下整个利用过程,以及在各个部分踩的坑。我们可以简单的把漏洞利用链分为4个大部分。

    1、通过Post Meta变量覆盖,修改媒体库中图片的_wp_attached_file变量。

    这个漏洞是整个利用链的核心点,而WordPress的修复方式也主要是先修复了这个漏洞。WordPress很良心的在所有的release版本中都修复了这个问题(官网下载的5.0.0已经修复了),由于原文中曾提到整个利用链受到4.9.9和5.0.1的另一个安全补丁影响,所以只有5.0.0受影响。在分析还原WordPress的更新commit中,我们寻找到了这个漏洞的修复commit,并获得了受该漏洞影响的最新版本为WordPress commit <= 43bdb0e193955145a5ab1137890bb798bce5f0d2 (WordPress 5.1-alpha-44280)

    2、通过图片的裁剪功能,将裁剪后的图片写到任意目录下(目录穿越漏洞)

    在WordPress的设定中,图片路径可能会收到某个插件的影响而不存在,如果目标图片不在想要的路径下时,WordPress就会把文件路径拼接为形似http://127.0.0.1/wp-content/uploads/2019/02/2.jpg 的url链接,然后从url访问下载原图

    如果我们构造?或者#后面跟路径,就能造成获取图片位置和写入图片位置的不一致。。

    这部分最大问题在于,前端的裁剪功能并不是存在漏洞的函数,我们只能通过手动构造这个裁剪请求来完成。

    ps: 当后端图片库为Imagick时,Imagick的Readimage函数不能读取远程http协议的图片,需要https.

    3、通过Post Meta变量覆盖,设置_wp_page_template变量。

    这部分在原文中一笔带过,也是整个分析复现过程中最大的问题,现在公开的所有所谓的WordPress RCE分析,都绕开了这部分。其中有两个最重要的点:

    • 如何设置这个变量?
    • 如何触发这个模板引用?

    这个部分在下文中会详细解释。

    4、如何让图片在被裁剪过之后,保留或者出现包含php敏感代码。

    这部分就涉及到了后端图片库的问题,WordPress用到的后端图片处理库有两个,gd和imagick,其中默认优先使用imagick做处理。

    • imagick
      利用稍微比较简单,imagick不会处理图片中的exif部分。将敏感代码加入到exif部分就可以不会改动。
    • gd
      gd的利用就比较麻烦了,gd不但会处理图片的exif部分,还会删除图片中出现的php代码。除非攻击者通过fuzz获得一张精心构造的图片,可以在被裁剪处理之后刚好出现需要的php代码(难度较高)。

    最后通过链接上述4个流程,我们就可以完整的利用这个漏洞了,接下来我们详细分析一下。

    Post Meta变量覆盖

    当你对你上传的图片,编辑修改其信息时,你将会触发action=edit_post

    post data来自于POST

    如果是修复过的,在line 275行有修复patch

    https://github.com/WordPress/WordPress/commit/43bdb0e193955145a5ab1137890bb798bce5f0d2

    这个patch直接禁止了传入这个变量

    一路跟下去这个函数可以一直跟到wp-includes/post.php line 3770

    update_post_meta会把所有字段遍历更新

    就会更新数据库中的相应字段

    配合变量覆盖来目录穿越写文件

    根据原文的描述,我们首先需要找到相应的裁剪函数

    这里传入的变量src就是从修改过的_wp_attached_file而来。

    在代码中,我们可以很轻易的验证一个问题。在WordPress的设定中,图片路径可能会受到某个插件的影响而不存在,如果目标图片不在想要的路径下时,WordPress就会把文件路径拼接为形似 http://127.0.0.1/wp-content/uploads/2019/02/2.jpg 的url链接,然后从url访问下载原图

    这里的_load_image_to_edit_path就是用来完成这个操作的。

    也正是因为这个原因,假设我们上传的图片名为2.jpg,则原本的_wp_attached_file2019/02/2.jpg

    然后我们通过Post Meta变量覆盖来修改_wp_attached_file2019/02/1.jpg?/../../../evil.jpg

    这里的原图片路径就会拼接为{wordpress_path}/wp-content/uploads/2019/02/1.jpg?/../../../evil.jpg,很显然这个文件并不存在,所以就会拼接链接为http://127.0.0.1/wp-content/uploads/2019/02/2.jpg?/../../../evil.jpg,后面的部分被当作GET请求,原图片就会成功的获取到。

    紧接着进入save函数的新图片路径会拼接为{wordpress_path}/wp-content/uploads/2019/02/1.jpg?/../../../cropped-evil.jpg,我们就能成功写入新文件了。

    后面的save函数会调用你当前图片库的裁剪功能,生成图片结果。(默认为imagick)

    但这里看上去没有任何限制,实际上不是的。在写入的目标目录下,存在一个假目录,为1.jpg?

    • 而linux、mac支持这种假目录,可以使用?号
    • 但windows在路径中不能有?号,所以这里改用了#号

    成功写入文件

    控制模板参数来导致任意文件包含

    进度进展到这就有点儿陷入僵局,因为原文中关于这部分只用了一句话带过,在实际利用的过程中遇到了很多问题,甚至不同版本的WordPress会有不同的表现,其中诞生了多种利用方式,这里我主要讲1种稳定利用的方式。

    设置_wp_page_template

    首先我们先正向分析,看看在什么情况下我们可以设置_wp_page_template

    首先可以肯定的是,这个变量和_wp_attached_file一样都属于Post Meta的一部分,可以通过前面的操作来对这个变量赋值

    但实际测试过程中,我们发现,我们并不能在任何方式下修改并设置这个值。

    • 如果你设置了这个值,但这个文件不存在,则会被定义为default
    • 如果该值被设置,则没办法通过这种方式修改。

    所以这里我们可能需要新传一个媒体文件,然后通过变量覆盖来设置这个值。

    加载模板

    当我们成功设置了该变量之后,我们发现,并不是所有的页面都会加载模板,我们重新回到代码中。

    最终加载模板的地方在

    只要是在$template_names中需要被加载的文件名,会在当前主题的目录下遍历加载。

    回溯跟入

    继续回溯我们就能发现一些端倪,当你访问页面的时候,页面会通过你访问的页面属性,调用不同的模板加载函数。

    在这么多的模板调用函数中只有两个函数get_page_templateget_single_template这两个在函数中调用了get_page_template_slug函数。

    get_page_template_slug函数从数据库中获取了_wp_page_template

    只要我们能让模板加载时进入get_page_templateget_single_template,我们的模板就可以成功被包含。

    由于代码和前端的差异,我们也没有完全找到触发的条件是什么,这里选了一个最简单的,即上传一个txt文件在资源库,然后编辑信息并预览。

    生成图片马

    这部分就涉及到了后端图片库的问题,WordPress用到的后端图片处理库有两个,gd和imagick,其中默认优先使用imagick做处理。

    • imagick

    利用稍微比较简单,imagick不会处理图片中的exif部分。将敏感代码加入到exif部分就可以不会改动。

    • gd

    gd的利用就比较麻烦了,gd不但会处理图片的exif部分,还会删除图片中出现的php代码。除非攻击者通过fuzz获得一张精心构造的图片,可以在被裁剪处理之后刚好出现需要的php代码(难度较高)。

    由于这不是漏洞最核心的部分,这里就不赘述了。

    修复

    1、由于该漏洞主要通过图片马来完成RCE,而后端图片库为gd时,gd会去除图片信息中exif部分,并去除敏感的php代码。但如果攻击者精心设计一张被裁剪后刚好生成含有敏感代码的图片时,就可以造成RCE漏洞。如果后端图片库为imagick时,则将敏感代码加入到图片信息的exif部分,就可以造成RCE漏洞。

    官网上可供下载的所有release版本中都修复了这个漏洞,更新至最新版或者手动将当前版本覆盖安装即可。

    2、 通用防御方案
    使用第三方防火墙进行防护(如创宇盾[https://www.yunaq.com/cyd/])。

    3、技术业务咨询
    知道创宇技术业务咨询热线 :
    400-060-9587(政府,国有企业)、028-68360638(互联网企业)

    总结

    整个RCE的利用链由4部分组成,深入WordPress的底层Core逻辑,原本来说这4个部分无论哪个都很难造成什么危害,但却巧妙地连接在一起,并且整个部分意外的都是默认配置,大大增加了影响面。在安全程度极高的WordPress中能完成这种的攻击利用链相当难得,从任何角度都是一个非常nice的漏洞:>

    最后再次感谢我的小伙伴们以及整个过程中给我提供了很大帮助的朋友们:>


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/822/

    作者:吴烦恼 | Categories:安全科普技术分享 | Tags:
  • 从 0 开始学 Linux 内核之 android 内核栈溢出 ROP 利用

    2019-02-15

    作者:Hcamael@知道创宇404实验室

    最近在研究一个最简单的android内核的栈溢出利用方法,网上的资料很少,就算有也是旧版内核的,新版的内核有了很大的不同,如果放在x86上本应该是很简单的东西,但是arm指令集有很大的不同,所以踩了很多坑

    把上一篇改了一下名字,换成了从0开始学Linux内核,毕竟不是专业搞开发的,所以驱动开发没必要学那么深,只要会用,能看懂代码基本就够用了。本篇开始学Linux kernel pwn了,而内核能搞的也就是提权,而提权比较多人搞的就是x86和arm指令集的Linux系统提权了,arm指令集的基本都是安卓root和iOS越狱,而mips指令集的几乎没啥人在搞,感觉是应用场景少。

    环境准备

    android内核编译

    下载相关源码依赖

    android内核源码使用的是goldfish[1],直接clone下来,又大又慢又久,在git目录下编译也麻烦,所以想搞那个版本的直接下那个分支的压缩包就好了

    本文使用的工具的下载地址:

    PS:git clone速度慢的话可以使用国内镜像加速:s/android.googlesource.com/aosp.tuna.tsinghua.edu.cn/

    修改内核

    学android kernel pwn最初看的是Github上的一个项目[3],不过依赖的是旧内核,估计是android 3.4以下的内核,在3.10以上的有各种问题,所以我自己做了些修改,也开了一个Github源:https://github.com/Hcamael/android_kernel_pwn

    对kernel源码有两点需要修改:

    1.添加调试符号

    首先需要知道自己要编译那个版本的,我编译的是32位Android内核,使用的是goldfish_armv7,配置文件在: arch/arm/configs/goldfish_armv7_defconfig

    但是不知道为啥3.10里没有该配置文件,不过用ranchu也一样:

    给内核添加调试符号,只需要在上面的这个配置文件中添加:CONFIG_DEBUG_INFO=y,如果是goldfish就需要自己添加,ranchu默认配置已经有了,所以不需要更改。

    2.添加包含漏洞的驱动

    目的是研究Android提权利用方法,所以是自己添加一个包含栈溢出的驱动,该步骤就是学习如何添加自己写的驱动

    上面给了一个我的Github项目,把该项目中的vulnerabilities/目录复制到内核源码的驱动目录中:

    修改Makefile:

    导入环境变量后,使用一键编译脚本进行编译:

    PS: 在docker中复现环境的时候遇到一个问题,可以参考:https://stackoverflow.com/questions/42895145/cross-compile-the-kernel

    编译好后的内核在/tmp/qemu-kernel目录下,有两个文件,一个zImage,内核启动镜像,一个vmlinux是kernel的binary文件,丢ida里面分析内核,或者给gdb提供符号信息

    Android模拟环境准备

    内核编译好后,就是搞Android环境了,可以直接使用Android Studio[2]一把梭,但是如果不搞开发的话,感觉Studio太臃肿了,下载也要下半天,不过还好,官方提供了命令行工具,觉得Studio太大的可以只下这个

    PS: 记得装java,最新版的java 11不能用,我用的是java 8

    建一个目录,然后把下载的tools放到这个目录中

    首先需要使用tools/bin/sdkmanager装一些工具

    PS:因为是32位的,所以选择的是armeabi-v7a

    PSS: 我一共测试过19, 24, 25,发现在24,25中,自己写的包含漏洞的驱动只有特权用户能访问,没去仔细研究为啥,就先使用低版本的android-19了

    创建安卓虚拟设备:

    启动:

    去测试下我写的exp:

    编译好了之后运行,记得要用普通用户运行:

    Android 内核提权研究

    环境能跑通以后,就来说说我的exp是怎么写出来的。

    首先说一下,我的环境都是来源于AndroidKernelExploitationPlayground项目[3],但是实际测试的发现,该项目中依赖的估计是3.4的内核,但是现在的emulator要求内核版本大于等于3.10

    从内核3.4到3.10有许多变化,首先,对内核的一些函数做了删减修改,所以需要改改驱动的代码,其次就是3.4的内核没有开PXN保护,在内核态可以跳转到用户态的内存空间去执行代码,所以该项目中给的exp是使用shellcode,但是在3.10内核中却开启了PXN保护,无法执行用户态内存中的shellcode

    提权思路

    搞内核Pwn基本都是一个目的——提权。那么在Linux在怎么把权限从普通用户变成特权用户呢?

    一般提权的shellcode长这样:

    这个shellcode提权的思路有三步:

    1. prepare_kernel_cred(0) 创建一个特权用户cred
    2. commit_creds(prepare_kernel_cred(0)); 把当前用户cred设置为该特权cred
    3. MSR CPSR_c,R3 从内核态切换回用户态(详情自己百度这句指令和CPSR寄存器)

    切换回用户态后,当前程序的权限已经变为root,这时候就可以执行/bin/sh

    再继续深入研究,就涉及到内核的三个结构体:

    每个进程都有一个单独thread_info结构体,我们来看看内核是怎么获取到每个进程的thread_info结构体的信息的:

    有点内核基础知识的应该知道,内核的栈是有大小限制的,在arm32中栈的大小是0x2000,而thread_info的信息储存在栈的最底部

    所以,如果我们能获取到当前进程在内核中运行时的其中一个栈地址,我们就能找到thread_info,从而顺藤摸瓜得到cred的地址,如果能任意写内核,则可以修改cred的信息,从而提权

    总得来说,内核提权其实只有一条路可走,就是修改cred信息,而commit_creds(prepare_kernel_cred(0));不过是内核提供的修改cred的函数罢了。

    我们来通过gdb展示下cred数据:

    通过gdb可以获取到:$sp : 0xd415bf40

    从而计算出栈底地址:0xd415a000

    然后我们就能获取到thread_info的信息,然后得到task_struct的地址:0xd4d16680

    接着我们查看task_struct的信息,得到了cred的地址:0xd4167780

    然后查看cred的信息:

    把uid和gid的十六进制转换成十进制,发现就是当前进程的权限

    使用ROP绕过PXN来进行android提权

    既然我们已经知道了怎么修改权限,那么接下来就研究一下如何利用漏洞来提权,因为是研究利用方式,所以自己造了一个最基础的栈溢出

    因为开了PXN,所以没办法使用shellcode,然后我第一个想到的思路就是使用ROP来执行shellcode的操作

    这里说一下,不要使用ROPgadget,用这个跑内核的ELF,要跑贼久,推荐使用ROPPER[4]

    然后就是找commit_credsprepare_kernel_cred这两个函数的地址,在没有开启kalsr的内核中,我们可以直接把vmlinux丢到ida里面,找这两个函数的地址

    到这里,我们可以构造出如下的rop链:

    在成功修改当前进程的权限之后,我们需要把当前进程从内核态切换回用户态,然后在用户态执行/bin/sh,就能提权成功了

    但是这里遇到一个问题,在shellcode中,使用的是:

    我也很容易能找到gadget: msr cpsr_c, r4; pop {r4, pc};

    但是却没法成功切换回用户态,网上相关的资料几乎没有,我也找不到问题的原因,在执行完msr cpsr_c, r4指令以后,栈信息会发现变化,导致没法控制pc的跳转

    不过后来,我跟踪内核的执行,发现内核本身是通过ret_fast_syscall函数来切换回用户态的:

    经过我测试发现,使用msr SPSR_fsxc, r1可以成功从内核态切换回用户态,但是该指令却只存在于该函数之前,无法找到相关的gadget,之后我想了很多利用该函数的方法,最后测试成功的方法是:

    计算有漏洞的溢出函数的栈和ret_fast_syscall函数栈的距离,在使用ROP执行完commit_creds(prepare_kernel_cred(0));之后,使用合适的gadget来修改栈地址(比如: add sp, sp, #0x30; pop {r4, r5, r6, pc};),然后控制pc跳转到0xc000df90 <ret_fast_syscall+16>:,这样程序就相当于执行完了内核的syscall,然后切换回用户进程代码继续执行,在我们的用户态代码中后续执行如下函数,就能成功提权:

    完整exp可以见我的Github。

    ROP只是其中一种利用方法,后续还会研究其他利用方法和在64位android下的利用。

    参考

    1. https://android.googlesource.com/kernel/goldfish/
    2. https://developer.android.com/studio/?hl=zh-cn#downloads
    3. https://github.com/Fuzion24/AndroidKernelExploitationPlayground
    4. https://github.com/sashs/Ropper

    Paper本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/808/

    作者:Nanako | Categories:安全研究技术分享 | Tags:
  • apt apt-get 中的远程执行代码

    2019-01-24

    原文:Remote Code Execution in apt/apt-get
    作者:Max Justicz
    译者:Nanako@知道创宇404实验室

    TL,DR:
    我在apt中发现了一个漏洞,它允许网络中间人(或恶意包镜像)以root身份在安装软件包的机器上执行任意代码。该漏洞已在apt最新版本中被修复。如果您担心在更新过程中被利用,可以通过禁用http重定向来保护自己。为此,请运行:

    如果当前镜像包在默认情况下重定向(意味着出现该标志时无法更新apt),则需要选择其它镜像或直接下载程序包。该链接可以找到有关Debian升级的具体说明。Ubuntu的声明可以在这里找到。

    作为证明,我录制了一段攻击如下Dockerfile的视频:

    背景

    在获取数据时,apt将各种不同的数据传输协议的工作进程分离。然后,父进程通过stdin/stdout与这些工作人员进行通信, 利用一个类似http的协议告诉他们要下载的内容并将它放到文件系统上。例如,在一台机器上运行 apt install cowsay并用http请求下载相应包的时候,apt将提供/usr/lib/apt/methods/http目录,并返回100 Capabilities消息:

    然后,父进程发送其配置并请求资源,如下所示:

    然后工作进程会像下方这样响应:

    当http服务器根据重定向进行响应时,工作进程返回103 Redirect而非201 URI Done。父进程根据此响应来确定接下来应该请求的资源:

    漏洞

    不幸的是,进行http下载的进程会对HTTP Location头进行url解码,并直接附加到103 Redirect响应中:

    (注意:不同版本的apt之间存在重要差异。上述代码来自Debian最近使用的1.4.y版本。一些Ubuntu版本使用的是1.6.y,它不仅仅是直接附加URI。然而在后续的http提取程序发出的600 URI Acquire请求中仍然存在注入漏洞。其他版本我并没有做检查。)

    因此,如果http服务器发送Location: /new-uri%0AFoo%3A%20Bar,http提取进程将回复以下内容:

    或者,如果http服务器发送:

    那么http提取进程会回复:

    注入恶意包

    因为我在我的验证程序中注入201 URI Done响应,所以我不得不处理没有下载任何包的问题。我需要一种方法让恶意的.deb进入系统,以便在Filename参数中使用。

    为了实现这点,我利用了apt updaterelease.gpg文件具有可塑性,并安装在可预测的位置这个特点。具体来说,Release.gpg包含的PGP签名,如下所示:

    只要注入的内容不接触到签名内容,apt的签名验证程序就不会报错,所以我拦截了release.gpg请求,并用我的恶意deb进行了预处理:

    然后,我在201 URI Done响应中设置Filename参数:

    http / https争议

    默认情况下,Debian和Ubuntu都使用开箱即用的http存储库(Debian允许您在安装过程中选择所需镜像,但实际上不支持https存储库 - 您必须先安装apt-transport-https)。

    如果程序包清单已签名,为什么还要使用https?毕竟,由于包的大小有限,隐私获益是最小的。而且使用https会使缓存受限。

    也有对此很感兴趣的人。某些网站专门解释为什么在apt上下文中使用https没有意义。

    这些都是很好的观点,但是我这篇文章中的bug是存在的。无独有偶——这是JannHorn在2016年发现的另一个具有相同影响的bug。没错,即使使用的是https,恶意镜像依然可以利用这样的漏洞。但我觉得,与其攻击使用http或TLS证书的deb.debian.org,还不如直接攻击目标服务器上的应用服务。

    (假设apt-transport-https本身没有灾难性的破坏。我并没有审计,但它看起来像是围绕libcurl的一个相对较薄的包装。)

    支持http是个好事。我只是认为把https作为更安全的默认存储库是值得的,如果用户选择这样做的话,允许他们降低安全级别。如果服务器包默认使用的是https,我就无法利用本文顶部的dockerfile。

    总结

    感谢apt维护者及时修补此漏洞,并感谢Debian安全团队协助披露。这个漏洞已经注册编号:CVE-2019-3462。


     

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/799/

    作者:Nanako | Categories:技术分享 | Tags:
  • 多种设备基于 SNMP 协议的敏感信息泄露漏洞数据分析报告

    2019-01-24

    作者:知道创宇404实验室
    English version:https://paper.seebug.org/796/

    1. 更新情况

    2. 事件概述

    SNMP协议[1],即简单网络管理协议(SNMP,Simple Network Management Protocol),默认端口为 161/UDP,目前一共有3个版本:V1,V2c,V3。V3是最新的版本,在安全的设计上有了很大改进,不过目前广泛应用的还是存在较多安全问题的V1和V2c版本。SNMP协议工作的原理简单点来说就是管理主机向被管理的主机或设备发送一个请求,这个请求包含一个community和一个oid。oid就是一个代号,代表管理主机这个请求想要的信息。被管理的主机收到这个请求后,看请求community是否和自己保存的一致,如果一致,则把相应信息返回给管理主机。如果不一致,就不会返回任何信息。所以community相当与一个认证的口令。V1和V2c版本的SNMP协议都是明文传输数据的,所以可以通过抓包嗅探等手段获取认证需要的community。

    2018年12月25日,Seebug 平台收录了多个基于SNMP协议的敏感信息泄露漏洞[2]。在多种厂商提供的网关类设备中,可以使用任意 community非常容易地读取SNMP提供的明文形式的的Web管理系统的用户名和密码、Wi-Fi凭证等信息。也可以使用任意community通过SET协议指令发送配置更新或控制请求,攻击者可以注入恶意的配置,如在Cisco DPC3928SL通过注入SSID造成Web管理系统的XSS(CVE-2018-20379)。

    该漏洞最早于 2017 年 4 月 4 日曝出,CVE编号为CVE-2017-5135,漏洞发现者将该漏洞称之为 Stringbleed[3]。2018年12月22日,时隔一年多,漏洞发现者进行全球探测后提供了一个很全的漏洞影响列表,其中包含23个不同厂商78个不同型号的网关设备,同时申请了多个CVE编号(CVE-2018-20380~CVE-2018-20401)。关于漏洞的成因一直都在争论之中,截止目前依然没有最终定论[4]。该类设备一般由ISP提供,我们暂时没有找到漏洞设备或固件对漏洞原理进行研究。根据社区的讨论结果,产生该漏洞的原因可能有以下几种情况:

    • 这些存在漏洞的设备使用了同一个存在逻辑缺陷的SNMP协议实现,该实现代码没有正确处理 community 字符串认证,导致任意 community 均可以通过认证,进一步导致敏感信息泄露。
    • ISP 配置错误,无效的访问控制规则。

    本文不包含漏洞分析,而是针对全球该类设备漏洞存在情况的数据分析报告。

    3. 漏洞复现

    直接使用 snmpget 命令发送 SNMP GET 请求即可, -c 选项指定任意字符串作为 community 均可通过认证。

    复现结果如下:

    img

    如果目标设备开放了Web服务,则可使用泄露的用户名和密码登陆Web管理系统,如下:

    img

    值得一提的是,用户名和密码存在为空的情况。

    img

    发送 SNMP SET 请求进行配置更新,-c 选项指定任意 community。如下所示,我们通过snmpset修改了 Web 系统用户名。

    img

    4. 漏洞影响范围

    我们通过提取漏洞设备相关的“关键词”,在ZoomEye网络空间搜索引擎[5]上共发现了1,241,510个 IP数据。

    img

    通过使用 zmap 对这 124 万的IP数据进行存活检测,发现约有 23 万的IP 存活。进一步对存活的 23 万IP进行漏洞存在检验,发现有15882 个目标设备存在该敏感信息泄露漏洞,涉及23个厂商的多个型号设备的多个固件版本。

    对这 15882 个漏洞设备的信息进行聚合,得到厂商及版本等统计信息如下(各个型号的ZoomEye dork 为: Vendor +Model +相应型号,如搜索DPC3928SL的语法为:Vendor +Model +DPC3928SL)

    漏洞设备的厂商分布饼图如下(有一点需要说明的是,DPC3928SL网关设备属于受此漏洞影响最严重的网络设备之一,原来属于Cisco公司, 现在属于Technicolor。)

    img

    国家分布前十如下,主要分布在中国、泰国、韩国等国家。

    img

    中国存在漏洞的设备全部分布在广东、台湾两个省份,呈现一定的地域性。其中广东最多,为6318 台。

    img

    进一步分析发现,在原全球124万161/udp 端口的该类设备IP数据中,属于中国的几乎全部分布在广东省和台湾省,其他省份基本上没有探测到公网上该类设备端口开放(运营商禁用了SNMP服务或者没有使用同类设备?)。

    img

    广东省受影响的设备的ISP分布如下,98% 以上归属于 “珠江宽频/联通“ 这个ISP,存在漏洞的设备大部分为Technicolor CWA0101 Wireless Gateway ,version :gz5.0.2。

    img

    台湾的181台漏洞设备都归属于ISP:twmbroadband.com,存在漏洞的设备大部分为Ambit T60C926。结合以上数据分析,我们断定中国存在该漏洞设备的地理分布和当地的ISP有很大关系。

    针对所有存在该漏洞的设备,我们统计了凭证的使用情况,如下:

    常用用户名,主要包含admin、login、user、dlink等。

    img

    常用密码,主要包含 admin、password、dream01、空、br0adband、gzcatvnet、user、Broadcom、dlink、ambit、root等,大部分为常见的弱密码。

    img

    非常有意思的是,我们发现以下使用次数最多的用户名密码组合,和使用该凭证组合最多的漏洞设备,以及漏洞设备所在的国家,都存在一定的关联性。

    (如第一行记录:中国所有含有该漏洞的设备中约有 5502 台都使用了 admin:admin 凭证,且受影响设备型号数量最多的为 Technicolor/CWA0101。)

    5. 总结

    基本可以肯定的是,这不是SNMP协议本身的问题,而是协议的实现代码存在漏洞或者ISP配置错误。该漏洞影响厂商、设备型号非常多,且呈现出一定的区域性。

    路由器、Modem、摄像头等IoT设备的信息泄露漏洞层出不穷,对个人隐私造成极大的威胁,关闭非必要的端口或者使用防火墙限制非法访问是个值得考虑的举措。

    系统的安全性往往取决于最短的那块木板-“木桶效应”,通过SNMP协议泄露HTTP服务凭证很好的说明了这一点。

    用户可根据PoC自行验证设备是否存在漏洞,如果存在漏洞可联系相应的ISP寻求解决方案。

    6. 相关链接

    [1] SNMP 协议
    https://baike.baidu.com/item/SNMP/133378?fr=aladdin

    [2] Seebug 漏洞收录
    https://www.seebug.org/vuldb/ssvid-97741
    https://www.seebug.org/vuldb/ssvid-97742
    https://www.seebug.org/vuldb/ssvid-97736

    [3] Stringbleed
    https://stringbleed.github.io/#

    [4] 关于该漏洞的讨论
    https://www.reddit.com/r/netsec/comments/67qt6u/cve_20175135_snmp_authentication_bypass/

    [5] ZoomEye网络空间搜索引擎
    https://www.zoomeye.org/searchResult?q=MODEL%20%2BVENDOR%20%2Bport%3A%22161%22

    [6] SNMP 历史漏洞参考
    http://drops.the404.me/1033.html


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/795/

    作者:Nanako | Categories:安全研究技术分享 | Tags:
  • 知道创宇404实验室2018年网络空间安全报告

    2019-01-17

    作者:知道创宇404实验室

    2018年是网络空间基础建设持续推进的一年,也是网络空间对抗激烈化的一年。IPV6的规模部署,让网络空间几何倍的扩大,带来的将会是攻击目标和攻击形态的转变。更多0day漏洞倾向于在曝光前和1day阶段实现价值最大化,也对防御方有了更高的要求。一手抓建设,一手抓防御,让2018年挑战与机遇并存。

    2018年知道创宇404实验室(以下简称404实验室)一共应急了135次,Seebug漏洞平台收录了664个漏洞,相比于2017年,应急的漏洞数量更多、涉及的设备范围更广。

    2018年上半年虚拟货币价值高涨所带来的是安全事件频发。区块链产业安全建设无法跟上虚拟货币的价值提升必然会导致安全事件的出现。由于区块链相关的攻击隐蔽且致命,监测、防御、止损等,都成为了区块链安全所需要面临的问题。“昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台。《知道创宇以太坊合约审计CheckList》涵盖了超过29种会在以太坊审计过程中会遇到的问题,其中部分问题更是会影响到74.48%已公开源码的合约。

    2018年网络空间攻击正呈现出0day/1day漏洞的快速利用化、历史漏洞定期利用化的特点。勒索病毒和挖矿产业在2018年大行其道,僵尸网络在未来的网络空间对抗中也有可能被赋予新的使命。虚拟货币价值高涨让部分存在漏洞的高性能服务器成为挖矿产业的目标,IoT漏洞的不断涌现也让历史僵尸网络不断补充弹药库。

    2018年是数据泄漏事件频发曝光的一年。随着暗网用户的增多,黑市及虚拟货币的发展,暗网威胁必定会持续增长。知道创宇404安全研究团队也会持续通过技术手段来测绘暗网,提供威胁情报,追踪和对抗来自暗网的威胁。

    完整报告请参阅:《[知道创宇404实验室]2018年网络空间安全报告》


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/788/

    作者:Nanako | Categories:安全研究技术分享 | Tags:
  • lucky 勒索病毒分析与文件解密

    2018-12-17
    作者:Hcamael & 0x7F@知道创宇404实验室
    时间:2018年12月4日

    0x00 前言

    近日,互联网上爆发了一种名为 lucky 的勒索病毒,该病毒会将指定文件加密并修改后缀名为 .lucky

    知道创宇 404 实验室的炼妖壶蜜罐系统最早于 2018.11.10 就捕捉到该勒索病毒的相关流量,截止到 2018.12.04 日,该病毒的 CNC 服务器依然存活。

    根据分析的结果可以得知 lucky 勒索病毒几乎就是 Satan 勒索病毒,整体结构并没有太大改变,包括 CNC 服务器也没有更改。Satan 病毒一度变迁:最开始的勒索获利的方式变为挖矿获利的方式,而新版本的 lucky 勒索病毒结合了勒索和挖矿。

    知道创宇 404 实验室在了解该勒索病毒的相关细节后,迅速跟进并分析了该勒索病毒;着重分析了该病毒的加密模块,并意外发现可以利用伪随机数的特性,还原加密密钥,并成功解密了文件,Python 的解密脚本链接: https://github.com/knownsec/Decrypt-ransomware

    本文对 lucky 勒索病毒进行了概要分析,并着重分析了加密流程以及还原密钥的过程。

    0x01 lucky 病毒简介

    lucky 勒索病毒可在 Windows 和 Linux 平台上传播执行,主要功能分为「文件加密」、「传播感染」与「挖矿」。

    文件加密
    lucky 勒索病毒遍历文件夹,对如下后缀名的文件进行加密,并修改后缀名为 .lucky

    为了保证系统能够正常的运行,该病毒加密时会略过了系统关键目录,如:

    传播感染
    lucky 勒索病毒的传播模块并没有做出新的特色,仍使用了以下的漏洞进行传播:

    挖矿
    该勒索病毒采用自建矿池地址:194.88.105.5:443,想继续通过挖矿获得额外的收益。同时,该矿池地址也是 Satan 勒索病毒变种使用的矿池地址。

    运行截图

    0x02 病毒流程图

    lucky 勒索病毒的整体结构依然延续 Satan 勒索病毒的结构,包括以下组件:

    流程图大致如下:

    lucky 勒索病毒的每个模块都使用了常见的壳进行加壳保护,比如 UPXMPRESS,使用常见的脱壳软件进行自动脱壳即可。

    0x03 加密流程

    对于一个勒索病毒来说,最重要的就是其加密模块。在 lucky 勒索病毒中,加密模块是一个单独的可执行文件,下面对加密模块进行详细的分析。(以 Windows 下的 cpt.exe 作为分析样例)

    1.脱去upx
    cpt.exe 使用 upx 进行加壳,使用常见的脱壳工具即可完成脱壳。

    2.加密主函数
    使用 IDA 加载脱壳后的 cpt.exe.unp,在主函数中有大量初始化的操作,忽略这些操作,跟入函数可以找到加密逻辑的主函数,下面对这些函数进行标注:

    generate_key: 生成 60 位随机字符串,用于后续加密文件。
    wait_sleep: 等待一段时间。
    generate_session: 生成 16 位随机字符串,作为用户的标志(session)。
    lucky_crypto_entry: 具体加密文件的函数。
    send_info_to_server: 向服务器报告加密完成。

    大致的加密流程就是函数标注的如此,最后写入一个文件 c:\\_How_To_Decrypt_My_File_.Dic,通知用户遭到了勒索软件加密,并留下了比特币地址。

    3.generate_key()
    该函数是加密密钥生成函数,利用随机数从预设的字符串序列中随机选出字符,组成一个长度为 60 字节的密钥。

    byte_56F840 为预设的字符串序列,其值为:

    4.generate_session()
    加密模块中使用该函数为每个用户生成一个标识,用于区分用户;其仍然使用随机数从预设的字符串序列中随机选出字符,最后组成一个长度为 16 字节的 session,并存入到 C:\\Windows\\Temp\\Ssession 文件下。

    其中 byte_56F800 字符串为:

    5.lucky_crypto_entry()

    文件名格式

    该函数为加密文件的函数入口,提前拼接加密文件的文件名格式,如下:

    被加密的文件的文件名格式如下:

    其中 filename 是文件本身的名字,后续的字符串是用户的 session。

    通知服务器

    在加密前,还会首先向服务器发送 HTTP 消息,通知服务器该用户开始执行加密了:

    HTTP 数据包格式如下:

    文件筛选

    在加密模块中,lucky 对指定后缀名的文件进行加密:

    被加密的后缀名文件包括:

    6.AES_ECB 加密方法
    lucky 使用先前生成的长度为 60 字节的密钥,取前 32 字节作为加密使用,依次读取文件,按照每 16 字节进行 AEC_ECB 加密。

    除此之外,该勒索病毒对于不同文件大小有不同的处理,结合加密函数的上下文可以得知,这里我们假设文件字节数为 n:

    1. 对于文件末尾小于 16 字节的部分,不加密
    2. 若 n > 10000000 字节,且当 n > 99999999 字节时,将文件分为 n / 80 个块,加密前 n / 16 个块
    3. 若 n > 10000000 字节,且当 99999999 <= n <= 499999999 字节时,将文件分为 n / 480 个块,加密前 n / 16 个块
    4. 若 n > 10000000 字节,且当 n > 499999999 字节时,将文件分为 n / 1280 个块,加密前 n / 16 个块

    对于每个文件在加密完成后,lucky 病毒会将用于文件加密的 AES 密钥使用 RSA 算法打包并添加至文件末尾。

    7.加密完成
    在所有文件加密完成后,lucky 再次向服务器发送消息,表示用户已经加密完成;并在 c:\\_How_To_Decrypt_My_File_.Dic,通知用户遭到了勒索软件加密。

    加密前后文件对比:

    0x04 密钥还原

    在讨论密钥还原前,先来看看勒索病毒支付后流程。

    如果作为一个受害者,想要解密文件,只有向攻击者支付 1BTC,并把被 RSA 算法打包后的 AES 密钥提交给攻击者,攻击者通过私钥解密,最终返回明文的 AES 密钥用于文件解密;可惜的是,受害者即便拿到密钥也不能立即解密,lucky 勒索病毒中并没有提供解密模块。

    勒索病毒期待的解密流程:

    那么,如果能直接找到 AES 密钥呢?

    在完整的分析加密过程后,有些的小伙伴可能已经发现了细节。AES 密钥通过 generate_key() 函数生成,再来回顾一下该函数:

    利用当前时间戳作为随机数种子,使用随机数从预设的字符串序列中选取字符,组成一个长度为 60 字节的密钥。

    随机数=>伪随机数
    有过计算机基础的小伙伴,应该都知道计算机中不存在真随机数,所有的随机数都是伪随机数,而伪随机数的特征是「对于一种算法,若使用的初值(种子)不变,那么伪随机数的数序也不变」。所以,如果能够确定 generate_key() 函数运行时的时间戳,那么就能利用该时间戳作为随机种子,复现密钥的生成过程,从而获得密钥。

    确定时间戳

    爆破

    当然,最暴力的方式就是直接爆破,以秒为单位,以某个有标志的文件(如 PDF 文件头)为参照,不断的猜测可能的密钥,如果解密后的文件头包含 %PDF(PDF 文件头),那么表示密钥正确。

    文件修改时间

    还有其他的方式吗?文件被加密后会重新写入文件,所以从操作系统的角度来看,被加密的文件具有一个精确的修改时间,可以利用该时间以确定密钥的生成时间戳:

    如果需要加密的文件较多,加密所花的时间较长,那么被加密文件的修改时间就不是生成密钥的时间,应该往前推移,不过这样也大大减少了猜测的范围。

    利用用户 session

    利用文件修改时间大大减少了猜测的范围;在实际测试中发现,加密文件的过程耗时非常长,导致文件修改时间和密钥生成时间相差太多,而每次都需要进行检查密钥是否正确,需要耗费大量的时间,这里还可以使用用户 session 进一步缩小猜测的范围。

    回顾加密过程,可以发现加密过程中,使用时间随机数生成了用户 session,这就成为了一个利用点。利用时间戳产生随机数,并使用随机数生成可能的用户 session,当找到某个 session 和当前被加密的用户 session 相同时,表示该时刻调用了 generate_session() 函数,该函数的调用早于文件加密,晚于密钥生成函数。

    找到生成用户session 的时间戳后,再以该时间为起点,往前推移,便可以找到生成密钥的时间戳。

    补充:实际上是将整个还原密钥的过程,转换为寻找时间戳的过程;确定时间戳是否正确,尽量使用具有标志的文件,如以 PDF 文件头 %PDF 作为明文对比。

    还原密钥
    通过上述的方式找到时间戳,利用时间戳就可以还原密钥了,伪代码如下:

    文件解密
    拿到了 AES 密钥,通过 AES_ECB 算法进行解密文件即可。

    其中注意两点:

    0x05 总结

    勒索病毒依然在肆掠,用户应该对此保持警惕,虽然 lucky 勒索病毒在加密环节出现了漏洞,但仍然应该避免这种情况;针对 lucky 勒索病毒利用多个应用程序的漏洞进行传播的特性,各运维人员应该及时对应用程序打上补丁。

    除此之外,知道创宇 404 实验室已经将文中提到的文件解密方法转换为了工具,若您在此次事件中,不幸受到 lucky 勒索病毒的影响,可以随时联系我们。


    References:
    tencent: https://s.tencent.com/research/report/571.html
    绿盟: https://mp.weixin.qq.com/s/uwWTS_ta29YlYntaZN3omQ
    深信服: https://mp.weixin.qq.com/s/zA1bK1sLwaZsUvuOzVHBKg
    Python 的解密脚本: https://github.com/knownsec/Decrypt-ransomware


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/758/

    作者:Nanako | Categories:安全研究技术分享 | Tags:
  • Code Breaking 挑战赛 Writeup

    2018-12-10
    作者:LoRexxar'@知道创宇404实验室
    时间:2018年12月7日
    @phith0n 在代码审计小密圈二周年的时候发起了Code-Breaking Puzzles挑战赛,其中包含了php、java、js、python各种硬核的代码审计技巧。在研究复现the js的过程中,我花费了大量的精力,也逐渐找到代码审计的一些技巧,这里主要分享了5道ez题目和1道hard的the js这道题目的writeup,希望阅读本文的你可以从题目中学习到属于代码审计的思考逻辑和技巧。

    easy - function

    思路还算是比较清晰,正则很明显,就是要想办法在函数名的头或者尾找一个字符,不影响函数调用。

    简单实验了一下没找到,那就直接fuzz起来吧

    很容易就fuzz到了就是\这个符号

    后来稍微翻了翻别人的writeup,才知道原因,在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

    紧接着就到了如何只控制第二个参数来执行命令的问题了,后来找到可以用create_function来完成,create_function的第一个参数是参数,第二个参数是内容。

    函数结构形似

    然后执行,如果我们想要执行任意代码,就首先需要跳出这个函数定义。

    这样一来,我们想要执行的代码就会执行

    exp

    easy pcrewaf

    这题自己研究的时候没想到怎么做,不过思路很清楚,文件名不可控,唯一能控制的就是文件内容。

    所以问题的症结就在于如何绕过这个正则表达式。

    简单来说就是<后面不能有问号,<?后面不能有(;?>反引号,但很显然,这是不可能的,最少执行函数也需要括号才行。从常规的思路肯定不行

    https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

    之后看ph师傅的文章我们看到了问题所在,pcre.backtrack_limit这个配置决定了在php中,正则引擎回溯的层数。而这个值默认是1000000.

    而什么是正则引擎回溯呢?

    在正则中.*表示匹配任意字符任意位,也就是说他会匹配所有的字符,而正则引擎在解析正则的时候必然是逐位匹配的,对于

    这段代码来说

    但很显然,服务端不可能不做任何限制,不然如果post一个无限长的数据,那么服务端就会浪费太多的资源在这里,所以就有了pcre.backtrack_limit,如果回溯次数超过100万次,那么匹配就会结束,然后跳过这句语句。

    回到题目来看,如果能够跳过这句语句,我们就能上传任意文件内容了!

    所以最终post就是传一个内容为

    对于任何一种引擎来说都涉及到这个问题,尤其对于文件内容来说,没办法控制文件的长度,也就不可避免的会出现这样的问题。

    对于PHP来说,有这样一个解决办法,在php的正则文档中提到这样一个问题

    preg_match返回的是匹配到的次数,如果匹配不到会返回0,如果报错就会返回false

    所以,对preg_match来说,只要对返回结果有判断,就可以避免这样的问题。

    easy - phpmagic

    题目代码简化之后如下

    稍微阅读一下代码不难发现问题有几个核心点

    1、没办法完全控制dig的返回,由于没办法命令注入,所以这里只能执行dig命令,唯一能控制的就是dig的目标,而且返回在显示之前还转义了尖括号,所以

    2、in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)这句过滤真的很严格,实在的讲没有什么直白的绕过办法。

    3、log前面会加上$_SERVER['SERVER_NAME']

    第一点真的是想不到,是看了别人的wp才想明白这个关键点 http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/

    之前做题的时候曾经遇到过类似的问题,可以通过解base64来隐藏自己要写入的内容绕过过滤,然后php在解析的时候会忽略各种乱码,只会从<?php开始,所以其他的乱码都不会影响到内容,唯一要注意的就是base64是4位一解的,主要不要把第一位打乱掉。

    简单测试一下

    这样一来我们就能控制文件内容了,而且可以注入<?php

    接下来就是第二步,怎么才能控制logname为调用php伪协议呢?

    问题就在于我们如何控制$_SERVER['SERVER_NAME'],而这个值怎么定是不一定的,这里在这个题目中是取自了头中的host。

    这样一来头我们可以控制了,我们就能调用php伪协议了,那么怎么绕过后缀限制呢?

    这里用了之前曾经遇到过的一个技巧(老了记性不好,翻了半天也没找到是啥比赛遇到的),test.php/.就会直接调用到test.php

    通过这个办法可以绕过根据.来分割后缀的各种限制条件,同样也适用于当前环境下。

    最终poc:

    easy - phplimit

    这个代码就简单多了,简单来说就是只能执行一个函数,但不能设置参数,这题最早出现是在RCTF2018中

    https://lorexxar.cn/2018/05/23/rctf2018/

    在原来的题目中是用next(getallheaders())绕过这个限制的。

    但这里getallheaders是apache中的函数,这里是nginx环境,所以目标就是找一个函数其返回的内容是可以控制的就可以了。

    问题就在于这种函数还不太好找,首先nginx中并没有能获取all header的函数。

    所以目标基本就锁定在会不会有获取cookie,或者所有变量这种函数。在看别人writeup的时候知道了get_defined_vars这个函数

    http://php.net/manual/zh/function.get-defined-vars.php

    他会打印所有已定义的变量(包括全局变量GET等)。简单翻了翻PHP的文档也没找到其他会涉及到可控变量的

    在原wp中有一个很厉害的操作,直接reset所有的变量。

    http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/

    然后只有当前get赋值,那么就只剩下get请求的变量了

    后面就简单了拼接就好了

    然后...直接列目录好像也是个不错的办法2333

    easy - nodechr

    nodejs的一个小问题,关键代码如下

    这里的注入应该是比较清楚的,直接拼接进查询语句没什么可说的。

    然后safekeyword过滤了select union -- ;这四个,下面的逻辑其实说简单的就一句

    如何构造这句来查询flag,开始看到题一味着去想盲注的办法了,后来想明白一点,在注入里,没有select是不可能去别的表里拿数据的,而题目一开始很明确的表明flag在flag表中。

    所以问题就又回到了最初的地方,如何绕过safekeyword的限制。

    ph师傅曾经写过一篇文章 https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

    在js中部分字符会在toLowerCase和toUpperCase处理的时候发生难以想象的变化

    用在这里刚好合适不过了。

    hard - thejs

    javascript真难....

    关键代码以及注释如下

    由于对node不熟,初看代码的时候简单研究了一下各个部分都是干嘛的。然后就发现整个站几乎没什么功能,就是获取输入然后取其中固定的输出,起码就自己写的代码来说不可能有问题。

    再三思考下觉得可能问题在引入的包中...比较明显的就是lodash.merge这句,这句代码在这里非常刻意,于是就顺着这个思路去想,简单翻了一下代码发现没什么收获。后来@spine给了我一个链接

    https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf

    js特性

    首先我们可以先回顾一下js的一部分特性。

    由于js非常面向对象的编程特性,js有很多神奇的操作。

    在js中你可以用各种方式操作自己的对象。

    在js中,所有的对象都是从各种基础对象继承下来的,所以每个对象都有他的父类,通过prototype可以直接操作修改父类的对象。

    而且子类会继承父类的所有方法

    在js中,每个对象都有两个魔术方法,一个是constructor另一个是__proto__

    对于实例来说,constructor代表其构造函数,像前面说的一样,函数可以通过prototype获取其父对象

    而另一个魔术方法__proto__就等价于.constructor.prototype

    由于子类会继承父类的所有方法,所以如果在当前对象中找不到该方法,就会到父类中去找,直到找不到才会爆错

    在复习了上面的特性之后,我们回到这个漏洞

    回到漏洞

    在漏洞分析文中提到了这样一种方式

    假设对于语句

    如果我们控制a为constructor,b为prototype,c为某个key,我们是不是就可以为这个对象父类初始化某个值,这个值会被继承到当前对象。同理如果a为__proto__,b也为__proto__,那么我们就可以为基类Object定义某个值。

    当然这种代码不会随时都出现,所以在实际场景下,这种攻击方式会影响什么样的操作呢。

    首先我们需要理解的就是,我们想办法赋值的__proto__对象并不是真正的这个对象,如图

    所以想要写到真正的__proto__中,我们需要一层赋值,就如同原文范例代码中的那样

    通过这样的操作,我们就可以给Object基类定义一个变量名。

    由于子类会继承父类的所有方法,但首先需要保证子类没有定义这个变量,因为只有当前类没有定义这个变量,才会去父类寻找

    在js代码中,经常能遇到这样的代码

    这种情况下,js会去调用obj的aaa方法,如果aaa方法undefined,那么就会跟入到obj的父类中(js不会直接报该变量未定义并终止)。

    这种情况下,我们通过定义obj的基类Object的aaa方法,就能操作这个变量,改变原来的代码走向。

    最后让我们回到题目中来。

    回到题目

    回到题目中,这下代码的问题点很清楚了。整个代码有且只有1个输入点也就是req.body,这个变量刚好通过lodash.merge合并.

    这里的lodash.merge刚好也就是用于将两个对象合并,成功定义了__proto__对象的变量。

    我们也可以通过上面的技巧去覆盖某个值,但问题来了,我们怎么才能getshell呢?

    顺着这个思路,我需要在整个代码中寻找一个,在影响Object之后,且可以执行命令的地方。

    很幸运的是,虽然我没有特别研究明白nodejs,但我还是发现模板是动态生成的。

    这里的代码是在请求后完成的(动态渲染?)

    跟入到template函数中,可以很清楚的看到

    接下来就是这一大串代码中寻找一个可以影响的变量,我们的目标是找一个未定义的变量,且后面有判断调用它

    这里的sourceURL刚好符合这个条件,我们直接跟入前面的options定义处,进入函数一直跟下去,直到lodash.js的3515行。

    可以看到object本身没有这个方法,但仍然遍历到了,成功注入了这个变量,紧接着渲染模板就成功执行代码了。

    完成攻击

    其实发现可以注入代码之后就简单了,我朋友说他不能用child_process来执行命令,我测试了一下发现是可以的,只是不能弹shell回来不知道怎么回事。思考了一下决定直接wget外带数据出来吧。

    poc

    需要注意一定要是json格式,否则__proto__会解成字符串,开始坑了很久。

    直接偷懒用ceye接请求,其实用什么都行


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/755/

    作者:Nanako | Categories:安全研究技术分享 | Tags:
  • Ethereum Smart Contract Audit CheckList

    2018-12-10
    Author: Knownsec 404 Blockchain Security Research Team
    Time: 2018.12.05
    Chinese Version: https://paper.seebug.org/741/
    Project link: https://github.com/knownsec/Ethereum-Smart-Contracts-Security-CheckList
    In the Ethereum contract audit CheckList, I divided the 29 issues encountered in Ethereum contract auditing into five major categories, including coding specification issues, design defect issues, coding security issues, coding design issues, and coding security issues. This will help smart contract developers and security workers get started quickly with smart contract security.This CheckList refers to and collates with the research results of the major blockchain security research teams in the completion process. Once imperfections/errors occurred, welcome to submit issues.Because this article is mainly a CheckList, the article will not contain too detailed vulnerability/hazard information, and most of the vulnerability analysis will be mentioned in the scanning report.

    1. Coding specification issue

    (1) Compiler version

    In the contract code, the compiler version should be specified. It is recommended to use the latest compiler version.

    Compilers of older versions may cause various known security issues, such as https://paper.seebug.org/631/#44-dividenddistributor

    V0.4.23 updates a compiler vulnerability. In this version, if both constructors are used, i.e.,

    one of the constructors will be ignored, which only affects v0.4.22. V0.4.25 fixes the uninitialized storage pointer problem mentioned below.

    https://etherscan.io/solcbuginfo

    (2) Constructor writing issue

    The correct constructor should be used for different compiler versions, otherwise the contract owner may change.

    In the solidify compiler syntax requirements of versions less than 0.4.22, the contract constructor must be equal to the contract name, and the name is affected by the case, e.g.,

    After version 0.4.22, the constructor keyword was introduced as a constructor declaration. But no function is required.

    If you don't follow the corresponding method, the constructor will be compiled into a normal function, which can be called arbitrarily, leading to more serious consequences such as owner permission.

    (3) Return standard

    Following the ERC20 specification, the transfer and approve functions should return a bool value, and a return value code needs to be added.

    The result of transferFrom should be consistent with the result returned by transfer.

    (4) Event standard

    Follow the ERC20 specification and require the transfer and approve functions to trigger the corresponding event.

    (5) Fake recharge issue

    In the transfer function, the judgment of the balance and the transfer amount needs to use the require function to throw an error, otherwise it will judge that the transaction is successful mistakingly.

    The above code may cause false recharge.

    The correct code is as follows:

    2. Design defect issue

    (1) Approve authorization function conditional competition

    Conditional competition should be avoided in the approve function. Before modifying the allowance, you should change it to 0 first and then to _value.

    The reason for this vulnerability is that in order to encourage miners to mine in the underlying miners' agreement, the miners can decide what to pack for themselves. In order to make more profits, the miners generally choose to package the deals with larger gas prices, rather than relying on the order of transactions.

    By setting 0, the hazards arising from the conditional competition can be alleviated to some extent. The contract manager can check the log to determine if there is a conditional competition. The greater significance of this fix is to remind users who use the approve function. The operation of this function is irreversible to some extent.

    The above code may lead to conditional competition.

    So add the following in the approve function:

    Change the allowance to 0 and then the corresponding number.

    (2) Loop dos issue

    [1] Loop consumption issue

    It is not recommended to use too many loops in contracts.

    In Ethereum, each transaction consumes a certain amount of gas, and the actual consumption is determined by the complexity of the transaction. The larger the number of loops, the higher the complexity of the transaction. When the maximum allowable gas consumption is exceeded, the transaction will fail.

    Real world event

    Simoleon (SIM)

    Pandemica

    [2] Loop security issue

    In the contract, the number of loops should be prevented from being controlled by the user. And the attacker may use an excessive loop to complete the Dos attack.

    When a user needs to transfer money to multiple accounts at the same time, we need to traverse the transfer of the target account list, which may lead to DoS attacks.

    In the above situation, it is recommended to use withdrawFunds to let the user retrieve their token instead of sending it to the corresponding account. This can reduce the hazard to a certain extent.

    If the above code controls a function call, then it can construct a huge loop to consume gas, causing a Dos problem.

    3. Coding security issue

    (1) Overflow issue

    [1] Arithmetic overflow

    When calling addition, subtraction, multiplication and division, you should use the safeMath library instead, otherwise it will easily lead to calculation overflow, resulting in inevitable loss.

    balances[msg.sender] - _value >= 0

    You can bypass the judgment by underflow.

    The usual fix is to use openzeppelin-safeMath, but it may also be limited by judging different variables. However, it is difficult to impose restrictions on multiplication and exponential multiplication.

    The correct writing:

    Real world event

    Hexagon

    SMT/BEC

    [2] Coin/destroy overflow issue

    In the coin/destroy function, the upper limit should be set for totalSupply to avoid the increase in malicious coinage events due to vulnerabilities such as arithmetic overflow.

    There is no limit to totalSupply in the above code, which may cause the exponential arithmetic overflow.

    The correct writing:

    Real world event

    (2) Reentrancy vulnerability

    Avoid using call to trade in smart contracts to avoid reentrancy vulnerabilities.

    In the smart contract, call, send, and transfer are provided to trade eth. The biggest difference for call is that there is no limit for gas. The other two, when the gas is not enough, will report out of gas.

    There are several characteristics of reentrancy vulnerability.

    1. Using the call function.
    2. There is no limit for the call function gas.
    3. Deducting the balance after the transfer.
    4. Adding () to execute the fallback

    The above code is a simple demo of reentrancy vulnerability. A large number of contract tokens are recursively transferred by reentrancy vulnerabilities.

    For possible reentrancy issues, use the transfer function to complete the transfer as much as possible, or limit the gas execution of the call. These can effectively reduce the harm.

    The above code is a way to use mutex lock to avoid recursive protection.

    Real world event

    The Dao

    (3) Call injection

    When the call function is invoked, you should do strict permission control, or write the function invoked to hardcode directly.

    In the design of EVM, if the parameter data of the call is 0xdeadbeef (assumed function name) + 0x0000000000.....01, then it is the invoke function.

    Call function injection can lead to token stealing and permission bypass. Private functions and even partially high-privilege functions can be called through call injection.

    For example, when the delegatecall function must call another contract within the contract, the keyword library can be used to ensure that the contract is static and indestructible. By forcing the contract to be static, the storage environment can be simple to a certain extent and preventing the attacker from attacking the contract by modifying the state.

    Real world events

    call injection

    (4) Permission control

    Different functions in the contract should have reasonable permission settings.

    Check whether the functions in the contract use public, private and other keywords correctly for visibility modification. Check whether the contract is correctly defined and use the modifier to restrict access to key functions to avoid unauthorized control.

    The above code should not be a public function.

    Real world event

    Parity Multi-sig bug 1

    Parity Multi-sig bug 2

    Rubixi

    (5) Replay attack

    If the contract involves the demands for entrusted management, attention should be paid to the non-reusability of verification to avoid replay attacks.

    In the asset management system, there are often cases of entrusted management. The principal gives the assets to the trustee for management and pays a certain fee to the trustee. This business scenario is also common in smart contracts.

    Here is an example of the transferProxy function, which is used when user1 transfers token to user3 but does not have eth to pay for gas price. In this case, user2 is delegated for payment by calling transferProxy.

    The problem with this function is that the nonce value is predictable. Replay attacks can be performed with other variables unchanged which lead to multiple transfers.

    The vulnerability stems from the DEF CON 2018 topics.

    Replay Attacks on Ethereum Smart Contracts Replay Attacks on Ethereum Smart Contracts pdf

    4. Coding design issue

    (1) Address initialization issue

    When the address is involved in a function, it is recommended to add the verification of require(_to!=address(0)) to effectively avoid unnecessary loss caused by user misuse or unknown errors.

    The address that EVM initializes when compiling the contract code is 0. If the developer initializes an address variable in the code without setting an initial value, or the user does not initialize the address variable upon any mis-operation, and this variable is called in the following code, unnecessary security risks may rise.

    This type of check can be used in the simplest way to avoid issues such as unknown errors or short address attacks.

    (2) Judgment function issue

    When the conditional judgment is involved, the require function instead of the assert function is used. Because assert will cause the remaining gas to be consumed, but they are consistent in other aspects.

    It is worth noting that the assert has mandatory consistency. For static variables, assert can be used to avoid some unknown problems, because it will force the termination of the contract and make it invalid. And in some conditions, assert may be more suitable.

    (3) Balance judgment issue

    Don't assume that the contract is created with a balance of 0 and the transfer can be forced.

    Be cautious to write invariants for checking account balances, because an attacker can send wei to any account forcibly, even if the fallback function throws.

    The attacker can create a contract with 1wei and then call selfdestruct(victimAddress) to destroy it. This balance is forcibly transferred to the target, and the target contract has no code to execute and cannot be blocked.

    It is worth noting that during the packaging process, the attacker can transfer before the contract is created through race condition so that the balance is not 0 when the contract is created.

    (4) Transfer function issue

    Upon completing a transaction, it is recommended to use transfer instead of send by default.

    When the target of the transfer or send function is a contract, the contract's fallback function will be invoked. But if the fallback function failed to execute, transfer will throw an error and automatically roll back, and send will return false. Therefore, you need to judge the return type when using send. Otherwise, the transfer may fail and the balance will decrease.

    The above code use the send() function to transfer, because there is no check for the returned value of the send() function.

    If msg.sender fail to call the contract account fallback(), send() returns false, which eventually results in a reduction in the account balance with money loss.

    (5) External call design issue

    For external contracts, pull instead of push is preferred.

    In the case of external calls, unpredictable failure happens. In order to avoid unknown loss, the external operations should be changed into user's own disposal.

    Error example:

    When a transfer to a party is required, the transfer is changed to define the withdraw function, allowing the user to execute the function by himself and withdraw the balance. This will avoid unknown losses to the greatest extent.

    Example code:

    (6) Error handling

    When the contract involves a call or other methods that operates at the base level of the address function, make reasonable error handling.

    If such an operation encounters an error, it will not throw an exception but return false and continue the execution.

    The above code does not verify the return value of send. If msg.sender is a contract account, send returns false when the fallback call fails.

    So when using the above method, you need to check the return value and make error handling.

    https://paper.seebug.org/607/#4-unchecked-return-values-for-low-level-calls

    It's worth noting that as a part of the EVM design, the following functions will return True if the contract being called does not exist.

    Before calling such functions, you need to check the validity of the address.

    (7) Weak random number issue

    The method of generating random numbers on smart contracts requires more considerations.

    The Fomo3D contract introduces the block information as a parameter for generating the random number seed in the airdrop reward, which causes the random number seed to be affected only by the contract address and cannot be completely random.

    The above code directly led to the Fomo3D incident causing more than a few thousands eth loss.

    So when it comes to such applications in a contract, it is important to consider a more appropriate generation method and a reasonable order of use.

    Here is a reasonable random number generation method hash-commit-reveal, i.e., the player submits the action plan and the hash to the back end, which then generates the corresponding hash value as well as the random number to reveal and returns the corresponding random number to commit. In this way, the server can't get the action plan, and the client can't get the random number.

    One great implementation code is the random number generation code for dice2win.(https://etherscan.io/address/0xD1CEeeefA68a6aF0A5f6046132D986066c7f9426)

    But the biggest problem with hash-commit-reveal is that the server will get all the data in the process briefly after user submits. Maliciously suspending the attack will destroy the fairness to some extent. Detailed analysis can be found in the smart contract game - Dice2win security analysis.

    Of course, hash-commit is also a good implementation in some simple scenarios, i.e., the player submits the action plan hash before generating a random number and submitting the action plan.

    Real world event

    Fomo3D Incident

    Last Winner

    (8) Variable coverage vulnerability

    Avoid the key of the array variable in contract being controlled.

    In EVM, arrays are different from other types. As arrays are dynamically sized, array data is calculated as

    The key is the position defined by the map variable, i.e., 1. The offset refers to the offset in the array, e.g., for map[2], the offset is 2.

    The address of map[2] is sha3(1)+2. Assuming map[2]=2333, storage[sha3(1)+2]=2333.

    This is a problem because offset is controllable so that we can write values to any address of the storage.

    This may overwrite the value of any address in the storage, affecting the logic of the code itself, leading to even more serious problems.

    For detailed principles, please refer to - 以太坊智能合约 OPCODE 逆向之理论基础篇 - https://paper.seebug.org/739

    5. Code hidden danger

    (1) Grammatical property issue

    Be careful with the rounding down of integer division in smart contracts.

    In smart contracts, all integer divisions are rounded down to the nearest integer. For higher precision, a multiplier is needed to increase this number.

    If the problem occurs explicitly in the code, the compiler will raise an error and cannot continue compiling. However, if it appears implicitly, the round-down approach will be taken.

    Error example:

    Correct code:

    (2) Data privacy

    note that all data in the chain is public.

    In the contract, all data including private variables are public. Privacy data cannot be stored on the chain.

    (3) Data reliability

    In the contract, the timestamp should not be allowed to appear in the code to avoid interference by the miners. Instead, the constant data such as block.height should be used.

    (4) Gas consumption optimization

    For some functions and variables that do not involve state changes, you can add constant to avoid gas consumption.

    (5) Contract users

    In the contract, we should try to consider the situation when the trading target is the contract and avoid the various malicious uses incurred thereby.

    The above contract is a typical case when the contract is not considered as a user. This is a simple bidding code to compete for the king. When the trade ether is bigger than the highestBid in the contract, the current user will become the current "king" of the contract, and his trading amount will become the new highestBid.

    However, when a new user tries to become the new "king" and the code executes to require(currentLeader.send(highestBid));, the fallback function in the contract is triggered. If the attacker adds a revert() function to the fallback function, the transaction will return false and the transaction will never be completed. Then the current contract will always be the current "king" of the contract.

    (6) Log records

    Key events should have an Event record. In order to facilitate operation, maintenance and monitoring, in addition to functions such as transfer and authorization, other operations also need to add detailed event records such as administrator permission transfer and other special main functions.

    (7) Fallback function

    Define the Fallback function in the contract and make the Fallback function as simple as possible.

    The fallback will be called when there is a problem with the execution of the contract (if there is no matching function). When the send or transfer function is called, only 2300 gas is used to execute the fallback function after the failure. The 2300 gas only allows a set of bytecode instructions to be executed and needs to be carefully written to avoid the use of gas.

    Some examples:

    (8) Owner permission issue

    Avoiding the owner permission is too large.

    For contract owner permissions that are too large, the owner can freely modify various data in the contract, including modification rules, arbitrary transfer, and any coinage. Once a safety issue occurs, it may lead to serious results.

    Regarding the owner permission issue, several requirements should be followed:

    1. After the contract is created, no one can change the contract rules, including the size of the rule parameters.
    2. Only the owner is allowed to withdraw the balance from the contract

    (9) User authentication issue

    Don't use tx.origin for authentication in the contract.

    Tx.origin represents the initial address. If user a invokes contract c through contract b, for contract c, tx.origin is user a, and msg.sender is contract b. This represents a possible phishing attack, which is extremely dangerous for authentication.

    Here's an example:

    We can construct an attack contract:

    When the user is spoofed and invokes the attack contract, it will bypass the authentication directly and transfer the account successfully. Here you should use msg.sender to do permission judgment.

    https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin

    (10) Race condition issue

    Try to avoid relying on the order of transactions in the contract.

    In smart contracts, there is often a reliance on the order of transactions. Such as the rule of occupying a hill to act as a lord or the last winner rule. These are rules that are designed because of the strong dependence on the order of transactions. But the bottom rule of Ethernet is based on the law of maximum interests of miners. Within a certain degree of limit, as long as the attackers pay enough costs, he can control the order of the transactions to a certain extent. Developers should avoid this problem.

    Real world event

    Fomo3D Incident

    (11) Uninitialized storage pointer

    Avoiding initializing struct variables in functions.

    A special data structure is allowed to be a struct structure in solidity, and local variables in the function are stored by default using storage or memory.

    Storage and memory are two different concepts. Solidity allows pointers to point to an uninitialized reference, and uninitialized local storage causes variables to point to other stored variables. This can lead to variable coverage and even more serious consequences.

    After the above code is compiled, s.x and s.y will point incorrectly to owner and a.

    After the attacker executes fake_foo, the owner will be changed to himself.

    The above issue was fixed in the latest version of 0.4.25.

    CheckList audit reports

    REF


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/754/

    作者:Nanako | Categories:技术分享 | Tags:
  • LCTF2018 ggbank 薅羊毛实战

    2018-11-21
    作者:LoRexxar'@知道创宇404区块链安全研究团队
    时间:2018年11月20日
    11.18号结束的LCTF2018中有一个很有趣的智能合约题目叫做ggbank,题目的原意是考察弱随机数问题,但在题目的设定上挺有意思的,加入了一个对地址的验证,导致弱随机的难度高了许多,反倒是薅羊毛更快乐了,下面就借这个题聊聊关于薅羊毛的实战操作。

    分析

    源代码
    https://ropsten.etherscan.io/address/0x7caa18d765e5b4c3bf0831137923841fe3e7258a#code

    首先我们照例来分析一下源代码

    和之前我出的题风格一致,首先是发行了一种token,然后基于token的挑战代码,主要有几个点

    跟着看checkfriend函数

    checkfriend就是整个挑战最大的难点,也大幅度影响了思考的方向,这个稍后再谈。

    空投函数没看有什么太可说的,就是对每一个新用户都发一次空投。

    然后就是goodluck函数

    然后只要余额大于200000就可以拿到flag。

    其实代码特别简单,漏洞也不难,就是非常常见的弱随机数问题。

    随机数的生成方式为

    另一个的生成方式为

    其实非常简单,这两个数字都是已知的,msg.sender可以直接控制已知的地址,那么左值就是已知的,剩下的就是要等待一个右值出现,由于block.number是自增的,我们可以通过提前计算出一个block.number,然后写脚本监控这个值出现,提前开始发起交易抢打包,就ok了。具体我就不详细提了。可以看看出题人的wp。

    https://github.com/LCTF/LCTF2018/tree/master/Writeup/gg%20bank

    但问题就在于,这种操作要等block.number出现,而且还要抢打包,毕竟还是不稳定的。所以在做题的时候我们关注到另一条路,薅羊毛,这里重点说说这个。

    合约薅羊毛

    在想到原来的思路过于复杂之后,我就顺理成章的想到薅羊毛这条路,然后第一反正就是直接通过合约建合约的方式来碰这个概率。

    思路来自于最早发现的薅羊毛合约https://paper.seebug.org/646/

    这个合约有几个很精巧的点。

    首先我们需要有基本的概念,在以太坊上发起交易是需要支付gas的,如果我们不通过合约来交易,那么这笔gas就必须先转账过去eth,然后再发起交易,整个过程困难了好几倍不止。

    然后就有了新的问题,在合约中新建合约在EVM中,是属于高消费的操作之一,在以太坊中,每一次交易都会打包进一个区块中,而每一个区块都有gas消费的上限,如果超过了上限,就会爆gas out,然后交易回滚,交易就失败了。

    上述的poc中,有一个很特别的点就是我加入了checkfriend的判断,因为我发现循环中如果新建合约的函数调用revert会导致整个交易报错,所以我干脆把整个判断放上来,在判断后再发起交易。

    可问题来了,我尝试跑了几波之后发现完全不行,我忽略了一个问题。

    让我们回到checkfriend

    checkfriend只接受地址中带有7d7ec的地址交易,光是这几个字母出现的概率就只有1/36*1/36*1/36*1/36*1/36这个几率在每次随机生成50个合约上计算的话,概率就太小了。

    必须要找新的办法来解决才行。

    python脚本解决方案

    既然在合约上没办法,那么我直接换用python写脚本来解决。

    这个挑战最大的问题就在于checkfriend这里,那么我们直接换一种思路,如果我们去爆破私钥去恢复地址,是不是更有效一点儿?

    其实爆破的方式非常多,但有的恢复特别慢,也不知道瓶颈在哪,在换了几种方式之后呢,我终于找到了一个特别快的恢复方式。

    我们拿到了地址之后就简单了,首先先转0.01eth给它,然后用私钥发起交易,获得空投、转账回来。

    需要注意的是,转账之后需要先等到转账这个交易打包成功,之后才能继续下一步交易,需要多设置一步等待。

    有个更快的方案是,先跑出200个地址,然后再批量转账,最后直接跑起来,不过想了一下感觉其实差不太多,因为整个脚本跑下来也就不到半小时,速度还是很可观的。

    脚本如下

    最终效果显著


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/747/

    作者:Nanako | Categories:安全研究技术分享 | Tags:
  • 以太坊合约审计 CheckList 之变量覆盖问题

    2018-11-16
    作者:LoRexxar'@知道创宇404区块链安全研究团队
    时间:2018年11月16日
    系列文章:

    2018年11月6日,DVP上线了一场“地球OL真实盗币游戏”,其中第二题是一道智能合约题目,题目中涉及到的了一个很有趣的问题,这里拿出来详细说说看。

    https://etherscan.io/address/0x5170a14aa36245a8a9698f23444045bdc4522e0a#code

    Writeup

    看着代码那么长,但其实核心代码就后面这点。

    fallback函数

    简单来说就是每个地址只发一次空投,然后如果余额空投完了就会销毁自己转账。

    guess函数

    lottery函数

    其实回到题目本身来看,我们的目的是要拿走合约里的所有eth,在合约里,唯一仅有的转账办法就是selfdestruct,所以我们的目的就是想办法触发这个函数。

    销毁函数只在fallback和lottery函数中存在,其实阅读一下不难发现,lottery不可能有任何操作,没办法溢出,没办法修改,除非运气逆天,否则不可能从lottery函数触发这个函数。

    所以目光回到fallback函数,要满足转账,我们需要想办法让balanceOf返回0,如果我们想要通过薅羊毛的方式去解决的话简单测试就会明白这不可能,因为一次只能转100,余额如果我没记错的话,应该超过万亿以上。

    很显然,想通过空投要薅羊毛来获得flag基本不太可能,所以我们的目标就是,如何影响到balanceOf的返回。

    而balanceOf这个函数是来自于token变量的

    而token变量是一个全局变量,在开始被定义

    在 EVM 中存储有三种方式,分别是 memory、storage 以及 stack

    memory : 内存,生命周期仅为整个方法执行期间,函数调用后回收,因为仅保存临时变量,故GAS开销很小 storage : 永久储存在区块链中,由于会永久保存合约状态变量,故GAS开销也最大 stack : 存放部分局部值类型变量,几乎免费使用的内存,但有数量限制

    而全局变量就是存在storage中的,合约中的全局变量有以下几个

    而token就是第一个全局变量,则storage[0]就存了token变量

    然后回到我们前面的需求,我们怎么才有可能覆盖storage的第一块数据呢,让我们再回到代码。guess中有这么一段代码。

    在EVM中数组和其他类型不同,因为数组时动态大小的,所以数组类型的数据计算方式为

    其中array_slot就是map变量数据的位置,也就是1,offset就是数组中的偏移,比如map[2],offset就是2.

    这样一来,map[2]的地址就是sha(1)+2,假设map[2]=2333,则storage[sha(1)+2]=2333

    这样一来就出现问题了,由于offset我们可控,我们就可以向storage的任意地址写值。

    再加上storage不是无限大的,它最多只有2**256那么大,sha(1)是固定的0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6。

    也就是说我们设置x为2**256-0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6,storage就会溢出,并覆盖token变量。

    所以思路就比较清楚了,构造攻击合约,然后定义balanceOf返回0,调用fallback函数,然后返回即可。

    利用合约大致如下

    在题目之后

    在题目之后,我们不难发现,整个漏洞的成因与未初始化storage指针非常像,要明白这个漏洞,首先我们需要明白在EVM中对变长变量的定义和储存方式。

    array

    map就是一个uint类型的数组,在storage中,map变量的储存地址计算公式如下

    刚才说到array_slot就是数组变量在全局变量中声明的位置,比如map是第二个全局变量

    mapping

    balances是一个键为address类型,值为uint型的mapping字典,在storage中,balances变量的储存地址计算公式如下

    其中key就是mapping类型中的键名,slot就是balances变量在全局变量中声明的位置,比如balances是第一个全局变量:

    mapping + struct

    people是一个键为address类型,值为struct的mapping,在storage中,people变量的储存地址计算公式如下

    其中key就是mapping类型中的键名,slot就是people变量在全局变量中声明的位置,offset就是变量在结构体内的位置,比如people是第一个全局变量:

    对于上面的三种典型结构来说,虽然可以保证sha3的结果不会重复,但很难保证sha3(a)+b不和sha3(c)重复,所以,虽然几率很小,但仍然可能因为hash碰撞导致变量被覆盖。

    再回到攻击者角度,一旦变长数组的key可以被控制,就有可能人为的控制覆盖变量,产生进一步利用。

    详细的原理可以参照以太坊智能合约 OPCODE 逆向之理论基础篇

    漏洞影响范围

    经过研究,我们把这类问题统一归结是变量覆盖问题,当array变量出现,且参数可控时,就有可能导致恶意利用了。

    “昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台。目前Haotian平台智能合约审计功能已经集成了该规则。

    截至2018年11月15日,我们使用HaoTian对全网公开的智能合约进行扫描,其中共有277个合约存在潜在的该问题,其中交易量最高的10个合约情况如下:

    总结

    这是一起涉及到底层设计结构的变量覆盖问题,各位智能合约的开发者们可以关于代码中可能存在的这样的问题,避免不必要的损失。

    上述变量覆盖问题已经更新到以太坊合约审计checkList

    REF


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/745/

    作者:Nanako | Categories:安全研究技术分享 | Tags: