← Week of October 5, 2026
THE DEEP DIVE / CVE-2026-108707 9.8 CRITICAL

Wukong HRM.

Wukong_HRM through commit 186115e contains an authentication bypass vulnerability in ParamAspect that allows unauthenticated attackers to call every HRM API endpoint by omitting the AUTH-TOKEN header. Attackers gain HR administrator access to read payslips, salary history and employee personal data, download attachments, and modify or delete company-wide HR records.

READING MODE

Concepts, source walkthroughs, diagrams, evidence, and limitations.

Full analysis selected.

Evidence-backed draft · source analysis only. Code excerpts come from the pinned Wukong_HRM commit and read-only PoCs cited below. No Wukong_HRM server or third-party target was contacted by this application.

Authentication is implemented as an aspect

Wukong_HRM uses Spring AOP to run authentication logic around mapped controller methods. Instead of every endpoint calling an authentication helper directly, ParamAspect.around() reads the request token, resolves a user from Redis, places that user in UserUtil, and then allows the controller to proceed.

That architecture makes the aspect a high-value trust boundary. If it fails open, many unrelated controllers inherit the same bypass.

Rendering diagram…An HTTP request is mapped to a controller method. ParamAspect intercepts that method, reads the authentication token, checks Redis, and stores a user object in UserUtil before allowing controller and service logic to run.
Processing stepData storeLabelled direction
Data-flow diagramParamAspect sits between Spring route selection and controller execution.
Read the diagram as text

An HTTP request is mapped to a controller method. ParamAspect intercepts that method, reads the authentication token, checks Redis, and stores a user object in UserUtil before allowing controller and service logic to run.

The critical question is what happens when the header is absent or the cache lookup fails. Secure middleware should stop at that point. In the affected code, a default object makes the intended null check unreachable.

The bug is visible in four lines

At commit 186115e1…, ParamAspect initializes info with a new object before it validates the token:

String token = request.getHeader(Const.DEFAULT_TOKEN_NAME);
UserInfo info = new UserInfo();

if (StrUtil.isNotEmpty(token)
        && redis.exists(token + Const.TOKEN_CACHE_NAME)) {
    info = redis.get(token);
}

if (ObjectUtil.isNull(info)) {
    throw new NoLoginException();
}

UserUtil.setUser(info);
JAVA codeA non-null default UserInfo makes the later null guard unreachable on tokenless requests.

Consider the tokenless path one statement at a time:

  1. request.getHeader(...) returns null or an empty value.
  2. new UserInfo() creates a non-null object.
  3. StrUtil.isNotEmpty(token) is false, so Redis is not consulted.
  4. ObjectUtil.isNull(info) is false because info still references that new object.
  5. The aspect stores an empty UserInfo and calls the controller.

The exception exists, but the default initialization prevents the missing-token path from reaching it. This is a fail-open state initialization bug, not a cryptographic weakness in the token itself.

Rendering diagram…An unauthenticated client sends a request without a token. ParamAspect creates an empty but non-null UserInfo. It skips Redis, the null check evaluates false, and the aspect stores the empty identity before proceeding to the controller.
Actor or componentNumbered message directionLifetime during sequence
Sequence diagramThe tokenless path bypasses the intended exception and reaches the controller.
Read the diagram as text

An unauthenticated client sends a request without a token. ParamAspect creates an empty but non-null UserInfo. It skips Redis, the null check evaluates false, and the aspect stores the empty identity before proceeding to the controller.

Why the pointcut increases the blast radius

The @Around expression targets request-mapped controller methods across com.kakarote.*.controller namespaces:

@Around(
  "(execution(* com.kakarote.*.controller..*(..)) || " +
  " execution(* com.kakarote.*.*.controller..*(..))) && " +
  "execution(@(org.springframework.web.bind.annotation.*Mapping) * *(..)) && " +
  "!execution(@(com.kakarote.core.common.ParamAspect) * *(..))"
)
public Object around(ProceedingJoinPoint point) throws Throwable {
    // authentication context setup
    return point.proceed();
}
JAVA codeThe broad pointcut wraps mapped controller methods across Wukong namespaces.

The precise endpoint impact still depends on other checks in each controller, service, gateway, or employee-scoping aspect. But the central gate no longer guarantees that a request has an authenticated principal. Endpoints that assume ParamAspect already performed that work can become reachable with an empty context.

This is why the CVE is described as an authentication bypass across HRM APIs rather than one isolated missing annotation.

A concrete read-only path: attachment download

The public PoC repository documents a bounded, read-only example around:

GET /adminFile/download/{fileId} HTTP/1.1
Host: wukong-hrm.local
# Deliberately no AUTH-TOKEN header
HTTP exchangeA read-only lab control requests one pre-created synthetic attachment without a token.

Its decision logic does not treat any HTTP 200 as proof. It requires a non-empty body and records details such as the content type and disposition:

resp = client.get(DOWNLOAD % file_id, anonymous=True)

record = {
    "http_status": resp.status,
    "bytes": len(resp.body or b""),
    "content_disposition": resp.content_disposition,
    "gated": resp.verdict_class in ("NOT_LOGIN", "NO_AUTH"),
}

confirmed = (
    record["http_status"] == 200
    and record["bytes"] > 0
)
PYTHON codeThe PoC requires both an HTTP success and a non-empty response before recording confirmation.

That distinction matters. A redirect, gateway-generated page, empty body, or nonexistent file is not the same as retrieving a stored attachment. A defensible reproduction needs a synthetic file created specifically for the lab and must compare anonymous and authenticated requests.

Rendering diagram…Create one synthetic private attachment and record its identifier. Request only that identifier anonymously, validate status and known content, compare an authenticated control, apply the authentication fix, and repeat both requests.
Processing stepLabelled direction
Data-flow diagramA safe attachment test uses a known fixture and paired anonymous/authenticated controls.
Read the diagram as text

Create one synthetic private attachment and record its identifier. Request only that identifier anonymously, validate status and known content, compare an authenticated control, apply the authentication fix, and repeat both requests.

The PoC also describes an optional fileList/{batchId} oracle, but a minimal lab should avoid broad enumeration. If the test already knows the synthetic file ID, no discovery step is needed.

A second example shows why authentication and authorization are separate

Another cited PoC targets POST /hrmEmployee/export with a body containing {"pageType": 0}. Its source comments trace the route through controller, service, and mapper code and assert that pageType=0 can raise the page limit to 10,000 rows.

A safe request shape for an isolated fixture is:

POST /hrmEmployee/export HTTP/1.1
Host: wukong-hrm.local
Content-Type: application/json
# no AUTH-TOKEN in the anonymous case

{"pageType": 0, "deptId": "synthetic-lab-department"}
HTTP exchangeThe export control targets only a synthetic lab department and omits the token deliberately.

The response assertion should check for a workbook signature only after populating the lab with synthetic employees:

XLSX_MAGIC = b"PK\x03\x04"
confirmed = (
    response.status == 200
    and response.body.startswith(XLSX_MAGIC)
    and b"LAB-EMPLOYEE" in parsed_synthetic_rows
)
PYTHON codeA bounded assertion checks the workbook signature and a lab-only marker rather than real employee data.

This route illustrates two layers:

  • The ParamAspect flaw can remove the authentication prerequisite.
  • A route that exports too broad a population can have a separate authorization/data-scope issue even for a logged-in user.

Fixing the first does not automatically prove the second is fixed. A technical test plan should keep separate assertions for anonymous, ordinary-employee, and HR-administrator sessions.

Evidence limitations and a responsible local reproduction

A complete lab needs more than running the PoC against an arbitrary URL. It should pin commit 186115e1…, start only required Wukong_HRM services on a private VM network, and populate synthetic records.

Recommended test matrix:

CaseTokenFixtureExpected vulnerable resultExpected fixed result
Attachment controlvalid HR tokenknown lab fileknown file returnedknown file returned
Attachment bypassabsentsame known fileknown file returned if affected401/403 before controller
Export controlvalid HR tokentwo synthetic employeesworkbook containing fixturesworkbook containing fixtures
Export bypassabsentsame fixturesworkbook if affected401/403 before export logic
Invalid IDabsentnonexistent filemust not count as proofmust not count as proof

Capture the HTTP status, relevant response headers, a hash of the synthetic response body, application logs showing which controller ran, and the exact commit. Do not retain real employee or attachment data.

This service has not completed that environment build, so the article remains source analysis only rather than “reproduced locally.” The existing QEMU preflight established repository evidence but not application behavior.

What a fail-closed fix should look like

The narrow initialization correction is to start with no identity and require a successful lookup:

String token = request.getHeader(Const.DEFAULT_TOKEN_NAME);
UserInfo info = null;

if (StrUtil.isNotEmpty(token)
        && redis.exists(token + Const.TOKEN_CACHE_NAME)) {
    info = redis.get(token);
}

if (ObjectUtil.isNull(info)) {
    throw new NoLoginException();
}

UserUtil.setUser(info);
JAVA codeFail-closed initialization leaves info null unless token validation retrieves a real user.

That snippet expresses the intended invariant, but production remediation should be reviewed more broadly:

  • A missing, expired, malformed, or unknown token should all fail before point.proceed().
  • Authentication should preferably live in a well-tested Spring Security filter/interceptor rather than depend only on mutable thread-local context.
  • Controller methods handling attachments, exports, payroll, or employee records need object- and role-level authorization after authentication.
  • UserUtil.removeUser() should remain in finally so pooled request threads do not retain a previous identity.
  • Regression tests should assert both status and the absence of controller/service side effects.

Detection and mitigation

Follow the upstream/CNA guidance for a fixed revision when one is published and verify that deployed services actually include it. Until then, restrict HRM APIs to trusted networks or an authenticated gateway, but treat that as defense in depth.

Review access logs for sensitive routes with no AUTH-TOKEN, especially successful attachment downloads, employee exports, payslip reads, and bulk list operations. A gateway may rewrite or strip headers, so correlate gateway and application logs before drawing conclusions.

Sources

FOLLOW THE EVIDENCE

Sources & further reading

  1. github.com ↗
  2. github.com ↗
  3. github.com ↗
  4. github.com ↗
  5. www.vulncheck.com ↗

Technical details can change. Consult upstream advisories before making remediation decisions.