学习web安全的那些二三事-php篇(1)

从大一入学学习安全到现在,我已经见识到许多基本漏洞以及这些漏洞的变形,这里就写几篇博客总结一下这段时间的学习,顺便也记录一下一些漏洞,以便查看

这里从一些常见的php的语言特性和函数开始说起

1.intval

细说一下intval的具体用法

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

intval用于获取变量的整数值,$value为任意类型的变量,后面为进制数,意思是经过转化后输出该进制的数,如果没有数或者输入的数为0,则默认为十进制

这里罗列了以下几种输入(注释内容为intval转化后的内容):

1
2
3
4
5
6
7
8
9
10
11
<?php
$a = 1; // 1
$b = 1.1; // 1
$c = '1c';// 1
$d = 'c1';// 0
$j = '2543sqd';// 2543
$e = 2e10;// 20000000000
$f = '2e10'; // 20000000000
$g = array(); // 0
$h = array(12); // 1
$i = array('1ad','wd'); // 1

这里说一下e和f

$e作为一个浮点数,因为它使用了科学计数法,2e10代表2 * 10^10^,如果超出范围,则会输出0或者其他不确定的数(这里不是随机数,每一个数对应一个定值

$f作为一个字符串,intval会先将其转化为数字然后再作进一步修改,如果转化后的数超出范围,则取最大的整数

有的ctf招新会这样出:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$num = $_GET['num'];
if(!preg_match("/[0-9]/",$num)){
if(intval($num)){
echo $flag;
}else{
die("easy php!");
}
}else{
die("hacker!");
}
?>

这里就可以利用上述例子提到的数组绕过

不过get传数组跟php里面传数组时处理的结果会不一样,get传参对数组的处理似乎有点复杂?(找时间复现一下然后加在这下面),而php里面,由于已经明确要传递空数组,因此判断方便(目前只能大体说说)

num就可以这样赋值:num[]=(啥都行)

2.弱类型比较

我们常见的弱类型比较就是==

当数据在使用==,php会“悄悄”把数据类型进行转化,这样就不用其他的数据转化脚本。

这种“悄悄”的转化方式称为隐式类型转换,它应用于很多编程语言,例如c,javascript等

在8.0版本以前,==的处理比较简单,举个栗子

1
2
3
4
5
6
7
8
9
var_dump('234' == 234); // bool(true)
var_dump('234s'== 234);// bool(true)
var_dump('ss' == 0);// bool(true)
var_dump('ss1' == 0);// bool(true)
var_dump('1ss' == 0);// bool(false)
var_dump('0x9' == 9);// bool(false)
var_dump(0x9 == 9);// bool(true)
var_dump('2e3' == 2000);// bool(true)
var_dump(2e3 == 2000);// bool(true)

这里的弱比较相当于在两端加上了intval

在8.0版本以后,开发者对==进行优化,对于数值字符串有一个很好的转换方法:

  1. 若字符串符合数值字符串的定义,则直接作为字符串进行比较.

  2. 若字符串不符合数值字符串的定义,则将数值转化为字符串后再进行比较。

数值字符串是指一个包含数字字符的字符串,而像0x7,0b1100110这种有明确进制标识符的不是数值字符串(当然,要符合进制的要求)

:::warning

如果像0x9这种数值被包上引号后则会被标识为数值字符串(这么说会感觉有点矛盾),没有被包上引号则作为数值出现

:::

以上数据输出如下:

1
2
3
4
5
6
7
8
9
var_dump('234' == 234); // bool(true)
var_dump('234s'== 234);// bool(false)
var_dump('ss' == 0);// bool(false)
var_dump('ss1' == 0);// bool(false)
var_dump('1ss' == 0);// bool(false)
var_dump('0x9' == 9);// bool(false)
var_dump(0x9 == 9);// bool(true)
var_dump('2e3' == 2000);// bool(true)
var_dump(2e3 == 2000);// bool(true)

更多内容的可以看看这个博客

附上几种数值的常见标识符:

二进制(0b),八进制(0),十六进制(0x),科学计数法([0-9]e)

有的比赛会根据弱比较类型来考怎么绕过,这里列几个比较常见的

md5绕过

对于md5而言,目前还没有一个好的算法来对md5编码进行解密,很多网站上的md5解密实际上是通过查询变量的md5值来进行破解的。说白了,破解者通过某些算法来生成字典并对这个md5进行爆破。因此,想通过某一个md5的值来破解得到明文在算法上并不可行

当md5出现在php时,又该怎么应对呢

一道经典的题型如下:

1
2
3
4
5
<?php
if($_GET['a'] != $_GET['b'] && md5($_GET['a']) === md5($_GET['b'])){
echo $flag;
}
?>

对于这种题,我们可以采用数组绕过,因为md5对数组编码后返回为空,我们只需要让数组名不同就行

特别地,若题目变成这样

1
2
3
4
5
<?php
if($_GET['a'] != $_GET['b'] && md5($_GET['a']) == md5($_GET['b'])){
echo $flag;
}
?>

由于弱比较的特性,我们可以通过爆破来传入相应的值。之前有个例子提到关于科学计数法,e是个比较特殊的字母,我们可以利用0e来进行绕过(毕竟0乘任何数都是0)

根据这个,我们可以查询哪些数值或字符串能够在md5编码后开头为0e

:::info

可以思考一下[0-9]e后如果先接字符后数字或者先数字再字符时会输出什么

:::

3.preg_match

preg_match用于执行匹配正则表达式,说白了就是查找选定字符串中有没有能匹配得上正则表达式部分的。这个只需知道如何看出匹配哪些字符就行,根据条件来写传入的数据

php文档给出的匹配模式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[abc]      单个字符:a、b 或 c
[^abc] 任何单个字符,但不是 a、b 或 c
[a-z] 范围 a-z 内的任何单个字符
[a-zA-Z] 范围 a-z 或 A-Z 内的任何单个字符
^ 行首
$ 行尾
\A 字符串的开头
\z 字符串的结尾
. 任意单个字符
\s 任意空白字符
\S 任意非空白字符
\d 任意数字
\D 任意非数字
\w 任意单词字符(字母、数字、下划线)
\W 任意非单词字符
\b 任意单词边界字符
(...) 捕获括号内的所有内容
(a|b) a 或 b
a? 零个或一个 a
a* 零个或多个 a
a+ 一个或多个 a
a{3} 正好 3 个 a
a{3,} 3 个或更多 a
a{3,6} 3 到 6 个 a

i 不区分大小写
m 使点号匹配换行符
x 忽略正则表达式中的空白
o 仅进行一次 #{...} 替换

例如preg_match(/[a-z][A-Z][0-9]/, $a)表示的是查看$a中是否含有字母和数字,其中字母包含大小写

在审计这种代码时,需要记住匹配模式中每一个正则表达式表示的是什么

这里拿一个ctfshow的题目出来说说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
?>

第一层匹配是查询所有行中是否至少有一行是php,第二层则是查询第一行中是否只有php

在/i模式下只能匹配一行,但是加了/m后,php就会解析每一行

因此可以传入cmd=php%0a123,其中%0a为换行符的url编码

:::info

可能有同学会不理解第一次和第二次都能过,因为在/im模式下,php会将%0a识别为换行符,因此我们穿的一行数据在经过解析之后实际上是两行数据;而在/i模式下,%0a只是一个普通的字符串而已

之前也提过,preg_match('/^php$/im', $a)是检查某一行中的从头到尾是否是php,这就意味着你有一行只能是php

:::

额外补充一个

strpos用于查找某个字符或某个字符串在某个变量中第一次出现的位置,当匹配字符串时,返回变量中匹配的字符串的第一个字符的位置

1
2
3
4
5
6
7
<?php
$a = '010101';
echo(strpos($a,'0')); // 0
echo(strpos($a,'1')); // 1
echo(strpos($a,'2')); // 无输出
echo(strpos($a,'010')); // 0
?>

4.highlight_file

官方文档给出的用法:highlight_file([string] $filename, [bool] $return = false): [string]|[bool]

我们用到的highlight_file一般用于高亮一个文件(展示一个文件的内容并将对应的代码块进行高亮),因此bool类型一般不会用到

而$filename传入的实际上是文件的地址

5.file_put_contents

php官方文档:file_put_contents(
[string] $filename, [mixed] $data,
[int] $flags = 0, [?][resource] $context = **null**
`): [int]|[false]``

其中主要的是filename,需要对内容进行写入的文件(传的是文件名而非地址,注意和其他函数的区别),data就是写入的内容

file_put_contents用于向一个文件里面写内容,针对的是当前目录下的文件,如果当前目录下存在目标文件名,则将该文件进行覆盖重写,反之则创建一个新文件并将内容写入其中

6.call_user_func

php官方文档:

call_user_func([callable] $callback, [mixed] ...$args): [mixed]

call_user_func把第一个参数作为回调函数调用

:::info no-icon

回调:一个过程,将某个函数作为参数放到另外一个函数中,当另外一个函数执行完后,再转过头来执行这个函数

而回调函数实际上是一个参数

:::

第一个传递的是php的函数,例如eval,hex2bin等

第二个传递的是回调函数的参数

学习web安全的那些二三事-php篇(1)

http://luffys93.github.io/2024/06/25/Webbbb/PHP/php-first/

作者

Ins0mn1a

发布于

2024-06-25

更新于

2024-07-31

许可协议

评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...