悟空科技股份有限公司 後台可被公開發現,且未登入即可取得包含管理功能路徑、API 端點、角色模型與業務邏輯的前端資源 - HITCON ZeroDay

Vulnerability Detail Report

Vulnerability Overview

  • ZDID: ZD-2026-00956
  •  發信 Vendor: 悟空科技股份有限公司
  • Title: 悟空科技股份有限公司 後台可被公開發現,且未登入即可取得包含管理功能路徑、API 端點、角色模型與業務邏輯的前端資源
  • Introduction: 網站後台可由公開網路發現,且未登入即可取得包含管理功能路徑、API 端點、角色與站別邏輯,以及敏感業務功能資訊的前端 JavaScript bundle。

處理狀態

目前狀態

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

處理歷程

  • 2026/07/28 10:24:10 : 新提交 (由 更新此狀態)
  • 2026/07/28 10:31:52 : 新提交 (由 更新此狀態)
  • 2026/07/28 11:39:39 : 新提交 (由 更新此狀態)
  • 2026/08/03 12:20:26 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
  • 2026/08/05 18:56:56 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
  • 2026/08/05 18:56:56 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
  • 2026/08/05 18:56:56 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
  • 2026/09/27 03:00:07 : 公開 (由 HITCON ZeroDay 平台自動更新)

詳細資料

  • ZDID:ZD-2026-00956
  • 通報者: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
(本欄位資訊由系統根據漏洞類別自動產生,做為漏洞參考資料。)

相關網址

https://helper.diyidphoto.com/
https://helper.diyidphoto.com/_nuxt/<dynamic_bundle>.js
> 這裡請注意,實際 JavaScript bundle 檔名可能會因部署版本、hash 或 build ID 而改變,此時可使用本報告提供的關鍵字搜尋 bundle 以完成驗證。

敘述

0. 摘要(Summary)

https://helper.diyidphoto.com/ 是 https://diyidphoto.com/zh-tw/ 與 https://jp.diyidphoto.com/ 的後台管理頁面。

使用 Google dork site:diyidphoto.com 進行查詢,可在搜尋結果中發現 https://helper.diyidphoto.com。

未登入訪客開啟後台登入頁面時,瀏覽器除取得登入流程所需的 HTML 與 JavaScript 外,也會載入包含管理端架構與業務邏輯的 Nuxt / Vite 前端資源。透過靜態分析這些匿名可存取的資源,可整理出:

  • 管理端 route map
  • API 端點路徑、HTTP method 與部分請求參數
  • admin、tw-service、jp-service 等角色模型
  • 台灣站與日本站的 site gating 邏輯
  • 訂單查詢、訂單修改、退款、取消發票、重寄郵件等敏感業務操作
  • 照片測試、Redis 測試、佇列監控及集合管理等功能

上述資訊超出登入流程所需,且可在訪客完成身份驗證前取得;

雖然這不等同於已取得後台資料或成功執行管理操作,但會降低攻擊者建立後台攻擊面地圖、枚舉端點及進行針對性授權測試的成本。


1. 受影響功能(Affected Feature)

  • 功能:後台登入頁面及其載入的管理端前端資源
  • 存取條件:未登入即可取得前端 HTML 與 JavaScript bundle
  • 主要受影響資源:

    • GET /
    • GET /login
    • 登入前載入的 Nuxt / Vite JavaScript bundle
    • 後台 route map
    • 後台 API endpoint map
    • 後台角色與站別限制邏輯(role/site gating)
    • 與敏感業務操作相關的前端請求格式

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

  • 使用瀏覽器 DevTools 或 Burp Suite 等 proxy 工具,連線至 https://helper.diyidphoto.com/,觀察登入頁面載入的 HTML 與 JavaScript bundle,即可驗證匿名資訊暴露。

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

以下重現步驟皆使用 Burp Suite 作為工具。

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


步驟 1:找到並開啟後台登入頁面

  1. 使用瀏覽器或 Burp Suite 瀏覽器開啟全新無痕視窗。
  2. 使用 Google dork site:diyidphoto.com 進行查詢,可在搜尋結果中發現 https://helper.diyidphoto.com。
  3. 點擊後會連到 https://helper.diyidphoto.com/login。

此步驟證明後台登入頁面可由公開網路發現與存取。

圖片

步驟 2:確認匿名訪客可取得後台前端資源

在 Burp Suite 的 Proxy 頁籤中的 HTTP history,或瀏覽器 Network 面板中,可觀察到:

GET /
Host: helper.diyidphoto.com

伺服器回傳 HTML,且瀏覽器後續取得多個 /_nuxt/*.js 資源,回應狀態為 200。

代表性載入項目如下:

<link rel="modulepreload" as="script" crossorigin href="/_nuxt/B3wXvxCI.js">
<link rel="modulepreload" as="script" crossorigin href="/_nuxt/CzvXVHZO.js">
<link rel="modulepreload" as="script" crossorigin href="/_nuxt/B-4rHLY0.js">
<link rel="modulepreload" as="script" crossorigin href="/_nuxt/BNTErtew.js">
<link rel="modulepreload" as="script" crossorigin href="/_nuxt/CciwWcDq.js">
<link rel="modulepreload" as="script" crossorigin href="/_nuxt/BXAsOW6G.js">
<link rel="modulepreload" as="script" crossorigin href="/_nuxt/CHENSK3N.js">
<link rel="preload" as="fetch" fetchpriority="low" crossorigin="anonymous" href="/_nuxt/builds/meta/92f778a5-fa0c-4d28-ba54-a465b827fa8c.json">
<script type="module" src="/_nuxt/B3wXvxCI.js" crossorigin></script>

圖片

步驟 3:從匿名可取得的 HTML 識別管理功能

HTML 中可直接觀察到路徑與功能名稱,例如:

/orders          訂單查詢
/shipping        郵寄管理
/idphoto         快洗證件照
/gallery         已上傳照片集
/stats           數據統計
/photos-test     照片測試
/redis-test      Redis 測試
/queue-monitor   佇列監控
/collections     集合管理
/free-codes      新增重製碼

完整代表性 HTML 證據見附錄 A。

圖片

步驟 4:從 bundle 識別 route map

在目前版本的主要 bundle 中搜尋 orders、gallery、idphoto 等字串,可取得 route name、path、component dependency 及部分 middleware 設定。

代表性結果:

{
  name: "orders",
  path: "/orders",
  meta: {
    middleware: "auth",
  },
  component: () =>
    Ee(
      () => import("./ClSXETeI.js"),
      __vite__mapDeps([8, 5, 9, 10, 2, 1, 11]),
      import.meta.url
    ),
},

{
  name: "gallery",
  path: "/gallery",
  component: () =>
    Ee(
      () => import("./DQfvhMUT.js"),
      __vite__mapDeps([14, 5, 2, 10, 15]),
      import.meta.url
    ),
},

{
  name: "idphoto",
  path: "/idphoto",
  meta: {
    middleware: "auth",
  },
  component: () =>
    Ee(
      () => import("./CmMMjIBn.js"),
      __vite__mapDeps([16, 5, 1, 2, 17]),
      import.meta.url
    ),
},

由此可建立管理頁面與對應前端 module 的關係。完整 route map 證據見附錄 B。

圖片

步驟 5:從 bundle 識別角色與站別模型

在 bundle 中搜尋 admin 可取得角色與頁面之間的對應關係(低權限後台帳號可繞過 RBAC會進一步測試角色邏輯),例如:

{
  name: "訂單查詢",
  path: "/orders",
  icon: "i-heroicons-document-text",
  roles: [
    "admin",
    "tw-service",
    "jp-service",
  ],
},

{
  name: "郵寄管理",
  path: "/shipping",
  icon: "i-heroicons-truck",
  roles: [
    "admin",
    "tw-service",
  ],
},

{
  name: "快洗證件照",
  path: "/idphoto",
  icon: "i-heroicons-camera",
  roles: [
    "admin",
    "tw-service",
  ],
},

圖片

接著以角色(admin、tw-service、jp-service)作為關鍵字進一步搜尋 bundle;

也可取得角色與站別之間的對應關係,以及後台登入使用的 PocketBase 驗證路徑、請求方法及參數:

const p = {
  admin: [
    "tw",
    "ja",
  ],
  "tw-service": [
    "tw",
  ],
  "jp-service": [
    "ja",
  ],
};
const g = async (s, i) => {
  try {
    const o = await $fetch(
      c.siteConfig.apiPrefix +
        "/pb/api/collections/users/auth-with-password",
      {
        method: "POST",
        body: {
          identity: s,
          password: i,
        },
      }
    );

這些資訊使匿名訪客可在沒有帳號的情況下理解管理角色、功能範圍與跨站別設計,以及後台登入或驗證邏輯。完整的角色、站別與驗證邏輯證據見附錄 C。

圖片

步驟 6:從 bundle 識別敏感管理 API 與請求格式

分析 /orders 對應的 bundle,可見各項請求皆透過 f.siteConfig.apiPrefix 組合 API 路徑,因此可識別以下相對端點:

POST {apiPrefix}/orders/search
POST {apiPrefix}/orders/search/date
POST {apiPrefix}/orders/edit
POST {apiPrefix}/refund
POST {apiPrefix}/cancel-invoice
GET  {apiPrefix}/orders/resend-email/<OrderID>

在目前部署環境中,實際觀察到的請求前綴為 /api,因此對應的完整路徑包括 /api/orders/search、/api/refund 等端點。

圖片

同時可識別部分請求參數及 Bearer token 使用方式,例如:

const s = await $fetch(
      `${f.siteConfig.apiPrefix}/refund`,
      {
        method: "POST",

        body: {
          OrderID: n.orderID,
          Token: n.token,
        },

        headers: {
          Authorization: `Bearer ${F.token}`,
        },
      }
    );

此證據證明匿名訪客可在登入前取得敏感管理功能的 API path、method、參數名稱與呼叫方式。完整 API 證據見附錄 D。

步驟 7:使用其它關鍵字整理後台 route、API path 與 module

可在 bundle 中繼續搜尋其它關鍵字,例如以下後台 route、API path 與 module 名稱:

orders
shipping
idphoto
gallery
stats
click-stats
photos-test
redis-test
queue-monitor
collections
free-codes
admin
pb
api
photo
image
free code
collection
click stats

依序搜尋上述關鍵字及進入相關 /_nuxt/*.js 後,即可整理出相關路徑、端點與 API 功能。

匿名訪客可藉此快速建立後台攻擊面地圖,並鎖定涉及訂單、退款、郵件、測試與監控等敏感 module 進行針對性安全測試。

由於篇幅有限且本報告聚焦於前端資訊洩漏,因此不一一列出所有搜尋結果。此資訊暴露所形成的實際串聯風險,請見低權限後台帳號可繞過 RBAC。


4. 預期行為(Expected Behavior)

在使用者完成身份驗證前,登入頁面應僅提供完成登入流程所需的最小前端資源。

與管理功能相關的 route、module、角色矩陣、站別邏輯及敏感業務 API 實作,應在可行範圍內:

  • 延後至身份驗證後載入;
  • 依角色或站別切分;
  • 避免集中於匿名可取得的選單、設定或 bundle;
  • 移除正式環境不必要的測試及監控功能。

所有後台 API 均應在伺服器端獨立驗證身份、角色、站別及物件層級權限。前端程式碼切分只能減少資訊暴露,不能取代伺服器端授權。

若此後台原本僅供內部使用,則應另以 VPN、allowlisted IP、SSO 或其他存取閘道限制公開網路存取。


5. 實際行為(Actual Behavior)

未登入訪客開啟後台登入頁面後,即可取得包含管理端資訊的 HTML 與 JavaScript bundle,並可據此整理出:

  • 管理端頁面及 module
  • 管理、測試與監控 route
  • API 端點、method 及參數
  • 角色與站別限制邏輯
  • 訂單修改、退款、取消發票及重寄郵件等敏感操作的前端實作資訊

匿名訪客取得上述資訊不需要帳號、登入狀態或特殊權限。


6. 影響(Impact)

此問題造成 匿名可取得的管理端架構與業務邏輯資訊洩漏。

攻擊者可使用這些資訊:

  1. 快速建立後台 route 與 API map。
  2. 識別優先測試的高價值功能,例如退款、訂單修改、取消發票及重寄郵件。
  3. 理解 admin、tw-service、jp-service 的角色範圍。
  4. 理解台灣站與日本站的站別邏輯,並據此設計跨站別授權測試。
  5. 找到正式環境中的測試、Redis 及佇列監控功能。
  6. 減少端點猜測、HTTP method 判斷及請求格式重建所需時間。

本漏洞單獨存在時,其直接影響主要是降低攻擊者的偵察、端點枚舉及針對性授權測試成本。

但由於後台存在權限漏洞(請見低權限後台帳號可繞過 RBAC與後台身份驗證可被繞過),這些資訊亦可提高既有漏洞的利用效率及攻擊鏈建立效率。


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

問題可能出在前端建置與部署方式未區分「匿名登入流程」及「完成身份驗證後的管理功能」,導致登入前即向瀏覽器提供大量管理端程式碼。

可能原因包括:

  1. 登入頁面與登入後管理介面共用相同的前端 build 或 dependency graph。
  2. 應用程式未將匿名登入流程與管理端功能進行最小化程式碼切分。
  3. 管理選單、route、role matrix、site gating 及 API 呼叫資訊被集中放置於匿名可取得的 bundle。
  4. 後台網域可被搜尋引擎索引,使暴露內容更容易被發現。
  5. 部署設計可能過度依賴前端 middleware、隱藏選單或程式碼混淆降低可見性。

8. 嚴重性評估(Severity Assessment)

  • 單獨風險:低(Low)
  • 與其它後台漏洞串聯後的整體風險:中高(Medium-High)

單獨風險評估

  • 匿名訪客可取得大量超出登入流程所需的管理端資訊。
  • 暴露內容涉及敏感管理功能、角色模型、站別邏輯、測試/監控 route 及 API 請求格式。
  • 相關資訊可降低攻擊者進行後台偵察、端點枚舉及針對性授權測試的成本。
  • 於本次檢視的前端資源中,未發現 hardcoded credential、有效 token、私密金鑰或其他可直接利用的 secret。
  • 若不考慮其它後台權限漏洞,且所有後台 API 均能正確回應 401/403 並執行伺服器端身份驗證與授權,則實際利用程度有限。

串聯後風險評估

本漏洞可與後台權限漏洞-例如低權限後台帳號可繞過 RBAC與後台身份驗證可被繞過-串聯。

暴露的路由、角色、站別及 API 資訊可降低偵察與請求建構成本,提升敏感功能定位與權限測試效率,因此整體串聯風險評估為中高(Medium-High)。


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]

附錄 A:管理選單 HTML 證據

<div class="layout-container" data-v-180a7943>
  <!---->

  <aside class="sidebar" data-v-180a7943>
    <div class="sidebar-header" data-v-180a7943>
      <a
        href="/orders"
        class="brand-link"
        data-v-180a7943
      >
        <div
          class="brand"
          style="color:#00307c;"
          data-v-180a7943
        >
          DIY
        </div>
      </a>

      <button
        class="site-toggle-btn"
        title="切換至 DIYJP"
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:arrow-path"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>
      </button>

      <button
        class="toggle-btn desktop-only"
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:chevron-left"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>
      </button>

      <button
        class="close-btn mobile-only"
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:x-mark"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>
      </button>
    </div>

    <nav class="nav-menu" data-v-180a7943>
      <!--[-->

      <a
        href="/orders"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:document-text nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          訂單查詢
        </span>
      </a>

      <a
        href="/shipping"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:truck nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          郵寄管理
        </span>
      </a>

      <a
        href="/idphoto"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:camera nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          快洗證件照
        </span>
      </a>

      <a
        href="/gallery"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:photo nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          已上傳照片集
        </span>
      </a>

      <a
        href="/stats"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:chart-bar nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          數據統計
        </span>
      </a>

      <a
        href="/photos-test"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:beaker nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          照片測試
        </span>
      </a>

      <a
        href="/redis-test"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:circle-stack nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          Redis 測試
        </span>
      </a>

      <a
        href="/queue-monitor"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:queue-list nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          佇列監控
        </span>
      </a>

      <a
        href="/collections"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:rectangle-stack nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          集合管理
        </span>
      </a>

      <a
        href="/free-codes"
        class="nav-item"
        title
        data-v-180a7943
      >
        <span
          class="iconify i-heroicons:ticket nav-icon"
          aria-hidden="true"
          style=""
          data-v-180a7943
        ></span>

        <span
          class="nav-label"
          data-v-180a7943
        >
          新增重製碼
        </span>
      </a>

      <!--]-->
    </nav>
  </aside>
</div>

附錄 B:Route Map 證據

Ks = [
  {
    name: "index",
    path: "/",
    component: () =>
      Ee(
        () => import("./CHENSK3N.js"),
        [],
        import.meta.url
      ),
  },
  {
    name: "login",
    path: "/login",
    meta: ay || {},
    component: () =>
      Ee(
        () => import("./DqBU8ekB.js"),
        __vite__mapDeps([0, 1, 2, 3]),
        import.meta.url
      ),
  },
  {
    name: "stats",
    path: "/stats",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./CKh3ZThO.js"),
        __vite__mapDeps([4, 5, 6, 2, 1, 7]),
        import.meta.url
      ),
  },
  {
    name: "orders",
    path: "/orders",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./ClSXETeI.js"),
        __vite__mapDeps([8, 5, 9, 10, 2, 1, 11]),
        import.meta.url
      ),
  },
  {
    name: "photos",
    path: "/photos",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./8Pm2CsGl.js"),
        __vite__mapDeps([12, 5, 10, 2, 9, 1, 13]),
        import.meta.url
      ),
  },
  {
    name: "gallery",
    path: "/gallery",
    component: () =>
      Ee(
        () => import("./DQfvhMUT.js"),
        __vite__mapDeps([14, 5, 2, 10, 15]),
        import.meta.url
      ),
  },
  {
    name: "idphoto",
    path: "/idphoto",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./CmMMjIBn.js"),
        __vite__mapDeps([16, 5, 1, 2, 17]),
        import.meta.url
      ),
  },
  {
    name: "shipping",
    path: "/shipping",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./TAFRDC8E.js"),
        __vite__mapDeps([18, 5, 10, 2, 9, 1, 19]),
        import.meta.url
      ),
  },
  {
    name: "free-codes",
    path: "/free-codes",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./BdmxFsof.js"),
        __vite__mapDeps([20, 5, 9, 1, 2, 21]),
        import.meta.url
      ),
  },
  {
    name: "redis-test",
    path: "/redis-test",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./B-OE6OVH.js"),
        __vite__mapDeps([22, 5, 1, 2, 23]),
        import.meta.url
      ),
  },
  {
    name: "click-stats",
    path: "/click-stats",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./DjRc2lXa.js"),
        __vite__mapDeps([24, 5, 6, 2, 1, 25]),
        import.meta.url
      ),
  },
  {
    name: "collections",
    path: "/collections",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./rVvee4MR.js"),
        __vite__mapDeps([26, 5, 1, 2, 27]),
        import.meta.url
      ),
  },
  {
    name: "photos-test",
    path: "/photos-test",
    component: () =>
      Ee(
        () => import("./BqZzGmSL.js"),
        __vite__mapDeps([28, 5, 29]),
        import.meta.url
      ),
  },
  {
    name: "queue-monitor",
    path: "/queue-monitor",
    meta: {
      middleware: "auth",
    },
    component: () =>
      Ee(
        () => import("./BtzjN0-8.js"),
        __vite__mapDeps([30, 5, 2, 1, 31]),
        import.meta.url
      ),
  },
];

附錄 C:Role 與 Authentication 邏輯證據

C.1 Role Map

const o = [
  {
    name: "訂單查詢",
    path: "/orders",
    icon: "i-heroicons-document-text",
    roles: [
      "admin",
      "tw-service",
      "jp-service",
    ],
  },
  {
    name: "郵寄管理",
    path: "/shipping",
    icon: "i-heroicons-truck",
    roles: [
      "admin",
      "tw-service",
    ],
  },
  {
    name: "快洗證件照",
    path: "/idphoto",
    icon: "i-heroicons-camera",
    roles: [
      "admin",
      "tw-service",
    ],
  },
  {
    name: "已上傳照片集",
    path: "/gallery",
    icon: "i-heroicons-photo",
    roles: [
      "admin",
    ],
  },
  {
    name: "數據統計",
    path: "/stats",
    icon: "i-heroicons-chart-bar",
    roles: [
      "admin",
    ],
  },
  {
    name: "照片測試",
    path: "/photos-test",
    icon: "i-heroicons-beaker",
    roles: [
      "admin",
    ],
  },
  {
    name: "Redis 測試",
    path: "/redis-test",
    icon: "i-heroicons-circle-stack",
    roles: [
      "admin",
    ],
  },
  {
    name: "佇列監控",
    path: "/queue-monitor",
    icon: "i-heroicons-queue-list",
    roles: [
      "admin",
    ],
  },
  {
    name: "集合管理",
    path: "/collections",
    icon: "i-heroicons-rectangle-stack",
    roles: [
      "admin",
    ],
  },
  {
    name: "新增重製碼",
    path: "/free-codes",
    icon: "i-heroicons-ticket",
    roles: [
      "admin",
      "tw-service",
      "jp-service",
    ],
  },
];

C.2 Authentication 與 Site Mapping

const C = y("auth", () => {
  const c = _();

  const r = {
    sameSite: "lax",
    secure: !0,
    maxAge: 3600 * 24 * 7,
    path: "/",
  };

  const l = u("uid", r);
  const n = u("access_token", r);
  const m = u("pb_email", r);
  const v = u("pb_username", r);
  const a = u("pb_role", r);

  const t = E(
    l.value
      ? {
          id: l.value,
          email: m.value,
          username: v.value,
          roles: Array.isArray(a.value)
            ? a.value
            : a.value
              ? [a.value]
              : [],
        }
      : null
  );

  const k = h(() => !!t.value && !!n.value);

  const g = async (s, i) => {
    try {
      const o = await $fetch(
        c.siteConfig.apiPrefix +
          "/pb/api/collections/users/auth-with-password",
        {
          method: "POST",
          body: {
            identity: s,
            password: i,
          },
        }
      );

      if (o?.token && o?.record) {
        const {
          record: e,
          token: f,
        } = o;

        const S = Array.isArray(e.role)
          ? e.role
          : e.role
            ? [e.role]
            : [];

        return (
          (t.value = {
            id: e.id,
            email: e.email,
            username: e.username,
            roles: S,
          }),
          (l.value = e.id),
          (n.value = f),
          (m.value = e.email),
          (v.value = e.username),
          (a.value = S),
          {
            success: !0,
          }
        );
      }

      return {
        success: !1,
        message: "認證失敗",
      };
    } catch (o) {
      return (
        console.error("Login error:", o),
        {
          success: !1,
          message:
            o.data?.message ||
            "登入失敗,請檢查帳號密碼",
        }
      );
    }
  };

  const w = () => {
    t.value = null;
    l.value = null;
    n.value = null;
    m.value = null;
    v.value = null;
    a.value = null;
  };

  const p = {
    admin: [
      "tw",
      "ja",
    ],
    "tw-service": [
      "tw",
    ],
    "jp-service": [
      "ja",
    ],
  };

附錄 D:訂單功能與 API 證據

try {
  let s = "";
  let a = {};

  if (n === "keyword") {
    s = `${f.siteConfig.apiPrefix}/orders/search`;

    a = {
      keyword: u.value.keyword.trim(),
    };
  } else {
    s = `${f.siteConfig.apiPrefix}/orders/search/date`;

    const l = u.value.startDate
      ? `${u.value.startDate} 00:00:00`
      : "";

    const x = u.value.endDate
      ? `${u.value.endDate} 23:59:59`
      : "";

    a = {
      Start: l,
      End: x,
    };
  }

  const e = await $fetch(s, {
    method: "POST",

    body: a,

    query: {
      diy_type: f.currentSite,
    },

    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${F.token}`,
    },
  });

  E.value = Array.isArray(e.data)
    ? e.data
    : [];

  I.value = 1;
} catch (s) {
  console.error("Fetch orders error:", s);

  alert("獲取訂單失敗");
} finally {
  v.value = !1;
}
try {
  v.value = !0;

  const a = await $fetch(
    `${f.siteConfig.apiPrefix}/orders/edit`,
    {
      method: "POST",

      body: {
        orderID: p.value.orderID,
        editReq: s,
      },

      headers: {
        Authorization: `Bearer ${F.token}`,
      },
    }
  );

  a.error_code === 0
    ? (
        alert("客戶資料已更新"),
        T(),
        O(
          u.value.keyword
            ? "keyword"
            : "date"
        )
      )
    : alert(
        `更新失敗: ${
          a.error_text || "未知錯誤"
        }`
      );
} catch (a) {
  console.error("Edit order error:", a);

  alert("更新發生錯誤");
} finally {
  v.value = !1;
}
if (
  confirm(
    `確定要為訂單 ${n.orderID} 進行退款嗎?`
  )
) {
  try {
    v.value = !0;

    const s = await $fetch(
      `${f.siteConfig.apiPrefix}/refund`,
      {
        method: "POST",

        body: {
          OrderID: n.orderID,
          Token: n.token,
        },

        headers: {
          Authorization: `Bearer ${F.token}`,
        },
      }
    );

    s.error_code === 0
      ? (
          alert("退款成功"),
          O(
            u.value.keyword
              ? "keyword"
              : "date"
          )
        )
      : alert(
          `退款失敗: ${
            s.error_text || "未知錯誤"
          }`
        );
  } catch (s) {
    console.error("Refund error:", s);

    alert("退款失敗");
  } finally {
    v.value = !1;
  }
}
const s = prompt(
  `確定要取消訂單 ${n.orderID} 的發票嗎?
請輸入取消原因 (選填):`,
  ""
);

if (s !== null) {
  try {
    v.value = !0;

    const a = await $fetch(
      `${f.siteConfig.apiPrefix}/cancel-invoice`,
      {
        method: "POST",

        body: {
          OrderID: n.orderID,
          Token: n.token,
          Remark: s,
        },

        headers: {
          Authorization: `Bearer ${F.token}`,
        },
      }
    );

    a.error_code === 0
      ? (
          alert("發票已取消"),
          O(
            u.value.keyword
              ? "keyword"
              : "date"
          )
        )
      : alert(
          `取消發票失敗: ${
            a.error_text || "未知錯誤"
          }`
        );
  } catch (a) {
    console.error("Cancel invoice error:", a);

    alert("取消發票發生錯誤");
  } finally {
    v.value = !1;
  }
}
if (
  confirm(
    `確定要重寄郵件給 ${n.customer.name} (${n.customer.email}) 嗎?`
  )
) {
  try {
    v.value = !0;

    const s = await $fetch(
      `${f.siteConfig.apiPrefix}/orders/resend-email/${n.orderID}`,
      {
        method: "GET",

        headers: {
          Authorization: `Bearer ${F.token}`,
        },

        query: {
          diy_type: f.currentSite,
        },
      }
    );

    s.error_code === 0
      ? alert(
          `已重寄檔案至 ${n.customer.email}`
        )
      : alert(
          `重寄郵件失敗: ${
            s.error_text || "未知錯誤"
          }`
        );
  } catch (s) {
    console.error("Resend email error:", s);

    alert("重寄郵件發生錯誤");
  } finally {
    v.value = !1;
  }
}

修補建議

## 修補建議(Remediation Suggestions)

1. 將匿名登入流程與登入後管理功能拆分為不同的前端 chunk/entry point,避免登入前載入與登入無關的管理 module。
2. 依身份驗證狀態、角色及站別延後載入管理頁面程式碼;避免在匿名可取得的選單、設定或 bundle 中集中列出完整 route、role matrix 與敏感業務操作。
3. 確保所有後台 API 均在伺服器端驗證身份、角色、站別與物件層級權限,不應依賴前端 route middleware、隱藏按鈕或角色陣列作為安全控制。
4. 若此後台原本僅供內部使用,應透過 VPN、allowlisted IP、SSO、零信任存取閘道或其它網路層控制限制公開存取。
5. 使用 `noindex`、適當的搜尋引擎控制及移除快取,降低後台網域被搜尋結果直接發現的機率;此措施只能降低可發現性,不能取代身份驗證與授權。
6. 檢查前端資源中是否存在 source map、測試設定、內部主機名稱、憑證、API key、token 或其它不應公開的秘密。

擷圖

留言討論

聯絡組織

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