# ping CTF 2023 [toc] ## path-traversal-101 FLAGはいくつかのtaskをクリアすれば手に入りそうです。 `index.js` ```javascript= } else if (!session.task3) { const result = task3(solution); console.log("task3: " + result); if (result) { session.task3 = true; res.render("exam", { task: FLAG, }); } else { res.render("exam", { task: tasks[2], error: "Try again!", }); } } ``` **ソースコードを眺めると、全てのリクエストの`User-Agent` に`robot`と必要なことがわかります。** `robot.js` ```javascript= export default (req, res, next) => { const userAgent = req.get("User-Agent"); if (userAgent == "robot") { next(); } else { res.render("robot", { error: "🤖🤖🤖🤖🤖" }); } }; ``` そして、フラグを取得するには、TASKとやらをこなしていく必要がある。 ```javascript= if (!session.task1) { const result = task1(solution); console.log("task1: "); console.log(result); if (result) { session.task1 = true; res.render("exam", { task: tasks[1], }); } else { res.render("exam", { task: tasks[0], error: "Try again!", }); } } else if (!session.task2) { const result = task2(solution); console.log("task2: " + result); if (result) { session.task2 = true; res.render("exam", { task: tasks[2], }); } else { res.render("exam", { task: tasks[1], error: "Try again!", }); } } else if (!session.task3) { const result = task3(solution); console.log("task3: " + result); if (result) { session.task3 = true; res.render("exam", { task: FLAG, }); } else { res.render("exam", { task: tasks[2], error: "Try again!", }); } } ``` 条件自体はそこまで厳しくないのでやっていくだけ。 まず最初に、以下のURLにアクセスを行い、User-Agentをrobotに変更する。 ```text= https://path-traversal-101.knping.pl/%F0%9F%A4%96 ``` ![image](https://hackmd.io/_uploads/BkCBkQM8a.png) Task1は、以下の条件。 ```javascript= export const task1 = (solution) => { preTask(solution); if (!solution.startsWith("/robot") || solution.endsWith("/flag")) { throw new Error( "You cannot access the flag!!! You are task1 UNAUTHORIZED!!! 🤖🤖🤖🤖🤖" ); } const solutionPath = path.join("/", solution); return solutionPath === "/flag"; }; ``` 最初が`/robot`で終わる必要があり、`/flag`で終わってはいけなさそうなのでそれに従う。 ``` /robot/../flag/. ``` ![image](https://hackmd.io/_uploads/r11RJXGUp.png) 次はTask2 ```javascript= export const task2 = (solution) => { preTask(solution); solution = solution.replaceAll("../", ""); if (solution === "/flag") { throw new Error( "You cannot ACCESS the flag!!! You are UNAUTHORIZED!!! 🤖🤖🤖🤖🤖" ); } const solutionPath = path.join("/", solution); return solutionPath === "/flag"; }; ``` これは、`/flag`ならだめというものなので、`./flag`でよし。と思わせておいて、実は`preTask`関数に阻まれるためうまくいかない。 ```javascript const preTask = (solution) => { if (typeof solution !== "string") { throw new Error("Solution must be a string"); } if (solution.length > 512) { throw new Error("Solution must be less than 512 characters"); } if (solution === "flag") { throw new Error("Your solution can't be 'flag'"); } if (solution === "./flag") { throw new Error("Your solution can't be './flag'"); } }; ``` がこれもそこまできつい条件ではないので、 `././flag`にすれば良いことがわかる。 最後のTask3 ```javascript export const task3 = (solution) => { preTask(solution); if (solution.includes("../") || solution === "/flag") { throw new Error( "You CANNOT ACCESS the flag!!! You are UNAUTHORIZED!!! 🤖🤖🤖🤖🤖" ); } const solutionPath = path.join("/", solution); return solutionPath === "/flag"; }; ``` ../が含まれていてはならず、`/flag`でもダメ。 先ほどと同じ値でバイパスができる。 これで`FAKE`フラグを取得可能なので、本番環境に打ち込めばヨシ ![image](https://hackmd.io/_uploads/SkaNZmGIT.png) `flag` ![image](https://hackmd.io/_uploads/rkCa6ff8a.png) ## i-see-no-vulnerability コードを読むと、画像にある文字を、出力するようなアプリケーションであることがわかります。 `visioedDict[uuid]`には、画像から読み取った文字が入ります。 ```javascript const unsafe_text = visionedDict[uuid]; if (unsafe_text === undefined) { return res.redirect("/"); } const text = DOMPurify.sanitize(unsafe_text); const page = readFileSync("./templates/result.html", "utf8") .replaceAll("{{VISION_TEXT}}", text) .replaceAll("{{IMAGE}}", uuid); res.send(page); ``` textを、`{{VISION_TEXT}}`と置き換えますが、rsult.htmlをしっかり読まなかったせいで、当初、`Dompurify`のバイパスをしなければならないと考えていました。 `result.html` ```html <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Image {{IMAGE}}</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css" /> </head> <body> <section class="hero"> <div class="hero-body"> <p class="title">I'm a visionary!!!</p> <p class="subtitle">I see...</p> <div id="vision">{{VISION_TEXT}}</div> </div> </section> <footer class="footer"> <div class="content has-text-centered"> <p><a href="/">Go back</a></p> <p> NSFW? <form method="post" action="/report/{{IMAGE}}"><input type="submit" value="Click here to report" class="button" /></form> </p> </div> </footer> <script> const text = "{{VISION_TEXT}}"; if (text.length === 0) { vision.innerHTML = "<img src='/i-see-nothing.gif' />"; } </script> </body ``` ですが、`DOMpurify`のバイパスなどはせずとも、以下の箇所ですぐにXSSを発火させることができることに気づきます。 ```htmlembedded <script> const text = "{{VISION_TEXT}}"; if (text.length === 0) { vision.innerHTML = "<img src='/i-see-nothing.gif' />"; } </script> ``` ここまで気づけばもうあとはやるだけです。 以下の画像を、アップロードあと、botに対して送れば終わりです。 ![text_image](https://hackmd.io/_uploads/r1j-xB7I6.png) ## 復習 ##  pocket-app **解けなかったので復習。** ## dont-be-alarmed **解けなかったので復習。**