C#|多次握手登录

社会险恶啊,现在做系统做个简单的用户密码判断然后登录已经不好使了。安保的搞个爬虫抓包登录请求就可以让爬虫用登录持有会话然后爬行网站。然后就各种提,我这用个Cache库,都是对象存储的,如果被抓到一个表报错是SQL就提你系统有SQL注入漏洞,真是有理说不清。先不说我这是Cache对象存储。就是关系库我用SQL参数了啊。被你抓个SQL日志就SQL注入风险了,真是无语。
安保的得力工具是爬虫。拿个爬虫去现场,一个什么不懂的小白都能提一堆你不得不改的问题来。爬虫爬行整个网站还得抓一下登录的包,然后用登录提交的东西模拟登录保持会话爬行。所以首先把门得守住,让爬虫不能轻易抓取登录时候请求模拟登录,简单的一次性提交登录肯定是不行了。理论上把登录的口子守住了系统起码安全一半。
所以改进登录为多次握手登录,大概以下步骤。
1.js提交验证用户密码时候顺带生成一个时间戳和他的哈希值提交给后台,并且提交用RSA加密(服务端给JS加密数据用一对秘钥,JS给服务端加密数据用一对秘钥)。
2.服务端收到数据后用服务端持有的RSA秘钥界面JS数据。然后哈希校验时间戳。对非法时间戳直接拉黑IP一天访问。
3.服务端如果校验时间戳通过且验证用户密码通过。那么把JS提交时间戳加上服务器时间形成累计的时间戳,再哈希得到验证值。然后把服务端组装的新时间戳串用JS提交的时间戳哈希值当做密码DES加密。再把加密数据用服务端RSA秘钥加密返回给JS端。
4.JS端登录时候需要把服务端返回的时间戳和登录新构造的时间戳提交到后台。
5.后台解密服务端时间戳,先验证里面第一次验证用户密码时候的时间戳合法性。然后再验证服务端时间戳整体合法性。然后再计算当前时间和服务端时间戳时间差不能大于三分钟。
6.然后再验证JS第二次时间戳和第一次时间戳之差不能大于3分钟。
7.验证登录后再登录构造会话,构造会话成功后马上把当前用户最后时间戳存入会话。如果登录提交的时间戳已经使用过就不给登录。防止爬虫跟随登录页抓包潜入进来(相同的服务端时间戳只让登录一次)。
通过上面多次握手和多种加密结合防止爬虫轻易抓包模拟登录。
【C#|多次握手登录】JS得到时间戳方法,结合MD5和RSA加密:

//得到时间戳 function GetTimeSpan() { var myDate = new Date(); var hours = myDate.getHours(); if (hours < 10) { hours = "0" + hours; } var minutes = myDate.getMinutes(); if (minutes < 10) { minutes = "0" + minutes; } var seconds = myDate.getSeconds(); if (seconds < 10) { seconds = "0" + seconds; } var timeStr = GetCurentDate() + " " + hours + ":" + minutes + ":" + seconds; var random = Math.random(); var transStr = hex_md5(timeStr + "_" + random).toUpperCase(); return RsaEncrypt(timeStr + "_" + random+ "^" + transStr ); }

验证密码提交第一次时间戳
C#|多次握手登录
文章图片

校验片段示例(实际会加拉黑操作):
//时间戳 string TimeSpan = Helper.ValidParam(LIS.Core.MultiPlatform.LISContext.GetRequest(Request, "TimeSpan"), ""); //解密时间戳数据 TimeSpan = LIS.Core.Util.RsaUtil.RsaDecrypt(TimeSpan); //验证时间戳 string[] timeSpanArr = TimeSpan.Split('^'); if (timeSpanArr.Length != 2) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "非法的时间戳数据!"; return Helper.Object2Json(errRet); } else { string timeSource = timeSpanArr[0]; timeSource = LIS.Util.Md5Util.getMd5Hash(timeSource); if (timeSpanArr[1] != timeSource) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "非法的时间戳数据!"; return Helper.Object2Json(errRet); } } //拼接服务器时间做服务器时间戳 string RetTimeSpanStr = TimeSpan + "^" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //得到时间戳验证哈希 string RetTimeSpanStrHash = LIS.Util.Md5Util.getMd5Hash(RetTimeSpanStr); //返回的时间戳。用JS的时间戳哈希值当密码加密 string RetTimeSpan = LIS.Common.Login.DesUtilCommon.EncryptDES(RetTimeSpanStr + "@" + RetTimeSpanStrHash, timeSpanArr[1]); //二次加密 RetTimeSpan = LIS.Common.Login.DesUtilCommon.EncryptDES(RetTimeSpan + "@" + timeSpanArr[1], "testzz"); RetResultMsg ret = LgService.CheckUser(userCode, password, Session); //返回时间戳 ret.TimeSpan = RetTimeSpan; //返回之前再RSA加密

登录时候把第二次JS时间戳和服务端时间戳抛给后台:
C#|多次握手登录
文章图片

登录时候验证片段,从第一段哈希开始每段都得验证符合,并且在时间范围才放行(时间会加拉黑一天操作)。
//JS时间戳 string JSTimeSpan = Helper.ValidParam(LIS.Core.MultiPlatform.LISContext.GetRequest(Request, "JSTimeSpan"), ""); //解密时间戳数据 JSTimeSpan = LIS.Core.Util.RsaUtil.RsaDecrypt(JSTimeSpan); //验证时间戳 string[] jsTimeSpanArr = JSTimeSpan.Split('^'); //JS第一次时间戳时间 string JSOneTime = ""; //JS第二次时间戳时间 string JSTowTime = ""; if (jsTimeSpanArr.Length != 2) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "非法的时间戳数据!"; return Helper.Object2Json(errRet); } else { string timeSource = jsTimeSpanArr[0]; timeSource = LIS.Util.Md5Util.getMd5Hash(timeSource); if (jsTimeSpanArr[1] != timeSource) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "非法的时间戳数据!"; return Helper.Object2Json(errRet); } JSTowTime = jsTimeSpanArr[0].Split('_')[0]; }string LISTimeSpan = Helper.ValidParam(LIS.Core.MultiPlatform.LISContext.GetRequest(Request, "LISTimeSpan"), ""); LISTimeSpan = LIS.Core.Util.RsaUtil.RsaDecrypt(LISTimeSpan); //一次解密 LISTimeSpan = LIS.Common.Login.DesUtilCommon.DecryptDES(LISTimeSpan, "testzz"); string[] timeSpanArr = LISTimeSpan.Split('@'); //二次解密 LISTimeSpan = LIS.Common.Login.DesUtilCommon.DecryptDES(timeSpanArr[0], timeSpanArr[1]); timeSpanArr = LISTimeSpan.Split('@'); if (timeSpanArr.Length != 2) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "非法的时间戳数据!"; return Helper.Object2Json(errRet); } else { string timeSource = timeSpanArr[0]; string timeSourceSpan = LIS.Util.Md5Util.getMd5Hash(timeSource); if (timeSpanArr[1] != timeSourceSpan) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "非法的时间戳数据!"; return Helper.Object2Json(errRet); } string[] timeArr = timeSource.Split('^'); if (timeArr.Length != 3) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "非法的服务时间戳数据!"; return Helper.Object2Json(errRet); } else { string timeSourceSpanJS = LIS.Util.Md5Util.getMd5Hash(timeArr[0]); if (timeSourceSpanJS != timeArr[1]) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "非法的JS时间戳数据!"; return Helper.Object2Json(errRet); } JSOneTime = timeArr[0].Split('_')[0]; string timeStr = timeArr[2]; DateTime spanTime = Convert.ToDateTime(timeStr); if ((DateTime.Now - spanTime).TotalMinutes > 3) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "时间戳已过期,请重新校验!"; return Helper.Object2Json(errRet); } //比较两次JS时间戳的时间差 DateTime JSOneSpanTime = Convert.ToDateTime(JSOneTime); DateTime JSTowSpanTime = Convert.ToDateTime(JSTowTime); if ((JSTowSpanTime - JSOneSpanTime).TotalMinutes > 3) { RetResultMsg errRet = new RetResultMsg(); errRet.Code = -1; errRet.IsOk = false; errRet.Message = "JS时间戳已过期,请重新校验!"; return Helper.Object2Json(errRet); } } }//上一个时间戳 string PreLISTimeSpan = LIS.Core.MultiPlatform.LISContext.GetSession(Session, "TimeSpan_" + UserLogin.UserDR); //防止爬虫跟随进来 if (PreLISTimeSpan == LISTimeSpan) { return Helper.Error("爬虫恶意的跟随登录!"); } RetResultMsg ret = LgService.UserLogin(UserLogin.SysID, UserLogin, Session); //登录成功 if (ret.IsOk) { LIS.Core.MultiPlatform.LISContext.SetSession(Session, "TimeSpan_" + UserLogin.UserDR, LISTimeSpan); }

通过多次交互,来回累计哈希和加密解密来防止爬虫简单抓包登录。这样爬虫得把每步交互搞清除,并且实现所有JS交互部分逻辑才能爬行登录。并且错误尝试还会受到拉黑一天的处理。从而包含网站不被轻易爬取。
反正就是一条,交互越复杂越好,频繁的累计哈希戳,任意一步构造数据不符合验证就拉黑。
交互示例:
验证密码提交
C#|多次握手登录
文章图片

验证密码返回
C#|多次握手登录
文章图片

登录提交,一个服务时间戳只让登进去一个会话
C#|多次握手登录
文章图片

一样的串跟随提交是不会给他构造会话的
C#|多次握手登录
文章图片

想靠抓包重复使用登录连接也是不可以的,有时效
C#|多次握手登录
文章图片

伪造提交串也是有严格流程格式限制的
C#|多次握手登录
文章图片

    推荐阅读