变量覆盖

漏洞产生:
变量覆盖漏洞的产生原理:函数使用不当和$$使用不当。

1
extract()、parse_str()、import_request_variables()

一、extract

会出现extract变量覆盖,
第一种是第二个参数为EXTR_OVERWRITE,
第二种是第二个参数为EXTR_IF_EXISTS,
第三种是只传入第一个参数,第二个参数默认值为EXTR_OVERWRITE

1
2
3
4
5
6
<?php
$b = 2;
$a = array("b"=>"333");
extract($a);
echo($b);
?>

输出:333

可见变量$b值被覆盖,$b值输出为333,当数组value值可控时,$b值可控。

2、parse_str
这个函数的作用是把查询字符串注册成变量中

1
2
3
4
5
<?php
$b = 2;
parse_str('b=3333');
echo($b);
?>

输出:3333

3、import_request_variables

1
2
3
4
5
<?php
$b=2;
import_request_variables('GP');
echo($b);
?>

浏览器提交/?b=1,得到的变量b的输出为1,可见原本的变量被覆盖

4、ctf题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

$flag='xxx';
extract($_GET);
if(isset($shiyan)){
var_dump($shiyan);
var_dump(trim(file_get_contents($flag)));
$content=trim(file_get_contents($flag));
if($shiyan==$content)
{
echo'ctf{xxx}';
}
else
{
echo'Oh.no';
}
}

?>

shiyan=&flag=1

1
2
3
4
5
<?php
$b = 'flag{"xxxxxxsx"}';
extract($_GET);
echo($b);
?>

b=123123
防御方案
变量覆盖漏洞常见的漏洞点是做变量注册的时候没有验证变量是否已经存在,所以想要防御变量覆盖漏洞最好使用原始的变量数组,如$_GET、$_POST,或者在注册变量前验证变量是否存在。

弱类型比较

弱类型与强类型
通常语言有强类型和弱类型两种,强类型指的是强制数据类型的语言,就是说,一个变量一旦被定义了某个类型,如果不经过强制类型转换,这个变量就一直是这个类型,在变量使用之前必须声明变量的类型和名称,且不经强制转换不允许两种不同类型的变量互相操作。我们称之为强类型,而弱类型可以随意转换变量的类型例如可以这样
类型转换问题
类型转换最常见的就是int转String,String转int。

Int转String:

$num = 5;
方式1:𝑖𝑡𝑒𝑚=(𝑠𝑡𝑟𝑖𝑛𝑔)num;
方式2:𝑖𝑡𝑒𝑚=𝑠𝑡𝑟𝑣𝑎𝑙(num);

1
2
3
4
5
var_dump(intval(4))//4

var_dump(intval(‘1asd’))//1

var_dump(intval(‘asd1’))//0

“= =”与“= = =”比较操作符问题
php有两种比较方式,一种是“= =”一种是“= = =”这两种都可以比较两个数字的大小,但是有很明显的区别。

“= =”:会把两端变量类型转换成相同的,在进行比较。

“= = =”:会先判断两端变量类型是否相同,在进行比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if (isset($_GET['Username']) && isset($_GET['password'])) {
$logined = true;
$Username = $_GET['Username'];
$password = $_GET['password'];
if (!ctype_alpha($Username)) {$logined = false;}
if (!is_numeric($password) ) {$logined = false;}
if (md5($Username) != md5($password)) {$logined = false;}
if ($logined){
echo "successful";
}else{
echo "login failed!";
}
}
?>

0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0。所以我们只需要输入一个数字和字符串进行MD5加密之后都为0e的即可得出答案。md5(‘240610708’) == md5(‘QNKCDZO’)成功绕过!。

十六进制转换问题

首先我们看一下例子:

1
2
3
"0x1e240"=="123456" //true
"0x1e240"==123456 //true
"0x1e240"=="1e240"//false

php在接受一个带0x的字符串的时候,会自动把这行字符串解析成十进制的再进行比较,0x1e240解析成十进制就是123456,并且与字符串类型的123456和int型的123456都相同。

布尔值转换问题

1
2
3
4
<?php
If (true=“name”){
echo “success”;
}

布尔值可以和任何字符串相等。
输入一个数组进行json解码,如果解码后的message与key值相同,会得到flag,主要思想还是弱类型进行绕过,我们不知道key值是什莫,但是我们知道一件事就是它肯定是字符串,这样就可以了,上文讲过,两个等号时会转化成同一类型再进行比较,直接构造一个0就可以相等了。最终payload message={“key”:0}。

switch绕过
缺陷原理相同,绕过姿势相同,如果switch是数字类型的case的判断时,switch会将其中的参数转换为int类型。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
$i ="3name";
switch ($i) {
case 0:
case 1:
case 2:
echo "this is two";
break;
case 3:
echo "flag";
break;
}
?>

strcmp绕过这个时候程序输出的是,类型转换的i,结果为3返回flag

MD5 ,sha1绕过

首先还是介绍一下这两个函数,这俩都是加密函数,分别进行的时给字符串进行MD5加密和计算字符串的 SHA-1 散列。

但是这个函数都有着缺陷,就是不能处理数组。

这样就很容易绕过了

1
2
3
4
5
6
7
8
9
<?php
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
die('Flag: '.$flag);
else
print 'Wrong.';
}
?>

payload: a[]=1&b[]=2
array_search()、in_array()绕过

首先介绍一下什莫是array_search()函数, array_search() 函数在数组中搜索某个键值,并返回对应的键名。in_array() 函数搜索数组中是否存在指定的值。基本功能是相同的,也就是说绕过姿势也相同。Array系列有两种安全问题,一种是正常的数组绕过,一种是“= =”号问题。先讲第一个数组绕过。

举例:

复制代码

复制代码

这段代码的意思就是先判断是不是数组,然后在把数组中的内容一个个进行遍历,所有内容都不能等于admin,类型也必须相同,然后转化成int型,然后再进行比较如果填入值与admin相同,则返回flag,如何绕过呢?

基本思路还是不变,因为用的是三个等于号,所以说“= =”号这个方法基本不能用,那就用第二条思路,利用函数接入到了不符合的类型返回“0”这个特性,直接绕过检测。所以payload:test[]=0。

in_array()

1
2
3
4
5
6
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array)); //true
var_dump(in_array('1bc', $array)); //true


通过例子我们就知道了,这个松散的判断就是等于号,所以出现了“= =”号的特性“abc”==0、“1bc”==1,如果不加true的话就可以利用“= =”轻松绕过。array_search同理。
反序列化

PHP反序列化漏洞又称PHP对象注入,是因为程序对输入数据处理不当导致的。
比较典型的PHP反序列化漏洞中可能会用到的魔术方法:

1
2
3
4
5
6
7
8
9
__construct: 在创建对象时候初始化对象,一般用于对变量赋初值。
__destruct: 和构造函数相反,当对象所在函数调用完毕后执行。
__toString:当对象被当做一个字符串使用时调用。
__sleep:序列化对象之前就调用此方法(其返回需要一个数组)
__wakeup:反序列化恢复对象之前调用该方法
__call:当调用对象中不存在的方法会自动调用该方法。
__get:在调用私有属性的时候会自动执行
__isset():在不可访问的属性上调用isset()或empty()触发
__unset():在不可访问的属性上使用unset()时触发

析构函数会回显$test的值,我们可以构造一个对象,控制$test的值,达到控制数据流的目的,实现反序列化漏洞的利用。

1、__destruct

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class A{
public $target = "demo";
function __destruct(){
echo "destruct!<br/>";
echo $this->target."</br>";
echo "destructed!";
}
}
$a = $_GET['test'];
$a_user = unserialize($a);
?>

当new A 资源被回收前会执行__destruct

1
2
3
4
5
6
7
<?php
class A{
public $target="xxxxxxx";
}
$a = serialize(new A);
echo $a;
?>

注入对象构造方法