Vulnerability Detail Report
Vulnerability Overview
- ZDID: ZD-2026-00956
- Vendor: 悟空科技股份有限公司
- Title: 悟空科技股份有限公司 後台可被公開發現,且未登入即可取得包含管理功能路徑、API 端點、角色模型與業務邏輯的前端資源
- Introduction: 網站後台可由公開網路發現,且未登入即可取得包含管理功能路徑、API 端點、角色與站別邏輯,以及敏感業務功能資訊的前端 JavaScript bundle。
處理狀態
目前狀態
-
新提交
-
已審核
-
已通報
-
未回報修補狀況
-
未複測
-
公開
處理歷程
- 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 平台自動更新)
詳細資料
參考資料
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/_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:找到並開啟後台登入頁面
- 使用瀏覽器或 Burp Suite 瀏覽器開啟全新無痕視窗。
- 使用 Google dork
site:diyidphoto.com進行查詢,可在搜尋結果中發現https://helper.diyidphoto.com。 - 點擊後會連到
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)
此問題造成 匿名可取得的管理端架構與業務邏輯資訊洩漏。
攻擊者可使用這些資訊:
- 快速建立後台 route 與 API map。
- 識別優先測試的高價值功能,例如退款、訂單修改、取消發票及重寄郵件。
- 理解
admin、tw-service、jp-service的角色範圍。 - 理解台灣站與日本站的站別邏輯,並據此設計跨站別授權測試。
- 找到正式環境中的測試、Redis 及佇列監控功能。
- 減少端點猜測、HTTP method 判斷及請求格式重建所需時間。
本漏洞單獨存在時,其直接影響主要是降低攻擊者的偵察、端點枚舉及針對性授權測試成本。
但由於後台存在權限漏洞(請見低權限後台帳號可繞過 RBAC與後台身份驗證可被繞過),這些資訊亦可提高既有漏洞的利用效率及攻擊鏈建立效率。
7. 可能的根本原因(Likely Root Cause)
問題可能出在前端建置與部署方式未區分「匿名登入流程」及「完成身份驗證後的管理功能」,導致登入前即向瀏覽器提供大量管理端程式碼。
可能原因包括:
- 登入頁面與登入後管理介面共用相同的前端 build 或 dependency graph。
- 應用程式未將匿名登入流程與管理端功能進行最小化程式碼切分。
- 管理選單、route、role matrix、site gating 及 API 呼叫資訊被集中放置於匿名可取得的 bundle。
- 後台網域可被搜尋引擎索引,使暴露內容更容易被發現。
- 部署設計可能過度依賴前端 middleware、隱藏選單或程式碼混淆降低可見性。
8. 嚴重性評估(Severity Assessment)
- 單獨風險:低(Low)
- 與其它後台漏洞串聯後的整體風險:中高(Medium-High)
單獨風險評估
- 匿名訪客可取得大量超出登入流程所需的管理端資訊。
- 暴露內容涉及敏感管理功能、角色模型、站別邏輯、測試/監控 route 及 API 請求格式。
- 相關資訊可降低攻擊者進行後台偵察、端點枚舉及針對性授權測試的成本。
- 於本次檢視的前端資源中,未發現 hardcoded credential、有效 token、私密金鑰或其他可直接利用的 secret。
- 若不考慮其它後台權限漏洞,且所有後台 API 均能正確回應
401/403並執行伺服器端身份驗證與授權,則實際利用程度有限。
串聯後風險評估
本漏洞可與後台權限漏洞-例如低權限後台帳號可繞過 RBAC與後台身份驗證可被繞過-串聯。
暴露的路由、角色、站別及 API 資訊可降低偵察與請求建構成本,提升敏感功能定位與權限測試效率,因此整體串聯風險評估為中高(Medium-High)。
9. 補充說明(Researcher Note)
-
本報告之測試僅限確認漏洞存在,未進行批次化、大量化或自動化操作,也未進行破壞性操作。
-
由於此系統與技術牽涉到三家公司-台灣公司為誠和國際股份有限公司(
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 或其它不應公開的秘密。