Skip to content

CVE-2025-65542 : SQL Injection Vulnerability Report #127

@YLChen-007

Description

@YLChen-007

CVE-ID: CVE-2025-65542

Vulnerability Description

File Location: youlai-admin/admin-boot/src/main/java/com/youlai/admin/service/impl/SysDeptServiceImpl.java:181-187

Severity Level: High

Attack Vector: REST API Endpoint

Vulnerable Endpoint

@Operation(summary = "Delete Department")
@DeleteMapping("/{ids}")
@PreAuthorize("@ss.hasPerm('sys:dept:delete')")
public Result deleteDepartments(
        @Parameter(description = "Department ID, multiple IDs separated by comma (,)") 
        @PathVariable("ids") String ids
) {
    boolean result = deptService.deleteByIds(ids);
    return Result.judge(result);
}

Vulnerable Code

@Override
public boolean deleteByIds(String ids) {
    AtomicBoolean result = new AtomicBoolean(true);
    List<String> idList = Arrays.asList(ids.split(","));
    // Delete department and sub-departments
    Optional.ofNullable(idList).orElse(new ArrayList<>()).forEach(id ->
            result.set(this.remove(new LambdaQueryWrapper<SysDept>()
                    .eq(SysDept::getId, id)
                    .or()
                    .apply("concat (',',tree_path,',') like concat('%,',{0},',%')", id)))
    );
    return result.get();
}

Vulnerability Details

The vulnerability exists in the data flow from the REST API endpoint to the database query:

  1. Entry Point: The ids parameter is received from the URL path variable through the @PathVariable annotation in the controller
  2. Propagation: This unsanitized input is passed directly to the deleteByIds() service method
  3. Exploitation: The LambdaQueryWrapper.apply() method performs raw string substitution rather than parameterized queries, allowing SQL injection

The path variable ids is directly concatenated into the SQL statement through the {0} placeholder without any escaping or validation.

Proof of Concept

An attacker with the sys:dept:delete permission can craft a malicious request:

DELETE /api/v1/depts/1)%20OR%201=1-- HTTP/1.1
Host: target.com
Authorization: Bearer <valid_token>

This causes the generated SQL to become:

DELETE FROM sys_dept
WHERE id = 1
   OR concat(',', tree_path, ',') LIKE concat('%,', '1') OR 1=1--', ',%')

Since OR 1=1 is always true and -- comments out the subsequent content, this will delete all records in the entire department table.


Fix Recommendations

Solution 1: Use like Method (Recommended)

Replace apply with the safe method provided by MyBatis-Plus:

@Override
public boolean deleteByIds(String ids) {
    AtomicBoolean result = new AtomicBoolean(true);
    List<String> idList = Arrays.asList(ids.split(","));
    
    Optional.ofNullable(idList).orElse(new ArrayList<>()).forEach(id -> {
        // Pre-construct the matching pattern
        String pattern = "%," + id + ",%";
        result.set(this.remove(new LambdaQueryWrapper<SysDept>()
                .eq(SysDept::getId, id)
                .or()
                .like(SysDept::getTreePath, pattern)  // Use like method
        ));
    });
    return result.get();
}

Note: This solution requires ensuring the tree_path field format is ,1,2,3, (with commas at both ends).

Solution 2: Use Parameterized apply (Alternative)

If the concat function must be used, pre-calculate the pattern value:

Optional.ofNullable(idList).orElse(new ArrayList<>()).forEach(id -> {
    String pattern = "%," + id + ",%";
    result.set(this.remove(new LambdaQueryWrapper<SysDept>()
            .eq(SysDept::getId, id)
            .or()
            .apply("concat(',', tree_path, ',') like {0}", pattern)  // Pass complete pattern
    ));
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions