Vulnerability Detail Report
Vulnerability Overview
- ZDID: ZD-2026-00510
- Vendor: 悟空科技股份有限公司
- Title: 悟空科技股份有限公司-誠和證件快照*水啦!* 付款流程伺服器端驗證不足,導致訂單金額可被竄改,且同一筆訂單可重複建立付款流程
- Introduction: 付款流程中的價格與金額參數可被修改,導致訂單金額可低於原價,且同一筆訂單可再次建立付款流程並重新指定金額。依目前測試,Line Pay 流程可重現此問題。
處理狀態
目前狀態
-
新提交
-
已審核
-
已通報
-
未回報修補狀況
-
未複測
-
公開
處理歷程
- 2026/04/06 01:16:16 : 新提交 (由 更新此狀態)
- 2026/04/06 01:42:10 : 新提交 (由 更新此狀態)
- 2026/04/06 01:49:36 : 新提交 (由 更新此狀態)
- 2026/04/06 01:57:13 : 新提交 (由 更新此狀態)
- 2026/04/06 02:24:19 : 新提交 (由 更新此狀態)
- 2026/04/06 02:33:48 : 新提交 (由 更新此狀態)
- 2026/04/07 22:50:44 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/04/09 18:44:59 : 審核完成 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/04/09 18:44:59 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/04/09 18:44:59 : 通報未回應 (由 HITCON ZeroDay 服務團隊 更新此狀態)
- 2026/06/06 03:00:08 : 公開 (由 HITCON ZeroDay 平台自動更新)
詳細資料
參考資料
漏洞說明: OWASP - Testing for business logic
https://www.owasp.org/index.php/Testing_for_business_logic
漏洞說明: CWE-840: Business Logic Errors
https://cwe.mitre.org/data/definitions/840.html
相關網址
https://diy.goqoo.com.tw/QKPhoto/Chi/CreateAlbumOrder.php
https://diy.goqoo.com.tw/QKPhoto/Chi/CreatePayment.php
https://diy.goqoo.com.tw/QKPhoto/Chi/SelectPayment.php?ano=...
https://diy.goqoo.com.tw/QKPhoto/Chi/SelectPaymentSave.php
https://diy.goqoo.com.tw/QKPhoto/Chi/PayByPrime.php
https://diy.goqoo.com.tw/QKPhoto/Chi/ViewOrder.php?ano=...
敘述
0. 摘要(Summary)
【誠和證件快照*水啦!*】網站的付款流程存在伺服器端驗證不足問題,主要影響 Line Pay 流程。
我在測試時發現,建立訂單時帶入的 Price,以及付款流程中的 Amount,都會影響後續頁面與付款初始化結果。實際上,當金額改成 非 0 且低於原價 時,系統仍可能正常建立付款流程並回傳 payment_url ,跳轉 Line Pay 掃碼付款頁面。
另外,同一筆訂單的付款流程也可以重新建立。重新建立後,Amount 還可以再被改,ViewOrder.php 顯示的金額也會跟著變。
整體看起來,系統沒有把 原始商品價格、訂單狀態、付款初始化流程、最後送去付款的金額 做好一致的伺服器端綁定,所以才會出現:
- 訂單金額可被竄改
- 非 0 且低於原價的金額仍可能建立付款流程
- 同一筆訂單可再次建立付款流程並重新指定金額
1. 受影響功能(Affected Feature)
-
功能:線上照片訂單付款流程(建立訂單、建立付款頁、送出付款資訊、Line Pay 付款初始化)
-
存取條件:無需登入
-
主要測試付款通路:
PayType=3(Line Pay) -
主要參數:
anoAlbumNoPriceAmount
-
已觀察到的相關端點:
POST /QKPhoto/Chi/CreateAlbumOrder.phpPOST /QKPhoto/Chi/CreatePayment.phpGET /QKPhoto/Chi/SelectPayment.php?ano=...POST /QKPhoto/Chi/SelectPaymentSave.phpPOST /QKPhoto/Chi/PayByPrime.phpGET /QKPhoto/Chi/ViewOrder.php?ano=...
2. 前置條件(Preconditions)
- 無需登入帳號
- 無需具備特殊權限
- 只要能正常走一次照片訂單流程,並在過程中用瀏覽器 / Burp Suite 修改請求參數,即可驗證此問題
- 依目前測試結果,問題已可在 Line Pay 流程中重現
- 若要繼續驗證
PayByPrime.php,需要重新取得有效的prime - 不需要真的完成付款,也不需要真的列印或取件,就可以看出金額與付款流程已有異常
3. 重現步驟(Steps to Reproduce)
以下以 沖印生活照 + Line Pay 為例說明。依測試結果,加印證件照 流程中也能看到同類型問題。
我把測試分成兩條路徑:
- 建立訂單時就改金額
- 同一筆訂單重新建立付款流程,再改金額
路徑 A:建立訂單時就改金額
步驟 1:建立新訂單並攔截 CreateAlbumOrder.php
-
進入網站首頁:
https://diy.goqoo.com.tw/QKPhoto/Chi/MainMenu.php -
點選【沖印生活照】。
-
用 Burp Suite 攔截:
POST /QKPhoto/Chi/CreateAlbumOrder.php -
可以看到請求裡有商品與金額相關參數,例如:
AlbumNo=0&MachineNo=0&PhotoType=6&PhotoKind=0&LayoutNo=111&Price=50其中
Price=50是原本商品價格。
步驟 2:把 Price 改掉
-
把
Price改成其他值,例如:Price=0或:
Price=1 -
送出後,伺服器仍會正常回應
302,並導到新的訂單頁面,例如:Location: UploadFile6.php?ano=...
步驟 3:看上傳頁面金額有沒有變
-
開啟系統導向的上傳頁面,正常上傳一張測試照片。
-
這時就可以直接看到頁面顯示的金額跟著變了:
Price=0時,頁面顯示 0 元Price=1時,頁面顯示 1 元
也就是說,這個價格不是只有在請求裡改掉而已,後續訂單流程頁面也真的吃到了這個值。
步驟 4:繼續走到付款流程
-
在訂單頁面按【結帳】。
-
系統會送出:
POST /QKPhoto/Chi/CreatePayment.php -
接著會導到:
/QKPhoto/Chi/SelectPayment.php?ano=... -
在付款頁選 Line Pay,填入測試用手機號碼、Email 後送出。這時可看到:
POST /QKPhoto/Chi/SelectPaymentSave.php
步驟 5:看後面的付款初始化結果
-
如果前面把
Price改成 0,依測試結果:ViewOrder.php會顯示 TWD 0 元- 後續系統仍會嘗試往下做付款初始化
- 但到
POST /QKPhoto/Chi/PayByPrime.php時,伺服器會回:
{"status":510,"msg":"Invalid arguments : amount"} -
如果前面把
Price改成 1,依測試結果:- 系統仍可進到後面的付款初始化
POST /QKPhoto/Chi/PayByPrime.php會成功- 回應中的
amount會是 1 - 回應中會有
payment_url - 前端可正常轉跳到 Line Pay 付款頁
ViewOrder.php顯示金額也會是 TWD 1 元
這裡可以看出,系統不是完全沒檢查金額,因為 0 元會被擋;但對 非 0 且低於原價 的金額,檢查明顯不夠。
路徑 B:同一筆訂單重新建立付款流程,再改金額
步驟 6:先完成一筆正常訂單流程
-
先照正常流程建立一筆照片訂單,讓系統產生有效的
ano / AlbumNo。 -
進到付款頁後,可以看到整個付款流程大致包含:
POST /QKPhoto/Chi/CreatePayment.php GET /QKPhoto/Chi/SelectPayment.php?ano=... POST /QKPhoto/Chi/SelectPaymentSave.php POST /payment/line-pay/production/get-prime (Host: js.tappaysdk.com) POST /QKPhoto/Chi/PayByPrime.php
步驟 7:重送同一筆訂單的 CreatePayment.php
-
把這筆訂單原本的:
POST /QKPhoto/Chi/CreatePayment.php送到 Repeater 再送一次。
-
依測試結果,伺服器仍會正常回
302,並重新導到:SelectPayment.php?ano=...
也就是說,同一筆訂單的付款頁面是可以再被建立一次的,沒有被明確拒絕。
步驟 8:在重新建立的付款流程中改 Amount
-
接著攔截同一筆訂單的:
POST /QKPhoto/Chi/SelectPaymentSave.php -
請求中可以看到像這樣的欄位:
AlbumNo=15679&Amount=50&Prime=&PayType=3&... -
把
Amount改成別的值,例如:Amount=0或:
Amount=1或:
Amount=2 -
送出後,依測試結果,伺服器仍會回:
{"err":0,"url":"ViewOrder.php?ano=..."} -
打開
ViewOrder.php?ano=...後,可以看到顯示金額真的變了:Amount=0時顯示 TWD 0 元Amount=1時顯示 TWD 1 元Amount=2時顯示 TWD 2 元
這表示 Amount 不只是前端暫時顯示而已,後端回來的訂單頁面內容也已經被影響。
步驟 9:重新取得有效 prime
-
如果要繼續測
PayByPrime.php,需要新的prime值。正常流程裡會看到第三方請求:POST /payment/line-pay/production/get-prime Host: js.tappaysdk.com -
把這筆請求送到 Repeater 重送,可取得新的
prime值。 -
如果直接拿舊的、已經用過的
prime值去送:POST /QKPhoto/Chi/PayByPrime.php伺服器會回:
{"status":121,"msg":"Invalid arguments : prime"}
所以這裡的重點在於商家端怎麼處理訂單金額與付款上下文, 而非 prime 可被重放。
步驟 10:用新 prime 值測修改後金額能不能建立付款流程
-
把新取得的
prime值帶入:POST /QKPhoto/Chi/PayByPrime.php並測不同
Amount。 -
如果
Amount=0,依測試結果,伺服器會回:{"status":510,"msg":"Invalid arguments : amount"} -
如果
Amount=1或其他非 0 低額值(例如2),依測試結果:PayByPrime.php會回成功msg會是Success- 回應中的
amount會是修改後的值 - 會拿到
payment_url - 前端可正常轉跳到 Line Pay 付款頁
ViewOrder.php顯示金額也會是修改後的值
這代表 非 0 且低於原價 的金額,系統仍可能接受並建立付款流程。
步驟 11:再看 Price 和 Amount 有沒有被一致性檢查
-
我另外測了幾組
Price / Amount組合,例如:Price=50、Amount=0Price=1、Amount=2
-
依測試結果:
Price=50、Amount=0時,付款頁和ViewOrder.php會顯示 0 元,但到PayByPrime.php又會因amount=0被拒絕Price=1、Amount=2時,付款頁和ViewOrder.php顯示的是 2 元,而且付款流程仍可成功建立
也就是說,系統看起來沒有把 Price 和 Amount 做嚴格一致性檢查,最後真正往後面付款流程走的金額,主要比較像是受 Amount 影響。
補充觀察
依測試結果,prime 值本身像是一次性的。直接重送已使用過的 prime 值不會成功。
所以這份報告的重點在於商家端對 訂單金額、付款初始化狀態、付款交易上下文 的伺服器端控制不夠嚴謹,而非第三方金流 token 被重放。
4. 概念驗證(Proof of Concept)
以下 PoC 是我在實際測試時看到的重點結果。
這版因為 HITCON 提交附件最多只能上傳 10 張圖,所以我只附足以證明主問題成立的必要代表性截圖。其餘測過的金額組合、完整流程截圖與對應 request / response 我都有保留,若需要可再補充。
1. 建立訂單時改 Price,上傳頁金額就會變
我先在:
POST /QKPhoto/Chi/CreateAlbumOrder.php
把原本 Price=50 改成 0 或 1。
送出後,系統沒有拒絕,還是正常建立訂單並導到上傳頁。接著在上傳頁面就能直接看到:
Price=0時顯示 0 元Price=1時顯示 1 元
對應截圖:
2. SelectPaymentSave.php 的 Amount 會直接影響 ViewOrder.php
我先保留一組正常流程的 Amount=50 當對照,再去改:
POST /QKPhoto/Chi/SelectPaymentSave.php
把 Amount 改成 1 後,伺服器仍會回:
{"err":0,"url":"ViewOrder.php?ano=..."}
接著打開 ViewOrder.php,頁面就會顯示 TWD 1 元。
也就是說,這個值已經不是只有封包裡改了,訂單頁面顯示也真的跟著變。
本次附圖放的是:
- 正常
Amount=50 - 修改成
Amount=1
另外我也有測 0、2,模式相同,但這次因附件數量限制先不全部附上。
對應截圖:
3. Amount=0 會被擋,但 Amount=1、Amount=2 這種低額值仍可成功建立付款流程
我進一步拿新的 prime 去送:
POST /QKPhoto/Chi/PayByPrime.php
測到的結果很清楚:
-
正常
Amount=50時,會成功,並回payment_url -
Amount=0時,會回:{"status":510,"msg":"Invalid arguments : amount"} -
Amount=1時,仍會成功,並回:msg: "Success"- 修改後的
amount - 可用的
payment_url
-
Amount=2時,依測試結果也可成功,並回:msg: "Success"- 修改後的
amount - 可用的
payment_url
另外,依完整測試紀錄, 將可用的 payment_url 手動貼到瀏覽器網址列開啟後,可正常進入 Line Pay 付款頁 ;就 Burp 中可觀察到的 request flow 而言,和正常原價流程未見明顯差異。
不過因本次提交受附件數量限制,這裡先不另外附上轉跳流程截圖,如有需要可再補充。
這代表系統不是完全沒驗證金額,但它只擋掉 0,沒有把 非 0 且低於原價 的情況擋下來。
對應截圖:
4. 同一筆訂單可以再建立一次付款流程,而且金額還能再改
我把同一筆訂單原本的:
POST /QKPhoto/Chi/CreatePayment.php
重送一次後,伺服器仍正常回 302,並重新導到:
SelectPayment.php?ano=...
也就是說,同一筆訂單的付款流程是可以再被建立的。
接著我在這個重新建立的流程裡,再去改 Amount,發現後續 flow 會繼續往下跑,且 ViewOrder.php 顯示的金額也會跟著更新。
本次附圖放的是其中一個代表案例:同一筆訂單重建付款流程後,金額可被改成其他非原始價格。
其他數值組合我也有測,模式一致,但因附件上限,這次先放最能代表問題的圖。
對應截圖:
以 Repeater 重送
CreatePayment.php後,伺服器仍重新導到同一筆訂單之SelectPayment.php?ano=...。
本圖在此請看下半部:可見同一筆訂單(same
AlbumNo/ sameano)的付款 flow 被重新建立,且後續PayByPrime.php請求中可見修改後的Amount。
5. Price 和 Amount 看起來沒有被嚴格綁在一起檢查
我另外挑了一組代表性案例,去看 Price 跟 Amount 是否真的有被做一致性驗證。
例如我測到一組:
Price=1Amount=2
依測試結果,後續付款流程仍可能採用 Amount=2 去建立付款流程。
換句話說,系統看起來沒有嚴格要求 Price 和 Amount 必須一致,也沒有在每個節點都重新用伺服器端正確價格覆蓋掉。
對應截圖:
引用前述同一張圖。
- 本圖上半部:Repeater 中
Price=1與Amount=2有關的請求內容- 本圖下半部:Proxy HTTP history flow 中
POST /QKPhoto/Chi/PayByPrime.php的amount=2也就是用同一張圖同時看出:前面價格情境是
Price=1,但後面真正送去付款初始化的是Amount=2。
預期行為(Expected Behavior)
正常來說,伺服器應該在付款流程每一步都自己確認:
- 這筆訂單原本應付多少
- 目前是否還允許建立付款流程
- 送去付款的金額是不是跟訂單內容一致
- 同一筆訂單是否已經有既有付款流程或既定狀態
如果金額被改掉、或付款請求跟既有訂單上下文不一致,就應直接拒絕,而不是繼續建立付款流程。
實際行為(Actual Behavior)
依目前測試結果,系統在建立訂單、送出付款資訊、付款初始化等步驟中,都可能接受被修改過的金額。
雖然 0 元請求會被擋下來,但 非 0 且低於原價 的金額仍可能成功建立付款流程;另外,同一筆訂單也可再次建立付款流程,並重新指定金額。
整體來看,後端對 訂單金額、訂單狀態、付款交易上下文 的伺服器端驗證明顯不足。
5. 影響(Impact)
依本次測試結果,已可確認:
- 訂單金額可被改成非原始商品價格
ViewOrder.php顯示的消費金額會跟著變- 非 0 且低於原價 的金額,仍可能成功建立 Line Pay 付款流程
- 同一筆訂單可再次建立付款流程,且金額可被重新指定
這會帶來的風險包括:
- 交易金額完整性受影響
- 付款流程可信度下降
- 訂單狀態可能變得不一致
- 低於原價的付款請求可能被系統接受
另外,我也觀察到某些情況下,就算付款初始化沒有成功,ViewOrder.php 仍可能顯示異常金額並產生可用的訂單頁面內容。
如果後續列印 / 取件流程(https://www.seiwainc.com.tw/articles/how-to-print)沒有再獨立驗證實際付款狀態,而是過度依賴訂單頁面或訂單編號,風險可能還會往後延伸。
不過,這部分我沒有進一步做實體列印 / 取件驗證,所以這裡只保守寫成可能風險,不寫成既成事實。
6. 可能的根本原因(Likely Root Cause)
從測試結果看起來,問題不像是單一欄位沒檢查而已,而比較像整個付款流程的伺服器端控制不夠完整。
比較可能的情況是:
- 伺服器沒有在每個關鍵步驟重新計算並強制採用正確金額
- 後端過度信任前端送來的
Price、Amount、ano、AlbumNo - 同一筆訂單是否能再次建立付款流程,沒有被嚴格檢查
- 訂單內容、應付金額、付款通路、付款初始化狀態之間,沒有被做好一致綁定
- 不同流程節點之間,缺少統一且不可繞過的金額一致性驗證
簡單說,這比較像是 付款交易上下文綁定失敗,而不是單純某一個 API 少檢查一個欄位。
7. 嚴重性評估(Severity Assessment)
高風險(High)
理由:
- 可直接影響交易金額完整性
- 非
0且低於原價的金額仍可能成功建立付款流程 - 同一筆訂單可再次建立付款流程,顯示狀態控制不足
- 問題發生在核心交易流程,不是單一資訊頁面
- 無需登入即可測試,利用門檻相對不高
我目前 沒有實際完成付款,也沒有實際做列印 / 取件驗證,所以這份報告先保守評成 High,不直接寫成 Critical。
但如果廠商後續確認以下任一情況成立,實際風險就可能更高:
- 低價付款建立的訂單,可被後續系統視為有效完成訂單
- 未完成預期付款驗證,仍可直接取件或完成履約(
https://www.seiwainc.com.tw/articles/how-to-print) - 其他付款通路或其他商品流程也有相同問題
8. 補充說明(Researcher Note)
本次測試僅限確認漏洞存在,未進行批次化、大量化、自動化濫用測試,也未進行破壞性操作。
另外:
- 未實際完成付款
- 未實際列印、取件或驗證履約
- 未測試其他付款通路是否也有相同問題
- 未使用真實 LINE Pay 身分完成操作
- 未為了驗證而實際掃碼或完成支付
prime相關測試僅限確認一次性特性,未嘗試繞過第三方金流平台本身的安全機制
測試過程中若需要填寫手機號碼、Email 或上傳照片,均使用非真實個資與測試素材,例如十分鐘手機號碼、十分鐘信箱及公開可得圖片,並未使用真實聯絡資訊或個人照片。
修補建議
1. 所有付款相關金額都應由伺服器端重新計算並強制採用,不應直接信任前端傳來的 `Price` / `Amount`
2. 不要讓前端可直接指定最終付款金額
3. 將訂單編號、商品內容、應付金額、付款通路、付款初始化狀態等資訊,在伺服器端做一致綁定
4. 限制同一筆訂單重複建立付款流程
5. 在 `CreateAlbumOrder.php`、`CreatePayment.php`、`SelectPaymentSave.php`、`PayByPrime.php`、`ViewOrder.php` 之間建立統一且不可繞過的金額一致性檢查
6. 檢查後續列印 / 取件流程(`https://www.seiwainc.com.tw/articles/how-to-print`)是否有獨立驗證實際付款狀態
7. 一併檢查其他付款通路與其他商品流程是否也有相同問題