CTF系列-Web網路安全基礎I
先前高中一直沒有整理CTF相關的內容,決定將第一篇的整理留給入門曲線最和善的Web了XD在所有的CTF題目種類當中,Web時常被定義為容易入門的項目,但相對其他種類來說,它的變化也相當繁複,通常一道題中會夾雜非常多知識點(或通靈能力?),因此需要透過全盤的接觸才能慢慢掌握到Web題目的解題策略。這篇裡面會整理一些常見的Web基礎攻擊技巧與SQL Injection,同時會推薦一些適合的工具,歡迎各位來訊說明或補充筆者不足的部分喔!🥳
Tricks
首先是一些相對雜亂的小技法,如目錄洩漏、Payload建構等,這些小地方雖然雜且少,但在Web的競賽中有時會扮演決定性的角色,因此不可小看這些看似微不足道的技巧喔!
Git
Git是目前非常廣為使用的一項去中心化版本控制系統(DRCS),但在架設網站時,若開發者沒有注意.git/
資料夾存在在網站上,很可能導致攻擊者能夠從資料夾中直接獲取網站開發的原始碼,進而發現網站弱點進行攻擊。
注意!.git/
資料夾無存取權(403)並不代表檔案無法被攻擊者透過工具取得(因資料夾存在)
常用工具
- Scrabble:存取並基本還原
.git/
資料夾到本機 - GitHacker.py:幾乎可完整還原
.git/
資料夾中所有資料 - Git:一般git工具
攻擊手法
- 直接存取
.git/
資料夾挖掘漏洞
簡單暴力,直接clone資料夾尋找漏洞檔案,屬於較一般的git漏洞挖掘 - 透過
git
指令存取分支(branches)git
在進行版本修改時能夠存在多個分支,敏感檔案可能不存在於當前分支下,此時可透過此方法切換分支來存取不同分支下的檔案。
常用指令如下:
1 | git log --all //觀察資料夾曾經修改了哪些項目 |
- 透過
git
指令回溯檔案更改內容
若是敏感檔案內容曾經在某次commit中出現,但其後被更改或刪除,此時可透過此方法來還原檔案的原先內容。
常用指令如下:
1 | git reset --hard HEAD^ //HEAD為目前版本,HEAD^表示切換到上一版本 |
目錄爆破
簡單而言是利用一些常見檔案名稱作為字典檔進行網站目錄暴力搜索,列出可存取的一些目錄以供攻擊者存取,尋找漏洞。
常用工具
- dirsearch:搜索常見檔案目錄工具
備份檔案洩漏
備份檔案洩漏的發生,常常是因為開發者在更改網站資料時所遺留在目錄中的備份檔案遭到攻擊者惡意存取,導致重要的原始碼或資料外洩導致,常見的種類有gedit、vim等文字編輯器的備份檔案,以及一些常見的經典檔案,如robots.txt
等。
常用工具
- ds_store_exp.py:還原
.DS_Store
中紀錄的檔案路徑並存取到本機
攻擊手法
- vim:
.<filename>.swp
.<filename>.swo
.<filename>.swn
vim回復檔案方法:vim -r <filename>
(當前目錄下有備份檔)
- gedit:
<filename>~
robots.txt
、.DS_Store
、.htaccess
、readme.md
、www.zip/.rar/.tar.gz
Payload Tricks
這裡是一些建構Payload的小技巧,若是在建構時發現被阻擋了某些條件,不妨試試這裡所整理的小技巧吧!
*
:代表任意數個字串?
:代表任意一個字串<cmd_1> && <cmd_2>
:<cmd_1>
執行完畢接續執行<cmd_2>
<cmd_1> || <cmd_2>
:無論<cmd_1>
有無執行,完畢後接續執行<cmd_2>
<cmd_1> & <cmd_2>
:<cmd_1>
丟入背景執行完畢接續執行<cmd_2>
<cmd_1> | <cmd_2>
:pipe管線指令,將<cmd_1>
的輸出做為<cmd_2>
的輸入執行- 跳脫字元:
\a
、\b
等,不等同於a
、b
- 特殊字元:
\n
(若單行限制可以此換行用)、\r
、\t
- 空白字元:除
%20
外,可嘗試%09
、%0a
、%0c
等(黑名單可試) - `<cmd>`:等同
system('<cmd>')
、$(<cmd>)
(某些情況)(黑名單可試)
後端攻擊
有時透過不同的伺服器支持系統,如apache、nginx等,會發展出不同的漏洞方式,通常是針對同一種資料做出不同解讀所導致的漏洞繞過,如LFI等攻擊,這些漏洞會留待後日介紹到相關內容後再一併介紹。
SQL Injection
SQL Injection是針對SQL database所發展出的一種攻擊模式,基於開發者過濾SQL表達式的不嚴謹,攻擊者能夠透過各種方式擾亂SQL表達式來執行惡意指令,從資料庫中讀取敏感內容。在此以CTF競賽中最常出現的MySQL作為說明。筆者在此預設讀者已經能夠讀懂SQL語法,若不熟悉者可參考此教學。
取得資料庫內部名稱
在一般的情況下,除非取得原始碼或其他資料,否則我們並不會知道開發者所使用的資料庫中名稱為何,因此在開始注入前,我們需要透過一些方法來得到資料庫中表名及欄位名稱。最簡單的方法可以透過MySQL 5.0後開始支援的預設資料庫information_schema
從中查詢,假設以下方數字植入的部分作為範例,我們可以透過以下表達式來獲取這個資料庫中的所有表名:
1 | SELECT title, content FROM NYCU_POST WHERE id=-1 UNION SELECT 1,group_concat(table_name) FROM information_schema.tables WHERE table_schema=database() |
其中,group_concat
函數會將所有回傳的內容以,
連接成一個字串,而information_schema.tables
即是在取得所有的表名,最後table_schema
所存取的參數是欲查詢的名稱,database()
則會回傳當前的資料庫名稱。
同理,當我們已知表名,欲獲取所有NYCU_USER
的欄位名稱時,表達式如下:
1 | SELECT title, content FROM NYCU_POST WHERE id=-1 UNION SELECT 1,group_concat(column_name) FROM information_schema.columns WHERE table_schema='NYCU_USER' |
UNION植入
表達式範例:
1 | SELECT title, content FROM NYCU_POST WHERE id=".$_GET['id'] |
猜測方式:輸入算式可執行
詳解:
若我們將id
設為3-2
,SQL將會回傳id=1
的內容給使用者,是典型的數字植入表達式,漏洞是基於id
在加入表達式之前沒有經過任何轉換就直接拼接。
此時,我們可以透過UNION
表達式,利用聯集查詢注入來繞過這個漏洞。若是我們將id
設為1 UNION SELECT user, passwd FROM NYCU_USER
,整個表達式就會變成"SELECT title, content FROM NYCU_POST WHERE id=1 UNION SELECT user, passwd FROM NYCU_USER"
,因此在顯示id=1
的內容同時,也會印出所有在NYCU_USER
裡面的使用者與密碼。
注意!若是印出時有行數限制,可搭配LIMIT
條件限定關鍵字使用。LIMIT 2,1
:取查詢結果第2筆之後的1筆紀錄LIMIT 1,2
:取查詢結果第1筆之後的2筆紀錄
P.S.也可以將id
設為-1
,這樣id
查詢不到之後,就會直接印出NYCU_USER
的內容囉!
Boolean植入
表達式範例:
1 | SELECT title, content FROM NYCU_POST WHERE id='".$_GET['id']."'" |
猜測方式:輸入數字與其他字元(強迫轉換為0)字串拼接可執行
詳解:
若我們將id
設為3-2
,此次將不再像上面的範例能夠顯示id=1
的內容,但當我們將輸入改為3M
時,其將會回傳id=3
的內容,因為非數字的字元被強制轉換為0。基於表達式的單引號可閉合,這個範例也能夠搭配註解#
阻斷,利用上述的UNION植入來獲取資料,但我們這次嘗試不同的植入方式,布林植入。
布林植入簡單而言就是利用表達式回傳的正確與否來判斷資料庫中取得的內容,常見使用substring()
、mid()
等函式來取得字串位置。如若我們要取得NYCU_USER
的使用者名稱與密碼,可使用下列表達式:
1 | SELECT title, content FROM NYCU_POST WHERE id='1' AND (SELECT mid((SELECT concat(user, 0x7e, passwd) FROM NYCU_USER),1,1))='a'# |
concat
函式會將所有的參數連接成一個字串,0x7e
代表~
,而mid(<parameter>,1,1)
則會從<parameter>
中第1
個位置開始取1
位,本判斷式會判斷第一位是否為a
,若AND
後判斷式為假,則AND
結果為假,即不會輸出id=1
的內容,可以此來一位一位判斷資料內容,得到完整的資訊。
Tips:若是植入過程過於冗長,可考慮使用>'a'
等語句進行二分搜,可加快尋找字元的效率喔!
時間盲注
某些題目的SQL查詢結果並不會出現在可見的頁面上,此時我們就只能透過sleep()
函數製造時間延遲來決定判斷式的真假。此以第一種範例,無引號SQL表達式作為說明:
1 | SELECT title, content FROM NYCU_POST WHERE id=".$_GET['id'] |
表達式相同,唯一的差別為不會印出在葉面上,此時我們利用IF()
判斷式來構造判斷式:
1 | SELECT title, content FROM NYCU_POST WHERE id=1 AND 1=IF(mid((SELECT concat(user, 0x7e, passwd) FROM NYCU_USER),1,1)='a', sleep(5), 1)" |
IF()
判斷式會將第一項參數設為條件式,若其為真則執行第二個參數,否則執行第三個參數,在這裡若是我們拼接出的參數第一位為a
,則頁面會暫停五秒,否則會正常運行。
顯示錯誤植入
表達式範例:
1 | ("SELECT title, content FROM NYCU_POST WHERE id=".$_GET['id']) OR VAR_DUMP(mysqli_error($<conn>)) |
猜測方式:輸入錯誤表達式會導致輸出錯誤訊息
詳解:
這種會輸出錯誤訊息的利用方式,是利用mysqli_error()
會將前方錯誤語句執行後加入VAR_DUMP()
輸出所導致。此時我們可以透過updatexml()
製造錯誤,正常而言其第二個參數應為合法XPATH路徑,否則會進入錯誤訊息連帶輸出第二個參數的執行訊息。構造表達式如下所示:
1 | ("SELECT title, content FROM NYCU_POST WHERE id='1' OR updatexml(1,concat(0x7e, (SELECT passwd FROM NYCU_USER)),1)") OR VAR_DUMP(mysqli_error($<conn>)) |
基於SELECT passwd FROM NYCU_USER
回傳值非正規XPATH路徑,畫面顯示錯誤訊息連帶輸出concat(0x7e, (SELECT passwd FROM NYCU_USER))
的執行結果。
Tips:植入方式嘗試優先序:UNION > 顯示錯誤 > Boolean > 時間盲注
特殊限制繞過
replace()
替換replace('SELECT','')
:SELSELECTECT
可繞過replace(' ','')
:- 透過URLencode,
%20
外另有%09
、%0a-%0d
可用 - 透過特殊代換,如空註解
/**/
、空括號()
皆會替換成空白
- 透過URLencode,
- Blacklist
- 大小寫替換:
SELECT
、sELecT
功能相同,黑名單判斷不同 - Regex繞過:
\bSELECT\b
可利用/*!50000SELECT*/
繞過 - 其餘可參考此cheatsheet
- 大小寫替換:
\
未跳脫為\\
- 直接在
'
、"
前輸入\
可將其跳脫為字串一部份 - 範例:
SELECT * FROM NYCU WHERE user='<parameter_1>' AND title='<parameter_2>'
<parameter_1> = M3t30r\
、<parameter_2> = OR SLEEP(5)#
SELECT * FROM NYCU WHERE user='M3t30r\' AND title='OR SLEEP(5)#'
'M3t30r\' AND title='
變為user
參數,系統執行暫停5秒,可攻擊
- 直接在
- 引號逃逸
- 輸入者可控點遺漏限制:輸入漏洞
- base64、URL等編碼完成後直接執行表達式,未進行
addslashes()
(php):可任意閉合引號 - 二次注入:
- 開發者認定從資料庫中取得的資訊皆無害
- 利用
INSERT
關鍵字將資料存入資料庫,再從資料庫中取出執行導致漏洞 - 範例:資料庫中存有使用者
M3t30r
之名稱與密碼 - 使用者先輸入
M3t30r'or'1
作為名稱,基於安全,開發者會自動將'
跳脫,變為M3t30r\'or\'1
- 表達式:
INSERT INTO NYCU_USER VALUES(2, 'M3t30r\'or\'1','fake_password')
- 資料庫中存在
M3t30r
與M3t30r'or'1
兩位使用者 - 當資料庫執行
SELECT * FROM NYCU_USER WHERE user='<user>'
取得密碼時,處理M3t30r'or'1
使用者時會變成SELECT * FROM NYCU_USER WHERE user='M3t30r'or'1'
(在資料庫內部不會處理跳脫),直接取得M3t30r
之密碼,攻擊成功
- 字串截斷:限制使用者可用的字串長度,則可利用多餘字串截斷的方式,單獨取得
\
做為輸入,成功跳脫
統整
SQL Injection的主要重點在於判斷SQL表達式的漏洞位置與寫法以構造合適的攻擊語句,從上述的幾個技巧中幾乎可以完成75%以上的基本SQL Injection題目,但在現實上,若是成功打進伺服器獲得高層權限,除了可能可以埋進木馬等惡意檔案進行攻擊,也可以控制整個資料庫,使用load_file()
(但需要繁複權限設定)或LOAD DATA LOCAL INFILE
語法(很少有機會執行完整指令,除了SELECT/UPDATE/INSERT
之外)獲取與控制資料,甚至是直接執行伺服器系統指令,造成資料外洩,因此使用SQL資料庫時,務必做好各種必要的防護措施,避免惡意攻擊使資料庫資料流出。