(1)类型转换

之前提到过的是字符串转为整型。其实php手册里面提到了各种类型的转换。先把他放在这里。

https://www.php.net/manual/zh/language.types.type-juggling.php#language.types.typecasting

我们按照php手册上的顺序来介绍,只介绍几个比较常用的。

①转化为bool

image-20240701220431198

②转化为整型int

0x01:从bool转换为int

false为0,true为1

0x02:从float转换为int

向下取整(直接舍去小数位)

0x03:从string转换为int

如果 string 是 numeric(数字字符串) 或者前导数字, 则将它解析为相应的 int 值,否则将转换为零(0

数字字符串: 这只是一个字符串,其开头类似于数字字符串,后跟任何字符。但是如果含 e 的字符串转换成 int 类型时会被当做科学计数法处理, 123e456 表示 123 的 456 次方。

0x04:从null转换为int

为0

③转换为float

0x01:从string转换为float

如果 string 是 numeric(数字字符串) 或者前导数字, 则将它解析为相应的 int 值,否则将转换为零(0

0x02:其他类型转换为float

先将其他类型转换为int,再由int转换为float。(而其他类型转换为float,就在上面的②)


(2)=====

== 弱类型比较, 仅要求两边变量类型转换后的值相等

=== 强类型比较, 不仅要求两个变量的值相等, 还要求变量的类型相同

同理 != 是弱类型比较, 而 !== 是强类型比较


(3)intval()函数绕过

首先我们去php手册了解这是什么东西。

intval()

获取变量的整数值。下面的base是转换的进制,默认是0

复制

intval(mixed $value, int $base = 10): int

intval主要利用有三个。

①特性一:返回值

image-20240701220606060

这个重点在非空的数组会返回1。

['']这个不是数组为空,这个数组有一个元素,元素为空元素。要是[]才是空数组

②特性二:base转化的进制

image-20240701220651503

前提是base是0的时候,由value来决定

image-20240701220718985

③特性三:弱类型比较下的base转换开始和结束

这个是说在弱类型比较的前提下,本来在base=0的情况下,要看value的结果来判断,这个判断是从数字或者正负号开始才做转换,直到遇到非数字,或者字符串的结束符(\0)结束转。这句话的意思是比如我们要得到带4476的字符串,应该是4476abc这种。不然以abc4476是被转换为0的。

④不影响结果的在开头的元素

空格+(表示正号)


(4)preg_match()函数绕过

preg_match(): 执行匹配正则表达式

>preg_match(
 string $pattern,
 string $subject,
    array &$matches = null,
    int $flags = 0,
    int $offset = 0
   ): int|false

①特性一:传递数组

image-20240701220814608

上面显示subject得是字符串,也就是说如果是数组,会返回false。

②特性二:换行符绕过

换行符这个大部分都只有一个,但是其实在做题之类的看到了两个。

1)/m

首先我们需要知道这个/m是什么

手册上太复杂了,所以我直接用自己的理解来理解吧。就是m代表multiline mode,是多行模式。这个多行模式是什么意思呢。我们在正则表达式里面有说过^代表匹配开头,$代表匹配结尾。但是如果没有/m就是单纯的单行的匹配,如果我们开启了/m就是可以多行的匹配。就是每一行都可以匹配由^开头,以$结尾的。

我们接下来继续举例理解。比如我们有字符串

line1
line2
line3

我们如果用正则表达式/^line/m,如果开启了多行,就每一行都可以匹配,就是能匹配到line1,line2,line3,如果我们没开就是只能匹配搭配line1。

但是我们可以截断这个m的作用。利用%0a

当出现%0a的时候它会被认为是一个换行符,是什么意思呢,就是从字符串的开头到%0a作为了第一行,从%0a到字符串末尾变成了第二行。就是说我们如果第一行匹配不了,但是第二行匹配成功了也行。

<?php
$a=$_GET['a'];
if(preg_match('/^php$/im',$a))
{
    echo "hacking attempt .....";
}
else
{
    echo "success";
}
image-20240701221229593

2).

.也是截断输入,这个输入的原理是因为。.可以匹配除换行符(\n)之外的任意单个字符。举个例子,我们比如我们有一个字符串/hello\nworld这个字符串,然后我们正则表达式/h.llo/会得到的是hello,这个.匹配到了一个e。但是他不能匹配\n。这个\n我们要用%0a来表示。

3)/s

%0a即换行符的url编码,在preg_match没启动/s模式(单行匹配模式)时,正则表达式是无法匹配换行符(%0a,\n)的,且会自动忽略末尾的换行符。但是我们可以用这个去绕过一些限制

③回溯绕过*

0X01:

这里推荐一个工具https://blog.robertelder.org/regular-expression-visualizer/

这个工具可以展示正则表达式的详细匹配过程。我们就用这个工具来理解p神的回溯过程也不失为一种高效的办法。

所以回溯绕过就是利用的PHP的PCRE库也是利用的NFA为正则引擎,这样就要回溯找到其他状态。

<?php
function is_php($data){  
    return preg_match('/<\?.*[(`;?>].*/is', $data);  
}

if(!is_php($input)) {
    // fwrite($f, $input); ...
}

详细原理->

PHP利用PCRE回溯次数限制绕过某些安全限制 | 离别歌 (leavesongs.com)


(5)strpos绕过

image-20240701222401648

需要特别注意的是,顺序是从0开始的。如果找到了就返回位置。如果没有找到就是返回false。

①特性一:八进制首位绕过

如果对八进制(首位是0)遇到了if(!strpos($string,0))进行过滤,我们想让八进制还能成功通过的话,需要在首位想想办法。一般是在前面加空格和加号。我们可以进行实验

<?php 
$string=$_GET["string"];
if(!strpos($string,0))
{
    echo "hello,world";
}
else
{
    echo "success";
}

并且空格还可以用%20,%0a,%09来代替,加号可以用%2b代替。

②特性二:传递数组

对于如果strpos传递的不是字符串,而是数组的话,返回值为null。

结论对strrpos() stripos() strripos() 同理


(6)is_numeric()函数绕过

image-20240701222456713

①特性一:科学计数法绕过

我们知道科学计数法是含有e的数字字符串,对于is_numeric来说,他是能识别科学计数法的,并且能返回true。试验一下

<?php 
$string= 11e11;
if(is_numeric($string))
{
    echo "hello,world";
}
else
{
    echo "failure";
}
image-20240701222530985

所以,对于某些字符串,我们可以尝试利用 base64 + bin2hex 找到一些只含 e 和数字的 payload

②特性二:开头加特殊字符

在数字前面加上/n,/t等这种特殊字符仍然可以返回true,但是放在数字后面就不行了。


(7)in_array()函数

image-20240701222655924

如果找到了返回true,没找到返回false。

①特性一:自动转换

函数在作用时会将needle的值自动转化为和array一个类型。

其实这个实验在我的环境下失败了,这结论也不在成立。后面我看了手册,发现了还是版本问题。


(8)trim()函数

image-20240701222730924

返回值是过滤后的字符串。

①特性一:不过滤/f

我们可以看到,上面写了去除\t,\n,\r,\0,\v但是他没有过滤\f(换页符),他的url编码是%0c36


(9)md5()和sha1()

image-20240701222750628

返回值sha1的散列值的字符串形式。

image-20240701222804805

以 32 字符的十六进制数形式返回散列值。

两函数的性质啥的都是一样的。所以我们直接一起说了,有些情况只列举了一个,但是对两个函数都是一样的。

①特性一:比较缺陷(或者说0e漏洞)

PHP`在处理哈希字符串时,通过`!=`或`==`来对哈希值进行比较,它把每一个以`0e`开头的哈希值都解释为`0`,所以如果两个不同的密码经过哈希以后,其哈希值都是以`0e`开头的,那么`PHP`将会认为他们相同,都是`0。这个通常是对于科学计数法的时候会遇到0e漏洞。

可以实验一下。

<?php 
$a="0e123";
$b="0e456";
if($a==$b)
{
    echo "equal";
}
else
{
    echo "wrong";
}

?>

所以我们如果如果我们要绕过if(md5($a)==md5($b))这个条件的话。我们可以选择一些md5编码后都为0e的a,b字符串。

所以这里介绍一些。

  • md5加密后以0e开头的字符串
1.   QNKCDZO             ----------0e830400451993494058024219903391
2.   s878926199a         ----------0e545993274517709034328855841020
3.   s155964671a         ----------0e342768416822451524974117254469
4.   s214587387a         ----------0e848240448830537924465865611904
5.   s214587387a         ----------0e848240448830537924465865611904
6.   s878926199a         ----------0e545993274517709034328855841020
7.   s1091221200a        ----------0e940624217856561557816327384675
  • sha1加密后以0e开头的字符串
1.   aaroZmOk            ----------0e66507019969427134894567494305185566735
2.   aaK1STfY            ----------0e76658526655756207688271159624026011393
3.   aaO8zKZF            ----------0e89257456677279068558073954252716165668
4.   aa3OFF9m            ----------0e36977786278517984959260394024281014729
5.   0e1290633704        ----------0e19985187802402577070739524195726831799
6.   10932435112         ----------0e07766915004133176347055865026311692244

注意这个特性是在弱类型的条件下。

②特性二:数组绕过

在强类型的条件下,我们特性一就失效了,这时候我们可以用数组绕过。我们知道md5和sha1是对字符串起的作用,如果我们传入的是数组:

  • md5()函数获取不到数组的值,默认数组为0
  • sha1()函数无法处理数组类型,将报错并返回false

所以我们可以对于if(md5($a)===md5($b)),可以用**取a,b为数组**来绕过。

这个对于强类型和弱类型比较都可以成功。

应该是有版本要求

在8.3的版本实验的时候发现全部都是报错,无法执行最后预期的结果。


(10)路径穿越

①特性一:利用绝对路径和相对路径

路径绕过就是通过绝对路径或者相对路径去绕过正则对文件名的检测。比如preg_match('/flag.php/', $str)

(关于路径的知识忘记的,可以去看我们当时在讲file://协议中讲过的。

我们可以写

./flag.php
/var/www/html/flag.php
./ctfshow/../flag.php

②特性二:利用linux下的软链接绕过

我们首先介绍一下什么叫软链接。

软链接:软链接文件有类似于 Windows 的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。其实我的理解是他很像c语言的指针的作用,软链接是一个指向某个特定文件的指针,指针的数据块内包含的是文件的位置信息。如果文件被删除了,但是指针还存在,只是指向的位置是个无效的罢了。

/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/
self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se
lf/root/proc/self/root/var/www/html/flag.php

(11)运算优先级

image-20240701223139262

(12)三目运算符

构造不带分号的payload需要用到

return 1?phpinfo():1;

这样就永远都会执行phpinfo(),这个phpinfo可以替换为其他的代码。


(13)函数与数字运算

在php中,函数和数字一起运算,函数能被正常执行。比如

1+phpinfo()+1;

phpinfo()能被正常执行。以下符号都能够正常执行。

复制

+ - * / && ||

(14)php伪随机数漏洞

其实这个是做题目的时候,其实知道是伪随机数,但是自己爆破根本爆破不动。

其实就是爆破出种子的具体值,这里主要是用一个php_mt_seed的脚本。

学c语言的时候就已经学过真随机数,伪随机数,这里就不赘述了。

①PHP的伪随机数有关的两个函数

Ⅰ.mt_rand()

产生随机数的发生器

mt_rand(min,max)

Ⅱ.mt_srand()

产生种子的发生器

mt_srand(seed)

②PHP自动播种

php每次调用mt_rand()函数时,都会先检查是否已经播种,如果已经播种,就直接产生随机数,否则调用php_mt_srand来播种,也就是说每个php cgi进程期间,只有第一次调用mt_rand()自动播种,接下来都会根据这个第一次播种的种子来生成随机数。

③php_mt_rand

其实就是一个穷举脚本,只不过这个比我们自己写的要快很多。这里就讲解它的用法,下载在linux里面,其实windows也是可以的。

④php_mt_rand用法

Ⅰ首先对数据进行格式转换

str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='8D2SWQCXgH'
length = len(str2)
res=''
for i in range(len(str2)):
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print(res)

Ⅱ在php_mt_rand目录下将得到的数列带入爆破脚本,找到种子,再将种子转换为随机数

输入指令

./php_mt_seed 34 34 0 61 39 39 0 61 28 28 0 61 54 54 0 61 58 58 0 61 52 52 0 61 38 38 0 61 59 59 0 61 6 6 0 61 43 43 0 61

后面是得到的数列。

然后得到结果

image-20240701223342064

所以我们的种子是880310233


未完待续,待补充