---
# System prepended metadata

title: Juice Shop Write-up
tags: [security, Web, JuiceShop, CTF]

---

Juice Shop Write-up
===
---

###### tags: `JuiceShop` `CTF` `security` `Web`
解題解的莫名其妙的心路歷程，流水帳
按照題目類型整理過後的md筆記在 https://hackmd.io/@LJP/HJJHMuNlH
本篇將持續更新直到題目全部解完
(解題數, 題目數) = (28, 86)

從 `Security Policy` 開始，未加解題數，未更新整理版 Write-up

---

[TOC]

# Write-up

## Score Board
在一開始 Load 網頁時就監控 Network 

![](https://i.imgur.com/NoGxhx8.png)

發現有一個特別牛逼

![](https://i.imgur.com/kuX2G9q.png)

看看內容是什麼

![](https://i.imgur.com/uzkjTF1.png)

後來閒閒沒事又把其他咚咚看了

![](https://i.imgur.com/Nyjf7UN.png)

看得了關鍵的 URL: /#/score-board

連進去看看 就解開這題了

## Error Handling

不知道怎樣 莫名其妙解了 好像是隨便造成 server 的 error 都可解這題

我是有亂踩 SQL Injection 測試


## Confidential Document

進去 About Us 後，看到一個
Check out our boring terms blablabla....

![](https://i.imgur.com/NuovJLg.png)

進去後是長這樣

![](https://i.imgur.com/hqg1iD0.png)

看看目錄有沒有設定讀取保護

![](https://i.imgur.com/XnbvjmL.png)

耶 看來是沒有 ㄎㄎ

![](https://i.imgur.com/90MPM0m.png)

可是有保護說副檔名要是 .md 或 .pdf

就先踩踩這個 md ㄅ

![](https://i.imgur.com/tMQLHQd.png)

就又解一題了 (????

## Easter Egg Tier 1

繼承上面
結果不知道為啥 incident-support.kdbx 可以下載!???!?

![](https://i.imgur.com/8nyGNKA.png)

注意下方 我把它 download 下來了
用 notepad++ 開

![](https://i.imgur.com/NgwAJe1.png)

well, 我還是用 binwalk 看看他是啥好了

![](https://i.imgur.com/ljIODUm.png)

結果啥都不是@@

好不重要，重要的是，我知道還是有辦法下載下來的

但後來餵狗後 發現我思考方式好像錯了

原本我想說 找找 GET .kdbx 與 GET 其他檔案 打的封包有什麼不同

後來發現更牛逼的方式

![](https://i.imgur.com/Nq6MqH2.png)

用 Burpsuit 攔截封包

![](https://i.imgur.com/lsiKVxm.png)

把第一行的 GET 改成
```
GET /ftp/eastere.gg%00.md HTTP/1.1
```
看看

欸豆 不行

![](https://i.imgur.com/PI1ohaH.png)

後來再查了一下，發現連 % 都要用URL Encoding
所以 % 要改成 %25

![](https://i.imgur.com/XM3gHxP.png)

耶~~ 下載下來ㄌ

內容長醬子

![](https://i.imgur.com/XKmhEDs.png)

那個一臉 Base64 樣的字串
```
L2d1ci9xcmlmL25lci9mYi9zaGFhbC9ndXJsL3V2cS9uYS9ybmZncmUvcnR0L2p2Z3V2YS9ndXIvcm5mZ3JlL3J0dA==
```
解碼後變成
```
/gur/qrif/ner/fb/shaal/gurl/uvq/na/rnfgre/rtt/jvguva/gur/rnfgre/rtt
```

欸先等等 已經解一題了 (不得不說 解題解的莫名其妙的

先記一下感覺，這解碼後的東東，可能可以試試看凱薩

## Forgotten Developer Backup
ㄜ 我想故技重施 把整個 FTP 看到的都載下來 就也解這題ㄌ

## Misplaced Signature File
同上

## Forgotten Sales Backup
同上

## Login Admin

再回來看看登入介面 嘗試看看 SQL Injection

![](https://i.imgur.com/FB71RoR.png)

回傳了...

![](https://i.imgur.com/KgJPW7a.png)

所以若我知道 admin 的信箱是啥
而且輸入的部分沒有檔 '--' 或 '#'
就可以直接 injection 登進去

就先用看看 
`' or 1=1 --`

就登入進去了

### 線索
後來登進後 隨便結個帳

![](https://i.imgur.com/y2w1lrm.png)

得知 admin 信箱為 admin@juice-sh.op
這個 Injection 就可以改成
`admin@juice-sh.op' --`

而且結帳的動作會在 ftp 中創一個 pdf

現在有幾個新思路:
1. **如果 pdf 內容可控，可能能搞事**
2. **bonus 能改嗎**

## Forged Feedback
主要是這個頁面

![](https://i.imgur.com/8rJ9DPM.png)

用 Network 看了一下執行流程

1. 先 GET http://192.168.124.137:3000/rest/captcha/ 
    取得一筆 json
    內容為 answer, captcha, captchaId
    ![](https://i.imgur.com/ktgEeqh.png)    
2. POST 到 http://192.168.124.137:3000/api/Feedbacks/
    POST 的欄位分別為 captcha, captchaId, comment, rating
    ![](https://i.imgur.com/sf7fPkr.png)

寫了個 python 壓力測試
```python=
import requests
import json

host = "192.168.124.137:3000"
captcha_url = "http://" + host + "/rest/captcha/"
feedback_url = "http://" + host + "/api/Feedbacks/"

for i in range(50):
    resp = requests.get(captcha_url)
    data = resp.json()
    postdata = {'UserId': 1, 
            'captcha': str(data['answer']),
            'captchaId': data['captchaId'],
            'comment': "YEEEEE",
            'rating': 0
            }
    r = requests.post(feedback_url, data = postdata)
```

就解ㄌ

## CAPTCHA Bypass Tier 1
上面的解法 可以一起把這題解掉

## Zero Stars
硬把 Submit button 的 disable 取消
就可以發送 0 星評價ㄌ

## 線索1
不過 奇怪的是
ftp/quarantine 裡面的東西就不能用這招下載了
(可惡 他的檔名害我最想看看他長怎樣QQ)

而且其實一開始為何 .kdbx 可以下載也是個謎

## 線索2
在 main.js 挖到寶

![](https://i.imgur.com/cf6WtCK.png)

看來是各種路徑，進看看 administration

![](https://i.imgur.com/mGO65PT.png)

也進進看 privacy-security

![](https://i.imgur.com/h71Yy4X.png)

## Admin Section
用 `admin@juice-sh.op' -- ` 登入後
再進 administration
就解這題了

![](https://i.imgur.com/q6p6jt6.png)

## Five-Star Feedback
進入 administration 後
砍掉五星評價

over

## Login Jim
用 `jim@juice-sh.op' -- ` 登入就解了 over

## Login Bender
用 `bender@juice-sh.op' -- ` 登入就解了 over

## Privacy Policy Tier 1

privacy-security 頁面進去就解這題了

![](https://i.imgur.com/zVvT90y.png)

## Basket Access Tier 1
觀察按下去 Your Basket 後會發送什麼封包

![](https://i.imgur.com/RomzCCJ.png)

![](https://i.imgur.com/rUXDzaO.png)

看到 Request URL: `http://192.168.124.141:3000/rest/basket/3`

3 是我現在登入的帳號的編號

很好，直接用 burpsuite 改改看那個數字

![](https://i.imgur.com/aNMCtAc.png)
改成 4 看看

就拿到帳號編號為 4 的 basket 了

以下這張為沒有竄改封包 本人的basket
![](https://i.imgur.com/EKCnJT4.png)

以下這張為帳號編號為 4 的 basket
![](https://i.imgur.com/EkbGzsC.png)

也解這題了

## 線索3
看看按 like 能不能也寫腳本自動化

看 reviews 時 會打 GET 到
`http://192.168.124.137:3000/rest/product/產品編號/reviews`
![](https://i.imgur.com/JmQ806F.png)
回傳一個 json
![](https://i.imgur.com/mLV1VVk.png)

觀察按下去後 會打 POST 到 `http://192.168.124.137:3000/rest/product/reviews`
![](https://i.imgur.com/DM1jwM9.png)
欄位是 id, 就是要填看產品時回傳的 json 中 comment 的 _id

我猜是根據 Cookie 與 Authorization 做使用者判斷
保險起見，Host, Origin, Referer 在寫腳本時一併也要模擬

## Forged Review
既然 basket 都能亂存取到其他帳號的 basket
那麼其他功能是否也能亂存取到其他帳號的呢 e.g. 留言 按讚 結帳

留言的更改 是打以下 request:
![](https://i.imgur.com/mYXKXlL.png)
Method 為 PATCH
API 在 /rest/product/reviews
id 推測為該留言的 id, message 為更改的內容

馬上來試改其他留言看看

先來查看隨便其中一個 product 的 review
![](https://i.imgur.com/8oV3Neq.png)

得到id，來改看看封包，先編輯自己的留言，並用burpsuite攔截下來
![](https://i.imgur.com/rDaILRE.png)

把id改掉
![](https://i.imgur.com/SgOSEix.png)

成功爆改並解題
![](https://i.imgur.com/grjLxK3.png)

cool

## Basket Access Tier 2
我在想能不能隨便幫其他 user 亂加東西到購物車

首先，先觀察加進購物車的流程

![](https://i.imgur.com/yaLrYR3.png)

![](https://i.imgur.com/sTWh7Db.png)

看來關鍵是這個API:
POST `http://192.168.124.141:3000/api/BasketItems/`
BasketId 為要加進的 Basket
ProductId 為產品
quantity 為數量

好像很簡單 馬上攔截下來爆改看看
![](https://i.imgur.com/K76Advd.png)
![](https://i.imgur.com/6HboENG.png)

欸欸欸 無效 BasketId ?

那我改成登入 admin，然後硬給 BasketId 3 加咚咚試試看
![](https://i.imgur.com/ahv3UrP.png)

結果還是一樣

那還是我刻意忽視掉的 API:
![](https://i.imgur.com/gxASFG8.png)
GET `http://192.168.124.141:3000/rest/basket/1`
是有作用的?

就是其實他有先打 GET rest/basket/{BasketId} (或有可能是 UserId)
再打 POST api/BasketItems

這次爆改 要連第一個GET都不放過試試看
登入admin，亂加購物車，攔截下來改:
![](https://i.imgur.com/MvsEhV4.png)

結果一樣不行

那會不會是，真的有判斷登入者是誰?

不過經過一番研究後

發現 如果我的 basket 中已經有一個商品
再按一次加入購物車 會呼叫這個 API
![](https://i.imgur.com/giDdE0M.png)
這個商品在我的購物車中 原先已經有了10個
感覺是JS運算給他 +1 並打這個 Request 過去修改數量

我先測試看看 這個數量是否是能修改的

![](https://i.imgur.com/WCXKAn6.png)
![](https://i.imgur.com/Nh3aZmQ.png)

OK 看來後端沒檢查值一定要是剛好 +1 過後的

這個API是這樣
PUT `http://192.168.124.141:3000/api/BasketItems/8`
quantity 是數量
網址列中 最後的數字 8 應該是指 BasketItemId

已知 id = 8 的 BasketItem 是屬於 admin 的
現在就來登入其他帳號，並且試著透過這個API來修改這 id = 8 的 BasketItem 的 quantity

原先封包
![](https://i.imgur.com/fzyQ40v.png)

改成
![](https://i.imgur.com/X5uKqHR.png)

好像修改成功了
![](https://i.imgur.com/GHQSPqt.png)
![](https://i.imgur.com/EAEbhIs.png)
BananaJuice 變 50 個啦

但我終極目的是要幫其他 user 亂加東西進購物車餒

我在想 打 POST api/BasketItems 這API
我竄改後，他Response是401
真的要登入的話 就要幹 cookie 或是 session
~~好麻煩~~

最後放棄 Google 了 Write-up 
看到結果後 還好我放棄了
![](https://i.imgur.com/VvAEsU2.png)
原來有這種東西 長知識了!

馬上來玩玩看
![](https://i.imgur.com/KPOfiJp.png)
Response:
![](https://i.imgur.com/ppkQrqr.png)

成功把它加進其他user的購物車!!!
太神啦!!!

成功解掉這題

cool

## Repetitive Registration
這題有小小看了一下 Write-up，才知道方向是啥

![](https://i.imgur.com/F8L6eeE.png)
砍掉 disable

![](https://i.imgur.com/muKtkj3.png)
來送一個 `password` 跟 `passwordRepeat` 欄位不一樣的 Request 試試看

![](https://i.imgur.com/cmqr7VX.png)
可以註冊，表示重複密碼這件事情只有在前端有檢查，後端卻沒擋掉。

## ? 
研究看看登入系統的 `Remember me` 這個功能

先辦了一個測試帳號
ljp@te.st
ljptest
登入一下 並且點選 Remember me
Session、Local Storage、Cookies 如下
![](https://i.imgur.com/TKLgySW.png)
![](https://i.imgur.com/6mx8nl0.png)
![](https://i.imgur.com/Ddgf1ud.png)
 
登出後 Session、Local Storage、Cookies 如下
![](https://i.imgur.com/SKMVilF.png)
![](https://i.imgur.com/ldugrQf.png)
![](https://i.imgur.com/oJL2NnY.png)
發現 Cookies 少了 token
Session 則少了 token 跟 bid

再登一次，但這次不要點選 Remember me
![](https://i.imgur.com/oCb4Vv1.png)
![](https://i.imgur.com/jz5KVVA.png)
![](https://i.imgur.com/GhOkyDa.png)
發現跟有按 Remember me 的差別在於 Local Storage 少了 email

## Password Strength
研究一下 logout
API:
![](https://i.imgur.com/toYJPk7.png)

Return:
![](https://i.imgur.com/S12og6W.png)

其中密碼是32個16進制組成的
讓人聯想到，是否原本密碼會以某種 128bits(32 * 4) hash/encrypt 加工並且回傳

若以未登入之姿進入這個API
![](https://i.imgur.com/3L7uOxg.png)
嗯，沒搞頭。

在其他題時就已經能登入 admin 了
看看 admin 登出後，那個看似 hash/encrypt 過的密碼長怎樣

![](https://i.imgur.com/GCclC4m.png)

```
email       admin@juice-sh.op
password    0192023a7bbd73250516f069df18b500
```

128bits hash algorithms 有 MD5
測試看看線上破解網站

![](https://i.imgur.com/RsLqjqd.png)

well 一個弱密碼
`admin123`

用原本的帳密登入 就解這題了

## ?
再來研究一下 forget password
![](https://i.imgur.com/PEmZawB.png)

發現每打一個字 都會打 Request

剛好我辦 ljp@te.st 帳號時，用了強制 enable 註冊按鈕，不用輸入安全提問。

拿另外一隻帳號有安全提問的
ljp2@te.st
ljptest

![](https://i.imgur.com/byQUzLx.png)
一搜尋到有此帳號，就會跳出他的安全提問。

所以可以用這個API來暴力尋找所有 email...囉?

測試看看有沒有 SQL Injection

狂踹猛踹 感覺不行

改帳密API如下
![](https://i.imgur.com/5uMJYCr.png)

成功改密碼畫面如下
![](https://i.imgur.com/uefO9MD.png)
![](https://i.imgur.com/2aZmGOm.png)

在想安全提問的字串比較會不會是以 PHP `==` 而非 `===`
如此，就可以用 PHP Type Juggling 特性來繞過檢查

要我解釋 PHP Type Juggling 是啥東，我用一張圖帶你入門
![](https://i.imgur.com/xvFOu8D.png)

可以參考: https://php.net/manual/en/types.comparisons.php

改看看
![](https://i.imgur.com/PG3nXeJ.png)

結果導致了 500 Server 錯誤
![](https://i.imgur.com/2pZ9cg2.png)

那不要TRUE，改成 0 看看
![](https://i.imgur.com/wp0wKJj.png)

雖然也是 500，但是 message 不一樣

回頭看改成 TRUE 後的 message，裡面提到了是爆炸在 JSON.parse()這個function，餵狗後:
![](https://i.imgur.com/C2TbXCC.png)

那我就試試看用原本的UI送true看看
結果果然不行。

不過突然想到，所以字串比較這件事情，是在 `/juice-shop/server.js` 完成的囉? 所以不應該用 PHP Type Juggling，而是 JS
參見: https://dorey.github.io/JavaScript-Equality-Table/

不過 JS Type Comparison 的字串部分就沒什麼搞頭了。

## ?
打 Web CTF 蠻常先直接 Access 看看 robots.txt 的
![](https://i.imgur.com/IMyf3aV.png)
well 這樣也能找到 /ftp

## Easter Egg Tier 2
繼承 Easter Egg Tier 1
當時是猜用凱薩密碼加密 ~~沒根據的亂猜~~
用網站 brute-force 了一下
![](https://i.imgur.com/jVvGiN5.png)
```
/gur/qrif/ner/fb/shaal/gurl/uvq/na/rnfgre/rtt/jvguva/gur/rnfgre/rtt
/THE/DEVS/ARE/SO/FUNNY/THEY/HID/AN/EASTER/EGG/WITHIN/THE/EASTER/EGG
```
該不會這是一個路徑吧

![](https://i.imgur.com/i44rGYP.jpg)
**WOW靠!! 是怎樣 這顏色不正確的地球還會動**

造訪了這顆星球後，這題就解了


















## Login MC SafeSearch
這題看了一下提示，聽了一下

先登入了 admin 看了一下 email 是 `mc.safesearch@juice-sh.op`
再來登看看就登入ㄌ
## ?
解了 Easter Egg Tier 2 後
的確是使用凱薩密碼

那麼 About us 頁面那些亂碼
是不是也是凱薩加密的???

解過後，看來不是，也有可能這些亂碼什麼都不是

## ?
之前有載到 package.json.bak
google search 看看裡面的每個 dependency 有沒有 vuln

我用這個搜尋格式 `node.js vuln <dependency-name>`

## Weird Crypto
這題題目寫說，透過 Contact Us 告訴 Server 一個被誤用的 Algorithm 或 Library

我是回傳 md5，不過理由是 logout 那邊會用 md5 hash 密碼並傳回來，而這件事情不應該發生

官方 write-up 對於 md5 是個答案的原因這樣寫:
> Passwords in the Users table are hashed with unsalted MD5
> 
我的理解是，密碼以 unsalted MD5 hash 過再存回資料庫，但照理說應該要加一些料，讓他不是單純的 MD5
不過以黑箱的角度，能觀察到這件事情嗎?
除非我能 SQL Injection 直接 dump 整個 database 出來看，才能知道這件事吧

但其實不用，在登入頁面時，觸發 SQL 錯誤，會回傳整個 SQL Statement
![](https://i.imgur.com/ixdEjWP.png)

那個 password 可以自行驗證一下，的確只是 md5 hash 過而已
得證資料庫中，Table Users 中的 password 為 unsalted md5 hash

## XSS Tier 0
```
<iframe src="javascript:alert(`xss`)">
```
以上打在 Track Orders 中
![](https://i.imgur.com/trQm8Ai.png)


## XSS Tier 1
```
<iframe src="javascript:alert(`xss`)">
```
以上打在 Search 中

![](https://i.imgur.com/xGuY7eI.png)

## XSS Tier 1.5
Set Username 中有套某些 RegEx，導致某種長相的字串會被取代

中間不小心試到一個 整個爆掉 = =
![](https://i.imgur.com/li47ULc.png)

中間嘗試了很多 pattern
以下列著比較有意義的嘗試
| Before | After 
| -------- | -------- |
| `<script>alert(`\``xss`\``)</script>`     | `lert(xss)</script>`    |
| `<</abcd>/abcd>` | `</abcd>` |
| `<<abcd>efgh>` | `<fgh>` |
| `<<a>efgh>` | `<fgh>` |
| `<<a>ascript>` | 頁面炸裂，`<script>` |
| `<<a>ascript></script>` | `<script></script>` |
| `<<a>ascript>alert(`\``xss`\``)</script>` | `<script>alert(`\``xss`\``)</script>` |

用最後一筆 pattern 就成功囉~

## XSS Tier 2
> Perform a persisted XSS attack with \<iframe src="javascript:alert(\`xss\`)"\> bypassing a client-side security mechanism.

persisted 的欄位，一開始直覺就是商品底下的 comment，但是踹 comment 沒有成功

回頭想想 persisted 的欄位、其他 User 看得到的，
J個嘛......

腦筋動到 User 本身上
- ~~User 名稱~~ (只能在自己的帳號管理頁面看到)
- User Email (admin 在主控台看得到，在 comment 中也會顯示)

User 名稱踹踹看，繼承 XSS Tier 1.5 嘗試中有些 RegEx 要閃
```
<<a>aiframe src="javascript:alert(`xss`)">
```

WoW
![](https://i.imgur.com/Zhc4CcJ.png)
(噢對了 我頭像有亂改過)

然後玩到這裡才突然想到，comment 中顯示的不是 Username 而是 email
果斷放棄改 Username

改踹 Email，可是 Email 在註冊時就決定了，所以只能踩看看註冊API了

不過這次硬把 Register Button Enable 起來，也送不出 Request

好吧，只好攔截爆改了
把 Email 改成
```
<iframe src='javascript:alert(`xss`)'>
```
![](https://i.imgur.com/8NY9178.png)
![](https://i.imgur.com/kxIyCeK.png)
唉呦 可以ㄛ
試試看登入，發現糗了
![](https://i.imgur.com/ixdEjWP.png)
登不進啦乾

but 還可以從 admin 頁面看到這個 email

![](https://i.imgur.com/dBqZhpQ.png)

一進來就 xss，表示真的成功了

![](https://i.imgur.com/exIXDKh.png)

阿可是這題沒解開，該不會是一定要打以下吧?
```
<iframe src="javascript:alert(`xss`)">
```

沒關係就來試試看

因為參數結構是
```
"email":"<blablabla...>"
```

要閃雙引號，所以改成如下
![](https://i.imgur.com/Lz1Aa9F.png)

欸，一送出去，連驗證 xss 有沒有成功都不用就解這題了
![](https://i.imgur.com/bDlXFqW.png)




## Security Policy
造訪一下 `http://192.168.124.144:3000/.well-known/security.txt` 就解了
此文件跟 robots.txt 作用類似

## Redirects Tier 1
![](https://i.imgur.com/3o0VzMJ.png)

觀察了一下結帳畫面下方 Merchandise
發現路徑是 `/redirect?to=一個連結`
按下去就會到那個連結

所以後端有個 redirect 是會接收 to 這個參數，並且連過去

看了一下這題的敘述
> Let us redirect you to one of our crypto currency addresses which are not promoted any longer

一個沒繼續 promoted 的加密貨幣連結(應該是斗內連結那種吧)

要從何找起哩? `main.js`?

![](https://i.imgur.com/wd3zvC7.png)

有喔，搜到了。

連過去
`http://192.168.124.144:3000/redirect?to=https://blockchain.info/address/1AbKfgvw9psQ41NbLi8kufDQTezwG8DRZm`
![](https://i.imgur.com/DTSlnNO.png)

就解這題了

## Deprecated Interface
登入後，在 complain 頁面
上傳檔案那邊，預設只能上傳 `*.pdf` 或 `*.zip`

硬是上傳其他副檔名的東西(我是拿 png 測) 會寫說不能上傳
雖然說到底能不能上傳又是另外一回事
但我還是先在 `main.js` 搜尋 `zip` 找到了一個區塊
![](https://i.imgur.com/a6MNWUA.png)

發現其實能上傳 `text/xml` `application/xml`
隨便上傳一個 xml 就解這題了

## ??

![](https://i.imgur.com/55EqCTA.png)




