随着代码安全的普及,愈来愈多的开发人员知道了如何防护sqli、xss等与语言无关的漏洞,可是对于和开发语言自己相关的一些漏洞和缺陷却知之甚少,因而这些点也就是咱们在Code audit的时候的重点关注点。本文旨在总结一些在PHP代码中常常形成问题的点,也是咱们在审计的时候的关注重点。(PS:本文也只是简单的列出问题,至于形成问题的底层缘由未作详细解释,有兴趣的看官能够自行GOOGLE或者看看底层C代码。知其然,且知其因此然)php
本文如有写错的地方,还请各位大佬斧正 :)html
TODO: 继续丰富并增长各个点的实际漏洞事例linux
例如以下代码sql
<?php $filename = __DIR__ . '/tmp/' . $user['name']; $data = $user['info']; file_put_contents($filename, $data); if (file_exists($filename)) { unlink($filename); } ?>
这里引用小密圈中P牛的解读windows
查看php源码,其实咱们能发现,php读取、写入文件,都会调用php_stream_open_wrapper_ex来打开流,而判断文件存在、重命名、删除文件等操做则无需打开文件流。数组
咱们跟一跟php_stream_open_wrapper_ex就会发现,其实最后会使用tsrm_realpath函数来将filename给标准化成一个绝对路径。而文件删除等操做则不会,这就是两者的区别。安全
因此,若是咱们传入的是文件名中包含一个不存在的路径,写入的时候由于会处理掉“../”等相对路径,因此不会出错;判断、删除的时候由于不会处理,因此就会出现“No such file or directory”的错误。app
因而乎linux能够经过xxxxx/../test.php
、test.php/.
windows能够经过test.php:test
test.ph<
来绕过文件删除curl
此外发现还可使用伪协议php://filter/resource=1.php
在file_ge_contents、copy等中读取文件内容,却能够绕过文件删除xss
extract函数从数组导入变量(如\$_GET、 \$_POST),将数组的键名做为变量的值。而parse_str函数则是从相似name=Bill&age=60的格式字符串解析变量.若是在使用第一个函数没有设置EXTR_SKIP
或者EXTR_PREFIX_SAME
等处理变量冲突的参数时、第二个函数没有使用数组接受变量时将会致使变量覆盖的问题
所以,在32位系统上 intval('1000000000000') 会返回 2147483647
当小数小于10^-16后,PHP对于小数就大小不分了
var_dump(1.000000000000000 == 1) >> TRUE
var_dump(1.0000000000000001 == 1) >> TRUE
而'.'能够出如今任意位置,E、e能出如今参数中间,仍能够被判断为数字。也就是说is_numeric("\r\n\t 0.1e2") >> TRUE
int strcmp ( string $ str1 , string \$str2 )
参数 str1第一个字符串。str2第二个字符串。若是 str1 小于 str2 返回 < 0;
若是 str1 大于 str2 返回 > 0;若是二者相等,返回 0。
可是若是传入的两个变量是数组的话,函数会报错返回NULL,若是只是用strcmp()==0来判断的话就能够绕过
sha1() MD5()函数默认接收的参数是字符串类型,可是若是若是传入的参数是数组的话,函数就会报错返回NULL。相似sha1(\$_GET['name']) === sha1(\$_GET['password’])的比较就能够绕过
这方面问题普及的不少,不做过多的解释
md5('240610708'); // 0e462097431906509019562988736854
md5('QNKCDZO'); // 0e830400451993494058024219903391
md5('240610708') == md5('QNKCDZO')
md5('aabg7XSs') == md5('aabC9RqS')
sha1('aaroZmOk') == sha1('aaK1STfY')
sha1('aaO8zKZF') == sha1('aa3OFF9m')
'0010e2' == '1e3'
'0x1234Ab' == '1193131'
'0xABCdef' == ' 0xABCdef’
当转换为boolean时,如下只被认为是FALSE:FALSE、0、0.0、“”、“0”、array()、NULL
PHP 7 之前的版本里,若是向八进制数传递了一个非法数字(即 8 或 9),则后面其他数字会被忽略。var_dump(0123)=var_dump(01239)=83
PHP 7 之后,会产生 Parse Error。
字符串转换为数值时,若字符串开头有数字,则转为数字并省略后面的非数字字符。若一开头没有数字则转换为0
\$foo = 1 + "bob-1.3e3"; // $foo is integer (1)
\$foo = 1 + "bob3"; // $foo is integer (1)
\$foo = 1 + "10 Small Pigs"; // $foo is integer (11)
'' == 0 == false
'123' == 123
'abc' == 0
'123a' == 123
'0x01' == 1
'0e123456789' == ‘0e987654321'
[false] == [0] == [NULL] == [‘']
NULL == false == 0» true == 1
eregi()默认接收字符串参数,若是传入数组,函数会报错并返回NULL。同时还能够%00 截断进行绕过
parse_str("na.me=admin&pass wd=123",$test); var_dump($test); array(2) { ["na_me"]=> string(5) "admin" ["pass_wd"]=> string(3) "123" }
in_arrary(“1asd”,arrart(1,2,3,4)) => true in_arrary(“1asd”,arrart(1,2,3,4),TRUE) => false \\(须要设置strict参数为true才会进行严格比较,进行类型检测)
printf()和sprintf()函数中能够经过使用%接一个字符来进行padding功能
例如%10s 字符串会默认在左侧填充空格至长度为10,还能够 %010s 会使用字符0进行填充,可是若是咱们想要使用别的字符进行填充,须要使用 ‘ 单引号进行标识,例如 %’#10s 这个就是使用#进行填充(百分号不只会吃掉’单引号,还会吃掉\ 斜杠)
同时sprintf()可使用指定参数位置的写法
](http://or48znikk.bkt.clouddn.com/dmsj/dmsj17.png)
%后面的数字表明第几个参数,$后表明格式化类型
因而当咱们输入的特殊字符被放到引号中进行转义时,可是又使用了sprintf函数进行拼接时
例如%1$’%s’ 中的 ‘%被当成使用%进行padding,致使后一个’逃逸了
还有一种状况就是’被转义成了\’,例如输入%’ and 1=1#进入,存在SQL过滤,’被转成了\’
因而sql语句变成了 select * from user where username = '%\' and 1=1#’;
若是这个语句被使用sprintf函数进行了拼接,%后的\被吃掉了,致使了’逃逸
<?php $sql = "select * from user where username = '%\' and 1=1#';"; $args = "admin"; echo sprintf( $sql, $args ) ; //result: select * from user where username = '' and 1=1#' ?>
不过这样容易遇到 PHP Warning: sprintf(): Too few arguments
的报错
这个时候咱们可使用%1$来吃掉转移添加的\
<?php $sql = "select * from user where username = '%1$\' and 1=1#' and password='%s';"; $args = "admin"; echo sprintf( $sql, $args) ; //result: select * from user where username = '' and 1=1#' and password='admin'; ?>
例如is_numeric($a) and is_numeric($b)
程序本意是要a、b都为数字才会继续,可是当$a为数字and即返回TRUE,即 true and false >> true
http://user@eval.com:80@baidu.com
进行解析时,PHP获取的host是baidu.com是容许访问的域名,而最后调用libcurl进行请求时则是请求的eval.com域名,能够形成ssrf绕过https://evil@baidu.com
这样的域名进行解析时,php获取的host是evil@baidu.com
,可是libcurl获取的host倒是evil.comfilter_var()函数对于http://evil.com;google.com
会返回false也就是认为url格式错误,可是对于0://evil.com:80;google.com:80/
、0://evil.com:80,google.com:80/
、0://evil.com:80\google.com:80/
却返回true。
例如以下代码
if(filter_var($argv[1], FILTER_VALIDATE_URL)) { // parse URL $r = parse_url($argv[1]); print_r($r); // check if host ends with google.com if(preg_match('/baidu\.com$/', $r['host'])) { // get page from URL $a = file_get_contents($argv[1]); echo($a); } else { echo "Error: Host not allowed"; } } else { echo "Error: Invalid URL"; }
虽然经过filter_var函数对url的格式进行检查,而且使用正则对url的host进行限定
可是能够经过data://baidu.com/plain;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pgo=
页面会将<script>alert(1)</script>
返回给客户端,就有可能形成xss