cf_hb 发布的文章

碎碎念之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打码,输入验证码,进行测试了,这就不多说了。。

总结

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

谈谈Selenium Server的安全问题 - 未完

0x01 开篇

不知道大家在平日工作中有没有遇到过一些端口,使用浏览器打开是下面这样子的:

201803041520131466539800.png

上图中我找了几个在不同端口下的例子。

0x02 Selenium-开源的自动化测试利器

本篇主要的主角-Selenium究竟是什么呢?有过QA经验或安全自动化测试经验的朋友应该知道,以下文字来自百度百科:Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本。

官网地址:https://www.seleniumhq.org/

Github地址:https://github.com/SeleniumHQ/selenium/wiki/Grid2

Selenium支持本地和远程浏览器的自动化测试,在远程调用浏览器时需要在远程服务器上启动一个SeleniumServer,它会负责远程浏览器的启用和你的自动化脚本的执行。

官方给出的启用该SeleniumServer的命令:

java -jar selenium-server-standalone-<version>.jar -role node  -hub http://localhost:4444/grid/register

那么脚本远程调用可以如下:

//第一个参数:表示服务器的地址。第二个参数:表示预期的执行对象,其他的浏览器都可以以此类推
WebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub/"), DesiredCapabilities.chrome());

路径/wd/hub/我是怎么知道的?

  1. 点开console,自动跳转知道的。
  2. 百度selenium remote 知道的
  3. 查看源码发现是默认的- 源码

通过阅读源码能发现默认的端口为4444,如下图所示:

201803051520263000170174.png

下面是一个Python写的远程调用的Demo:

201803041520134142491036.png

0x03 Selenium-server分析

为了方便我们分析,我在Mac下启用一个Sever, 服务端就是一个独立端JAR文件,下载地址:点击下载

可以直接这样启用:

201803041520137267231202.png

浏览器访问,打开console看看,如下图:

201803041520137304734235.png

201803041520139782900165.gif

Selenium Server 给每一个远程调用浏览器进行自动化任务分配一个Session会话,该控制台可以新创建会话,可以在页面上给每一个会话下发自动化脚本到每一个会话对应到浏览器上执行。

观察加载Console页面时,加载了一个叫client.js的文件,从这个文件中可以找到一些有用的调用接口,比如我的地址:http://127.0.0.1:4444/wd/hub/ ,当前的SessionId为,179220de83fee4d6090502b003b692a0 简单整理可以GET访问的URL地址如下:

GET 方式 - BaseUrl = http://127.0.0.1:4444/wd/hub
URL ==> 对应函数
/sessions ==> getSessions 获取当前所有打开浏览器的Session信息
/status ==> getStatus 获取当前Server状态 
/session/179220de83fee4d6090502b003b692a0/url ==> getCurrentUrl  获取当前浏览器打开的URL
/session/179220de83fee4d6090502b003b692a0/alert_text ==> getAlertText  获取弹窗内容
/session/179220de83fee4d6090502b003b692a0/source ==> getPageSource  获取网页源码
/session/179220de83fee4d6090502b003b692a0/screenshot ==> screenshot   网页快照,返回Base64图片
/session/179220de83fee4d6090502b003b692a0/cookie ==> getCookies  获取当前页面Cookie

上面的只是举例说明,完整的接口定义可以去GitHub上看WIKI说明,地址:https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol

现在我们知道了Selenium Server给我们提供了很多API接口以供我们使用来完成我们对远程浏览器对控制操作,下面看看我们就一起研究下能怎么玩?

0x04 构想和实施你的玩法

问题:

在敌人后方,你拿到了一个完全可控的浏览器后,你能做些什么?

针对这种,下面是暂时想到的玩法。

实施对敌方远程浏览器自动化作业的监控(偷视)

利用上面学习的知识,我们通过接口:

http://127.0.0.1:4444/wd/hub/sessions

发现存在着有效的Session正在作业如图:

201803041520146295646570.png

那么我们可以通过自己编写脚本,调用API接口,实时获取该浏览器的信息。如:

比如下面展示的,某一个浏览器正在进行重置密码的-自动化操作,我们可以通过API接口实时拿到该浏览器当前的URL地址。(很关键拿到这个URL就可以为所欲为了。。。)

201803041520146861917279.png

201803041520147053363582.png

当然这个返回的是页面截图的Base64,转化一下如下:

201803041520147160885551.png

如果只是简单的URL监控,思路利用BurpSuite就可以实现,这里我实验了一下,如图:

201803041520147482205223.png

利用这类思路,我们可以自己编写脚本利用其提供的API接口,对网络空间里的这类Selenium Server的行为进行24h的实时监控、记录其xx行为。

以此为跳板,对内网实施攻击

利用Selenium Server对特点,创建或者可控一个正在作业的远程浏览器,向内网发起攻击。

说到利用跳板发起内网攻击,可能最开始想到的是被大家玩出花的SSRF了。相比一般的SSRF漏洞,这里我们手里拿到的攻击筹码是远超过一般的SSRF漏洞的。敌方后防一个完全可控的浏览器,能够下发任意的JS代码,控制浏览器访问任意的URL这简直不能再爽!--- 还在实践ing

file协议任意文件读取

我们知道浏览器支持使用file协议读取本地系统的文件,哪么我们可以利用控制的远程浏览器利用file协议读取系统敏感文件,甚至是重要的shadow等口令文件

在测试实践中,我找了几台靶机尝试读取系统/etc/passwd文件,发现甚至有的可以读取shadow文件。准备测试脚本很简单,几行代码比如:

def test_readfile(driver_url = '', filename=''):
    driver = webdriver.Remote(command_executor='http://' + driver_url + '/wd/hub',
                              desired_capabilities=DesiredCapabilities.CHROME)
    driver.get("file://%s" % filename)
    print driver.page_source

    driver.quit()

然后结果就是这样的:

201803041520169754704399.png

甚至读取到了shadow

201803041520172210136585.png

当然最简单的方式是直接在Console里直接操作,步骤:Create a New Session -> chrome or firefox -> Load Script ->添入 file:///etc/passwd 然后OK -> 然后 Take a ScreenShot你就能看到了。下面是测试的截图

201803041520169348222300.png

看到这里,是不是震惊了?赶紧回去问问你们的研发,你们的QA,有没有用到它,小心菊花不保!!

远程挖矿

近两年来很流行的浏览器挖矿,从去年6月后吧,利用浏览器进行虚拟货币挖矿的事件越来越多来,网站代码中暗藏JS挖矿机脚本

挖矿脚本上GitHub一搜索就能找到很多,Github搜索地址 这里就不做过多测试了,不玩这个!

0x05 网络空间调查

使用Shodan, Censys, Zoomeye, FOFA 等一些知名的网络搜索引擎进行关键字搜索,看看网络空间里有多少Selenium Server以及分布情况。

  • Zoomeye

搜索地址:搜索链接

搜索结果如下图:

201803051520229306291405.png

搜索发现在Zoomeye引擎中记录,在整个网络空间中存在有大约有 1.5万左右的Selenium Server运行着。

  • FOFA引擎

搜索地址:搜索链接

使用FOFA引擎,匿名用户 normal模式进行搜索,获得了1.3k左右的记录

搜索结果:

201803051520230301290760.png

  • Shodan引擎

搜索地址:搜索链接

在SHODAN引擎中关键字搜索,仅44条记录。

搜索结果:

201803051520230575847986.png

SHODAN引擎主要采集基础组件端口信息,采集网页信息较少,所以用网页关键字:SeleniumHQ 搜索的结果较少,但是我们如果搜索Selenium Server 所使用的组件 Jetty,就会发现搜索结果比较多了,如下图所示。这些结果中一定存在一定数量的Selenium Server运行着的。

搜索地址:搜索链接

搜索结果:

201803051520230961203412.png

0x06 进一步研究方向

能想到的可行的研究方向:

  • 网络空间的基于Selenium的自动化攻击监控

从网络空间搜索引擎里采集Selenium Server服务端IP,实时采集并记录其远程浏览器的自动化行为。

  • 主动探测网络空间里部署Selenium Server服务器分布

使用探测工具主动探测存在于网络空间的Selenium Server分布情况,记录和观测这些探测得到的IP,这些IP都有自动化攻击,薅羊毛的潜在可能。

个人博客:http://www.coffeehb.cn