2019*CTF之Web部分题解

栏目: PHP · 发布时间: 5年前

内容简介:这道题目比较有意思,我们可以看到一个变量置换表。通过编写程序进行自动替换,可生成美化后的代码。替换脚本如下:

这道题目比较有意思, Web+Pwn ,用 PHP 写了一个模拟数据进出栈的过程。程序源码经过 enphp 加密,当中有很多字符乱码,而且许多变量名也经过混淆。这里可以直接用 var_export 导出全局变量,或者直接用IDE动态调试。

2019*CTF之Web部分题解

我们可以看到一个变量置换表。通过编写程序进行自动替换,可生成美化后的代码。替换脚本如下:

<?php
include 'index.php';	// index.php为被加密的文件

function replaceString($match){
    global $O;
    $index = -1;
    if(false !== strpos($match[1], 'x')){
        $index = hexdec($match[1]);
    }
    else{
        $index = $match[1];
    }
    if( function_exists($O[$index]) || class_exists($O[$index]) || defined($O[$index]) ){
        return $O[$index];
    }
    else{
        return "'".addslashes($O[$index])."'";
    }

}

$encrypto_string = file_get_contents('index.php');
$encrypto_string = preg_replace_callback('/\$GLOBALS[\[\{]O0[\]\}][\[\{](\w{1,15})[\]\}]/','replaceString',$encrypto_string);
$decrypto_string = preg_replace_callback('/\$[O0]*[\[\{](\w{2,15})[\]\}]/','replaceString',$encrypto_string);
file_put_contents('decrypted.php', $decrypto_string);
?>

然后手动删掉乱码字符,最后生成的代码如下:

<?php
error_reporting(E_ALL^E_NOTICE);
define('O0', 'O');
require_once 'sandbox.php';

$seed = time();
srand($seed);
define(INS_OFFSET,rand(0x0000,0xffff));

$regs = array(
    'eax'=>0x0,
    'ebp'=>0x0,
    'esp'=>0x0,
    'eip'=>0x0,
);

function aslr(&$O00,$O0O){
    $O00 = $O00 + 0x60000000 + INS_OFFSET + 0x001 ;
}
$func_ = array_flip($func);
array_walk($func_,aslr);
$plt = array_flip($func_);


function handle_data($OOO){
    $OO0O=&$GLOBALS{O0};
    $O000 = strlen($OOO);
    $O00O = $O000/0x000004+(0x001*($O000%0x000004));
    $O0O0 = str_split($OOO,0x000004);
    $O0O0[$O00O-0x001] = str_pad($O0O0[$O00O-0x001],0x000004,'\0');

    foreach ($O0O0  as  $O0OO=>&$OO00){
        $OO00 = strrev(bin2hex($OO00));
    }
    return $O0O0;
}

function gen_canary(){
    $O0O00=&$GLOBALS{O0};
    $OOOO = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789';
    $O0000 = $OOOO[rand(0,strlen($OOOO)-0x001)];
    $O000O = $OOOO[rand(0,strlen($OOOO)-0x001)];
    $O00O0 = $OOOO[rand(0,strlen($OOOO)-0x001)];
    $O00OO = '\0';
    return handle_data($O0000.$O000O.$O00O0.$O00OO)[0];
}
$canary = gen_canary();
$canarycheck = $canary;

function check_canary(){
    global $canary;
    global $canarycheck;
    if($canary != $canarycheck){
        die('emmmmmm...Don\'t attack me!');
    }
}

Class O0OO0{
    private  $ebp,$stack,$esp;

    public  function __construct($O0OOO,$OO000) {
        $OO00O=&$GLOBALS{O0};
        $this->stack = array();
        global $regs;
        $this->ebp = &$regs['ebp'];
        $this->esp = &$regs['esp'];
        $this->ebp = 0xfffe0000 + rand(0x0000,0xffff);

        global $canary;
        $this->stack[$this->ebp - 0x4] = &$canary;
        $this->stack[$this->ebp] = $this->ebp + rand(0x0000,0xffff);
        $this->esp = $this->ebp - (rand(0x20,0x60)*0x000004);
        $this->stack[$this->ebp + 0x4] = dechex($O0OOO);

        if($OO000 != NULL)
            $this->{'pushdata'}($OO000);
    }

    public  function pushdata($OO0O0){ 
        $OOO00=&$GLOBALS{O0};
        $OO0O0 = handle_data($OO0O0);
        for($OO0OO=0;$OO0OO<count($OO0O0);$OO0OO++){
            $this->stack[$this->esp+($OO0OO*0x000004)] = $OO0O0[$OO0OO];
            //no args in my stack haha
            check_canary();
        }
    }

    public  function recover_data($OOO0O){
        $OOOO0=&$GLOBALS{O0};
        return hex2bin(strrev($OOO0O));
    }

    public  function outputdata(){
        $O0000O=&$GLOBALS{O0};
        global $regs;

        echo 'root says: ';
        while(0x001){
            if($this->esp == $this->ebp-0x4)
                break;
            $this->{'pop'}('eax');
            $OOOOO = $this->{'recover_data'}($regs['eax']);
            $O00000 = explode('\0',$OOOOO);
            echo $O00000[0];

            if(count($O00000)>0x001){
                break;
            }
        }
    }

    public  function ret(){
        $O000O0=&$GLOBALS{O0};
        $this->esp = $this->ebp;
        $this->{'pop'}('ebp');
        $this->{'pop'}('eip');
        $this->{'call'}();
    }

    public  function get_data_from_reg($O000OO){
        $O00OO0=&$GLOBALS{O0};
        global $regs;
        $O00O00 = $this->{'recover_data'}($regs[$O000OO]);
        $O00O0O = explode('\0',$O00O00);
        return $O00O0O[0];
    }

    public  function call(){
        $O0OO00=&$GLOBALS{O0};
        global $regs;
        global $plt;
        $O00OOO = hexdec($regs['eip']);

        if(isset($_REQUEST[$O00OOO])) {
            $this->{'pop'}('eax');
            $O0O000 = (int)$this->{'get_data_from_reg'}('eax');
            $O0O00O = array();
            for($O0O0O0=0;$O0O0O0<$O0O000;$O0O0O0++){
                $this->{'pop'}('eax');
                $O0O0OO = $this->{'get_data_from_reg'}('eax');
                array_push($O0O00O,$_REQUEST[$O0O0OO]);
            }
            call_user_func_array($plt[$O00OOO],$O0O00O);
        }
        else {
            call_user_func($plt[$O00OOO]);
        }
    }

    public  function push($O0OO0O){
        $O0OOOO=&$GLOBALS{O0};
        global $regs;
        $O0OOO0 = $regs[$O0OO0O];
        if( hex2bin(strrev($O0OOO0)) == NULL ) die('data error');
        $this->stack[$this->esp] = $O0OOO0;
        $this->esp -= 0x000004;
    }

    public  function pop($OO0000){
        global $regs;
        $regs[$OO0000] = $this->stack[$this->esp];
        $this->esp += 0x000004;
    }

    public  function __call($OO000O,$OO00O0){
        check_canary();
    }

}class_alias(O0OO0,stack,0);print_R(O0OO0);print_R(stack);

if(isset($_POST['data'])) {
        $phpinfo_addr = array_search(phpinfo, $plt);
        $gets = $_POST['data'];
        $main_stack = new stack($phpinfo_addr, $gets);
        echo '--------------------output---------------------</br></br>';
        $main_stack->{'outputdata'}();
        echo '</br></br>------------------phpinfo()------------------</br>';
        $main_stack->{'ret'}();
}

接下来我们来分段看这些函数的功能,代码当中还是有很多 0o 字符变量,我会将它们替换成其他有含义的变量名。

首先程序用 $regs 来模拟 eax、ebp、esp、eip 4个寄存器,然后定义了 aslr 函数模拟地址空间随机化,但是这个随机化的过程存在问题,因为用了可预测的变量做随机数种子,这样地址就会被计算出来。 handle_data 函数会对数据进行填充,用 \x00 将其长度填充成4的倍数,然后再以4长度分割。但是这个代码分割方式存在问题,例如我们输入的数据为 123 ,按照正常的逻辑经过处理后,数据应该变成 123\x00 ,但是这里的 handle_data 函数却会将其处理成 123\x00\x00\x00\x00\x00 (多了4个 \x00 )。

2019*CTF之Web部分题解

gen_canary函数用户生成用于防止栈溢出的 canary 字符串,代码如下:

2019*CTF之Web部分题解

接着通过 O0OO0 类的构造创建栈帧,其过程如下图右边所示:

2019*CTF之Web部分题解

接着开始将数据压栈,但是这里的 pushdata 方法貌似写的有问题,先进栈数据的地址应该要比后进栈的数据地址高,即 push 中的写法是正确的,但是却没调用。回到压栈操作本身,可以发现程序并未对数据的长度进行判断,这样有可能会导致数据过长而覆盖原有的数据。虽然有 canary 的保护,但是这个 canary 是可以泄露的,我们完全可以绕过。

2019*CTF之Web部分题解

完成数据压栈之后,开始进行数据出栈操作。程序会将 canary 到栈顶之间的所有数据都弹出去,之后结束弹栈操作。

2019*CTF之Web部分题解

完成数据出栈操作后,开始模拟 ret 指令,弹出下一条指令的地址并调用。 eip 上存放的是 phpinfo 函数的地址,但是这个地址可以利用前面 pushdata 可以覆盖成我们想要的函数地址。然后根据 REQUEST 请求参数中是否含有这个函数地址,分别进行有参函数、无参函数的调用。

2019*CTF之Web部分题解

这个时候,我们就可以执行任意函数了。但是从 phpinfo 的信息可以看出程序设置了 disable_functions

file_get_contents,file_put_contents,fwrite,file,chmod,chown,copy,link,fflush,mkdir,popen,rename,touch,unlink,pcntl_alarm,move_upload_file,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,fsockopen,pfsockopen,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,curl_init,curl_exec,curl_multi_init,curl_multi_exec,dba_open,dba_popen,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,mail,dl,putenv

然而没有禁用 create_function 函数,我们便可以利用该函数实现任意代码执行,具体可以参考: create_function函数如何实现RCE 。但是即便这样,我们也没办法绕过 disable_functions 执行 readflag 程序。

这时,可以把题目给的 dockerfile 拿来跑一下,看下其中开放的服务等等。我们会发现题目环境中开启了默认安装的 php-fpm 服务,这样我们可以通过 unix sockphp-fpm 进行通信。

2019*CTF之Web部分题解

其中,很重要的一点是,当我们使用 unix sockphp-fpm 进行通信,应用在 PHP 的配置文件是 php-fpm 独立的 php.ini 文件,而这个文件中默认 disable_functions 限制就少了很多。我们在题目上看到的 disable_functions ,实际上是 apache 自己的 php.ini 配置。这样,我们就可以通过 unix sockphp-fpm 进行通信,从而绕过 disable_functions 限制。

2019*CTF之Web部分题解

当我们通过 unix sockphp-fpm 进行通信,又会发现不管向 index.php、sandbox.php 哪个文件请求,都会触发如下代码:

2019*CTF之Web部分题解

我们可以通过搜索环境中默认安装 PHP 后存在的 PHP 文件,利用他们作为请求文件即可绕过上面代码。

2019*CTF之Web部分题解

然而实际上, PHP 中并不允许通过 ini_set 函数来设置 disable_functions 的值,即便你设置了也不会生效。

2019*CTF之Web部分题解

由于 apache 模式下设置的 disable_functions 没有禁用 stream_socket_client、stream_socket_sendto 这两个函数。我们可以利用其进行 SSRF ,与 PHP-FPM 进行通信,最后按照 fastcig 请求格式构造报文即可。在看官方 exploit.php 时,发现其通过 PHP_VALUE 设置了 disable_functions=空 ,而实际上 disable_functions 这个选项是PHP加载的时候就确定了,并不能通过 PHP_VALUE 设置生效。

2019*CTF之Web部分题解

mywebsql

这道题比较简单,直接利用admin/admin就可以登录mywebsql管理界面。然后利用mywebsql3.7的RCE直接写shell,具体参考: https://github.com/eddietcc/CVEnotes/blob/master/MyWebSQL/RCE/readme.md

之后要执行readflag程序,而这个程序会先给你一串表达式,你要快速的告诉他答案,答对即可获得flag。这里我使用如下PHP程序获得flag:

<?php
$descriptorspec = array(
    0 => array("pipe", "r"),  // 标准输入,子进程从此管道中读取数据
    1 => array("pipe", "w"),  // 标准输出,子进程向此管道中写入数据
    2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件
);

$process = proc_open('/readflag', $descriptorspec, $pipes, $cwd, $env);

if (is_resource($process)) {
    $question = fread($pipes[1],1024); // 获取程序问题
    $question = fread($pipes[1],1024); // 获取程序问题
    $question = trim($question);
    var_dump($question);
    eval('$result = '.$question.';');   // 计算问题结果
    fwrite($pipes[0], $result);         // 回答程序问题
    fclose($pipes[0]);
    var_dump($result);

    // $flag = stream_get_contents($pipes[1]);// getflag
    $flag = fread($pipes[1],1024);
    $flag = fread($pipes[1],1024);
    $flag = fread($pipes[1],1024);

    fclose($pipes[1]);
    var_dump($flag);
    
    $return_value = proc_close($process);

    echo "command returned $return_value\n";
}

?>

赛后还看到有选手逆向readflag程序,然后bypass ualarm函数的,具体参考 https://www.zhaoj.in/read-5479.html

参考


以上所述就是小编给大家介绍的《2019*CTF之Web部分题解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精彩绝伦的jQuery

精彩绝伦的jQuery

[美] Jake Rutter / 魏 忠 / 人民邮电出版社 / 2012-6 / 59.00元

内容简介: 通过创建更具交互性的富Web界面增强用户体验 本书阐述如何利用少量的JavaScript基础知识将jQuery框架整合进网站,以创建富Web界面,并建立兼容所有主流浏览器的交互性网站。作者是一位具有丰富经验的Web设计师和开发者,通过一系列指导性步骤清晰讲述了添加交互性以创建卓越Web应用的方法和技巧。 使用jQuery可以节省大量的开发时间,开发者在没有丰富编程经验......一起来看看 《精彩绝伦的jQuery》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码