先來看一副很有意思的漫畫:
今天我們來聊一聊SQL注入相關的內容。
何謂SQL注入?
SQL注入是一種非常常見的數據庫攻擊手段,SQL注入漏洞也是網絡世界中最普遍的漏洞之一。大家也許都聽過某某學長通過攻擊學校數據庫修改自己成績的事情,這些學長們一般用的就是SQL注入方法。
SQL注入其實就是惡意用戶通過在表單中填寫包含SQL關鍵字的數據來使數據庫執行非常規代碼的過程。簡單來說,就是數據「越俎代庖」做了代碼才能干的事情。這個問題的來源是,SQL數據庫的操作是通過SQL語句來執行的,而無論是執行代碼還是數據項都必須寫在SQL語句之中,這就導致如果我們在數據項中加入了某些SQL語句關鍵字(比如說SELECT、DROP等等),這些關鍵字就很可能在數據庫寫入或讀取數據時得到執行。
多言無益,我們拿真實的案例來說話。下面我們先使用SQLite建立一個學生檔案表。
SQL數據庫操作示例
import sqlite3 # 連接數據庫 conn = sqlite3.connect('test.db') # 建立新的數據表 conn.executescript('''DROP TABLE IF EXISTS students; CREATE TABLE students (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);''') # 插入學生信息 students = ['Paul','Tom','Tracy','Lily'] for name in students: query = "INSERT INTO students (name) VALUES ('%s')" % (name) conn.executescript(query); # 檢視已有的學生信息 cursor = conn.execute("SELECT id, name from students") print('IDName') for row in cursor: print('{0}{1}'.format(row[0], row[1])) conn.close()
點擊運行按鈕將會打印目前表中的內容。上述程序中我們建立了一個test.db數據庫以及一個students數據表,并向表中寫入了四條學生信息。
那么SQL注入又是怎么一回事呢?我們嘗試再插入一條惡意數據,數據內容就是漫畫中的"Robert');DROP TABLE students;--",看看會發生什么情況。
SQL數據庫注入示例
conn = sqlite3.connect('test.db') # 插入包含注入代碼的信息 name = "Robert');DROP TABLE students;--" query = "INSERT INTO students (name) VALUES ('%s')" % (name) conn.executescript(query) # 檢視已有的學生信息 cursor = conn.execute("SELECT id, name from students") print('IDName') for row in cursor: print('{0}{1}'.format(row[0], row[1])) conn.close()
你將會發現,運行后,程序沒有輸出任何數據內容,而是返回一條錯誤信息:表單students無法找到!
這是為什么呢?問題就在于我們所插入的數據項中包含SQL關鍵字DROP TABLE,這兩個關鍵字的意義是從數據庫中清除一個表單。而關鍵字之前的Robert');使得SQL執行器認為上一命令已經結束,從而使得危險指令DROP TABLE得到執行。也就是說,這段包含DROP TABLE關鍵字的數據項使得原有的簡單的插入姓名信息的SQL語句。
"INSERT INTO students (name) VALUES ('Robert')"
變為了同時包含另外一條清除表單命令的語句
"INSERT INTO students (name) VALUES ('Robert');DROP TABLE students;--"
而SQL數據庫執行上述操作后,students表單被清除,因而表單無法找到,所有數據項丟失。
如何防止SQL注入問題
那么,如何防止SQL注入問題呢?
大家也許都想到了,注入問題都是因為執行了數據項中的SQL關鍵字,那么,只要檢查數據項中是否存在SQL關鍵字不就可以了么?的確是這樣,很多數據庫管理系統都是采取了這種看似『方便快捷』的過濾手法,但是這并不是一種根本上的解決辦法,如果有個美國人真的就叫做『Drop Table』呢?你總不能逼人家改名字吧。
合理的防護辦法有很多。首先,盡量避免使用常見的數據庫名和數據庫結構。在上面的案例中,如果表單名字并不是students,則注入代碼將會在執行過程中報錯,也就不會發生數據丟失的情況——SQL注入并不像大家想象得那么簡單,它需要攻擊者本身對于數據庫的結構有足夠的了解才能成功,因而在構建數據庫時盡量使用較為復雜的結構和命名方式將會極大地減少被成功攻擊的概率。
使用正則表達式等字符串過濾手段限制數據項的格式、字符數目等也是一種很好的防護措施。理論上,只要避免數據項中存在引號、分號等特殊字符就能很大程度上避免SQL注入的發生。
另外,就是使用各類程序文檔所推薦的數據庫操作方式來執行數據項的查詢與寫入操作,比如在上述的案例中,如果我們稍加修改,首先使用execute()方法來保證每次執行僅能執行一條語句,然后將數據項以參數的方式與SQL執行語句分離開來,就可以完全避免SQL注入的問題,如下所示:
SQL數據庫反注入示例
conn = sqlite3.connect('test.db') # 以安全方式插入包含注入代碼的信息 name = "Robert');DROP TABLE students;--" query = "INSERT INTO students (name) VALUES (?)" conn.execute(query, [name]) # 檢視已有的學生信息 cursor = conn.execute("SELECT id, name from students") print('IDName') for row in cursor: print('{0}{1}'.format(row[0], row[1])) conn.close()
而對于php而言,則可以通過MySQL_real_escape_string等方法對SQL關鍵字進行轉義,必要時審查數據項目是否安全來防治SQL注入。當然,做好數據庫的備份,同時對敏感內容進行加密永遠是最重要的。某些安全性問題可能永遠不會有完美的解決方案,只有我們做好最基本的防護措施,才能在發生問題的時候亡羊補牢,保證最小程度的損失。
原文鏈接:http://netsecurity.51cto.com/art/201911/606877.htm