RSS Feed
更好更安全的互联网
  • WebLogic EJBTaglibDescriptor XXE漏洞(CVE-2019-2888)分析

    2019-10-30

    作者:Longofo@知道创宇404实验室
    时间:2019年10月16日

    这个漏洞和之前@Matthias Kaiser提交的几个XXE漏洞是类似的,而EJBTaglibDescriptor应该是漏掉的一个,可以参考之前几个XXE的分析。我和@Badcode师傅反编译了WebLogic所有的Jar包,根据之前几个XXE漏洞的特征进行了搜索匹配到了这个EJBTaglibDescriptor类,这个类在反序列化时也会进行XML解析。

    Oracle发布了10月份的补丁,详情见链接(https://www.oracle.com/technetwork/security-advisory/cpuoct2019-5072832.html)

    环境

    • Windows 10
    • WebLogic 10.3.6.0.190716(安装了19年7月补丁)
    • Jdk160_29(WebLogic 自带的JDK)

    漏洞分析

    weblogic.jar!\weblogic\servlet\ejb2jsp\dd\EJBTaglibDescriptor.class这个类继承自java\io\Externalizable

    因此在序列化与反序列化时会自动调用子类重写的writeExternalreadExternal

    看下writeExternal的逻辑与readExternal的逻辑,

    readExternal中,使用ObjectIutput.readUTF读取反序列化数据中的String数据,然后调用了load方法,

    在load方法中,使用DocumentBuilder.parse解析了反序列化中传递的XML数据,因此这里是可能存在XXE漏洞的

    writeExternal中,调用了本身的toString方法,在其中又调用了自身的toXML方法

    toXML的作用应该是将this.beans转换为对应的xml数据。看起来要构造payload稍微有点麻烦,但是序列化操作是攻击者可控制的,所以我们可以直接修改writeExternal的逻辑来生成恶意的序列化数据:

    漏洞复现

    1.重写 EJBTaglibDescriptor中的writeExternal函数,生成payload

    2.发送payload到服务器

    在我们的HTTP服务器和FTP服务器接收到了my.dtd的请求与win.ini的数据

    3.在打了7月份最新补丁的服务器上能看到报错信息


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • PHP-fpm 远程代码执行漏洞(CVE-2019-11043)分析

    2019-10-28

    作者:LoRexxar'@知道创宇404实验室 
    时间:2019年10月25日

    国外安全研究员 Andrew Danau在解决一道 CTF 题目时发现,向目标服务器 URL 发送 %0a 符号时,服务返回异常,疑似存在漏洞。

    2019年10月23日,github公开漏洞相关的详情以及exp。当nginx配置不当时,会导致php-fpm远程任意代码执行。

    下面我们就来一点点看看漏洞的详细分析,文章中漏洞分析部分感谢团队小伙伴@Hcamael#知道创宇404实验室

    漏洞复现

    为了能更方便的复现漏洞,这里我们采用vulhub来构建漏洞环境。

    git pulldocker-compose up -d

    访问http://{your_ip}:8080/

    下载github上公开的exp(需要go环境)。

    然后编译

    使用exp攻击demo网站

    攻击成功

    漏洞分析

    在分析漏洞原理之前,我们这里可以直接跟入看修复的commit

    -https://github.com/php/php-src/commit/ab061f95ca966731b1c84cf5b7b20155c0a1c06a#diff-624bdd47ab6847d777e15327976a9227

    从commit中我们可以很清晰的看出来漏洞成因应该是path_info的地址可控导致的,再结合漏洞发现者公开的漏洞信息中提到

    也就是说,当path_info被%0a截断时,path_info将被置为空,回到代码中我就不难发现问题所在了。

    其中env_path_info就是变量path_info的地址,path_info为0则plien为0.

    slen变量来自于请求后url的长度

    其中

    这两个变量的差就是后面的路径长度,由于路径可控,则path_info可控。

    由于path_info可控,在1222行我们就可以将指定地址的值置零,根据漏洞发现者的描述,通过将指定的地址的值置零,可以控制使_fcgi_data_seg结构体的char* pos置零。

    其中script_name同样来自于请求的配置

    而为什么我们使_fcgi_data_seg结构体的char* pos置零,就会影响到FCGI_PUTENV的结果呢?

    这里我们深入去看FCGI_PUTENV的定义.

    跟入函数fcgi_quick_putenv

    https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L1703

    函数直接操作request的env,而这个参数在前面被预定义。

    https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L908

    继续跟进初始化函数fcgi_hash_init.

    https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L254

    也就是说request->env就是前面提到的fcgi_data_seg结构体,而这里的request->env是nginx在和fastcgi通信时储存的全局变量。

    部分全局变量会在nginx的配置中定义

    其中变量会在堆上相应的位置储存

    回到利用过程中,这里我们通过控制path_info指向request->env来使request->env->pos置零。

    继续回到赋值函数fcgi_hash_set函数

    紧接着进入fcgi_hash_strndup

    这里h->data-》pos的最低位被置为0,且str可控,就相当于我们可以在前面写入数据。

    而问题就在于,我们怎么能向我们想要的位置写数据呢?又怎么向我们指定的配置写文件呢?

    这里我们拿exp发送的利用数据包做例子

    在数据包中,header中的最后两部分就是为了完成这部分功能,其中D-Gisos负责位移,向指定的位置写入数据。

    Ebut会转化为HTTP_EBUT这个fastcgi_param中的其中一个全局变量,然后我们需要了解一下fastcgi中全局变量的获取数据的方法。

    https://github.com/php/php-src/blob/5d6e923d46a89fe9cd8fb6c3a6da675aa67197b4/main/fastcgi.c#L328

    可以看到当fastcgi想要获取全局变量时,会读取指定位置的长度字符做对比,然后读取一个字符串作为value.

    也就是说,只要位置合理,var值相同,且长度相同,fastcgi就会读取相对应的数据

    HTTP_EBUTPHP_VALUE恰好长度相同,我们可以从堆上数据的变化来印证这一点。

    在覆盖之前,该地址对应数据为

    然后执行fcgi_quick_putenv

    该地址对应数据变为

    我们成功写入了PHP_VALUE并控制其内容,这也就意味着我们可以控制PHP的任意全局变量。

    当我们可以控制PHP的任意全局变量就有很多种攻击方式,这里直接以EXP中使用到的攻击方式来举例子。

    exp作者通过开启自动包含,并设置包含目录为/tmp,之后设置log地址为/tmp/a并将payload写入log文件,通过auto_prepend_file自动包含/tmp/a文件构造后门文件。

    漏洞修复

    在经过对漏洞的深入研究后,我们推荐两种方案修复这个漏洞。

    • 临时修复:

    修改nginx相应的配置,并在php相关的配置中加入

    在这种情况下,会有nginx去检查文件是否存在,当文件不存在时,请求都不会被传递到php-fpm。

    漏洞影响

    结合EXP github中提到的利用条件,我们可以尽可能的总结利用条件以及漏洞影响范围。

    1、Nginx + php_fpm,且配置location ~ [^/]\.php(/|$)会将请求转发到php-fpm。
    2、Nginx配置fastcgi_split_path_info并且以^开始以$,只有在这种条件下才可以通过换行符来打断正则表达式判断。 ps: 则允许index.php/321 -> index.php

    3、fastcgi_paramPATH_INFO会被定义通过fastcgi_param PATH_INFO $fastcgi_path_info;,当然这个变量会在fastcgi_params默认定义。
    4、在nginx层面没有定义对文件的检查比如try_files $uri =404,如果nginx层面做了文件检查,则请求不会被转发给php-fmp。

    这个漏洞在实际研究过程中对真实世界危害有限,其主要原因都在于大部分的nginx配置中都携带了对文件的检查,且默认的nginx配置不包含这个问题。

    但也正是由于这个原因,在许多网上的范例代码或者部分没有考虑到这个问题的环境,例如Nginx官方文档中的范例配置、NextCloud默认环境,都出现了这个问题,该漏洞也正真实的威胁着许多服务器的安全。

    在这种情况下,这个漏洞也切切实实的陷入了黑暗森林法则,一旦有某个带有问题的配置被传播,其导致的可能就是大批量的服务受到牵连,确保及时的更新永远是对保护最好的手段:>

    参考链接


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 硬件学习之通过树莓派操控 jtag

    2019-10-28

    作者:Hcamael@知道创宇404实验室
    时间:2019年10月21日

    最近在搞路由器的时候,不小心把CFE给刷挂了,然后发现能通过jtag进行救砖,所以就对jtag进行了一波研究。

    最开始只是想救砖,并没有想深入研究的想法。

    救砖尝试

    变砖的路由器型号为:LinkSys wrt54g v8

    CPU 型号为:BCM5354

    Flash型号为:K8D6316UBM

    首先通过jtagulator得到了设备上jtag接口的顺序。

    正好公司有一个jlink,但是参试了一波失败,识别不了设备。

    随后通过Google搜到发现了一个工具叫: tjtag-pi

    可以通树莓派来控制jtag,随后学习了一波树莓派的操作。

    树莓派Pins

    我使用的是rpi3,其接口编号图如下:

    或者在树莓派3中可以使用gpio readall查看各个接口的状态:

    rpi3中的Python有一个RPi.GPIO模块,可以控制这些接口。

    举个例子:

    首先是需要进行初始化GPIO的模式,BCM模式对应的针脚排序是上面图中橙色的部门。

    然后可以对各个针脚进行单独设置,比如上图中,把2号针脚设置为输出,3号针脚设置为输入。

    使用output函数进行二进制输出

    使用input函数获取针脚的输入。

    我们可以用线把两个针脚连起来测试上面的代码。

    将树莓派对应针脚和路由器的连起来以后,可以运行tjtag-pi程序。但是在运行的过程中却遇到了问题,经常会卡在写flash的时候。通过调整配置,有时是可以写成功的,但是CFE并没有被救回来,备份flash的数据,发现并没有成功写入数据。

    因为使用轮子失败,所以我只能自己尝试研究和造轮子了。

    jtag

    首先是针脚,我见过的设备给jtag一般是提供了5 * 2以上的引脚。其中有一般都是接地引脚,另一半只要知道4个最重要的引脚。

    这四个引脚一般情况下的排序是:

    TDI表示输入,TDO表示输出,TMS控制位,TCK时钟输入。

    jtag大致架构如上图所示,其中TAP-Controller的架构如下图所示:

    根据上面这两个架构,对jtag的原理进行讲解。

    jtag的核心是TAP-Controller,通过解析TMS数据,来决定输入和输出的关系。所以我们先来看看TAP-Controller的架构。

    从上面的图中我们可以发现,在任何状态下,输出5次1,都会回到TEST LOGIC RESET状态下。所以在使用jtag前,我们先通过TMS端口,发送5次为1的数据,jtag的状态机将会进入到RESET的复原状态。

    当TAP进入到SHIFT-IR的状态时,Instruction Register将会开始接收TDI传入的数据,当输入结束后,进入到UPDATE-IR状态时将会解析指令寄存器的值,随后决定输出什么数据。

    SHIFT-DR则是控制数据寄存器,一般是在读写数据的时候需要使用。

    讲到这里,就出现一个问题了,TMS就一个端口,jtag如何知道TMS每次输入的值是多少呢?这个时候就需要用到TCK端口了,该端口可以称为时钟指令。当TCK从低频变到高频时,获取一比特TMS/TDI输入,TDO输出1比特。

    比如我们让TAP进行一次复位操作:

    再比如,我们需要给指令寄存器传入0b10:

    1.复位

    2.进入RUN-TEST/IDLE状态

    3.进入SELECT-DR-SCAN状态

    4.进入SELECT-IR-SCAN状态

    5.进入CAPTURE-IR状态

    6.进入SHIFT-IR状态

    7.输入0b10

    随后就是进入EXIT-IR -> UPDATE-IR

    根据上面的理论我们就可以通过写一个设置IR的函数:

    把上面的代码理解清楚后,基本就理解了TAP的逻辑。接下来就是指令的问题了,指令寄存器的长度是多少?指令寄存器的值为多少时是有意义的?

    不同的CPU对于上面的答案都不一样,通过我在网上搜索的结果,每个CPU应该都有一个bsd(boundary scan description)文件。本篇文章研究的CPU型号是BCM5354,但是我并没有在网上找到该型号CPU的bsd文件。我只能找了一个相同厂商不同型号的CPU的bsd文件进行参考。

    bcm53101m.bsd

    在该文件中我们能看到jtag端口在cpu端口的位置:

    能找到指令长度的定义:

    能找到指令寄存器的有效值:

    当指令寄存器的值为IDCODE的时候,IDCODE寄存器的输出通道开启,我们来看看IDCODE寄存器:

    从这里我们能看出IDCODE寄存器的固定输出为: 0b00000000000011011111001011111111

    那我们怎么获取TDO的输出呢?这个时候数据寄存器DR就发挥作用了。

    1. TAP状态机切换到SHIFT-IR
    2. 输出IDCODE到IR中
    3. 切换到SHIFT-DR
    4. 获取INSTRUCTION_LENGTH长度的TDO输出值
    5. 退出

    用代码形式的表示如下:

    因为我也是个初学者,边界扫描描述文件中的内容并不是都能看得懂,比如在边界扫描文件中并不能看出BYPASS指令是做什么的。但是在其他文档中,得知BYPASS寄存器一般是用来做测试的,在该寄存器中,输入和输出是直连,可以通过比较输入和输出的值,来判断端口是否连接正确。

    另外还有边界扫描寄存器一大堆数据,也没完全研究透,相关的资料少的可怜。而且也找不到对应CPU的文档。

    当研究到这里的时候,我只了解了jtag的基本原理,只会使用两个基本的指令(IDCODE, BYPASS)。但是对我修砖没任何帮助。

    没办法,我又回头来看tjtag的源码,在tjtag中定义了几个指令寄存器的OPCODE:

    照抄着tjtag中flash AMD的操作,可以成功对flash进行擦除,写入操作读取操作。但是却不知其原理。

    这里分享下我的脚本:jtag.py

    flash文档:https://www.dataman.com/media/datasheet/Samsung/K8D6x16UTM_K8D6x16UBM_rev16.pdf

    接下来将会对该flash文档进行研究,并在之后的文章中分享我后续的研究成果。


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • WhatsApp UAF 漏洞分析(CVE-2019-11932)

    2019-10-28

    作者:SungLin@知道创宇404实验室
    时间:2019年10月23日

    0x00

    新加坡安全研究员Awakened在他的博客中发布了这篇[0]对whatsapp的分析与利用的文章,其工具地址是[1],并且演示了rce的过程[2],只要结合浏览器或者其他应用的信息泄露漏洞就可以直接在现实中远程利用,并且Awakened在博客中也提到了:

    1、攻击者通过任何渠道将GIF文件发送给用户其中之一可以是通过WhatsApp作为文档(例如,按“Gallery”按钮并选择“Document”以发送损坏的GIF)

    如果攻击者在用户(即朋友)的联系人列表中,则损坏的GIF会自动下载,而无需任何用户交互。

    2、用户想将媒体文件发送给他/她的任何WhatsApp朋友。因此,用户按下“Gallery”按钮并打开WhatsApp Gallery以选择要发送给他的朋友的媒体文件。请注意,用户不必发送任何内容,因为仅打开WhatsApp Gallery就会触发该错误。按下WhatsApp Gallery后无需额外触摸。

    3、由于WhatsApp会显示每个媒体(包括收到的GIF文件)的预览,因此将触发double-free错误和我们的RCE利用。

    此漏洞将会影响WhatsApp版本2.19.244之前的版本,并且是Android 8.1和9.0的版本。

    我们来具体分析调试下这个漏洞。

    0x01

    首先呢,当WhatsApp用户在WhatsApp中打开“Gallery”视图以发送媒体文件时,WhatsApp会使用一个本机库解析该库,libpl_droidsonroids_gif.so以生成GIF文件的预览。libpl_droidsonroids_gif.so是一个开放源代码库,其源代码位于[3],新版本的已经修改了decoding函数,为了防止二次释放,在检测到传入gif帧大小为0的情况下就释放info->rasterBits指针,并且返回了:

    而有漏洞的版本是如何释放两次的,并且还能利用,下面来调试跟踪下。

    0x02

    Whatsapp在解析gif图像时会调用Java_pl_droidsonroids_gif_GifInfoHandle_openFile进行第一次初始化,将会打开gif文件,并创建大小为0xa8的GifInfo结构体,然后进行初始化。

    之后将会调用Java_pl_droidsonroids_gif_GifInfoHandle_renderFrame对gif图像进行解析。

    关键的地方是调用了函数DDGifSlurp(GifInfo *info, bool decode, bool exitAfterFrame)并且传入decode的值为true,在未打补丁的情况下,我们可以如Awakened所说的,构造三个帧,连续两个帧的gifFilePtr->Image.Width或者gifFilePtr->Image.Height为0,可以导致reallocarray调用reallo调用free释放所指向的地址,造成double-free:

    然后android中free两次大小为0xa8内存后,下一次申请同样大小为0xa8内存时将会分配到同一个地址,然而在whatsapp中,点击gallery后,将会对一个gif显示两个Layout布局,将会对一张gif打开并解析两次,如下所示:

    所以当第二次解析的时候,构造的帧大小为0xa8与GifInfo结构体大小是一致的,在解析时候将会覆盖GifInfo结构体所在的内存。

    0x03

    大概是这样,和博客那个流程大概一致:

    第一次解析:

    申请0xa8大小内存存储数据

    第一次free

    第二次free

    ..

    .. 第二次解析:

    申请0xa8大小内存存储info

    申请0xa8大小内存存储gif数据->覆盖info

    Free

    Free

    ..

    ..

    最后跳转info->rewindFunction(info)

    X8寄存器滑到滑块指令

    滑块执行我们的代码

    0x04

    制作的gif头部如下:

    解析的时候首先调用Java_pl_droidsonroids_gif_GifInfoHandle_openFile创建一个GifInfo结构体,如下所示:

    我们使用提供的工具生成所需要的gif,所以说newRasterSize = gifFilePtr->Image.Width * gifFilePtr->Image.Height==0xa8,第一帧将会分配0xa8大小数据

    第一帧头部如下:

    接下来解析到free所需要的帧如下,gifFilePtr->Image.Width为0,gifFilePtr->Image.Height为0xf1c,所以newRasterSize的大小将会为0,reallocarray(info->rasterBits, newRasterSize, sizeof(GifPixelType))的调用将会free指向的info->rasterBits

    连续两次的free掉大小为x0寄存器指向的0x6FDE75C580地址,大小为0xa8,而x19寄存器指向的0x6FDE75C4C0,x19寄存器指向的就是Info结构体指针

    第一次解析完后info结构体数据如下,info->rasterBits指针指向了0x6FDE75C580,而这里就是我们第一帧数据所在,大小为0xa8:

    经过reallocarray后将会调用DGifGetLine解码LZW编码并拷贝到分配内存:

    第一帧数据如下,info->rasterBits = 0x6FDE75C580

    在经过double-free掉0xa8大小内存后,第二次解析中,首先创建一个大小为0xa8的info结构体,之后将会调用DDGifSlurp解码gif,并为gif分配0xa8大小的内存,因为android的两次释放会导致两次分配同一大小内存指向同一地址特殊性,所以x0和x19都指向了0x6FDE75C580,x0是gif数据,x19是info结构体:

    此时结构体指向0x6FDE75C580

    之后经过DGifGetLine拷贝数据后,我们gif的第一帧数据将会覆盖掉0x6FDE75C580,最后运行到函数末尾,调用info->rewindFunction(info)

    此时运行到了info->rewindFunction(info),x19寄存器保存着我们覆盖了的info指针,

    此时x8寄存器指向了我们需要的指令,在libhwui中:

    此时我们来分析下如何构造的数据,在我的本机上泄露了俩个地址,0x707d540804和0x707f3f11d8,如上所示,运行到info->rewindFunction(info)后,x19存储了我们覆盖的数据大小为0xa8,汇编代码如下:

    所以我们需要泄露的第一个地址要放在X19+0X80处为0x707d540804,而0x707d540804的指令如下,所以以如下指令作为跳板执行我们的代码:

    所以刚好我们x19+0x18放的是执行libc的system函数的地址0x707f3f11d8,而x19+20是我们执行的代码所在位置:

    提供的测试小工具中,我们将会遍历lib库中的指令直到找到我们所需滑板指令的地址:

    还有libc中的system地址,将这两个地址写入gif

    跳转到libhwui后,此地址指令刚好和我们构造的数据吻合

    X8寄存器指向了libc的system调用

    X0寄存器指向我们将要运行的代码:

    0x05

    参考链接如下:

    [0] https://awakened1712.github.io/hacking/hacking-whatsapp-gif-rce
    [1] https://github.com/awakened1712/CVE-2019-11932
    [2] https://drive.google.com/file/d/1T-v5XG8yQuiPojeMpOAG6UGr2TYpocIj/view
    [3] https://github.com/koral--/android-gif-drawable/releases


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 使用 Ghidra 分析 phpStudy 后门

    2019-10-22

    作者:lu4nx@知道创宇404积极防御实验室
    作者博客:《使用 Ghidra 分析 phpStudy 后门》

    这次事件已过去数日,该响应的也都响应了,虽然网上有很多厂商及组织发表了分析文章,但记载分析过程的不多,我只是想正儿八经用 Ghidra 从头到尾分析下。

    1 工具和平台

    主要工具:

    • Kali Linux
    • Ghidra 9.0.4
    • 010Editor 9.0.2

    样本环境:

    • Windows7
    • phpStudy 20180211

    2 分析过程

    先在 Windows 7 虚拟机中安装 PhpStudy 20180211,然后把安装完后的目录拷贝到 Kali Linux 中。

    根据网上公开的信息:后门存在于 php_xmlrpc.dll 文件中,里面存在“eval”关键字,文件 MD5 为 c339482fd2b233fb0a555b629c0ea5d5。

    因此,先去找到有后门的文件:

    将文件 ./PHPTutorial/php/php-5.4.45/ext/php_xmlrpc.dll 单独拷贝出来,再确认下是否存在后门:

    从上面的搜索结果可以看到文件中存在三个“eval”关键字,现在用 Ghidra 载入分析。

    在 Ghidra 中搜索下:菜单栏“Search” > “For Strings”,弹出的菜单按“Search”,然后在结果过滤窗口中过滤“eval”字符串,如图:

    从上方结果“Code”字段看的出这三个关键字都位于文件 Data 段中。随便选中一个(我选的“@eval(%s(‘%s’));”)并双击,跳转到地址中,然后查看哪些地方引用过这个字符串(右击,References > Show References to Address),操作如图:

    结果如下:

    可看到这段数据在 PUSH 指令中被使用,应该是函数调用,双击跳转到汇编指令处,然后 Ghidra 会自动把汇编代码转成较高级的伪代码并呈现在 Decompile 窗口中:

    如果没有看到 Decompile 窗口,在菜单Window > Decompile 中打开。

    在翻译后的函数 FUN_100031f0 中,我找到了前面搜索到的三个 eval 字符,说明这个函数中可能存在多个后门(当然经过完整分析后存在三个后门)。

    这里插一句,Ghidra 转换高级代码能力比 IDA 的 Hex-Rays Decompiler 插件要差一些,比如 Ghidra 转换的这段代码:

    在IDA中翻译得就很直观:

    还有对多个逻辑的判断,IDA 翻译出来是:

    Ghidra 翻译出来却是:

    而多层 if 嵌套阅读起来会经常迷路。总之 Ghidra 翻译的代码只有反复阅读后才知道是干嘛的,在理解这类代码上我花了好几个小时。

    2.1 第一个远程代码执行的后门

    第一个后门存在于这段代码:

    阅读起来非常复杂,大概逻辑就是通过 PHP 的 zend_hash_find 函数寻找 $_SERVER 变量,然后找到 Accept-Encoding 和 Accept-Charset 两个 HTTP 请求头,如果 Accept-Encoding 的值为 gzip,deflate,就调用 zend_eval_string 去执行 Accept-Encoding 的内容:

    这里 zend_eval_string 执行的是 local_10 变量的内容,local_10 是通过调用一个函数赋值的:

    函数 FUN_100040b0 最后分析出来是做 Base64 解码的。

    到这里,就知道该如何构造 Payload 了:

    朝虚拟机构造一个请求:

    结果如图:

    2.2 第二处后门

    沿着伪代码继续分析,看到这一段代码:

    重点在这段:

    变量 puVar8 是作为累计变量,这段代码像是拷贝地址 0x1000d66c 至 0x1000e5c4 之间的数据,于是选中切这行代码:

    双击 DAT_1000d66c,Ghidra 会自动跳转到该地址,然后在菜单选择 Window > Bytes 来打开十六进制窗口,现已处于地址 0x1000d66c,接下来要做的就是把 0x1000d66c~0x1000e5c4 之间的数据拷贝出来:

    1. 选择菜单 Select > Bytes;
    2. 弹出的窗口中勾选“To Address”,然后在右侧的“Ending Address”中填入 0x1000e5c4,如图:

    按回车后,这段数据已被选中,我把它们单独拷出来,点击右键,选择 Copy Special > Byte String (No Spaces),如图:

    然后打开 010Editor 编辑器:

    1. 新建文件:File > New > New Hex File;
    2. 粘贴拷贝的十六进制数据:Edit > Paste From > Paste from Hex Text

    然后,把“00”字节全部去掉,选择 Search > Replace,查找 00,Replace 那里不填,点“Replace All”,处理后如下:

    把处理后的文件保存为 p1。通过 file 命令得知文件 p1 为 Zlib 压缩后的数据:

    用 Python 的 zlib 库就可以解压,解压代码如下:

    执行结果如下:

    用 base64 命令把这段 Base64 代码解密,过程及结果如下:

    2.3 第三个后门

    第三个后门和第二个实现逻辑其实差不多,代码如下:

    重点在这段:

    后门代码在地址 0x1000d028~0x1000d66c 中,提取和处理方法与第二个后门的一样。找到并提出来,如下:

    把这段Base64代码解码:

    3 参考


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • CVE-2019-14287(Linux sudo 漏洞)分析

    2019-10-22

    作者:lu4nx@知道创宇404积极防御实验室
    作者博客:《CVE-2019-14287(Linux sudo 漏洞)分析》

    近日 sudo 被爆光一个漏洞,非授权的特权用户可以绕过限制获得特权。官方的修复公告请见:https://www.sudo.ws/alerts/minus_1_uid.html

    1. 漏洞复现

    实验环境:

    操作系统CentOS Linux release 7.5.1804
    内核3.10.0-862.14.4.el7.x86_64
    sudo 版本1.8.19p2

    首先添加一个系统帐号 test_sudo 作为实验所用:

    然后用 root 身份在 /etc/sudoers 中增加:

    表示允许 test_sudo 帐号以非 root 外的身份执行 /usr/bin/id,如果试图以 root 帐号运行 id 命令则会被拒绝:

    sudo -u 也可以通过指定 UID 的方式来代替用户,当指定的 UID 为 -1 或 4294967295(-1 的补码,其实内部是按无符号整数处理的) 时,因此可以触发漏洞,绕过上面的限制并以 root 身份执行命令:

    [test_sudo@localhost ~]

    $ sudo -u#4294967295 id uid=0(root) gid=1004(test_sudo) 组=1004(test_sudo) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

    2. 漏洞原理分析

    在官方代码仓库找到提交的修复代码:https://www.sudo.ws/repos/sudo/rev/83db8dba09e7

    从提交的代码来看,只修改了 lib/util/strtoid.c。strtoid.c 中定义的 sudo_strtoid_v1 函数负责解析参数中指定的 UID 字符串,补丁关键代码:

    llval 变量为解析后的值,不允许 llval 为 -1 和 UINT_MAX(4294967295)。

    也就是补丁只限制了取值而已,从漏洞行为来看,如果为 -1,最后得到的 UID 却是 0,为什么不能为 -1?当 UID 为 -1 的时候,发生了什么呢?继续深入分析一下。

    我们先用 strace 跟踪下系统调用看看:

    因为 strace -u 参数需要 root 身份才能使用,因此上面命令需要先切换到 root 帐号下,然后用 test_sudo 身份执行了 sudo -u#-1 id 命令。从输出的系统调用中,注意到:

    sudo 内部调用了 setresuid 来提升权限(虽然还调用了其他设置组之类的函数,但先不做分析),并且传入的参数都是 -1。

    因此,我们做一个简单的实验来调用 setresuid(-1, -1, -1) ,看看为什么执行后会是 root 身份,代码如下:

    注意,需要将编译后的二进制文件所属用户改为 root,并加上 s 位,当设置了 s 位后,其他帐号执行时就会以文件所属帐号的身份运行。

    为了方便,我直接在 root 帐号下编译,并加 s 位:

    [root@localhost tmp]

    # chmod +s a.out

    然后以 test_sudo 帐号执行 a.out:

    可见,运行后,当前身份变成了 root。

    其实 setresuid 函数只是系统调用 setresuid32 的简单封装,可以在 GLibc 的源码中看到它的实现:

    setresuid32 最后调用的是内核函数 sys_setresuid,它的实现如下:

    简单来说,内核在处理时,会调用 prepare_creds 函数创建一个新的凭证结构体,而传递给函数的 ruid、euid和suid 三个参数只有在不为 -1 的时候,才会将 ruid、euid 和 suid 赋值给新的凭证(见上面三个 if 逻辑),否则默认的 UID 就是 0。最后调用 commit_creds 使凭证生效。这就是为什么传递 -1 时,会拥有 root 权限的原因。

    我们也可以写一段 SystemTap 脚本来观察下从应用层调用 setresuid 并传递 -1 到内核中的状态:

    然后执行:

    接着运行前面我们编译的 a.out,看看 stap 捕获到的:


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 从 Masscan, Zmap 源码分析到开发实践

    2019-10-14

    作者:w7ay@知道创宇404实验室 
    日期:2019年10月12日

    Zmap和Masscan都是号称能够快速扫描互联网的扫描器,十一因为无聊,看了下它们的代码实现,发现它们能够快速扫描,原理其实很简单,就是实现两种程序,一个发送程序,一个抓包程序,让发送和接收分隔开从而实现了速度的提升。但是它们识别的准确率还是比较低的,所以就想了解下为什么准确率这么低以及应该如何改善。

    Masscan源码分析

    首先是看的Masscan的源码,在readme上有它的一些设计思想,它指引我们看main.c中的入口函数main(),以及发送函数和接收函数transmit_thread()receive_thread(),还有一些简单的原理解读。

    理论上的6分钟扫描全网

    在后面自己写扫描器的过程中,对Masscan的扫描速度产生怀疑,目前Masscan是号称6分钟扫描全网,以每秒1000万的发包速度。

    image-20191010142518478

    但是255^4/10000000/60 ≈ 7.047 ???

    之后了解到,默认模式下Masscan使用pcap发送和接收数据包,它在Windows和Mac上只有30万/秒的发包速度,而Linux可以达到150万/秒,如果安装了PF_RING DNA设备,它会提升到1000万/秒的发包速度(这些前提是硬件设备以及带宽跟得上)。

    注意,这只是按照扫描一个端口的计算。

    PF_RING DNA设备了解地址:http://www.ntop.org/products/pf_ring/

    那为什么Zmap要45分钟扫完呢?

    在Zmap的主页上说明了

    image-20191010151936899

    用PF_RING驱动,可以在5分钟扫描全网,而默认模式才是45分钟,Masscan的默认模式计算一下也是45分钟左右才扫描完,这就是宣传的差距吗 (-

    历史记录

    观察了readme的历史记录 https://github.githistory.xyz/robertdavidgraham/Masscan/blob/master/README.md

    之前构建时会提醒安装libpcap-dev,但是后面没有了,从releases上看,是将静态编译的libpcap改为了动态加载。

    C10K问题

    c10k也叫做client 10k,就是一个客户端在硬件性能足够条件下如何处理超过1w的连接请求。Masscan把它叫做C10M问题。

    Masscan的解决方法是不通过系统内核调用函数,而是直接调用相关驱动。

    主要通过下面三种方式:

    • 定制的网络驱动
      • Masscan可以直接使用PF_RING DNA的驱动程序,该驱动程序可以直接从用户模式向网络驱动程序发送数据包而不经过系统内核。
    • 内置tcp堆栈
      • 直接从tcp连接中读取响应连接,只要内存足够,就能轻松支持1000万并发的TCP连接。但这也意味着我们要手动来实现tcp协议。
    • 不使用互斥锁
      • 锁的概念是用户态的,需要经过CPU,降低了效率,Masscan使用rings来进行一些需要同步的操作。与之对比一下Zmap,很多地方都用到了锁。
        • 为什么要使用锁?
          • 一个网卡只用开启一个接收线程和一个发送线程,这两个线程是不需要共享变量的。但是如果有多个网卡,Masscan就会开启多个接收线程和多个发送线程,这时候的一些操作,如打印到终端,输出到文件就需要锁来防止冲突。
        • 多线程输出到文件
          • Masscan的做法是每个线程将内容输出到不同文件,最后再集合起来。在src/output.c中,

    随机化地址扫描

    在读取地址后,如果进行顺序扫描,伪代码如下

    但是考虑到有的网段可能对扫描进行检测从而封掉整个网段,顺序扫描效率是较低的,所以需要将地址进行随机的打乱,用算法描述就是设计一个打乱数组的算法,Masscan是设计了一个加密算法,伪代码如下

    随机种子就是i的值,这种加密算法能够建立一种一一对应的映射关系,即在[1...range]的区间内通过i来生成[1...range]内不重复的随机数。同时如果中断了扫描,只需要记住i的值就能重新启动,在分布式上也可以根据i来进行。

    无状态扫描的原理

    回顾一下tcp协议中三次握手的前两次

    1. 客户端在向服务器第一次握手时,会组建一个数据包,设置syn标志位,同时生成一个数字填充seq序号字段。
    2. 服务端收到数据包,检测到了标志位的syn标志,知道这是客户端发来的建立连接的请求包,服务端会回复一个数据包,同时设置syn和ack标志位,服务器随机生成一个数字填充到seq字段。并将客户端发送的seq数据包+1填充到ack确认号上。

    在收到syn和ack后,我们返回一个rst来结束这个连接,如下图所示

    image-20191003223330374
    image-20191003230816536

    Masscan和Zmap的扫描原理,就是利用了这一步,因为seq是我们可以自定义的,所以在发送数据包时填充一个特定的数字,而在返回包中可以获得相应的响应状态,即是无状态扫描的思路了。 接下来简单看下Masscan中发包以及接收的代码。

    发包

    main.c中,前面说的随机化地址扫描

    image-20191003232846484

    接着生成cookie并发送

    image-20191003233102015

    看名字我们知道,生成cookie的因子有源ip,源端口,目的ip,目的端口,和entropy(随机种子,Masscan初始时自动生成),siphash24是一种高效快速的哈希函数,常用于网络流量身份验证和针对散列dos攻击的防御。

    组装tcp协议template_set_target(),部分代码

    发包函数

    可以看到它是分三种模式发包的,PF_RING,WinPcap,LibPcap,如果没有装相关驱动的话,默认就是pcap发包。如果想使用PF_RING模式,只需要加入启动参数--pfring

    接收

    在接收线程看到一个关于cpu的代码

    image-20191004003419241

    大意是锁住这个线程运行的cpu,让发送线程运行在双数cpu上,接收线程运行在单数cpu上。但代码没怎么看懂

    接收原始数据包

    主要是使用了PFRING和PCAP的api来接收。后面便是一系列的接收后的处理了。在mian.c757行

    image-20191004004238243

    后面还会判断是否为源ip,判断方式不是相等,是判断某个范围。

    接着后面的处理

    Zmap源码分析

    Zmap官方有一篇paper,讲述了Zmap的原理以及一些实践。上文说到Zmap使用的发包技术和Masscan大同小异,高速模式下都是调用pf_ring的驱动进行,所以对这些就不再叙述了,主要说下其他与Masscan不同的地方,paper中对丢包问题以及扫描时间段有一些研究,简单整理下

    1. 发送多个探针:结果表明,发送8个SYN包后,响应主机数量明显趋于平稳
    2. 哪些时间更适合扫描
      1. 我们观察到一个±3.1%的命中率变化依赖于日间扫描的时间。最高反应率在美国东部时间上午7时左右,最低反应率在美国东部时间下午7时45分左右。
      2. 这些影响可能是由于整体网络拥塞和包丢失率的变化,或者由于只间断连接到网络的终端主机的总可用性的日变化模式。在不太正式的测试中,我们没有注意到任何明显的变化

    还有一点是Zmap只能扫描单个端口,看了一下代码,这个保存端口变量的作用也只是在最后接收数据包用来判断srcport用,不明白为什么还没有加上多端口的支持。

    宽带限制

    相比于Masscan用rate=10000作为限制参数,Zmap用-B 10M的方式来限制

    image-20191010154942162

    我觉得这点很好,因为不是每个使用者都能明白每个参数代表的原理。实现细节

    image-20191010155045099
    image-20191010155334018

    发包与解包

    Zmap不支持Windows,因为Zmap的发包默认用的是socket,在window下可能不支持tcp的组包(猜测)。相比之下Masscan使用的是pcap发包,在win/linux都有支持的程序。Zmap接收默认使用的是pcap。

    在构造tcp包时,附带的状态信息会填入到seq和srcport中

    image-20191010161356014

    在解包时,先判断返回dstport的数据

    image-20191012110543094

    再判断返回的ack中的数据

    image-20191012110655331

    用go写端口扫描器

    在了解完以上后,我就准备用go写一款类似的扫描器了,希望能解决丢包的问题,顺便学习go。

    在上面分析中知道了,Masscan和Zmap都使用了pcap,pfring这些组件来原生发包,值得高兴的是go官方也有原生支持这些的包 https://github.com/google/gopacket,而且完美符合我们的要求。

    image-20191012111724556

    接口没问题,在实现了基础的无状态扫描功能后,接下来就是如何处理丢包的问题。

    丢包问题

    按照tcp协议的原理,我们发送一个数据包给目标机器,端口开放时返回ack标记,关闭会返回rst标记。

    但是通过扫描一台外网的靶机,发现扫描几个端口是没问题的,但是扫描大批量的端口(1-65535),就可能造成丢包问题。而且不存在的端口不会返回任何数据。

    控制速率

    刚开始以为是速度太快了,所以先控制下每秒发送的频率。因为发送和接收都是启动了一个goroutine,目标的传入是通过一个channel传入的(go的知识点)。

    所以控制速率的伪代码类似这样

    本地状态表

    即使将速度控制到了最小,也存在丢包的问题,后经过一番测试,发现是防火墙的原因。例如常用的iptables,其中拒绝的端口不会返回信息。将端口放行后再次扫描,就能正常返回数据包了。

    此时遇到的问题是有防火墙策略的主机如何进行准确扫描,一种方法是扫描几个端口后就延时一段时间,但这不符合快速扫描的设想,所以我的想法是维护一个本地的状态表,状态表中能够动态修改每个扫描结果的状态,将那些没有返回包的目标进行重试。

    Ps:这是针对一个主机,多端口(1-65535)的扫描策略,如果是多个IP,Masscan的随机化地址扫描策略就能发挥作用了。

    设想的结构如下

    初始数据时status为0,当发送数据时,将status变更为1,同时记录发送时间time,接收数据时通过返回的标记,dstport,seq等查找到本地状态表相应的数据结构,变更status为2,同时启动一个监控程序,监控程序每隔一段时间对所有的状态进行检查,如果发现stauts为1并且当前时间-发送时间大于一定值的时候,可以判断这个ip+端口的探测包丢失了,准备重发,将retry+1,重新设置发送时间time后,将数据传入发送的channel中。

    概念验证程序

    因为只是概念验证程序,而且是自己组包发送,需要使用到本地和网关的mac地址等,这些还没有写自动化程序获取,需要手动填写。mac地址可以手动用wireshark抓包获得。

    如果你想使用该程序的话,需要修改全局变量中的这些值

    整个go语言源程序如下,单文件。

    运行结果如下

    image-20191012135527477

    但这个程序并没有解决上述说的防火墙阻断问题,设想很美好,但是在实践的过程中发现这样一个问题。比如扫描一台主机中的1000个端口,第一次扫描后由于有防火墙的策略只检测到了5个端口,剩下995个端口会进行第一次重试,但是重试中依然会遇到防火墙的问题,所以本质上并没有解决这个问题。

    Top端口

    这是Masscan源码中一份内置的Top端口表

    可以使用--top-ports = n来选择数量。

    这是在写完go扫描器后又在Masscan中发现的,可能想象到Masscan可能也考虑过这个问题,它的方法是维护一个top常用端口的排行来尽可能减少扫描端口的数量,这样可以覆盖到大多数的端口(猜测)。

    总结

    概念性程序实践失败了,所以再用go开发的意义也不大了,后面还有一个坑就是go的pcap不能跨平台编译,只能在Windows下编译windows版本,mac下编译mac版本。

    但是研究了Masscan和Zmap在tcp协议下的syn扫描模式,还是有很多收获,以及明白了它们为什么要这么做,同时对网络协议和一些更低层的细节有了更深的认识。

    这里个人总结了一些tips:

    • Masscan源码比Zmap读起来更清晰,注释也很多,基本上一看源码就能明白大致的结构了。
    • Masscan和Zmap最高速度模式都是使用的pfring这个驱动程序,理论上它两的速度是一致的,只是它们宣传口径不一样?
    • 网络宽带足够情况下,扫描单个端口准确率是最高的(通过自己编写go扫描器的实践得出)。
    • Masscan和Zmap都能利用多网卡,但是Zmap线程切换用了锁,可能会消耗部分时间。
    • 设置发包速率时不仅要考虑自己带宽,还要考虑目标服务器的承受情况(扫描多端口时)

    参考链接


    Paper

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

    作者:吴烦恼 | Categories:安全研究 | Tags:
  • 协议层的攻击——HTTP请求走私

    2019-10-11

    作者:mengchen@知道创宇404实验室
    日期:2019年10月10日

    1. 前言

    最近在学习研究BlackHat的议题,其中有一篇议题——"HTTP Desync Attacks: Smashing into the Cell Next Door"引起了我极大地兴趣,在其中,作者讲述了HTTP走私攻击这一攻击手段,并且分享了他的一些攻击案例。我之前从未听说过这一攻击方式,决定对这一攻击方式进行一个完整的学习梳理,于是就有了这一篇文章。

    当然了,作为这一攻击方式的初学者,难免会有一些错误,还请诸位斧正。

    2. 发展时间线

    最早在2005年,由Chaim Linhart,Amit Klein,Ronen Heled和Steve Orrin共同完成了一篇关于HTTP Request Smuggling这一攻击方式的报告。通过对整个RFC文档的分析以及丰富的实例,证明了这一攻击方式的危害性。

    https://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf

    在2016年的DEFCON 24 上,@regilero在他的议题——Hiding Wookiees in HTTP中对前面报告中的攻击方式进行了丰富和扩充。

    https://media.defcon.org/DEF%20CON%2024/DEF%20CON%2024%20presentations/DEF%20CON%2024%20-%20Regilero-Hiding-Wookiees-In-Http.pdf

    在2019年的BlackHat USA 2019上,PortSwigger的James Kettle在他的议题——HTTP Desync Attacks: Smashing into the Cell Next Door中针对当前的网络环境,展示了使用分块编码来进行攻击的攻击方式,扩展了攻击面,并且提出了完整的一套检测利用流程。

    https://www.blackhat.com/us-19/briefings/schedule/#http-desync-attacks-smashing-into-the-cell-next-door-15153

    3. 产生原因

    HTTP请求走私这一攻击方式很特殊,它不像其他的Web攻击方式那样比较直观,它更多的是在复杂网络环境下,不同的服务器对RFC标准实现的方式不同,程度不同。这样一来,对同一个HTTP请求,不同的服务器可能会产生不同的处理结果,这样就产生了了安全风险。

    在进行后续的学习研究前,我们先来认识一下如今使用最为广泛的HTTP 1.1的协议特性——Keep-Alive&Pipeline

    HTTP1.0之前的协议设计中,客户端每进行一次HTTP请求,就需要同服务器建立一个TCP链接。而现代的Web网站页面是由多种资源组成的,我们要获取一个网页的内容,不仅要请求HTML文档,还有JS、CSS、图片等各种各样的资源,这样如果按照之前的协议设计,就会导致HTTP服务器的负载开销增大。于是在HTTP1.1中,增加了Keep-AlivePipeline这两个特性。

    所谓Keep-Alive,就是在HTTP请求中增加一个特殊的请求头Connection: Keep-Alive,告诉服务器,接收完这次HTTP请求后,不要关闭TCP链接,后面对相同目标服务器的HTTP请求,重用这一个TCP链接,这样只需要进行一次TCP握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。当然,这个特性在HTTP1.1中是默认开启的。

    有了Keep-Alive之后,后续就有了Pipeline,在这里呢,客户端可以像流水线一样发送自己的HTTP请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。

    现如今,浏览器默认是不启用Pipeline的,但是一般的服务器都提供了对Pipleline的支持。

    为了提升用户的浏览速度,提高使用体验,减轻服务器的负担,很多网站都用上了CDN加速服务,最简单的加速服务,就是在源站的前面加上一个具有缓存功能的反向代理服务器,用户在请求某些静态资源时,直接从代理服务器中就可以获取到,不用再从源站所在服务器获取。这就有了一个很典型的拓扑结构。

    Topology

    一般来说,反向代理服务器与后端的源站服务器之间,会重用TCP链接。这也很容易理解,用户的分布范围是十分广泛,建立连接的时间也是不确定的,这样TCP链接就很难重用,而代理服务器与后端的源站服务器的IP地址是相对固定,不同用户的请求通过代理服务器与源站服务器建立链接,这两者之间的TCP链接进行重用,也就顺理成章了。

    当我们向代理服务器发送一个比较模糊的HTTP请求时,由于两者服务器的实现方式不同,可能代理服务器认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。

    3.1 CL不为0的GET请求

    其实在这里,影响到的并不仅仅是GET请求,所有不携带请求体的HTTP请求都有可能受此影响,只因为GET比较典型,我们把它作为一个例子。

    RFC2616中,没有对GET请求像POST请求那样携带请求体做出规定,在最新的RFC7231的4.3.1节中也仅仅提了一句。

    https://tools.ietf.org/html/rfc7231#section-4.3.1

    sending a payload body on a GET request might cause some existing implementations to reject the request

    假设前端代理服务器允许GET请求携带请求体,而后端服务器不允许GET请求携带请求体,它会直接忽略掉GET请求中的Content-Length头,不进行处理。这就有可能导致请求走私。

    比如我们构造请求

    前端服务器收到该请求,通过读取Content-Length,判断这是一个完整的请求,然后转发给后端服务器,而后端服务器收到后,因为它不对Content-Length进行处理,由于Pipeline的存在,它就认为这是收到了两个请求,分别是

    这就导致了请求走私。在本文的4.3.1小节有一个类似于这一攻击方式的实例,推荐结合起来看下。

    3.2 CL-CL

    RFC7230的第3.3.3节中的第四条中,规定当服务器收到的请求中包含两个Content-Length,而且两者的值不同时,需要返回400错误。

    https://tools.ietf.org/html/rfc7230#section-3.3.3

    但是总有服务器不会严格的实现该规范,假设中间的代理服务器和后端的源站服务器在收到类似的请求时,都不会返回400错误,但是中间代理服务器按照第一个Content-Length的值对请求进行处理,而后端源站服务器按照第二个Content-Length的值进行处理。

    此时恶意攻击者可以构造一个特殊的请求

    中间代理服务器获取到的数据包的长度为8,将上述整个数据包原封不动的转发给后端的源站服务器,而后端服务器获取到的数据包长度为7。当读取完前7个字符后,后端服务器认为已经读取完毕,然后生成对应的响应,发送出去。而此时的缓冲区去还剩余一个字母a,对于后端服务器来说,这个a是下一个请求的一部分,但是还没有传输完毕。此时恰巧有一个其他的正常用户对服务器进行了请求,假设请求如图所示。

    从前面我们也知道了,代理服务器与源站服务器之间一般会重用TCP连接。

    这时候正常用户的请求就拼接到了字母a的后面,当后端服务器接收完毕后,它实际处理的请求其实是

    这时候用户就会收到一个类似于aGET request method not found的报错。这样就实现了一次HTTP走私攻击,而且还对正常用户的行为造成了影响,而且后续可以扩展成类似于CSRF的攻击方式。

    但是两个Content-Length这种请求包还是太过于理想化了,一般的服务器都不会接受这种存在两个请求头的请求包。但是在RFC2616的第4.4节中,规定:如果收到同时存在Content-Length和Transfer-Encoding这两个请求头的请求包时,在处理的时候必须忽略Content-Length,这其实也就意味着请求包中同时包含这两个请求头并不算违规,服务器也不需要返回400错误。服务器在这里的实现更容易出问题。

    https://tools.ietf.org/html/rfc2616#section-4.4

    3.3 CL-TE

    所谓CL-TE,就是当收到存在两个请求头的请求包时,前端代理服务器只处理Content-Length这一请求头,而后端服务器会遵守RFC2616的规定,忽略掉Content-Length,处理Transfer-Encoding这一请求头。

    chunk传输数据格式如下,其中size的值由16进制表示。

    Lab 地址:https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te

    构造数据包

    连续发送几次请求就可以获得该响应。

    image-20191009002040605

    由于前端服务器处理Content-Length,所以这个请求对于它来说是一个完整的请求,请求体的长度为6,也就是

    当请求包经过代理服务器转发给后端服务器时,后端服务器处理Transfer-Encoding,当它读取到0\r\n\r\n时,认为已经读取到结尾了,但是剩下的字母G就被留在了缓冲区中,等待后续请求的到来。当我们重复发送请求后,发送的请求在后端服务器拼接成了类似下面这种请求。

    服务器在解析时当然会产生报错了。

    3.4 TE-CL

    所谓TE-CL,就是当收到存在两个请求头的请求包时,前端代理服务器处理Transfer-Encoding这一请求头,而后端服务器处理Content-Length请求头。

    Lab地址:https://portswigger.net/web-security/request-smuggling/lab-basic-te-cl

    构造数据包

    image-20191009095101287

    由于前端服务器处理Transfer-Encoding,当其读取到0\r\n\r\n时,认为是读取完毕了,此时这个请求对代理服务器来说是一个完整的请求,然后转发给后端服务器,后端服务器处理Content-Length请求头,当它读取完12\r\n之后,就认为这个请求已经结束了,后面的数据就认为是另一个请求了,也就是

    成功报错。

    3.5 TE-TE

    TE-TE,也很容易理解,当收到存在两个请求头的请求包时,前后端服务器都处理Transfer-Encoding请求头,这确实是实现了RFC的标准。不过前后端服务器毕竟不是同一种,这就有了一种方法,我们可以对发送的请求包中的Transfer-Encoding进行某种混淆操作,从而使其中一个服务器不处理Transfer-Encoding请求头。从某种意义上还是CL-TE或者TE-CL

    Lab地址:https://portswigger.net/web-security/request-smuggling/lab-ofuscating-te-header

    构造数据包

    image-20191009111046828

    4. HTTP走私攻击实例——CVE-2018-8004

    4.1 漏洞概述

    Apache Traffic Server(ATS)是美国阿帕奇(Apache)软件基金会的一款高效、可扩展的HTTP代理和缓存服务器。

    Apache ATS 6.0.0版本至6.2.2版本和7.0.0版本至7.1.3版本中存在安全漏洞。攻击者可利用该漏洞实施HTTP请求走私攻击或造成缓存中毒。

    在美国国家信息安全漏洞库中,我们可以找到关于该漏洞的四个补丁,接下来我们详细看一下。

    CVE-2018-8004 补丁列表

    注:虽然漏洞通告中描述该漏洞影响范围到7.1.3版本,但从github上补丁归档的版本中看,在7.1.3版本中已经修复了大部分的漏洞。

    4.2 测试环境

    4.2.1 简介

    在这里,我们以ATS 7.1.2为例,搭建一个简单的测试环境。

    环境组件介绍

    环境拓扑图

    ats-topology

    Apache Traffic Server 一般用作HTTP代理和缓存服务器,在这个测试环境中,我将其运行在了本地的Ubuntu虚拟机中,把它配置为后端服务器LAMP&LNMP的反向代理,然后修改本机HOST文件,将域名ats.mengsec.comlnmp.mengsec,com解析到这个IP,然后在ATS上配置映射,最终实现的效果就是,我们在本机访问域名ats.mengsec.com通过中间的代理服务器,获得LAMP的响应,在本机访问域名lnmp.mengsec,com,获得LNMP的响应。

    为了方便查看请求的数据包,我在LNMP和LAMP的Web目录下都放置了输出请求头的脚本。

    LNMP:

    LAMP:

    4.2.2 搭建过程

    在GIthub上下载源码编译安装ATS。

    安装依赖&常用工具。

    然后解压源码,进行编译&安装。