0x01漏洞描述
使用putty的pscp组件可以实现Windows和Linux服务器间远程拷贝文件。近期(3月7日)爆出pscp中存在一个缓冲区溢出漏洞,当从服务器端拷贝文件时,pscp客户端的sscanf 函数未对远程服务器返回的SCP-SINK文件的大小进行检查,在接收数据时产生溢出,从而引发一个标准的栈溢出漏洞。
漏洞公开后,putty官网迅速修复了这个漏洞,修复后的版本为putty0.67。GitHub上也已经出现对应的POC文件:
因此建议putty用户尽快升级至最新版本。
本文将在该POC的基础上,尝试重现该漏洞的场景并分析和利用这个漏洞。
0x02环境搭建
Ø Client OS:WinXP sp3(这里为了便于调试采用xp系统)
Putty pscp:version 0.62
Server:Kali-Linux-1.1.0-amd64
Poc:https://github.com/tintinweb/pub/tree/master/pocs/cve-2016-2563
Ø 服务器端python版本2.7,同时需要安装python ssh库paramiko
命令:pip install paramiko
在我测试的Kali上python默认安装了paramiko,但不是最新版的,因此需要升级至最新版。
命令:git clone https://github.com/paramiko/paramiko.git
进入paramiko目录安装:python setup.py install
测试paramiko是否安装成功,如果安装成功则如下图所示。
Ø 确保poc.py和test_rsa.key在同一个目录下,因为poc.py需要读取该key文件。
随后运行poc.py,关闭SSH服务:service ssh stop(必须关闭默认的SSH服务)
开启恶意的SSH连接
客户端运行pscp,执行下载命令:
输入密码后,pscp崩溃,恶意数据写入成功:
至此,漏洞触发成功!!!
0x03漏洞分析
程序崩溃后,附加Windbg查看崩溃现场。可以事先通过命令Windbg -I将Windbg设置成默认的调试器,这样程序crash后Windbg就会立刻捕捉到该异常:
可以看到POC发送的大量恶意数据(“AAAA…”)在客户端造成了缓冲区溢出,程序在返回时EIP被劫持了。
从崩溃点开始回溯堆栈,查看在程序崩溃前调用了哪些函数:
看到若干个疑似返回地址值,从溢出点可以看出肯定是在附近的几个函数造成的溢出,所以挨个尝试下,反汇编0x00406ff7处的指令:
带参数启动PSCP:-scp root@192.168.99.175:/etc/passwd .
在上述几个call处下断点:
可以看出执行完地址0x00406fd6处的函数时,发生了溢出,因此可以断定就是该函数造成的溢出。
通过漏洞的描述我们知道是由于pscp客户端的sscanf 函数造成的溢出,那么该地址处的函数应该就是sscanf了。IDA查看下该地址处的代码:
可以看到地址0x00406fd6处为sscanf函数,sscanf函数的原型为
int sscanf(const char *buffer,const char *format,[argument ]…)
将buffer内的值按照format的格式依次拷贝至[argument]…,因此如果不对拷贝的size进行限定的话,理论上是可以造成后续的[argument]溢出的,尤其是当后续的操作是函数返回的话,那么就可以造成一个栈溢出漏洞,可以看一下伪代码:
可以看到存在两次调用sscanf函数,这是由于在POC中存在两次发送数据:
rep_time = "T1444608444 0 1444608444 0\n"
rep_perm_size = "C755 %s \n"%('A'*200)
LOG.info("send (time): %s"%repr(rep_time))
channel.send(rep_time)
LOG.info("send (perm): %s"%repr(rep_perm_size))
channel.send(rep_perm_size)
调试发现:这两次数据并不是在同一处sscanf中接收的,第一次发送”T1444608444 0 1444608444 0”是调用地址0x00406F60处的sscanf,第二次调用发送C755 AAAA…,产生溢出。
现在屏蔽POC中第一次数据发送,看看是否还能造成溢出:
可以看到即使只发送一次数据依旧很稳定的造成了栈溢出。
由于这两次接收数据的sscanf都未检查数据的size,而拷贝的两个地址 &v47, &v63均为堆栈地址,因此可以造成堆栈溢出。
补丁对比:
补丁前接收数据sscanf函数:
sscanf(*(a1 + 4), "%lo %s %n", a1 + 12, &v47, &v63)
补丁后接收数据sscanf函数:
sscanf(*(a1 + 4), "%lo %39s %n", a1 + 12, &v56, &v71)
可以看到补丁后限定size大小为39。这样多余的字符都会被截断至39,避免溢出。有兴趣可以调试下。
最后想做一点说明:笔者调试了不同版本的pscp时发现,并不是版本<=0.66的pscp都能触发该溢出漏洞,例如pscp0.6.0.0不能触发该漏洞:
反汇编后发现该版本在接收数据时不存在sscanf函数的调用,因此不会触发漏洞。但是为了安全起见,还是建议大家升级到最新版本。
0x04漏洞利用
栈溢出漏洞最简单的利用方式就是将函数的返回地址重写成指定的代码地址,因此需要精确计算溢出字符串的长度,如果不想多事的话,可以通过二分法来慢慢测试返回地址的位置。
这里我们通过调试来确定返回地址的位置,溢出后堆栈中数据:
可以看到是从堆栈0x00127DFC处开始溢出的。
再看看程序返回时的地址:
返回地址为0x00127E50。
因此,在返回前填充的字符大小为:0x00127E50-0x00127DFC=0x54=084
所以在第84个A的位置为返回地址,插入一条jmp esp指令,后面跟上shellcode,即可完成在XP下的利用。
修改后的POC为:
运行POC和PSCP:
可以看到漏洞成功利用。