cf_hb 发布的文章

前端动态变化对抗Selenium类自动化工具思路探索

0x01 前言

这不是一篇安全技术文章,如果你关注业务安全,羊毛党对抗,爬虫对抗,可以慢慢观看。

在业务安全领域最大的困扰是来自各种各样的自动化工具的薅羊毛行为,羊毛党所使用的自动化武器五花八门,其中模拟更像真人的,使用比较多的是基于Selenium库实现的操作各种真实浏览器模拟的操作。Selenium库提供的webdriver,支持主流的浏览器如:chrome,firefox, ie,opera,phantomjs,safari 也支持浏览器的headless模式,更多的介绍可以看文章:https://www.cnblogs.com/zhaof/p/6953241.html

0x02 自动化工具的demo

羊毛党在薅羊毛前就会准备好自动化工具,如抢票活动的薅羊毛,他们需要自动化工具能够完成打开浏览器,打开登录网页,填充账号密码信息,点击完成登录,打开活动页面,点击抢票等一系列操作。

下面给出的一个demo是使用Selenium +Chrome 浏览器模拟的对测试网站demo. testfire.net进行自动化登录(或撞库,爆破密码)的代码。

201812051544014778183194.png

上面的代码中首先使用Selenium中的webdriver 打开本地的Chrome 浏览器,然后利用其提供的API 接口,直接打开登录地址。要完成填充账号,密码的信息,需要先找到输入位置。Selenium 提供了多种查找元素的接口,可以通过id ,name ,xpath ,css selector ,甚至通过文本来定位。当找到操作元素的位置后就可以对其进行相应的点击,输入,拖动等动作了。

0x03 GET一些知识背景

元素的定位应该是自动化测试的核心,要想操作一个对象,首先应该识别这个对象。 一个对象就是一个人一样,他会有各种的特征(属性),如比我们可以通过一个人的身份证号,姓名,或者他住在哪个街道、楼层、门牌找到这个人。

那么一个对象也有类似的属性,我们可以通过这个属性找到这对象。 webdriver 提供了一系列的对象定位方法,常用的有以下几种:

  • id
  • name
  • name
  • class name
  • link text
  • partial link text
  • tag name
  • xpath
  • css selector

我们拿百度搜索的页面来做例子,分别使用不同的定位方法下python的调用参数如下:

百度搜索输入框的input标签如下:

<input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off">

哪么使用Selenium的自动化工具实现定位元素的方式有如下:

1.通过id定位

u = dr.find_element_by_id('kw')

2.通过name定位

u = dr.find_element_by_name('wd')

3.通过class name定位

s = dr.find_element_by_class_name('s_ipt')

4.通过xpath定位

s = dr.find_element_by_xpath('//*[@id="kw"]')

5.通过css selector定位

s = dr.browser.find_element_by_css_selector('#kw')

以上5种是使用Selenium编写自动化工具定位元素时使用的最多的方式,(其他的就不做列举了)可以看出自动化工具查找元素时对于这个输入标签的id,name, class等值的依赖是十分的强的。

我们再看看浏览器上javascript提供的查找元素的接口

201812051544016163471346.png

同样是使用了id, name, class的值进行的元素查找.

0x04 思考对抗思路

这种类型的利用驱动程序调用浏览器模拟人为操作的攻击方式通过传统的js防爬,UA 黑名单等方式是比较难防护的,因为这种攻击是真实浏览器发起的操作。回想到上面,这类攻击实现是通过webdriver 驱动浏览器进行的自动化操作,它的原理是通过注入自己的js 脚本到浏览器的每一个页面,通过js 完成页面的自动化点击,输入操作。哪么第一个思路就是去检测webdriver注入的js,这是目前一些防自动化攻击的厂商产品思路。防护产品A给每一个页面注入检测js ,通过检测webdriver 注入js 时带来的函数名,变量名等,实现自动化工具识别。这个检测思路也确实有效,但是这就像是杀软对抗病毒一样,通过特征库进行查杀。一定特征函数名,特征变量名等发生了变化如攻击者重写了webdriver 的驱动程序,修改了特征,哪防护产品就毫无办法了。这也是杀软一直以来面临的困扰。。。

变化一下思路,我们知道通过js 操作页面,同样需要先查找到元素。通过document. find ElenentBy**,最常见的是通过ID ,name 来查找元素。如果让 webdriver 的js注入过程是成功的,如果动态变化了标签的id, name, class name值,那么注入的JS脚本的find 定位元素过程是失败的,同样也能起到防护自动化工具的作用。

新的思路1:标签属性动态变化,干扰js 查找元素过程

对于防护产品而言,第一阶段保留原来的检测webdriver 注入js 的防护方式。随机切换到第二阶段即放过webdriver 的js 检测,动态混淆关键标签的ID ,NAME ,class name的值

什么时候动态变化?我们可以在upstream 第一次返回内容就开始,往后我们hook 一些关键事件,当页面触发这类事件时主动变化。

新的思路2:随机插入不可见相同标签

在分析Selenium中发现它在查找元素定位时不可见标签的会对其有干扰,在Selenium的git hub有提到过,高版本的Selenium 支持查找disabled 的标签。哪么我们就可以构造ID ,name 等属性一样的但是hidden隐藏的,disabled 不可操作的标签,可以成功干扰它的定位元素过程,并且界面UI不会察觉。这种思路作用于,自动化脚本不是通过ID,NAME来做元素定位,而是通过xpath使用 css selector来做定位的情况。

举个栗子:

201812051544019146607737.png

自动化工具通过xpath语法可以定位上面的搜索位置,上面的ID, name动态方式就无法干扰了。

上面图搜索框上面的hidden的是插入的虚假标签,

未插入前:羊毛程序通过下面的结构避开id,name完成定位

#main > header > div.header-content.clearfix > div.header-search-module > div.header-search-block > input

插入隐藏标签后:羊毛程序需要修改xpath结构,才能找到正确的输入位置。

#main > header > div.header-content.clearfix > div.header-search-module > div.header-search-block > input:nth-child(2)

这里插入的标签是hidden,disabled的,他UI不可见,不会被form表单提交到Server端的。完美的干扰了羊毛程序的查找。

新的思路3:text插入不可见字符/其他字符

上面的思路几乎干扰了webdriver最常用的元素定位方式,剩下的是通过link文本的方式进行定位。Selenium支持根据标签的text值或部分值进行搜索定位元素。我们在原始的text里插入不影响页面UI明显变化的特殊字符,就可以干扰webdriver的这种定位元素的方式。

新的思路4:化被动为主动的对抗思路

这里的反攻思路主要是利用浏览器的缺陷,利用自动化工具的bug,甚至漏洞来反击。这个比较有意思,我们通过GitHub上查看Selenium,Chromedriver, geckodriver的issues,收集广大网友们在自动化测试(薅羊毛)中发现的bug,我们故意构造这样的环境,迫使羊毛程序自己崩溃。用这种方式来干扰阻挡羊毛行动。

0x05 demo一下

长篇大论说了那么多,你一定心里发毛了“show me the code...”。基于上面的多种思路现在开始demo一下,我用mitmproxy模拟反向代理工具,编写python脚本来对原站返回内容做修改,插入特定的JS代码。插入的JS代码实现上面的思路和hook一些Window的事件比如onkeypress, onclick, onchange事件,当触发这些事件时页面上发生一些动态变化,这些动态变化UI上没有明显变化,但是影响了Selenium注入的JS脚本的自动化行为过程。

Demo1­:动态变化id,name值

mitmproxy脚本编写如下,给特定的域名,登录页面注入我的们JS脚本,并放在了body最后,应该能尽量减少对原页面的影响。

201812051544017569103738.png

注入的JS脚本太长限于篇幅不贴出来了,主要实现:

通过document.getElementsByTagName遍历input标签,记录所有标签的id,name,class name值,定义change 函数,用于随机生成新的id,name等值,hook一些事件,当触发事件后调用change函数实现一次动态修改。

最后当然还需要hook 表单提交的onsubmit函数,先还原真实的id,name,class name值,然后走正常提交。

注入JS后的效果:

当页面每一次触发事件,关键标签input的id,name进行一次变化。

201812051544018018166191.png

正常人类在无感知下完成业务的正常工作。

201812051544018322300778.png

使用自动化工具进行测试如下:

首先是没有经过mitmproxy的修改注入JS的,自动化登录成功如下图。

201812051544018394910059.png

然后访问经过代理后的注入JS的地址,自动化登录失败。

201812051544018454832802.png

根据console的提示,可以看出自动化工具在工作中因触发了一些事件,id, name动态变化了,程序无法定位完成对应的数据输入。

Demo2:插入不可见的相同标签

注入JS后的效果,插入了hidden,disabled,id,name等属性相同的标签:

201812051544019562214217.png

当然不能影响正常业务啦,如下:

201812051544019625790680.png

webdriver测试如下:下面是成功干扰了Firefox的程序,它说找到的元素不可用的。。

201812051544019677488025.png

Demo3:text插入不可见字符/其他字符

Selenium 类工具支持一种叫link 定位的操作,有时候不是一个输入框也不是一个按钮,而是一个文字链接,可以通过 link进行定位。

这种情况下,自动化程序不用id, name和xpath结构,直接通过文本查找匹配进行定位。我们为了干扰,能做的就是动态修改这些关键位置的文本,让其程序无法用于定位。

比如下面的测试代码,程序自动打开网页,通过“Sign In”找到登录链接,然后打开,通过“Contact Us”找到联系商家,然后通过“online form” 找到订单页面等。。这一些列自动化过程仅通过页面的文本来查找定位。前面的思路都无法对其干扰,我们如果将页面的“Sign In” 修改为“Sign . In”这样,做一些UI上尽可能小的变化动作。同样可能成功干扰自动化程序。

201812051544020237881426.png

对Webdriver成功干扰效果如下:

201812051544020448896634.png

这里仅能做到一个思路demo的证明,真正要做好还是不容易,因为对我来说还不知道怎么找到,浏览器上UI不显示,但是text是不同的修改办法。

跪求前端大佬指教。。

Demo4:化被动为主动的对抗思路

这里思路还没有完全构思好,用JS反攻客户端的好点子还没有。这里抛2个bug,怎么很好的用bug和JS反攻,期待和大佬的交流。bug地址:https://github.com/SeleniumHQ/selenium/issues/5840https://github.com/mozilla/geckodriver/issues/1228

0x06 总结

本文的思路早也有人提出过,写完本文后才知道早在16年携程就有前辈写文《关于反爬虫,看这一篇就够了》地址:https://blog.csdn.net/u013886628/article/details/51820221,提到过了。提及的内容截图如下:

201812051544021106525820.png

可见携程的反爬虫历史很悠长哦。晚辈献丑了,路漫漫其修远兮,吾将上下而求索。

碎碎念之Afl-fuzz Docker实践

开篇

好久没有更新东西了,写一篇水文压压惊吧。这篇文章主要记录一点之前鼓捣Afl-fuzz的笔记,涉及的知识不深,看官们别见笑。作为程序猿周围的安全工程师,我们负责着产品线的测试。这有靠经验来做的黑盒测试,也有靠自己反编译安装包,鼓捣设备得到部会源码的进行灰盒测试,也开始引进Fuzz模糊测试-这就靠下面要说的Afl-fuzz来实现。因为众所周知的问题(搞安全的人在公司地位低),我们没有产品的代码级权限,我们用Afl-fuzz也就当黑盒一样的在玩,下面会解释到。开始吧。。少侠们。。。

初见Afl-fuzz

了解Afl-fuzz之前我们先了解一下什么是模糊测试,定义:模糊测试是一种通过自动或半自动的生成随机数据输入到一个程序中,并监视程序异常,以发现可能的安全漏洞的技术。Fuzzing技术是当前鉴别软件安全问题方面最强大测试技术, American Fuzzy Lop (简称:Afl-fuzz)是一个优秀的模糊测试工具。使用Afl-fuzz 在有源码条件下可以使用afl-gcc 或者 afl-clang-fast 编译源码,afl在编译的时候完成插桩,如果无源码条件下可以使用qemu模式进行插桩. 对于Fuzzing网络程序,可以使用persistent模式进行测试,我鼓捣的就是使用persistent模式进行Fuzzing。

服务器环境:Ubuntu14.04 4核心 8G内存
我的目的:我们用Afl-fuzz的目的很简单,实现快速的对多个样本同时进行Fuzzing。

关于Afl-fuzz的基础用法,有很多前辈写了,可以搜索一波自行学习,这里使用的是while (__AFL_LOOP(1000))写法即persistent模式,使用afl-clang-fast++编译c写的sender, 用于从文件读取Afl变异产生的payload,发起HTTP请求包,简单的可以参考@小葵师傅的https://www.jianshu.com/p/82c361c7d439

简单来说,使用persistent模式运行Afl-fuzz,需要准备这几个东西:

  1. seed 输入样本
  2. out 指定输出目录
  3. sender afl-clang-fast++编译的可执行程序
  4. case.txt 完整的请求包

过程就是: Afl-fuzz工具执行sender, 读取case.txt的基础请求包,使用基于seed进行变形产生的数据替换请求包中标记的位置,这样构造的新的请求包,再发给服务器。

我弄了一个shell脚本,方便我快速编译和运行Afl-fuzz对样本进行Fuzzing.脚本如下:

AFL-分布式模式尝试之shell脚本:

riversec@box:~/test/test/test_shell$ cat run_aflfuzz 
#./run_aflfuzz fuzz_cross_site_uri 5
cd $1
afl-clang-fast++ sender.c -o sender
screen -AmdS MasterFuzz_$1 bash
screen -S MasterFuzz_$1 -X screen afl-fuzz -M master -i seed/ -o out/ ./sender request.txt
for (( i=3; i<$(($2+3)); i++ ))
  do
   screen -AmdS ClusterFuzz$(($i-2))$1 bash
   screen -S ClusterFuzz$(($i-2))$1 -X screen afl-fuzz -S Cluster$(($i-2))$1 -i seed/ -o out/ ./sender request.txt
 done
screen -ls
echo "start over..."

上面的shell实现了afl-clang-fast++编译sender,用了screen管理挂起任务,使用AFL的 Master/Cluster模式进行分布式Fuzzing. 调用命令如下:

./run_aflfuzz fuzz_cross_site_uri 5
  • fuzz_cross_site_uri 是screen会话TAG,方便区分节点
  • request.txt 是burp抓的完整的HTTP请求包
  • seed 是输入,即从HTTP请包中,需要模糊测试的点,提取的值作为变异种子
  • out是输出,AFL的状态信息,输出文件保留在此
  • 数字5 是创建5个节点

虽然上面只对同一个样本进行Fuzzing, 但是实现了分布式Fuzzing,哪么是不是我只需要多执行几次run_aflfuzz命令,再对其他对样本进行测试,就能实现我想要的目的呢?

结果动手了才会发现,事情并不是那么简单。。。

遇到了什么问题?

发现问题1:CPU 核心限制了同时Fuzzing的样本数

因为平时工作时需要测试各种功能,就会有很多种类型的请求包需要进行Fuzzing,我们在同一台机器上想跑多个样本时,总会有遇到如下的提示:

201808231535036581290334.png

使用过Afl-fuzz的同学可能会很熟悉,这都是穷惹的祸啊。。4核心难道就只能跑4个样本了吗??

发现问题2:手动抓样本,制作seed,标记Fuzzing位置

目前条件下,每当我要Fuzzing一个样本,我需要重复做几件事:

  1. burp抓取HTTP包
  2. 使用$替换要Fuzzing的位置,比如tid=888,标记为tid=$
  3. 将888作为seed
  4. 使用afl-fuzz命令开启Fuzzing.

每一个样本都需要做这些操作,虽然没有几步,但是让人很不耐烦。。

一步一步实现

遇到上面的问题后,就开始思索怎么解决。首先解决第一个CPU核心数限制问题,我这里首先想到使用docker虚拟化技术来做,经过验证后发现是可行的。

下面具体说说如何实现。

1.安装docker

第一步安装docker,安装过程这里就不说了。

2.搜索images

搜索仓库里是否有前辈做好的afl的images,直接拿来使用。

docker search afl

搜出来的第一个,就可以直接拿来用,我用的就是这个。如图所示:

201808241535091650456788.png

pull下来做一些自己的定制化后,再docker commint 创建一个自己的新images.

3.创建container

创建容器就很讲究了,我们想要的是用docker来跑多个Fuzzing任务,我们不仅要让他跑起来,还得为后面查看其状态,了解任务信息做准备。Afl-fuzz运行起来后,会将状态信息实时更新在out目录下的文件里。

为了了解任务状态,宿主机就需要能够实时访问到这些记录任务状态的文件,我们就需要通过文件共享来,访问宿主机查看这些文件,所以创建命令如下:

docker run -td --privileged -v /var/docker_afl/share/case1:/case --name "fuzz_node1" -h "fuzz_node1" moflow/afl-tools /bin/bash

宿主机的/var/docker_afl/share/目录下创建case1文件,对应容器的/case目录如下截图:

201808241535093063259488.png

上图的case.txt,是一个准备好之后,创建的节点,里面包括了如下:case.txt文件:完整的HTTP包,out是输出目录,seed是输入目录,sender是用afl-clang-fast++编译的persistent模式写的发包程序,tool.py是写的辅助自动程序后面会说到。

4.准备数据

准备Afl-fuzz程序运行需要的数据,先Burp抓一请求包如下:

POST /ajax/login/backend/?b0QHe3bMd9fNm57=$$$K17GUKsYaGNxIHiFehHlOhNfyy_B9944oJJ8DW_2tsQgytxlxiHVKrGP362pXExTBoA0VwdqYDkheIat1EeiQymXPjk6ZNRPTkjyIo2W63tdF$$$ HTTP/1.1
Host: 10.10.66.132
Content-Length: 25
Accept: */*
Origin: http://10.10.66.132
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://10.10.66.132/ajax/login/
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cookie: csrftoken=fVYWMCFUG6gW4ONjm48179CsKAiamipA; send_log_path=%2Ftmp%2Flog%2Fnew%2Faccess.log.1; FSSBBIl2UgzbN7NCSRF=qDWzcpd3KP1mAw5r0gbsg06pu4TSyuYU; b0QHe3bMd9fNm57=K17GUKsYaGNxIHiFehHlOhNfyy_B9944oJJ8DW_2tsQgytxlxiHVKrGP362pXExTBoA0VwdqYDkheIat1EeiQymXPjk6ZNRPTkjyIo2W63tdF
Connection: close
username=1e&password=1212

这个请求包就是我们需要进行Fuzzing的case1,上面里我使用了$$$来标记需要Fuzz的点(和Sqlmap的引导注入相似)。标记后,其中的原始的参数:

K17GUKsYaGNxIHiFehHlOhNfyy_B9944oJJ8DW_2tsQgytxlxiHVKrGP362pXExTBoA0VwdqYDkheIat1EeiQymXPjk6ZNRPTkjyIo2W63tdF

应当作为seed做样本变异使用,Afl-fuzz工具根据其内部的变异算法产生新的值,然后替换这个,进行发包测试。编写一个辅助脚本tool.py帮助完成,提取标记的值,保存seed的工作,脚本实现如下:

import re
import sys
import os

def alter(inf, ouf):
    with open(inf, "r") as f1, open("%s.bak" % inf, "w") as f2:
        for line in f1:
            flg = re.findall('\$\$\$.+\$\$\$',line)
            if len(flg)>0 and len(flg[0])>1:
                flag = flg[0]
                print "[*] Found:\n\t", flag
                print "[*] seed: ", os.getcwd() + os.sep + "seed"
                with open(ouf, "w") as of:
                    of.write(flag.replace("$$$", ""))
                f2.write(line.replace(flg[0],'$'))
            else:
                f2.write(line)
    os.rename("%s.bak" % inf, inf)
print "[*] run checking...."
alter(sys.argv[1], sys.argv[2])
print "[*] run over...."

上面准备好后,编写sender.c,这个程序实现几个操作 1. 加载case.txt内容 2. 从输入流读取数据替换$case.txt标记的位置即:将Afl产生的变异数据结合成新的请求包,然后使用socket发送给Server.

部分代码如下:

while (__AFL_LOOP(1000)) { 
        memset(copy_file, 0, sizeof(copy_file));
               memcpy(copy_file,request.data(),numread);
                per_req.assign( copy_file );
            memset(target, 0, sizeof(target));
        read( STDIN_FILENO, target, 10240 );
        TrimSpace(target);
        per_req.replace(per_req.find("$"),1,target); 

               // printf( "%s\r\n", per_req.data() );
                
        sockfd                = socket( AF_INET, SOCK_STREAM, 0 );
        dest_addr.sin_family        = AF_INET;
        dest_addr.sin_port        = htons( destport );
        dest_addr.sin_addr.s_addr    = inet_addr( "10.10.66.138" );
        memset( &dest_addr.sin_zero, 0, 8 );


        connect( sockfd, (struct sockaddr *) &dest_addr, sizeof(struct sockaddr) );
        send( sockfd, per_req.data(), strlen( per_req.data() ), 0 );
        //recv( sockfd, buffer, 1024, 0 );
        //printf( "%s", buffer );
        close( sockfd );
    }

地址10.10.66.138就是被Fuzz的服务器地址。

5.初始化Afl-fuzz环境

上面的准备工作做好后,我们现在就可以在宿主机上完成容器里的Afl-fuzz程序运行的准备了。

  • 拷贝case,sender到,tool.py容器里
cp /var/docker_afl/cases/request.txt /var/docker_afl/share/node1/case.txt
cp /var/docker_afl/share/sender.c /var/docker_afl/share/node1/sender.c
cp /var/docker_afl/share/tool.py /var/docker_afl/share/node1/tool.py

这里的node1文件夹对应容器node1的/case目录。将必要的文件拷贝进去。

手动启用节点:(首次默认启用)

docker start fuzz_node1
  • 处理case为sender能处理的格式

在docker里执行命令完成创建环境,执行tool.py辅助脚本,完成提取$$$标记的值做为seed输入值. 创建seed目录

docker exec -it fuzz_node2 bash -c 'python /case/tool.py /case/case.txt /case/1'
docker exec -it fuzz_node2 bash -c 'mkdir /case/seed'
docker exec -it fuzz_node2 bash -c 'mv /case/1 /case/seed/1'

6.开始Fuzzing

现在准备好了环境后我们可以进入到容器开始跑Fuzzing了,有两种方式运行,第一种是进入到容器里,另外一种就是通过bash -c执行命令,让其内部运行起来。如:

docker exec -it $1 bash -c 'afl-fuzz -M master -i /case/seed/ -o /case/out/ /case/sender /case/case.txt'

实践中发现Afl-fuzz的需要保持在终端打开下,上面的方式都不是很妥当,这还得借助screen来实现。实践证明是可行的,命令如下:

screen -AmdS node1 bash
screen -S node1 -X screen docker exec -it fuzz_node1 bash -c 'afl-fuzz -M master -i /case/seed/ -o /case/out/ /case/sender /case/case.txt'

这两条命令执行以后,背后的Afl-fuzz就开始在工作了,可以使用screen -r node1切换进去,就可以看到亲切的Afl-fuzz的界面了。

201808241535098560494608.png

7.写一个shell吧

偷个懒,写一个shell吧,取名就叫:create.sh,内容如下:

docker run -td --privileged -v /var/docker_afl/share/$1:/case --name "$1" -h "$1" komi/afl-fuzz-v2.0 /bin/bash

cp /var/docker_afl/cases/request.txt /var/docker_afl/share/$1/case.txt
cp /var/docker_afl/share/sender.c /var/docker_afl/share/$1/sender.c
cp /var/docker_afl/share/tool.py /var/docker_afl/share/$1/tool.py

docker exec -it $1 bash -c 'python /case/tool.py /case/case.txt /case/1'
docker exec -it $1 bash -c 'mkdir /case/seed'
docker exec -it $1 bash -c 'mv /case/1 /case/seed/1'
docker exec -it $1 bash -c 'afl-clang-fast++ /case/sender.c -o /case/sender'

screen -AmdS $1 bash

效果如下:

201808241535098509748627.png

到这里,一个请求包的FUZZ工作就已经开始了,这是利用AFL生成大量的样本,发起网络请求的黑盒的FUZZ。

8.查看状态

如何查看FUZZ任务状态?文件夹/var/docker_afl/share下的node*, 表示一个独立的Fuzz任务。比如下面存在node0、node1、node2、node3、node9任务。

201808241535101012719458.png

AFL的FUZZ任务状态可以通过查看文件而知,

比如:

我们想要查看刚创建的node1任务的进度信息,需要切换到/var/docker_afl/share/node1/out/master路径,打开fuzzer_stats文件如下:

我们关注测试的速度,1834.11次/s,这表示基于标记的

201808241535101044432478.png

其他:

  1. 查看当前任务的seed

    cat /var/docker_afl/share/node1/seed/1

  2. 查看当前任务的请求case

    cat /var/docker_afl/share/node1/case.txt

  3. 查看AFL运行信息

    screen -r node1

201808241535101415242588.png

  1. 查看138的网络请求

命令:sudo tcpdump -s 1024 -l -A -n -i eth0 src 10.10.66.131

可以确认AFL正在工作...

201808241535101448357785.png

后记

暂无

参考

CVE-2018-1261: Unsafe Unzip with spring-integration-zip分析复现

开篇

日前,Spring的一个zip组件被爆了一个严重的漏洞,漏洞编号和标题为CVE-2018-1261 Unsafe Unzip with spring-integration-zip。根据描述在spring-integration-zip的1.0.1版本之前在对bzip2, tar, xz, war, cpio, 7z类型的压缩文件进行在解压的时候,如果压缩文件是恶意构造的不可信文件,可导致向任意目录写入文件。现在我们来复现和分析一下该漏洞的发生细节。

复现漏洞

为了复现漏洞,我们可以到Spring的GitHub下载存在漏洞版本的源码文件,地址:https://github.com/spring-projects/spring-integration-extensions/releases下载1.0.1之前的版本,然后在本地解压。

然后到spring-integration-zip目录下,使用命令:

./gradlew idea

生成idea的项目文件,执行完成后,即可用IDEA打开spring-integration-zip.ipr文件如下:

201805151526390164908264.png

根据描述,提到了

This specifically applies to the unzip transformer.

我们可以通过修改src/test/java/org.springframework.integration.zip/transformer文件下面的测试文件进行漏洞的复现和分析,

这个目录下面有这些文件:

201805151526390660203311.png

这里自带了2个测试类,分别是测试压缩和解压相关的。从漏洞描述里,我们大概可以猜到是在解压的压缩包文件里有文件名为:../../../../hack.txt这样的文件,在解压释放时将文件名直接和文件路径进行拼接,然后创建文件,因 ../../ 跳跃到了其他目录,文件被释放到了预期意外的目录下面从而导致了漏洞发生。

于是我们需要制作一个这样的“特殊”文件,这里可以通过ZipTransformerTests文件里的函数,加以修改来实现,修改测试代码如下:

201805151526391519652813.png

我们通过unzip命令看看压缩包文件的结构如下图:

201805151526391664308552.png

做好了压缩包后,下面是参考给出的测试方法,修改的解压代码截图如下:

201805151526393096305118.png

现在我们可以使用Junit运行这个测试函数,观察tmp目录,会生成txt文件,

201805151526393364145629.png

我们来调试看看,下断点跟进处理doZipTransform的实现代码里,如下图:

201805151526395851195117.png

这里的payload是MessageBuilder类的withPayload函数的参数,是一个输入流,读取加载的是我们给的zip文件

201805151526396199825873.png

下面是实现的是使用ZipUtil类遍历输入流里每一个Entry利用回调函数ZipEntryCallback处理它,

重点看处理的逻辑:

201805151526396871306660.png

继续跟进:

201805151526396973476088.png

文件成功释放到指定目录,如下截图:

201805151526397166529379.png

到此漏洞已经被成功利用,产生漏洞的原因是对压缩文件里的文件名没有任何过滤,直接进行文件路径+文件名的拼接,创建了新文件。

我们看看官方给的修复是怎样的,补丁地址,部分截图如下:

201805151526397427442105.png

首先删掉了直接拼接的代码,加入了一个checkPath函数,该函数代码如下:

201805151526397607951610.png

我们更新一下这块修复代码然后再次进行测试如下图:

201805151526398014190310.png

这里已经测试失败了,因为checkPath函数对destinationFile进行了判断,即判断了要写入的文件绝对路径值destinationFile.getCanonicalPath()函数是否包含了指定的释放目录,在这个下面

201805151526398279776249.png

这里很明显不满足,抛出异常程序终止了。

201805151526398450511120.png

想法很皮,实践测试中,发现似乎也不会出现想的那样,绕过了进行文件写入操作,报错如下:

201805151526399941396853.png

测试中发现更新了最新的补丁的现在(CVE-2018-1263补丁后),虽然不能任意目录跳了,但是可以在设置的workDirectory下面跳,最上层跳不出这个限制。

比如,制作压缩包:

201805161526401438177027.png

解压代码:

201805161526401424207075.png

解压后:

201805161526401517763498.png

这里举例说这个情况,是告诉大家配置不当会存在一定风险的,在配置workDirectory时尽量最小化配置,别配置成/var/ home/这一类的路径了,因为这些下面有一些地方被任意写入了文件是很可怕的。需要注意!!!

参考来源

CVE-2018-1259: XXE with Spring Data’s XMLBeam integration分析复现

前提

最近Spring几连发的漏洞里有一个漏洞是CVE-2018-1259,地址是https://pivotal.io/security/cve-2018-1259,根据我对描述的理解,这个锅不应该给Spring接啊,描述有一段话:

Spring Data Commons, versions prior to 1.13 to 1.13.11 and 2.0 to 2.0.6 used in combination with XMLBeam 1.4.14 or earlier versions contain a property binder vulnerability caused by improper restriction of XML external entity references as underlying library XMLBeam does not restrict external reference expansion.

描述中说Spring 的公用数据组件的1.13 到 1.13.11版本和2.0到2.0.6版本使用了XMLBeam 1.4.14 或更早的版本,它存在一个外部实体应用即XXE漏洞。问题出在XMLBeam身上,修复方式就是升级XMLBeam到1.4.15以上,这个锅Spring不应该背吧。

复现XMLBeam的XXE漏洞和分析

这个CVE应该给XMLBeam的,但是它的下载地址是https://xmlbeam.org/download.html,我们在GitHub找了一个使用XMLBeam的demo来进行漏洞复现和分析。

Demo地址:

https://github.com/olivergierke/spring-examples/tree/master/scratchpad/src

下载然后导入IDEA:

201805131526218553238503.png

这个Demo代码有点小情况需要处理才能很好的跑起来,导入IDEA后,用Maven开始构建。在site使用插件运行时,它使用的是Jetty组件运行,这个情况就是怎么访问uri地址/customers 都是404,一直很纳闷。在折腾中,我发现用junit测试是OK的,能复现和调试漏洞。后来同事提到用Spring-boot运行就很OK。于是这里记录一下怎么改用Spring-Boot运行(现学现用)。

201805131526218961914670.png

在上面的那个位置,添加SpringBoot启动代码,现在我们就可以右键直接run了。

201805131526219085500949.png

现在我们可以简单阅读demo代码,理解如何测试,找到XmlBeamExample.java文件如下:

201805131526219173589965.png

看看pom.xml确认一下XMLBeam的版本,用的是老版本1.4.6,目前最新的版本是1.4.14.

201805131526219445699743.png

环境准备好了,我们开始下断点,和构造测试的POST请求,构造POC如下:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<user>
    <firstname>&xxe;</firstname>
    <lastname></lastname>
</user>

根据代码,我们需要构造POST请求,构造一组参数,XMLBeam会根据参数map对应进行自动解析绑定。

先复现一下效果:

201805131526220995310268.png

这个漏洞的根本原因是由于XMLBeam的用法不当,我们在XMLBeamd 库里找到创建XML解析实体对象的地方,如下截图:

201805151526355342895156.png

上面的是1.4.6版本,从官方下载最新的1.4.14版本的jar包源码看看,同样存在问题的,如下截图:

201805151526355503355930.png

官方目前没有挂出更新后的版本,在maven仓库里我们可以搜到,已经有里修复版本1.4.15,地址:http://mvnrepository.com/artifact/org.xmlbeam/xmlprojector 下载最新的版本后,再查看此处配置如下:

201805151526355686183890.png

一些配置项如下:

201805151526355798781702.png

从更新的代码来看,已经使用了开发语言提供的禁用外部实体的方法修复了这个XXE漏洞。

Spring官方的修复方式也是更新了这个库到1.4.15版本,

https://jira.spring.io/browse/DATACMNS-1311?jql=project%20%3D%20DATACMNS%20AND%20fixVersion%20%3D%20%221.13.12%20(Ingalls%20SR12)%22

收获

  1. 关于如何安全的使用和解析XML文本,可以看看下面给的参考文
  2. 知道了Spring Boot的这种启动方式,Jetty的坑,更加发现了Maven+IDEA配合的强大

参考

[撞库测试] Selenium+验证码打码时的特殊情况-【遇到滚动条】

题外话

测试的目标网站如果登录接口有验证码+浏览器环境检测的时候,有时候脚本小子就望而却步了,比如我。因为正面对抗JS的环境检测和验证码是有难度的,这个时候我们可以借助Selenium + 打码平台来搞一搞。这里只做笔记记录,不做具体细节描述,如有兴趣可以私下交流。

测试目标

我们的假定目标如下,某贷网站的登录入口:

201805101525941070537294.png

关键点

  1. 需要自动填充账号、密码
  2. 需要将验证码进行截图,然后接入打码平台SDK
  3. 这里暂时不管短信验证码。

一般情况

使用Selenium进行自动化登录的基本操作是会的,结合打码平台的SDK的操作也是基本的,有时候会遇到验证码是特殊url,页面关联性很强的时候,想要打码,必须使用通过截图打码来完成登录。

关于selenium+验证码截图网上搜一搜有很多,比如你会搜到下面的:

201805101525942436360685.png

上面的这种在windosw上确实OK的

不一般的情况

上面的情况适合一部分Window用户,Mac电脑上就不一样了,多次的实践结果证明Mac上的对验证码截图部分代码应该是下面这样写的:

201805101525942650541222.png

(曾经去B站大佬面前演示过B站可被撞库)

特殊情况

今天遇到了不一样的情况了,这个情况就是开篇截图里的情况。当登录页面有滚动条的时候,上面的2种做法都行不通了。回顾截图的代码,思路是,先整体当前网页全屏截图,然后通过Selenium查找到验证码图片元素,拿到该图片元素的长-宽,以及在页面的location相对位置,然后通过计算得到截图的坐标,通过4个坐标点,进行截图得到了我们想要的验证码图片。

这里的新情况是,页面出现了滚动条,如下图:

201805101525943188398931.png

在有滚动条的时候,验证码图片的相对位置计算方式就不一样了,滚动条向下滚动了,验证码图相对于网页的左上角是更近了一些距离,这个距离就是滚动条的滚动距离。

所以4个点坐标的正确计算方式记录一下应该如下:

201805101525943599804798.png

上面的思路是:

  1. 获取当前滚动条滚动距离。
  2. 创建一个标签,记录该值,然后selenium找到这个标签,拿到这个值。
  3. 验证码元素的相对位置y值需要剪掉滚动的距离。

这样就能拿到验证码图片了

201805101525944044345501.png

剩下的工作就是SDK打码,输入验证码,进行测试了,这就不多说了。。

总结

确认过眼神,就是这么整。