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)