悟空科技股份有限公司-誠和證件快照*水啦!* 付款流程伺服器端驗證不足,導致訂單金額可被竄改,且同一筆訂單可重複建立付款流程 - HITCON ZeroDay

Vulnerability Detail Report

Vulnerability Overview

  • ZDID: ZD-2026-00510
  •  發信 Vendor: 悟空科技股份有限公司
  • Title: 悟空科技股份有限公司-誠和證件快照*水啦!* 付款流程伺服器端驗證不足,導致訂單金額可被竄改,且同一筆訂單可重複建立付款流程
  • Introduction: 付款流程中的價格與金額參數可被修改,導致訂單金額可低於原價,且同一筆訂單可再次建立付款流程並重新指定金額。依目前測試,Line Pay 流程可重現此問題。

處理狀態

目前狀態

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

處理歷程

  • 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 平台自動更新)

詳細資料

  • ZDID:ZD-2026-00510
  • 通報者:S_Cyrus ()
  • 風險:高
  • 類型:邏輯漏洞 (Logic Flaws)

參考資料

攻擊者可經由該漏洞繞過網站邏輯行為進行惡意攻擊。

漏洞說明: 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/MainMenu.php
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)

  • 主要參數:

    • ano
    • AlbumNo
    • Price
    • Amount
  • 已觀察到的相關端點:

    • POST /QKPhoto/Chi/CreateAlbumOrder.php
    • POST /QKPhoto/Chi/CreatePayment.php
    • GET /QKPhoto/Chi/SelectPayment.php?ano=...
    • POST /QKPhoto/Chi/SelectPaymentSave.php
    • POST /QKPhoto/Chi/PayByPrime.php
    • GET /QKPhoto/Chi/ViewOrder.php?ano=...

2. 前置條件(Preconditions)

  • 無需登入帳號
  • 無需具備特殊權限
  • 只要能正常走一次照片訂單流程,並在過程中用瀏覽器 / Burp Suite 修改請求參數,即可驗證此問題
  • 依目前測試結果,問題已可在 Line Pay 流程中重現
  • 若要繼續驗證 PayByPrime.php,需要重新取得有效的 prime
  • 不需要真的完成付款,也不需要真的列印或取件,就可以看出金額與付款流程已有異常

3. 重現步驟(Steps to Reproduce)

以下以 沖印生活照 + Line Pay 為例說明。依測試結果,加印證件照 流程中也能看到同類型問題。

我把測試分成兩條路徑:

  1. 建立訂單時就改金額
  2. 同一筆訂單重新建立付款流程,再改金額

路徑 A:建立訂單時就改金額

步驟 1:建立新訂單並攔截 CreateAlbumOrder.php

  1. 進入網站首頁:
    https://diy.goqoo.com.tw/QKPhoto/Chi/MainMenu.php

  2. 點選【沖印生活照】。

  3. 用 Burp Suite 攔截:

    POST /QKPhoto/Chi/CreateAlbumOrder.php
  4. 可以看到請求裡有商品與金額相關參數,例如:

    AlbumNo=0&MachineNo=0&PhotoType=6&PhotoKind=0&LayoutNo=111&Price=50

    其中 Price=50 是原本商品價格。


步驟 2:把 Price 改掉

  1. Price 改成其他值,例如:

    Price=0

    或:

    Price=1
  2. 送出後,伺服器仍會正常回應 302,並導到新的訂單頁面,例如:

    Location: UploadFile6.php?ano=...

步驟 3:看上傳頁面金額有沒有變

  1. 開啟系統導向的上傳頁面,正常上傳一張測試照片。

  2. 這時就可以直接看到頁面顯示的金額跟著變了:

    • Price=0 時,頁面顯示 0 元
    • Price=1 時,頁面顯示 1 元

也就是說,這個價格不是只有在請求裡改掉而已,後續訂單流程頁面也真的吃到了這個值。


步驟 4:繼續走到付款流程

  1. 在訂單頁面按【結帳】。

  2. 系統會送出:

    POST /QKPhoto/Chi/CreatePayment.php
  3. 接著會導到:

    /QKPhoto/Chi/SelectPayment.php?ano=...
  4. 在付款頁選 Line Pay,填入測試用手機號碼、Email 後送出。這時可看到:

    POST /QKPhoto/Chi/SelectPaymentSave.php

步驟 5:看後面的付款初始化結果

  1. 如果前面把 Price 改成 0,依測試結果:

    • ViewOrder.php 會顯示 TWD 0 元
    • 後續系統仍會嘗試往下做付款初始化
    • 但到 POST /QKPhoto/Chi/PayByPrime.php 時,伺服器會回:
    {"status":510,"msg":"Invalid arguments : amount"}
  2. 如果前面把 Price 改成 1,依測試結果:

    • 系統仍可進到後面的付款初始化
    • POST /QKPhoto/Chi/PayByPrime.php 會成功
    • 回應中的 amount 會是 1
    • 回應中會有 payment_url
    • 前端可正常轉跳到 Line Pay 付款頁
    • ViewOrder.php 顯示金額也會是 TWD 1 元

這裡可以看出,系統不是完全沒檢查金額,因為 0 元會被擋;但對 非 0 且低於原價 的金額,檢查明顯不夠。


路徑 B:同一筆訂單重新建立付款流程,再改金額

步驟 6:先完成一筆正常訂單流程

  1. 先照正常流程建立一筆照片訂單,讓系統產生有效的 ano / AlbumNo

  2. 進到付款頁後,可以看到整個付款流程大致包含:

    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

  1. 把這筆訂單原本的:

    POST /QKPhoto/Chi/CreatePayment.php

    送到 Repeater 再送一次。

  2. 依測試結果,伺服器仍會正常回 302,並重新導到:

    SelectPayment.php?ano=...

也就是說,同一筆訂單的付款頁面是可以再被建立一次的,沒有被明確拒絕。


步驟 8:在重新建立的付款流程中改 Amount

  1. 接著攔截同一筆訂單的:

    POST /QKPhoto/Chi/SelectPaymentSave.php
  2. 請求中可以看到像這樣的欄位:

    AlbumNo=15679&Amount=50&Prime=&PayType=3&...
  3. Amount 改成別的值,例如:

    Amount=0

    或:

    Amount=1

    或:

    Amount=2
  4. 送出後,依測試結果,伺服器仍會回:

    {"err":0,"url":"ViewOrder.php?ano=..."}
  5. 打開 ViewOrder.php?ano=... 後,可以看到顯示金額真的變了:

    • Amount=0 時顯示 TWD 0 元
    • Amount=1 時顯示 TWD 1 元
    • Amount=2 時顯示 TWD 2 元

這表示 Amount 不只是前端暫時顯示而已,後端回來的訂單頁面內容也已經被影響。


步驟 9:重新取得有效 prime

  1. 如果要繼續測 PayByPrime.php,需要新的 prime 值。正常流程裡會看到第三方請求:

    POST /payment/line-pay/production/get-prime
    Host: js.tappaysdk.com
  2. 把這筆請求送到 Repeater 重送,可取得新的 prime 值。

  3. 如果直接拿舊的、已經用過的 prime 值去送:

    POST /QKPhoto/Chi/PayByPrime.php

    伺服器會回:

    {"status":121,"msg":"Invalid arguments : prime"}

所以這裡的重點在於商家端怎麼處理訂單金額與付款上下文, 而非 prime 可被重放。


步驟 10:用新 prime 值測修改後金額能不能建立付款流程

  1. 把新取得的 prime 值帶入:

    POST /QKPhoto/Chi/PayByPrime.php

    並測不同 Amount

  2. 如果 Amount=0,依測試結果,伺服器會回:

    {"status":510,"msg":"Invalid arguments : amount"}
  3. 如果 Amount=1 或其他非 0 低額值(例如 2),依測試結果:

    • PayByPrime.php 會回成功
    • msg 會是 Success
    • 回應中的 amount 會是修改後的值
    • 會拿到 payment_url
    • 前端可正常轉跳到 Line Pay 付款頁
    • ViewOrder.php 顯示金額也會是修改後的值

這代表 非 0 且低於原價 的金額,系統仍可能接受並建立付款流程。


步驟 11:再看 PriceAmount 有沒有被一致性檢查

  1. 我另外測了幾組 Price / Amount 組合,例如:

    • Price=50Amount=0
    • Price=1Amount=2
  2. 依測試結果:

    • Price=50Amount=0 時,付款頁和 ViewOrder.php 會顯示 0 元,但到 PayByPrime.php 又會因 amount=0 被拒絕
    • Price=1Amount=2 時,付款頁和 ViewOrder.php 顯示的是 2 元,而且付款流程仍可成功建立

也就是說,系統看起來沒有把 PriceAmount 做嚴格一致性檢查,最後真正往後面付款流程走的金額,主要比較像是受 Amount 影響。


補充觀察

依測試結果,prime 值本身像是一次性的。直接重送已使用過的 prime 值不會成功。
所以這份報告的重點在於商家端對 訂單金額、付款初始化狀態、付款交易上下文 的伺服器端控制不夠嚴謹,而非第三方金流 token 被重放。


4. 概念驗證(Proof of Concept)

以下 PoC 是我在實際測試時看到的重點結果。
這版因為 HITCON 提交附件最多只能上傳 10 張圖,所以我只附足以證明主問題成立的必要代表性截圖。其餘測過的金額組合、完整流程截圖與對應 request / response 我都有保留,若需要可再補充。


1. 建立訂單時改 Price,上傳頁金額就會變

我先在:

POST /QKPhoto/Chi/CreateAlbumOrder.php

把原本 Price=50 改成 01

送出後,系統沒有拒絕,還是正常建立訂單並導到上傳頁。接著在上傳頁面就能直接看到:

  • Price=0 時顯示 0 元
  • Price=1 時顯示 1 元

對應截圖:

  • 圖片
  • 圖片

2. SelectPaymentSave.phpAmount 會直接影響 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

另外我也有測 02,模式相同,但這次因附件數量限制先不全部附上。

對應截圖:

  • 圖片
  • 圖片

3. Amount=0 會被擋,但 Amount=1Amount=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 / same ano)的付款 flow 被重新建立,且後續 PayByPrime.php 請求中可見修改後的 Amount


5. PriceAmount 看起來沒有被嚴格綁在一起檢查

我另外挑了一組代表性案例,去看 PriceAmount 是否真的有被做一致性驗證。

例如我測到一組:

  • Price=1
  • Amount=2

依測試結果,後續付款流程仍可能採用 Amount=2 去建立付款流程。
換句話說,系統看起來沒有嚴格要求 PriceAmount 必須一致,也沒有在每個節點都重新用伺服器端正確價格覆蓋掉。

對應截圖:

  • 圖片

引用前述同一張圖。

  • 本圖上半部:Repeater 中 Price=1Amount=2 有關的請求內容
  • 本圖下半部:Proxy HTTP history flow 中 POST /QKPhoto/Chi/PayByPrime.phpamount=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)

從測試結果看起來,問題不像是單一欄位沒檢查而已,而比較像整個付款流程的伺服器端控制不夠完整。

比較可能的情況是:

  1. 伺服器沒有在每個關鍵步驟重新計算並強制採用正確金額
  2. 後端過度信任前端送來的 PriceAmountanoAlbumNo
  3. 同一筆訂單是否能再次建立付款流程,沒有被嚴格檢查
  4. 訂單內容、應付金額、付款通路、付款初始化狀態之間,沒有被做好一致綁定
  5. 不同流程節點之間,缺少統一且不可繞過的金額一致性驗證

簡單說,這比較像是 付款交易上下文綁定失敗,而不是單純某一個 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. 一併檢查其他付款通路與其他商品流程是否也有相同問題

擷圖

留言討論

聯絡組織

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