0、寫在前面
本來只想做個驗證碼的功能實現,后來想想光要驗證碼也不行,干脆寫整個防爆破的實現吧
1、防護軟件/硬件Waf/Web服務器限制單IP固定時間段的登陸頻率
1.1 作用
通過WAF可以實現某一個IP訪問頻率過高時則將此IP加入黑名單一段時間
通過Nginx等Web服務器可以實現限制單IP固定時間段的登陸頻率,也就是限制流量
可以防爆破的同時一定程度上防止DDos攻擊
1.2 實現

1 http{ 2 ... 3 4 #定義一個名為allips的limit_req_zone用來存儲session,大小是10M內存, 5 #以$binary_remote_addr 為key,限制平均每秒的請求為20個, 6 #1M能存儲16000個狀態,rete的值必須為整數, 7 #如果限制兩秒鍾一個請求,可以設置成30r/m 8 9 limit_req_zone $binary_remote_addr zone=allips:10m rate=20r/s; 10 ... 11 server{ 12 ... 13 location { 14 ... 15 16 #限制每ip每秒不超過20個請求,漏桶數burst為5 17 #brust的意思就是,如果第1秒、2,3,4秒請求為19個, 18 #第5秒的請求為25個是被允許的。 19 #但是如果你第1秒就25個請求,第2秒超過20的請求返回503錯誤。 20 #nodelay,如果不設置該選項,嚴格使用平均速率限制請求數, 21 #第1秒25個請求時,5個請求放到第2秒執行, 22 #設置nodelay,25個請求將在第1秒執行。 23 24 limit_req zone=allips burst=5 nodelay; 25 ... 26 } 27 ... 28 } 29 ... 30 } 31
1.3 問題
攻擊者可以通過代理池的方式來繞過
2、WebApp限制單用戶固定時間段的登陸頻率
2.1 作用
極大的拖慢爆破的速度,通常一小時錯誤6次就要鎖賬號
而且可以被鎖定時發郵件提醒用戶(感覺最多也就是讓人心里有個數,但用沒什么卵用)
2.2 實現
用戶名密碼等信息為了簡化都保存在字典中,正常都應在數據庫中

1 # -*- coding: utf-8 -*- 2 from flask import Flask,render_template,request,flash,redirect,url_for 3 import datetime 4 5 app = Flask(__name__) 6 app.secret_key = 'zz' 7 8 user_dict = { 9 'admin':{ 10 'username': 'admin', 11 'password': 'admin', 12 'count': 0, 13 'last_time': '', 14 'new_time': '', 15 'locked': False, 16 }, 17 'test': { 18 'username': 'test', 19 'password': 'test', 20 'count': 0, 21 'last_time': '', 22 'new_time': '', 23 'locked': False, 24 } 25 } 26 27 28 29 @app.route('/',methods=['POST','GET']) 30 def hello_world(): 31 if request.method == 'POST': 32 username = request.form.get('username') 33 password = request.form.get('password') 34 #驗證用戶是否存在,存在則繼續,不存在則返回用戶不存在 35 if user_dict.get(username): 36 #驗證用戶是否被鎖 37 if user_dict[username]['locked']: 38 #驗證被鎖時間是否達到3600秒,達到則用戶登陸計數清零,鎖定狀態變為未鎖定 39 if (datetime.datetime.now()-user_dict[username]['last_time']).seconds>3600: 40 user_dict[username]['count'] = 0 41 user_dict[username]['locked'] = False 42 flash('you are unlocked') 43 return redirect(url_for('hello_world')) 44 flash('you are locked') 45 return redirect(url_for('hello_world')) 46 else: 47 if username==user_dict[username]['username'] and password==user_dict[username]['password']: 48 return 'ok' 49 else: 50 if user_dict[username]['count'] == 0: 51 user_dict[username]['count']+=1 52 user_dict[username]['last_time'] = datetime.datetime.now() 53 user_dict[username]['new_time'] = datetime.datetime.now() 54 flash('password is error') 55 elif user_dict[username]['count'] == 6: 56 user_dict[username]['new_time'] = datetime.datetime.now() 57 user_dict[username]['last_time'] = datetime.datetime.now() 58 user_dict[username]['locked'] = True 59 flash('you tried 6 times and you are locked 3600 seconds') 60 else: 61 user_dict[username]['count'] += 1 62 user_dict[username]['last_time'] = user_dict[username]['new_time'] 63 user_dict[username]['new_time'] = datetime.datetime.now() 64 flash('password is error') 65 return redirect(url_for('hello_world')) 66 else: 67 flash('username errors') 68 return redirect(url_for('hello_world')) 69 return render_template('index.html') 70 71 72 73 74 if __name__ == '__main__': 75 app.run()

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="" method="post"> 9 username: <input type="text" name="username"><br> 10 password: <input type="password" name="password"><br> 11 <input type="submit"><label>count:{{ count }}</label> 12 </form> 13 <hr> 14 {% for msg in get_flashed_messages() %} 15 <p>{{ msg }}</p> 16 {% endfor %} 17 </body> 18 </html>
2.3 問題
可以故意輸錯密碼讓原賬戶登陸不了,造成原用戶也無法使用
3、扭曲的圖片驗證碼
3.1、作用
為了防止重放攻擊
不能每次post和get都改變的驗證碼沒有意義
3.2、實現
驗證碼的生成、獲取、更新、核對全部在后端進行
這里為了簡化用戶名密碼沒有加密保存在數據庫中,驗證碼也沒有生成圖片顯示

1 from flask import Flask,render_template,request,session,flash,redirect,url_for 2 import random 3 4 app = Flask(__name__) 5 app.secret_key = 'zz' 6 7 user_dict = { 8 'username':'admin', 9 'password':'admin', 10 } 11 12 check_code_now = { 13 'check_code':'' 14 } 15 16 def get_code(): 17 ascii_num = [48,49,50,51,52,53,54,55,56,57] 18 ascii_chr1 = [i for i in range(65,91)] 19 ascii_chr2 = [i for i in range(97,123)] 20 ascii = ascii_chr1+ascii_chr2+ascii_num 21 random_pool = [chr(i) for i in ascii] 22 check_code = random.choice(random_pool)+random.choice(random_pool)+random.choice(random_pool)+random.choice(random_pool) 23 check_code_now['check_code'] = check_code 24 return check_code 25 26 27 @app.route('/',methods=['POST','GET']) 28 def hello_world(): 29 if request.method == 'POST': 30 username = request.form.get('username') 31 password = request.form.get('password') 32 check_code = request.form.get('check_code') 33 if check_code_now.get('check_code')!= check_code: 34 flash('check_code error') 35 get_code() 36 return redirect(url_for('hello_world')) 37 if username == user_dict.get('username') and password == user_dict.get('password'): 38 return 'ok' 39 else: 40 flash('username or password error') 41 get_code() 42 return redirect(url_for('hello_world')) 43 check_code = get_code() 44 return render_template('index.html',check_code = check_code) 45 46 47 48 49 if __name__ == '__main__': 50 app.run()

1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="" method="post"> 9 username: <input type="text" name="username"><br> 10 password: <input type="password" name="password"><br> 11 checkcode:<input type="text" name="check_code"><label style="color: red">{{ check_code }}</label><br> 12 <input type="submit"> 13 </form> 14 <hr> 15 {% for msg in get_flashed_messages() %} 16 <p>{{ msg }}</p> 17 {% endfor %} 18 </body> 19 </html>
3.3、問題
不要將驗證碼保存在session中
不管是Flask(將session加密存儲在瀏覽器cooikes中),還是Django(將session保存在數據庫中,cooikes只保存sessionID)
重放攻擊中每次的session都不變,去session中核對驗證碼每次都是不變的,那驗證碼將沒有意義
另外扭曲的驗證碼會讓用戶都看不明白,而且某些圖片識別還是能識別、、
4、IP段黑白名單
4.1 作用
通過Web服務器(Nginx等)實現
黑名單可以把某些IP直接Ban掉
對內網的WebApp白名單很有用
4.2 實現

1 location / { 2 deny 192.168.1.1; 3 allow 192.168.1.0/24; 4 allow 10.1.1.0/1 5 }
4.3 問題
有些用戶不知情的情況下被人當肉雞,然后被黑名單Ban掉,影響正常使用、、
5、寫在最后
還是那句老話,安全沒有銀彈
這些措施加在一起也並不能防止代理池+圖片識別工具,但可以大幅度增加爆破的時間
然而很可能對用戶正常使用產生影響、、