好久没写博客了,这2周都在搞Exchange安全相关的东西,终于搞完了,算告一段落吧,打算拿出来和大家分享一下。


很多公司Exchange部署 完成后都是通过TMG,或者直接把CAS发布到公网上,让用户在家或者非公司办公网络 登录邮箱进行邮件收发的。类似这种的文章大把大把,我就不多说了但是这种场景会存在安全风险


无法防止暴力破解


虽然AD账号有锁定机制,但是当有人暴力破解Exchange时,让你的账号全锁了,这个也是影响业务的


所以对于发布到公网的应用我们需要谨慎,接下来我分享一下我的方案,当然解决这个问题的方案不少,仅供参考,需要实现这套方案,开发能力是需要有的。


1.针对OWA,标准的WebSite,爆破一般人都能搞,都不用黑,针对发布到公网的OWA方案如下:


1.双因子认证,我这边用的是ADUser  ADPassword+动态口令的方式,意思是用户登录OWA是输入邮箱账号,密码输入邮箱密码+6位的动态口令


动态口令用的google authenticator ,讲白了这就是一套C/S的算法,可以接入任何用作身份验证的应用,因为我们的***用了他,所以OWA延续使用,具体怎么部署,网上大把


接下来就是反向代理的事情了,我用的是Nginx,需要对Nginx做二次开发,要么C,要么Lua,C我差点意思,但性能好,所以只能Lua了,那开始吧。


产品用的:openresty,为啥是他,因为他方便点,原生Nginx单独装一个lua的module也可以,安装openresty,去官网下载安装文档安装。安装完把OWA的证书改成linux的证书格式,然后在Nginx上配好,保证用户https-Nginx-OWA没问题,具体怎么配https,上网搜,不是关键点


OWA身份验证的页面:/owa/auth/logon.aspx

OWA处理Post请求的页面:/owa/auth.owa


 所以我们需要对这两个页面做Location 


Location /owa/auth/logon.aspx {proxy_pass https://10.169.100.34;}      //登陆页面直接做转发 

Location /owa/auth.owa {       //处理Post请求需要有lua做截包,然后处理完了再做转发 

    access_by_lua_file conf/lua/auth.lua;  

    proxy_pass https://10.169.100.34};

}

auth.lua文件的逻辑:


Username  Password Post过来,到Nginx上了,此时ngx.req.read_body() 读出Username和password两个参数,此时password取后六位,即动态密码,然后HttpPost到自己写的API上去身份验证,返回true,  通过ngx.req.set_body_data(newbody),把Username和真正的没有动态口令的Password,封存新的报文往后做转发,转发之前,HttpPost到自己写的API上生成Token,为啥要Token,待会解析,当Token返回True的时候,直接通过Nignx Set-Cookie把Token下发到客户端浏览器,整个验证的过程就结束了,期间任何一个返回false,就redirect到/owa/auth/logon.aspx 即可


现在来解释为啥要用Token,因为OWA的Session保持机制是这样子的:

用户登录后,服务器会生成一个加密的cookie下发给用户,接下来,用户和服务器通信都是用这个cookie,当然这个cookie是不能被伪造的,因为加密的算法你不知道,然后当用户不活动一段时间后,服务器检测不到用户的活动,那么客户端的cookie就会过期,此时服务器的session失效,用户需要重新登陆


这样子的机制其实是存在一定问题的,比如通过xss拿到用户的cookie,然后定时发个请求一直保持和服务器的连接,然服务器知道我一直在活动的,这样子邮箱就一直在线了,这个问题还是比较严重的,所以此时Token的介入就是为了解决这个问题,说白了就是我强制用户用token访问我的任何页面,token过期的话,nginx直接redirect,这样子其实是牺牲了一定用户体验的,比如我把token时效设置为8个小时,8个小时后,不管你用户是否一直在活动,网页直接让你重新登陆,但之前如果没有token的话,只要用户一直活动,sesstion一直有效。


好,接下来继续,token下发完成后,接下来任何一个owa的请求都得让nginx验证你的token是否合法,所以我们对于Token的加密算法需要仔细考虑一下,一般都是post过来的几个参数+言+动态因子,然后做个截断我觉得就Ok


Location /owa {       //每个页面的请求都做Token验证,然后处理完了再做转发 

    access_by_lua_file conf/lua/token.lua;  

    proxy_pass https://10.169.100.34};

}


token验证流程就是先拿到用户的cookie中的 token,然后HttpPost到自己写的API做token的验证,返回true,直接转发,false,直接redirect


这个流程就是这样子,上面牵涉到lua如何做http Post请求,代码如下:

先封一个module ,名字为http_post_module.lua,代码如下


local function http_post(url,body)

   local http = require("resty.http")

   local httpc = http.new()

   local resp, err = httpc:request_uri(url, {

         method = "POST",

         body = body,

         headers = {

                 ["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"

         }

   })

   local result = resp.body

   httpc:close()

   return result

end


local _M = {

   http_post = http_post

}


return _M


然后真正的lua文件中引用是:

local module = require("http_post_module")

local result = module.http_post(url,body)


当然默认lua是不支持httpPost的,所以最开始你需要去download两个类库放到指定的位置,如下:


/usr/local/openresty/lualib/resty  这下面需要放 http_headers.lua 和http.lua 这两个类,具体的需要去网上下载


剩下的就是Post过去你自己写的API了,这个就简单了,可以用你熟悉的任何语言写,我用的是php,后台DB用的mysql,存的token和用户的对应表

php的API 里面有的方法:


getDynamicCode()

getADPassword()

verifyDynamicCode()

sToken()

desToken()等等,逻辑自己写,比较简单,这里不罗嗦了,当然还有一些数据库的方增删改查的方法,很常规的,不多说


这样子整体就算差不多了,之所以写这个API,第一是lua不太熟,在上面实现API的逻辑比较麻烦,Php熟悉所以快,第二考虑nginx的性能,尽量让nginx上面的代码少


好了,owa的安全说完了,下次该说手机端了