04 26,2016

WEB/APP开发安全

最近公司被攻击了,然后才发现公司代码简直一点安全防范意识都没有,连基本的SQL,XSS过滤都没有,发现大部分PHP程序员都缺乏安全相关的了解。下面就我们遇到的一些攻击进行科普以及防范方案。

SQL注入

SQL注入是WEB攻击中最常见的注入方式。也是我们这次被攻击中最多的。
由于我们的程序中大部分都是进行拼装SQL的方式,然后对于用户输入的参数也没有进行过滤,导致给攻击者留下了漏洞。
具体为,登录的操作,一般是通过POST方式将username和password传递过来。

SELECT * FROM user WHERE username='$username' AND password='$password'

正常情况下,我们希望执行的SQL是

SELECT * FROM user WHERE username='vckai' AND password='123456'

但是如果攻击者传递的用户为非法用户名vckai' or 1=1--,在SQL里面—是注释标记,所以查询语句会在此中断。这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。

SELECT * FROM user WHERE username='vckai' or 1=1--' AND password='123456'

甚至于,攻击者可以用同样的方式执行其他SQL,查询出你其他DB和表数据。

现在知道SQL注入的危害了,那怎么预防呢?

  • 首先在PHP中,首先对于int类型的数据,一定要在接收数据的时候进行(int) $_POST['pagesize']进行强转义。
  • 根据业务做参数验证,如用户名一般只允许4-20位字符和数字。这样攻击者就没办法根据这个进行注入了。
  • 虽然MySQL和PHP本身由提供过滤函数addslashesmysql_real_escape_string进行转义,但是仍然有被绕过的风险,所以最佳方法是使用PDO的预处理。

XSS攻击

XSS通常可以分为两大类:一类是存储型XSS,主要出现在让用户输入数据,供其他浏览此页的用户进行查看的地方,包括留言、评论、博客日志和各类表单等。应用程序从数据库中查询数据,在页面中显示出来,攻击者在相关页面输入恶意的脚本数据后,用户浏览此类页面时就可能受到攻击。这个流程简单可以描述为:恶意用户的Html输入Web程序->进入数据库->Web程序->用户浏览器。另一类是反射型XSS,主要做法是将脚本代码加入URL地址的请求参数里,请求参数进入程序后在页面直接输出,用户点击类似的恶意链接就可能受到攻击。

XSS目前主要的手段和目的如下:

  • 盗用cookie,获取敏感信息。
  • 利用植入Flash,通过crossdomain权限设置进一步获取更高权限;或者利用Java等得到类似的操作。
  • 利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击者)用户的身份执行一些管理动作,或执行一些如:发微博、加好友、发私信等常规操作,前段时间新浪微博就遭遇过一次XSS。
  • 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
  • 在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果

如何预防XSS攻击
答案很简单,坚决不要相信用户的任何输入,并过滤掉输入中的所有特殊字符。这样就能消灭绝大部分的XSS攻击。

  • 使用htmlspecialchars函数进行参数过滤,将<>转换为HTML实体。注意,有一些APP开发的时候,会直接输出HTML实体,导致用户正常数据处理错误,所以返回给APP的时候,建议再转义回来htmlspecialchars_decode,或者让客户端做统一处理。

APP签名和重放

APP每次请求的时候一般是通过TOKEN方式进行身份验证,相当于我得到你的TOKEN就可以进行了任意操作,例如查看用户信息,http://api.vckai.com/user/get,POST:[token:xxx, uid:1],这个时候被抓包获取到该URL,攻击者就可以通过工具或者写个脚本很轻易的只要修改UID参数就可以将你的用户资料都抓取了,再有就是发表评论或者帖子其他的。
所以我们这里需要加上签名机制,必须验证该签名通过才允许访问,否则进行拒绝。
约定一些公共参数。如:appid, client_flag, client_version, time, sign。 其中sign参数就是签名参数了。time为发起请求的时间,为了每次请求的签名不重复。
签名的具体生成规则可以是:

1) 除“sig”外的所有参数按key进行字典升序排列,排列结果为:appid, client_flag, client_version, uid, time, token
2) 将排序后的参数(key=value)用&拼接起来appid=11&client_flag=test&client_version=1.0.0&uid=1&time=1461659587&token=xxx
3) 将上一步拼装生成的字符串加上密钥(xxx,该密钥为服务端约定),进行MD5加密,生成签名。
4) 所以最终的请求为`http://api.vckai.com/user/get,POST:[token:xxx, uid:1, appid:11, client_flag:test, client_version:1.0.0, time:1461659587, sign:xxx]`。
5) 服务端需要对每个请求首先进行签名验证,验证不通过则直接拒绝该请求。所以这个时候直接修改参数进行请求是无效的了!

实现代码如下:

<?php

    $req = $_POST;
    $sign = $req['sign'];
    unset($req['sign']);

    ksort($req);
    $md5Str = http_build_query($req);
    // 加上与客户端预定的密钥,上面有一个appid,这里可以根据这个appid区分每个app不同的密钥。
    $md5Str .= 'xxxxxx';

    $checkSign = md5($md5Str);

    if ($checkSign !== $sign) {
        //签名验证失败
        return false;
    }

    return true;

OK,目前已经加上了签名验证,那是不是就没问题了呢?试想这样一个场景,登陆的时候,请求被非法抓包了,usernamepassword参数,其中password进行了MD5加密(登陆请求一定要将用户密码进行加密,禁止明文传输),所以用户拿到该密码无效。但是攻击者是不是就可以拿到你这个URL一直进行登陆操作了呢?除非你修改了密码。另外,发帖之类也是同样原理。

所以我们在请求的时候应该加上个过期机制,比如,这个请求发起,服务端进行验证的时候,判断时间大于1-5分钟就直接返回该请求已过期,让客户端重新发起请求。之前定义的参数中,有一个time参数,这个参数就是预留来做过期机制的,但是之前的time是客户端的当前时间,所以我们并不能使用这个时间,否则用户修改了本地时间就会导致该客户端无法使用。这样就需要一个时间同步机制了,客户端本地需要维护一个时钟。

  • 客户端每次启动的时候请求服务端接口获取时间戳,该接口只返回服务端当前时间戳。
  • 客户端将该时间戳存在本地,启动本地时钟计算器。
  • 服务端每次接口都返回time字段,让客户端更新本地时间戳。
  • 客户端定期请求服务端获取时间戳接口更新时间戳。(这个的具体机制看双方协商)

在PC开发中也存在签名问题,但是这个签名其实可以由PHP生成,保证这个签名只有一次可用。确保了请求是我们发起的

最后

  • 对于所有变量都进行明确的类型转换,对比时使用===!==,具体见:弱类型的危险。
  • 对于所有用户的输入进行过滤,可以使用鸟哥的taint扩展进行检测。
  • 开发完成使用sqlmap进行SQL注入校验。
  • 对于所有的MySQL操作使用PDO的预处理进行处理。
  • 上传文件进行类型检测,上传目录禁止执行权限。
  • 可以使用waf进行攻击拦截,有一定的基本拦截功能。
  • 禁止代码中使用eval执行用户的输入。

另外,有一些其他攻击,我没有全部进行一一详解,如:session/cookie劫持,CRFS,上传文件攻击等等。
以上应该可以杜绝大部分常见攻击,个人浅见,欢迎交流。