fix: 分享链接认证机制存在认证伪造绕过漏洞

This commit is contained in:
fit2cloud-chenyw
2026-03-30 11:00:07 +08:00
committed by fit2cloud-chenyw
parent d0372ef3a6
commit 00c169caa5
5 changed files with 72 additions and 34 deletions

View File

@@ -1,18 +1,12 @@
package io.dataease.share.interceptor; package io.dataease.share.interceptor;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import io.dataease.auth.DeLinkPermit; import io.dataease.auth.DeLinkPermit;
import io.dataease.constant.AuthConstant; import io.dataease.constant.AuthConstant;
import io.dataease.exception.DEException; import io.dataease.exception.DEException;
import io.dataease.share.manage.XpackShareManage;
import io.dataease.share.util.LinkTokenUtil;
import io.dataease.utils.LogUtil; import io.dataease.utils.LogUtil;
import io.dataease.utils.ServletUtils; import io.dataease.utils.ServletUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
@@ -38,8 +32,6 @@ public class DeLinkAop {
private final ExpressionParser parser = new SpelExpressionParser(); private final ExpressionParser parser = new SpelExpressionParser();
@Resource
private XpackShareManage xpackShareManage;
@Around(value = "@annotation(io.dataease.auth.DeLinkPermit)") @Around(value = "@annotation(io.dataease.auth.DeLinkPermit)")
public Object logAround(ProceedingJoinPoint point) throws Throwable { public Object logAround(ProceedingJoinPoint point) throws Throwable {
@@ -60,18 +52,6 @@ public class DeLinkAop {
DEException.throwException("link token invalid"); DEException.throwException("link token invalid");
return false; return false;
} }
Long uid = jwt.getClaim("uid").asLong();
String secret = xpackShareManage.queryPwd(resourceId, uid);
if (StringUtils.isBlank(secret)) {
secret = LinkTokenUtil.defaultPwd;
}
Algorithm algorithm = Algorithm.HMAC256(secret);
Verification verification = JWT.require(algorithm);
JWTVerifier verifier = verification.build();
DecodedJWT decode = JWT.decode(linkToken);
algorithm.verify(decode);
verifier.verify(linkToken);
} }
try { try {
return point.proceed(params); return point.proceed(params);

View File

@@ -0,0 +1,35 @@
package io.dataease.share.manage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.dataease.exception.DEException;
import io.dataease.share.dao.auto.entity.XpackShare;
import io.dataease.share.dao.auto.mapper.XpackShareMapper;
import jakarta.annotation.Resource;
import lombok.Getter;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("shareSecretManage")
public class ShareSecretManage {
@Getter
@Value("${dataease.default-link-pwd:link-pwd-fit2cloud}")
private String defaultPwd;
@Resource
private XpackShareMapper xpackShareMapper;
public String getSecret(Long resourceId, Long uid) {
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("creator", uid);
queryWrapper.eq("resource_id", resourceId);
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(xpackShare)) DEException.throwException("Share resource do not exist");
String sharePwd = xpackShare.getPwd();
return StringUtils.isNotBlank(sharePwd) ? sharePwd : defaultPwd;
}
}

View File

@@ -62,6 +62,9 @@ public class XpackShareManage {
@Resource @Resource
private SysParameterManage sysParameterManage; private SysParameterManage sysParameterManage;
@Resource
private ShareSecretManage shareSecretManage;
public void deleteByResource(Long resourceId) { public void deleteByResource(Long resourceId) {
if (resourceId == null) { if (resourceId == null) {
return; return;
@@ -88,14 +91,6 @@ public class XpackShareManage {
return xpackShareMapper.selectOne(queryWrapper); return xpackShareMapper.selectOne(queryWrapper);
} }
public String queryPwd(Long resourceId, Long userId) {
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("creator", userId);
queryWrapper.eq("resource_id", resourceId);
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(xpackShare)) return null;
return xpackShare.getPwd();
}
@Transactional @Transactional
public void switcher(Long resourceId) { public void switcher(Long resourceId) {
@@ -266,7 +261,9 @@ public class XpackShareManage {
vo.setInIframeError(false); vo.setInIframeError(false);
return vo; return vo;
} }
String linkToken = LinkTokenUtil.generate(xpackShare.getCreator(), xpackShare.getResourceId(), xpackShare.getExp(), xpackShare.getPwd(), xpackShare.getOid()); String defaultPwd = shareSecretManage.getDefaultPwd();
String secret = StringUtils.isBlank(xpackShare.getPwd()) ? defaultPwd : xpackShare.getPwd();
String linkToken = LinkTokenUtil.generate(xpackShare.getCreator(), xpackShare.getResourceId(), xpackShare.getExp(), secret, xpackShare.getOid());
HttpServletResponse response = ServletUtils.response(); HttpServletResponse response = ServletUtils.response();
response.addHeader(AuthConstant.LINK_TOKEN_KEY, linkToken); response.addHeader(AuthConstant.LINK_TOKEN_KEY, linkToken);
Integer type = xpackShare.getType(); Integer type = xpackShare.getType();

View File

@@ -4,14 +4,11 @@ import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator; import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.algorithms.Algorithm;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Date; import java.util.Date;
public class LinkTokenUtil { public class LinkTokenUtil {
public static final String defaultPwd = "link-pwd-fit2cloud";
public static String generate(Long uid, Long resourceId, Long exp, String pwd, Long oid) { public static String generate(Long uid, Long resourceId, Long exp, String pwd, Long oid) {
pwd = StringUtils.isBlank(pwd) ? defaultPwd : pwd;
Algorithm algorithm = Algorithm.HMAC256(pwd); Algorithm algorithm = Algorithm.HMAC256(pwd);
JWTCreator.Builder builder = JWT.create(); JWTCreator.Builder builder = JWT.create();
builder.withClaim("uid", uid).withClaim("resourceId", resourceId).withClaim("oid", oid); builder.withClaim("uid", uid).withClaim("resourceId", resourceId).withClaim("oid", oid);

View File

@@ -1,5 +1,10 @@
package io.dataease.auth.filter; package io.dataease.auth.filter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import io.dataease.auth.bo.TokenUserBO; import io.dataease.auth.bo.TokenUserBO;
import io.dataease.constant.AuthConstant; import io.dataease.constant.AuthConstant;
import io.dataease.exception.DEException; import io.dataease.exception.DEException;
@@ -15,8 +20,10 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode; import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.ReflectionUtils;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Objects; import java.util.Objects;
@@ -72,8 +79,30 @@ public class TokenFilter implements Filter {
} }
String linkToken = ServletUtils.getHead(AuthConstant.LINK_TOKEN_KEY); String linkToken = ServletUtils.getHead(AuthConstant.LINK_TOKEN_KEY);
if (StringUtils.isNotBlank(linkToken)) { if (StringUtils.isNotBlank(linkToken)) {
TokenUserBO tokenUserBO = TokenUtils.validateLinkToken(linkToken); if (StringUtils.length(linkToken) < 100) {
UserUtils.setUserInfo(tokenUserBO); DEException.throwException("token is invalid");
}
DecodedJWT jwt = JWT.decode(linkToken);
Long userId = jwt.getClaim("uid").asLong();
Long oid = jwt.getClaim("oid").asLong();
Long resourceId = jwt.getClaim("resourceId").asLong();
if (ObjectUtils.isEmpty(userId)) {
DEException.throwException("link token格式错误");
}
Object shareSecretManage = CommonBeanFactory.getBean("shareSecretManage");
Method getSecretMethod = DeReflectUtil.findMethod(shareSecretManage.getClass(), "getSecret");
Object pwdObj = ReflectionUtils.invokeMethod(getSecretMethod, shareSecretManage, resourceId, userId);
String linkSecret = pwdObj.toString();
Algorithm algorithm = Algorithm.HMAC256(linkSecret);
Verification verification = JWT.require(algorithm);
JWTVerifier verifier = verification.build();
DecodedJWT decode = JWT.decode(linkToken);
algorithm.verify(decode);
verifier.verify(linkToken);
UserUtils.setUserInfo(new TokenUserBO(userId, oid));
filterChain.doFilter(servletRequest, servletResponse); filterChain.doFilter(servletRequest, servletResponse);
return; return;
} }