Selaa lähdekoodia

fix:三级等保渗透测试,【高危】 : 1、会话复用 2、垂直越权

lihao16 1 kuukausi sitten
vanhempi
sitoutus
7c2da54c8b

+ 36 - 0
src/main/java/com/inspur/smsb/gateway/config/SuperAdminPathProperties.java

@@ -0,0 +1,36 @@
+package com.inspur.smsb.gateway.config;
+
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+
+import java.util.List;
+
+/**
+ * 超级管理员路径配置
+ * @author lihao16
+ */
+@Slf4j
+@Component
+@ConfigurationProperties(prefix = "superadmin-path")
+public class SuperAdminPathProperties {
+
+    @Setter
+    private List<String> paths;
+
+    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
+
+
+    public boolean checkSuperAdminPath(String path){
+        // 判断该路径是否在超级管理员接口里
+        for (String url : paths) {
+            if (antPathMatcher.match(url, path)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 39 - 27
src/main/java/com/inspur/smsb/gateway/filter/WebFluxUserRequestInfoFilter.java

@@ -8,8 +8,10 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Strings;
 import com.inspur.smsb.gateway.config.AnonymousPathProperties;
+import com.inspur.smsb.gateway.config.SuperAdminPathProperties;
 import com.inspur.smsb.gateway.dto.KeycloakGroupsDto;
 import com.inspur.smsb.gateway.dto.KeycloakUserDto;
+import com.inspur.smsb.gateway.utils.ExpiredMapUtil;
 import com.inspur.smsb.gateway.utils.HttpClientUtil;
 import com.inspur.smsb.gateway.utils.TokenParseUtil;
 import com.nimbusds.jose.JWSObject;
@@ -25,6 +27,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
 import org.springframework.http.server.reactive.ServerHttpResponse;
 import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.DigestUtils;
 import org.springframework.util.StringUtils;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
@@ -73,12 +76,18 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
     @Value("${wxapplet.plusMinute:2}")
     private Integer plusMinute;
 
+    @Value("${superadmin-path.enable:false}")
+    private Boolean superAdminValidate;
+
     @Resource
     private ObjectMapper objectMapper;
 
     @Resource
     private AnonymousPathProperties anonymousPathProperties;
 
+    @Resource
+    private SuperAdminPathProperties superAdminPathProperties;
+
     private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
     @Override
@@ -196,24 +205,24 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
                 }
                 // 由于漏洞扫描发现退出登陆后,token在一定时间范围内还是有效,故此处做黑名单限制,
                 // 前端退出登陆时调用下/keycloak/userLogout接口,本接口仅做token存入黑名单操作,不涉及具体业务
-//                String logoutMd5 = DigestUtils.md5DigestAsHex(token.getBytes());
-//                if(exchange.getRequest().getURI().getPath().contains("userLogout")) {
-//                    log.warn("user logout logout={}",logoutMd5);
-//                    ExpiredMapUtil.put(logoutMd5,logoutMd5,ExpiredMapUtil.CACHE_HOLD_TIME_5M);
-//                }
-//                if(Objects.nonNull(ExpiredMapUtil.get(logoutMd5))) {
-//                    ServerHttpResponse response = exchange.getResponse();
-//                    response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
-//                    return response.writeWith(Mono.fromSupplier(() -> {
-//                        DataBufferFactory bufferFactory = response.bufferFactory();
-//                        try {
-//                            return bufferFactory.wrap(objectMapper.writeValueAsBytes(Response.buildFailure(String.valueOf(HttpStatus.PRECONDITION_FAILED.value()), "无访问权限")));
-//                        } catch (JsonProcessingException e) {
-//                            log.error("Error writing response", e);
-//                            return bufferFactory.wrap(new byte[0]);
-//                        }
-//                    }));
-//                }
+                String logoutMd5 = DigestUtils.md5DigestAsHex(token.getBytes());
+                if(exchange.getRequest().getURI().getPath().contains("userLogout")) {
+                    log.warn("user logout logout={}",logoutMd5);
+                    ExpiredMapUtil.put(logoutMd5,logoutMd5,ExpiredMapUtil.CACHE_HOLD_TIME_5M);
+                }
+                if(Objects.nonNull(ExpiredMapUtil.get(logoutMd5))) {
+                    ServerHttpResponse response = exchange.getResponse();
+                    response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+                    return response.writeWith(Mono.fromSupplier(() -> {
+                        DataBufferFactory bufferFactory = response.bufferFactory();
+                        try {
+                            return bufferFactory.wrap(objectMapper.writeValueAsBytes(Response.buildFailure(String.valueOf(HttpStatus.PRECONDITION_FAILED.value()), "无访问权限")));
+                        } catch (JsonProcessingException e) {
+                            log.error("Error writing response", e);
+                            return bufferFactory.wrap(new byte[0]);
+                        }
+                    }));
+                }
 
                 String realToken = token.replace("Bearer ", "");
                 JWSObject jwsObject = JWSObject.parse(realToken);
@@ -233,20 +242,19 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
                     .header("userName", userName)
                     .header("tenant", String.valueOf(tenant))
                     .build();
-
+                // 根据token, 获取当前用户的角色  ROLE_SUPER_ADMIN -> 超管, ROLE_ADMIN -> 租户管理员,tenant已经获取,再获取org
+                com.nimbusds.jose.shaded.json.JSONObject realmAccess = (com.nimbusds.jose.shaded.json.JSONObject)jwsObject.getPayload().toJSONObject().get("realm_access");
+                com.nimbusds.jose.shaded.json.JSONArray rolesArray = (com.nimbusds.jose.shaded.json.JSONArray) realmAccess.get("roles");
+                List<String> roles = new ArrayList<>();
+                for (Object role : rolesArray) {
+                    roles.add((String) role);
+                }
                 if (StringUtils.hasText(urlTenant) || StringUtils.hasText(urlOrg)) {
                     // 1、有urlOrg或者urlTenant时,才去做过滤
                     String urlRealTenant = Objects.isNull(urlTenant) ?
                         "/" + Arrays.stream(urlOrg.split("/")).filter(s -> !s.isEmpty()).collect(Collectors.toList()).get(0) : urlTenant;
                     String urlRealOrg = Objects.isNull(urlOrg) ? urlTenant : urlOrg;
 
-                    // 根据token, 获取当前用户的角色  ROLE_SUPER_ADMIN -> 超管, ROLE_ADMIN -> 租户管理员,tenant已经获取,再获取org
-                    com.nimbusds.jose.shaded.json.JSONObject realmAccess = (com.nimbusds.jose.shaded.json.JSONObject)jwsObject.getPayload().toJSONObject().get("realm_access");
-                    com.nimbusds.jose.shaded.json.JSONArray rolesArray = (com.nimbusds.jose.shaded.json.JSONArray) realmAccess.get("roles");
-                    List<String> roles = new ArrayList<>();
-                    for (Object role : rolesArray) {
-                        roles.add((String) role);
-                    }
                     // userOrg可能为空,userTenant应该是必有的
                     String userOrg = String.valueOf(jwsObject.getPayload().toJSONObject().get("org"));
                     String userTenant = tenant;
@@ -275,7 +283,11 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
                         }
                     }
                 }
-
+                if (superAdminValidate && superAdminPathProperties.checkSuperAdminPath(request.getPath().value())) {
+                    if (Boolean.FALSE.equals(roles.contains(ROLE_SUPER_ADMIN))) {
+                        return getErrorResponse(exchange);
+                    }
+                }
                 // 把新的 exchange 放回到过滤链
                 return chain.filter(exchange.mutate().request(request).build());
             } catch (ParseException e) {