php中的一些trucks
关于preg_replace
1 | preg_replace($pattern, $replacement, $subject) 正则搜索subject中匹配pattern的部分(默认是所有),以replacement进行替换。 |
第一个参数$pattern
这个参数是一个正则表达式,用来在$subject
进行正则匹配
php正则表达式的定界符可以是”/“和”#”,在正则表达式中使用到定界符时需要用”"来转义。
在定界符之后还可以添加php正则表达式修饰符,这些修饰符包括:
1 | i 忽略大小写,匹配不考虑大小写 |
第二个参数$replacement
这个参数是用于替换的字符串或字符串数组。只有当$pattern
是数组的时候这个参数为数组才有效,会对应数组中的元素进行替换。
$replacement
中可以包含向后引用的\\n
或$n
,其含义是被匹配到的第n个捕获子组捕获到的文本。而\\0
或$0
则表示完整的模式匹配文本
1 | echo preg_replace('/(\d+)(\W)(\d+)/', '${0}', '123,456') ==> 123,456 |
如果要在$replacement
中使用反斜线'\'
,必须使用四个'\\\\'
。
首先这是php的字符串,'\\\\'
经过转义后成为了'\\'
,再经过正则表达式引擎后才被认为是一个原文反斜线
简单的说,perg_replace
会将两个反斜线转化成一个反斜线
1 | echo preg_replace('/a/','\,','a') ==> \, |
一个例子:
1 |
|
这里可以有多种绕过姿势:
第一种:使用\'
逃逸单引号
如果我们传入\';phpinfo();//
,经过addslashes()
后的字符串为\\\';phpinfo();//
,而这个字符串经过preg_replace
后将前两个反斜线合并为一个反斜线后变成\\';phpinfo();//
,成功引入了一个单引号。
如果原来的option.php内容为<?php $option='123'; ?>
,经过这次替换则变成了<?php $option='\\';phpinfo();//'; ?>
,成功写入shell。
第二种:使用$0
的知识点进行二次替换
第一次传入;phpinfo();
,option.php内容变成<?php $option=';phpinfo();'; ?>
第二次传入%00
或$0
,如果传入的是%00
,即\0
,则经过addslashes
后变成了\\0
。
到preg_replace
的时候便成为了preg_replace('|\$option=\'.*\';|', "\$option='\\0';", "<?php \$option=';phpinfo();'; ?>")
。
此时\\0
表示完整的模式匹配文本,即"\$option='\$option=';phpinfo();';';"
。即可写入shell。
使用$0
也是同样的原理。
第三种:使用%0a
换行之后再进行二次替换
由于preg_replace
是一行一行的进行匹配的,所以可以先使用%0a
进行换行,然后进行二次替换。
第一次传入a';phpinfo();%0a//
,写入文件后,文件内容为
1 | $option='a\';phpinfo(); |
第二次随便传入一个值,比如1
,则把反斜线给去除了。此时文件内容为
1 | $option='1';phpinfo(); |
php文件操作相关的tricks
上传后删除
file_put_contents
、file_get_contents
、copy
等读取写入操作与unlink
、file_exists
等删除判断文件函数之间对于路径处理是不同的
php读取、写入文件,都会调用底层php_stream_open_wrapper_ex
来打开流,而判断文件存在、重命名、删除文件等操作则无需打开文件流,这就是二者的区别。
php_stream_open_wrapper_ex
在读和写文件有不同的操作,但是在最后都会使用tsrm_realpath_r
函数来将filename给标准化成一个绝对路径,并且会**去除路径末尾的、/
、//
、/./
、/.
**。
在这之前,会判断传入的路径是否是目录,可以有两种方式让php底层出错使得函数判断为不是目录:
- 可以利用多层软链接递归,使得地址栈崩掉
/proc/self/root
为根目录/
的软连接,通过多层这样的软连接可以使得地址栈崩掉,函数返回-1从而判断为不是目录 - 控制directory为false
如果的路径中有不存在的目录,directory就会是false,所以可以直接让函数判断为不是目录。
/aaa/../var/www/html/index.php/.
在写文件时,会使用virtual_file_ex
函数来判断传入路径是否为目录,比如/tmp/index.php/
它认为是个目录,而/tmp/index.php/.
则认为不是目录。
这样子,传入的a.php/.
首先判断不是目录,最后又通过tsrm_realpath_r
去除掉了末尾的/.
,解析成为了文件a.php
。需要注意的是,如果/tmp/index.php
文件存在,这个方式会出错
所以,如果我们传入的是文件名中包含一个不存在的路径,写入的时候因为会处理掉”../“等相对路径,所以不会出错;判断、删除的时候因为不会处理,所以就会出现”No such file or directory”的错误。
一个例子:
1 |
|
所以这里可以使用name=a.php/.
、name=xxx/../a.php
、name=a.php/ss/..
等来绕过,windows下还可以用name=test.php:test test.ph<
来绕过
如果file_put_contents
的第一个参数完全可控,则还可以使用php伪协议来绕过,file_exists
和unlink
并不能使用php伪协议
相同类型的还有:
1 |
|
file_put_contents写入文件
1 | file_put_contents($filename, $data) 将$data写入$filename |
需要注意的是,如果$data
为一个数组,则会将数组内的元素当成字符串拼接起来写入文件。
例如:
1 |
|
此时只要传入text[]=phpinfo();
即可绕过。
call_user_func调用类方法
1 | call_user_func($callback, $parameter) 把第一个参数作为回调函数调用,其余参数都是回调函数的参数 |
需要注意的是:当我们的第一个参数为数组时,会把第一个值当作类名,第二个值当作方法进行回调
例如
1 |
|
结果就会调用类test
中的hello
方法,输出hello
php变量名不合法字符转换成_
在PHP中,变量名称不能使用点号.
,例如$a.b
是一个不合法的变量名。因此,php会自动将点替换为下划线。
除了点,一些其他字符如果出现在GET参数名中,也会被自动的替换为下划线。
1 |
|
关于header
1 | header($string) 发送原生 HTTP 头 |
其参数为头字符串,有两种特别的头:
一种以”HTTP/“开头的,将会被用来计算将要发送的HTTP状态码。
例如:
1 | header("HTTP/1.0 404 Not Found"); |
第二种特殊情况是”Location:”头信息
它不仅把报文发送给浏览器,而且还将返回给浏览器一个REDIRECT
(302)的状态码
需要注意的点
- Location和”:”之间不能有空格,否则会出现错误
- 在用header前不能有任何的输出(包括空白)
- 使用
header()
进行跳转的时候没有使用exit()
或者是die()
,导致后续的代码任然可以执行。
如果后面存在危险函数,那么将会触发漏洞。
php可变变量与变量执行
在花括号内的代码是可以执行的
1 | echo "${phpinfo()}"; |
在任何php的版本中都可以执行的方法:
1 | "${ phpinfo()}"; //第一个字符为空格 |
原理:空格,tab,注释,回车是各种语法分析引擎中常见的分割字符,@是PHP语法的一个特殊的容错符号,所以可变变量内的花括号有这么一个规则,需要判断花括号内的内容是否为真正的代码,条件即是文本的第一个字符串是否为PHP语法解析引擎的分割字符和特殊的语法符号!
参考:https://blog.spoock.com/2017/07/18/php-variables-variable/
__HALT_COMPILER()函数的利用
__HALT_COMPILER()
在phar反序列化的时候遇到过,是用在phar的stub文件标识
其实,__halt_compitler()
是一个php函数,其作用是中断编译器的执行,即使后面的内容不符合php语法。
需要注意的是,该函数仅能够在最外层使用
也就是,__halt_compitler()
的功能类似于die();?>
的功能,可以让php编译器中断执行
总结描述:
1 | __halt_compitler(); ==> die(); |
php中的浮点数精度问题
php中的小数精度一般在$10^{-16}$,但是在浮点数转为字符串的时候,最多只会保存小数点后14位,小数点后第15位四舍五入。
1 | $a = 0.10000000000001; // 14位 |
php中小数精度一般在$10^{-16}$,所以超过16位小数的部分可能造成精度缺失
1 | $a = 0.1; |
所以,php的小数精度在转换成字符串时和平时是有差异的,利用好14和16位精度的差异就可以完成一些绕过:
1 | $a = 0.1; |
php的预定义变量
PHP中有两个特殊的数字相关的预定义变量:
- NAN 表示一个不存在的数
- INF 表示一个无穷大的数
而这两个变量转换成字符串的时候会转成字符形式的NAN
和INF
1 | var_dump(1/0); // float(INF) |