标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2025-1402] 作者: ecawen 发表于: [2018-03-25]
本文共 [501] 位读者顶过
随着代码安全的普及,越来越多的开发人员知道了如何防御sqli、xss等与语言无关的漏洞,但是对于和开发语言本身相关的一些漏洞和缺陷却知之甚少,于是这些点也就是我们在Code audit的时候的重点关注点。本文旨在总结一些在PHP代码中经常造成问题的点,也是我们在审计的时候的关注重点。(PS:本文也只是简单的列出问题,至于造成问题的底层原因未做详细解释,有兴趣的看官可以自行GOOGLE或者看看底层C代码。知其然,且知其所以然) 本文若有写错的地方,还请各位大佬斧正 :) TODO: 继续丰富并增加各个点的实际漏洞事例 file_put_contents、copy、file_get_contents等读取写入操作与unlink、file_exists等删除判断文件函数之间对于路径处理的差异导致的删除绕过例如如下代码 <?php $filename = __DIR__ . '/tmp/' . $user['name']; $data = $user['info']; file_put_contents($filename, $data); if (file_exists($filename)) { unlink($filename); } ?>
这里引用小密圈中P牛的解读
于是乎linux可以通过xxxxx/../test.php、test.php/. windows可以通过test.php:testtest.ph<来绕过文件删除 此外发现还可以使用伪协议php://filter/resource=1.php在file_ge_contents、copy等中读取文件内容,却可以绕过文件删除 extract()、parse_str() 等变量覆盖extract函数从数组导入变量(如\$_GET、 \$_POST),将数组的键名作为变量的值。而parse_str函数则是从类似name=Bill&age=60的格式字符串解析变量.如果在使用第一个函数没有设置EXTR_SKIP或者EXTR_PREFIX_SAME等处理变量冲突的参数时、第二个函数没有使用数组接受变量时将会导致变量覆盖的问题 intval()整数溢出、向下取整和整形判断的问题
因此,在32位系统上 intval('1000000000000') 会返回 2147483647
浮点数精度问题导致的大小比较问题当小数小于10^-16后,PHP对于小数就大小不分了 var_dump(1.000000000000000 == 1) >> TRUE var_dump(1.0000000000000001 == 1) >> TRUE is_numeric()与intval()特性差异
而'.'可以出现在任意位置,E、e能出现在参数中间,仍可以被判断为数字。也就是说is_numeric("\r\n\t 0.1e2") >> TRUE
strcmp()数组比较绕过int strcmp ( string $ str1 , string \$str2 ) 参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。 但是如果传入的两个变量是数组的话,函数会报错返回NULL,如果只是用strcmp()==0来判断的话就可以绕过 sha1()、md5() 函数传入数组比较绕过sha1() MD5()函数默认接收的参数是字符串类型,但是如果如果传入的参数是数组的话,函数就会报错返回NULL。类似sha1(\$_GET['name']) === sha1(\$_GET['password’])的比较就可以绕过 弱类型==比较绕过这方面问题普及的很多,不作过多的解释
eregi()匹配绕过eregi()默认接收字符串参数,如果传入数组,函数会报错并返回NULL。同时还可以%00 截断进行绕过 PHP变量名不能带有点[.] 和空格,否则在会被转化为下划线[_]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()函数默认进行松散比较(进行类型转换)in_arrary(“1asd”,arrart(1,2,3,4)) => true in_arrary(“1asd”,arrart(1,2,3,4),TRUE) => false \\(需要设置strict参数为true才会进行严格比较,进行类型检测)
htmlspecialchars()函数默认只转义双引号不转义单引号,如果都转义的话需要添加上参数ENT_QUOTES在php4、php<5.2.1中,变量的key值不受magic_quotes_gpc影响sprintf()格式化漏洞(可以吃掉转义后的单引号)printf()和sprintf()函数中可以通过使用%接一个字符来进行padding功能 例如%10s 字符串会默认在左侧填充空格至长度为10,还可以 %010s 会使用字符0进行填充,但是如果我们想要使用别的字符进行填充,需要使用 ‘ 单引号进行标识,例如 %’#10s 这个就是使用#进行填充(百分号不仅会吃掉’单引号,还会吃掉\ 斜杠) 同时sprintf()可以使用指定参数位置的写法 %后面的数字代表第几个参数,$后代表格式化类型 于是当我们输入的特殊字符被放到引号中进行转义时,但是又使用了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'; ?>
php中 = 赋值运算的优先级高于and$c = is_numeric($a) and is_numeric($b) 程序本意是要a、b都为数字才会继续,但是当$a为数字时,会先赋值给$c,所以可能导致$b绕过检测 parse_url与libcurl对与url的解析差异可能导致ssrf
url标准的灵活性导致绕过filter_var与parse_url进行ssrffilter_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。 通过file_get_contents获取网页内容并返回到客户端有可能造成xss例如如下代码 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 |