引子
事情是这样的,这次小马写了一个关于判断前端传参是否有汉字并对汉字进行校验的功能,代码如下:
又听到很多同学说,要特别注意urldecode的二次解码注入问题。于是搜索了相关资料,大部分说的关于这个urldecode二次解码引发注入的问题是这样的:
因为对于编码的字符串 ,PHP本身在处理提交的数据之前会进行一次urldecode解码(大豆存在于PHP5.3以上),然后一般后端接收到参数,自然而然会再进行一次urldecode函数解码,因为和urlencode()成双成对的嘛。这原本看起来也没什么问题,因为两次urldecode并不会影响结果值。比如urldecode('你好'),得到的还是‘你好’,自然问题不在于这里。
urldecode二次解码有注入风险
问题在于:如果我们构造字符串/getUser.php?id=%2527520%2527,提交后PHP自行解码,%25解码成了%,得到url是/getUser.php?id=%27520%27;注意了,这个时候,我们的urldecode()函数登场了,正儿八经地进行了一次解码,将%27解码成了’,于是最终得到的url是 /test.php?id='520'。原理就是: echo urldecode(urldecode('%2527520%2527')); 得到 '520'。
在后端得到这个url是非常敏感的,因为这很容易让人想到单引号SQL注入。PHP中常用的过滤函数如addslashes()、mysql_real_escape_string()、mysql_escape_string()或者使用魔术引号GPC开关来防止注入,原理都是给单引号(’)、双引号(”)、反斜杠(\)和NULL等特殊字符前面加上反斜杠来进行转义。而这些都容易被urlencode后的字符串绕过。如%2527520%2527很容易被校验和过滤函数直接通过,但其实解码后是含有特殊字符的,这样就容易有SQL注入风险,只要构造playload让id二次解码后等于 '520' or 1=1 就可以注入了。让URL解码后得到?id='520' or 1=1将得到如下语句,获取得到所有用户数据:
select * form user where id='520' or 1=1;
其实没那么可怕
不要慌,其实没那么可怕。这个只是在于参数校验失守,底层DAO又没在最终入库的时候转义特殊字符才有可能发生的。但是这两道防线目前基本很少被攻破,因为主流的框架大多数接收处理后,在入库之前都会进行参数校验和过滤,一般都封装了底层的DAO,在入库之前还有最后一层mysql_real_escape_string()转义特殊字符,如果不去修改底层,一般问题不大。除非自己写的架子,多注意一下就好了。
总结
如何避免这个问题的总结:
从前端接收完提交参数,urldecode完尽量先做参数处理,在参数进入逻辑之前最后进行参数校验和过滤,如开头的代码图,再次校验参数中含有的字符是否符合规范;前面不管如何处理,如何转码,入库之前一定要做最后的转义(转义在最后,不要先转义再urldecode再入库,这样就很危险了)。顺带一提:rawurldecode()也会产生同样的问题,因此在使用这两个函数都需要多注意一下就可以了。