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) |
