Web登陸防爆破的原理和實現


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  
nginx.conf

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()
app.py
 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>
index.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()
app.py
 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>
index.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 }
nginx.conf

4.3 問題

有些用戶不知情的情況下被人當肉雞,然后被黑名單Ban掉,影響正常使用、、

 

5、寫在最后

還是那句老話,安全沒有銀彈

這些措施加在一起也並不能防止代理池+圖片識別工具,但可以大幅度增加爆破的時間

然而很可能對用戶正常使用產生影響、、


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM