Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/main/java/org/jenkinsci/plugins/BitbucketSecurityRealm.java
### 代码内容：
package org.jenkinsci.plugins;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.api.BitbucketApiService;
import org.jenkinsci.plugins.api.BitbucketGroup;
import org.jenkinsci.plugins.api.BitbucketUser;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Header;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.scribe.model.Token;
import org.springframework.dao.DataAccessException;

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor;
import hudson.model.User;
import hudson.security.GroupDetails;
import hudson.security.SecurityRealm;
import hudson.security.UserMayOrMayNotExistException;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import jenkins.security.SecurityListener;

public class BitbucketSecurityRealm extends SecurityRealm {

    private static final String STATE_ATTRIBUTE = BitbucketSecurityRealm.class.getName() + ".state";
    private static final String REFERER_ATTRIBUTE = BitbucketSecurityRealm.class.getName() + ".referer";
    private static final Logger LOGGER = Logger.getLogger(BitbucketSecurityRealm.class.getName());

    private String clientID;
    @Deprecated
    private String clientSecret;
    private Secret secretClientSecret;

    @DataBoundConstructor
    public BitbucketSecurityRealm(String clientID, String clientSecret, Secret secretClientSecret) {
        super();
        this.clientID = Util.fixEmptyAndTrim(clientID);
        this.clientSecret = Util.fixEmptyAndTrim(clientSecret);
        this.secretClientSecret = secretClientSecret;
    }

    public BitbucketSecurityRealm() {
        super();
        LOGGER.log(Level.FINE, "BitbucketSecurityRealm()");
    }

    /**
     * @return the clientID
     */
    public String getClientID() {
        return clientID;
    }

    /**
     * @param clientID the clientID to set
     */
    public void setClientID(String clientID) {
        this.clientID = clientID;
    }

    /**
     * @return the clientSecret
     */
    @Deprecated
    public String getClientSecret() {
        return clientSecret;
    }

    /**
     * @param clientSecret the clientSecret to set
     */
    @Deprecated
    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    /**
     * @return the secretClientSecret
     */
    public Secret getSecretClientSecret() {
        // for backward compatibility
        if (StringUtils.isNotEmpty(clientSecret)) {
            return Secret.fromString(clientSecret);
        }
        return secretClientSecret;
    }

    /**
     * @param secretClientSecret the secretClientSecret to set
     */
    public void setSecretClientSecret(Secret secretClientSecret) {
        this.secretClientSecret = secretClientSecret;
    }

    public HttpResponse doCommenceLogin(StaplerRequest request, @Header("Referer") final String referer)
            throws IOException {

        String state = RandomStringUtils.random(64, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
        request.getSession().setAttribute(STATE_ATTRIBUTE, state);
        request.getSession().setAttribute(REFERER_ATTRIBUTE, referer);

        Jenkins jenkins = Jenkins.getInstance();
        if (jenkins == null) {
            throw new RuntimeException("Jenkins is not started yet.");
        }
        String rootUrl = jenkins.getRootUrl();
        if (StringUtils.endsWith(rootUrl, "/")) {
            rootUrl = StringUtils.left(rootUrl, StringUtils.length(rootUrl) - 1);
        }
        String callback = rootUrl + "/securityRealm/finishLogin";

        BitbucketApiService bitbucketApiService = new BitbucketApiService(clientID, getSecretClientSecret().getPlainText(), callback);

        return new HttpRedirect(bitbucketApiService.createAuthorizationCodeURL(null, state));
    }

    public HttpResponse doFinishLogin(StaplerRequest request) throws IOException {
        String code = request.getParameter("code");
        String state = request.getParameter("state");

        if (StringUtils.isBlank(code)) {
            LOGGER.log(Level.SEVERE, "doFinishLogin() code = null");
            return HttpResponses.redirectToContextRoot();
        }

        if (state == null || !StringUtils.equals(state, (String) request.getSession().getAttribute(STATE_ATTRIBUTE))) {
            LOGGER.log(Level.SEVERE, "doFinishLogin() invalid state parameter");
            return HttpResponses.redirectToContextRoot();
        }

        String rawClientSecret = getSecretClientSecret().getPlainText();

        Token accessToken = new BitbucketApiService(clientID, rawClientSecret).getTokenByAuthorizationCode(code, null);

        if (!accessToken.isEmpty()) {

            BitbucketAuthenticationToken auth = new BitbucketAuthenticationToken(accessToken, clientID, rawClientSecret);
            SecurityContextHolder.getContext().setAuthentication(auth);

            User u = User.current();
            if (u != null) {
                u.setFullName(auth.getName());
            }

            BitbucketUser userDetails = auth.getBitbucketUser();
            if (userDetails != null) {
                SecurityListener.fireAuthenticated(userDetails);
            }
        } else {
            LOGGER.log(Level.SEVERE, "doFinishLogin() accessToken = null");
        }

        // redirect to referer
        String referer = (String) request.getSession().getAttribute(REFERER_ATTRIBUTE);
        if (referer != null) {
            return HttpResponses.redirectTo(referer);
        } else {
            return HttpResponses.redirectToContextRoot();
        }
    }

    @Override
    public SecurityComponents createSecurityComponents() {
        return new SecurityRealm.SecurityComponents(new AuthenticationManager() {
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                if (authentication instanceof BitbucketAuthenticationToken) {
                    return authentication;
                }

                throw new BadCredentialsException("Unexpected authentication type: " + authentication);
            }
        }, new UserDetailsService() {
            public UserDetails loadUserByUsername(String username)
                    throws UserMayOrMayNotExistException, DataAccessException {
                throw new UserMayOrMayNotExistException("Cannot verify users in this context");
            }
        });
    }

    @Override
    public UserDetails loadUserByUsername(String username) {
        UserDetails result = null;
        Authentication token = SecurityContextHolder.getContext().getAuthentication();
        if (token == null) {
            throw new UsernameNotFoundException("BitbucketAuthenticationToken = null, no known user: " + username);
        }
        if (!(token instanceof BitbucketAuthenticationToken)) {
            throw new UserMayOrMayNotExistException("Unexpected authentication type: " + token);
        }
        result = new BitbucketApiService(clientID, getSecretClientSecret().getPlainText()).getUserByUsername(username);
        if (result == null) {
            throw new UsernameNotFoundException("User does not exist for login: " + username);
        }
        return result;
    }

    @Override
    public GroupDetails loadGroupByGroupname(String groupName) {
        if (groupName.contains("::")) {
            return new BitbucketGroup(groupName);
        } else {
            throw new UsernameNotFoundException("Group does not exist:" + groupName);
        }
    }

    @Override
    public boolean allowsSignup() {
        return false;
    }

    @Override
    public String getLoginUrl() {
        return "securityRealm/commenceLogin";
    }

    public static final class ConverterImpl implements Converter {

        public boolean canConvert(Class type) {
            return type == BitbucketSecurityRealm.class;
        }

        public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {

            BitbucketSecurityRealm realm = (BitbucketSecurityRealm) source;

            writer.startNode("clientID");
            writer.setValue(realm.getClientID());
            writer.endNode();

            writer.startNode("secretClientSecret");
            writer.setValue(realm.getSecretClientSecret().getEncryptedValue());
            writer.endNode();
        }

        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {

            String node = reader.getNodeName();

            reader.moveDown();

            BitbucketSecurityRealm realm = new BitbucketSecurityRealm();

            node = reader.getNodeName();

            String value = reader.getValue();

            setValue(realm, node, value);

            reader.moveUp();

            reader.moveDown();

            node = reader.getNodeName();

            value = reader.getValue();

            setValue(realm, node, value);

            reader.moveUp();

            if (reader.hasMoreChildren()) {
                reader.moveDown();

                node = reader.getNodeName();

                value = reader.getValue();

                setValue(realm, node, value);

                reader.moveUp();
            }
            return realm;
        }

        private void setValue(BitbucketSecurityRealm realm, String node, String value) {

            if (node.equalsIgnoreCase("clientid")) {
                realm.setClientID(value);
            } else if (node.equalsIgnoreCase("clientsecret")) {
                realm.setClientSecret(value);
            } else if (node.equalsIgnoreCase("secretclientsecret")) {
                realm.setSecretClientSecret(Secret.fromString(value));
            } else {
                throw new ConversionException("invalid node value = " + node);
            }

        }
    }

    @Extension
    public static final class DescriptorImpl extends Descriptor<SecurityRealm> {

        @Override
        public String getHelpFile() {
            return "/plugin/bitbucket-oauth/help/help-security-realm.html";
        }

        @Override
        public String getDisplayName() {
            return "Bitbucket OAuth Plugin";
        }

        public DescriptorImpl() {
            super();
        }

        public DescriptorImpl(Class<? extends SecurityRealm> clazz) {
            super(clazz);
        }
    }

}

Output:
【漏洞类型】会话固定漏洞
【严重性】High
【漏洞摘要】此代码存在会话固定漏洞，允许攻击者在用户登录过程中固定会话ID。具体表现为在`doFinishLogin`方法中，攻击者可以在`state`参数中注入已知的会话ID，然后引导用户通过认证过程。漏洞的根本原因是代码在验证`state`参数后，没有及时销毁旧的会话并创建新的会话。
【关键代码模式】`HttpSession session = request.getSession(false);`、`session.invalidate();`、`request.getSession(true);`、`doFinishLogin`方法中的`state`参数验证。
【修复方法说明】修复方法通过在成功验证`state`参数后，销毁旧的会话并创建一个新的会话，从而防止会话固定攻击。适用的最佳实践是确保在处理敏感操作（如认证）时，会话ID不会被预测或固定。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/main/java/org/jenkinsci/plugins/BitbucketSecurityRealm.java
### 代码内容：
package org.jenkinsci.plugins;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.api.BitbucketApiService;
import org.jenkinsci.plugins.api.BitbucketGroup;
import org.jenkinsci.plugins.api.BitbucketUser;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Header;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.scribe.model.Token;
import org.springframework.dao.DataAccessException;

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor;
import hudson.model.User;
import hudson.security.GroupDetails;
import hudson.security.SecurityRealm;
import hudson.security.UserMayOrMayNotExistException;
import hudson.util.Secret;
import jenkins.model.Jenkins;
import jenkins.security.SecurityListener;

public class BitbucketSecurityRealm extends SecurityRealm {

    private static final String STATE_ATTRIBUTE = BitbucketSecurityRealm.class.getName() + ".state";
    private static final String REFERER_ATTRIBUTE = BitbucketSecurityRealm.class.getName() + ".referer";
    private static final Logger LOGGER = Logger.getLogger(BitbucketSecurityRealm.class.getName());

    private String clientID;
    @Deprecated
    private String clientSecret;
    private Secret secretClientSecret;

    @DataBoundConstructor
    public BitbucketSecurityRealm(String clientID, String clientSecret, Secret secretClientSecret) {
        super();
        this.clientID = Util.fixEmptyAndTrim(clientID);
        this.clientSecret = Util.fixEmptyAndTrim(clientSecret);
        this.secretClientSecret = secretClientSecret;
    }

    public BitbucketSecurityRealm() {
        super();
        LOGGER.log(Level.FINE, "BitbucketSecurityRealm()");
    }

    /**
     * @return the clientID
     */
    public String getClientID() {
        return clientID;
    }

    /**
     * @param clientID the clientID to set
     */
    public void setClientID(String clientID) {
        this.clientID = clientID;
    }

    /**
     * @return the clientSecret
     */
    @Deprecated
    public String getClientSecret() {
        return clientSecret;
    }

    /**
     * @param clientSecret the clientSecret to set
     */
    @Deprecated
    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    /**
     * @return the secretClientSecret
     */
    public Secret getSecretClientSecret() {
        // for backward compatibility
        if (StringUtils.isNotEmpty(clientSecret)) {
            return Secret.fromString(clientSecret);
        }
        return secretClientSecret;
    }

    /**
     * @param secretClientSecret the secretClientSecret to set
     */
    public void setSecretClientSecret(Secret secretClientSecret) {
        this.secretClientSecret = secretClientSecret;
    }

    public HttpResponse doCommenceLogin(StaplerRequest request, @Header("Referer") final String referer)
            throws IOException {

        String state = RandomStringUtils.random(64, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
        request.getSession().setAttribute(STATE_ATTRIBUTE, state);
        request.getSession().setAttribute(REFERER_ATTRIBUTE, referer);

        Jenkins jenkins = Jenkins.getInstance();
        if (jenkins == null) {
            throw new RuntimeException("Jenkins is not started yet.");
        }
        String rootUrl = jenkins.getRootUrl();
        if (StringUtils.endsWith(rootUrl, "/")) {
            rootUrl = StringUtils.left(rootUrl, StringUtils.length(rootUrl) - 1);
        }
        String callback = rootUrl + "/securityRealm/finishLogin";

        BitbucketApiService bitbucketApiService = new BitbucketApiService(clientID, getSecretClientSecret().getPlainText(), callback);

        return new HttpRedirect(bitbucketApiService.createAuthorizationCodeURL(null, state));
    }

    public HttpResponse doFinishLogin(StaplerRequest request) throws IOException {
        String code = request.getParameter("code");
        String state = request.getParameter("state");

        if (StringUtils.isBlank(code)) {
            LOGGER.log(Level.SEVERE, "doFinishLogin() code = null");
            return HttpResponses.redirectToContextRoot();
        }

        if (state == null || !StringUtils.equals(state, (String) request.getSession().getAttribute(STATE_ATTRIBUTE))) {
            LOGGER.log(Level.SEVERE, "doFinishLogin() invalid state parameter");
            return HttpResponses.redirectToContextRoot();
        }

        String rawClientSecret = getSecretClientSecret().getPlainText();

        Token accessToken = new BitbucketApiService(clientID, rawClientSecret).getTokenByAuthorizationCode(code, null);

        if (!accessToken.isEmpty()) {

            BitbucketAuthenticationToken auth = new BitbucketAuthenticationToken(accessToken, clientID, rawClientSecret);
            SecurityContextHolder.getContext().setAuthentication(auth);

            User u = User.current();
            if (u != null) {
                u.setFullName(auth.getName());
            }

            BitbucketUser userDetails = auth.getBitbucketUser();
            if (userDetails != null) {
                SecurityListener.fireAuthenticated(userDetails);
            }
        } else {
            LOGGER.log(Level.SEVERE, "doFinishLogin() accessToken = null");
        }

        // redirect to referer
        String referer = (String) request.getSession().getAttribute(REFERER_ATTRIBUTE);
        if (referer != null) {
            return HttpResponses.redirectTo(referer);
        } else {
            return HttpResponses.redirectToContextRoot();
        }
    }

    @Override
    public SecurityComponents createSecurityComponents() {
        return new SecurityRealm.SecurityComponents(new AuthenticationManager() {
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                if (authentication instanceof BitbucketAuthenticationToken) {
                    return authentication;
                }

                throw new BadCredentialsException("Unexpected authentication type: " + authentication);
            }
        }, new UserDetailsService() {
            public UserDetails loadUserByUsername(String username)
                    throws UserMayOrMayNotExistException, DataAccessException {
                throw new UserMayOrMayNotExistException("Cannot verify users in this context");
            }
        });
    }

    @Override
    public UserDetails loadUserByUsername(String username) {
        UserDetails result = null;
        Authentication token = SecurityContextHolder.getContext().getAuthentication();
        if (token == null) {
            throw new UsernameNotFoundException("BitbucketAuthenticationToken = null, no known user: " + username);
        }
        if (!(token instanceof BitbucketAuthenticationToken)) {
            throw new UserMayOrMayNotExistException("Unexpected authentication type: " + token);
        }
        result = new BitbucketApiService(clientID, getSecretClientSecret().getPlainText()).getUserByUsername(username);
        if (result == null) {
            throw new UsernameNotFoundException("User does not exist for login: " + username);
        }
        return result;
    }

    @Override
    public GroupDetails loadGroupByGroupname(String groupName) {
        if (groupName.contains("::")) {
            return new BitbucketGroup(groupName);
        } else {
            throw new UsernameNotFoundException("Group does not exist:" + groupName);
        }
    }

    @Override
    public boolean allowsSignup() {
        return false;
    }

    @Override
    public String getLoginUrl() {
        return "securityRealm/commenceLogin";
    }

    public static final class ConverterImpl implements Converter {

        public boolean canConvert(Class type) {
            return type == BitbucketSecurityRealm.class;
        }

        public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {

            BitbucketSecurityRealm realm = (BitbucketSecurityRealm) source;

            writer.startNode("clientID");
            writer.setValue(realm.getClientID());
            writer.endNode();

            writer.startNode("secretClientSecret");
            writer.setValue(realm.getSecretClientSecret().getEncryptedValue());
            writer.endNode();
        }

        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {

            String node = reader.getNodeName();

            reader.moveDown();

            BitbucketSecurityRealm realm = new BitbucketSecurityRealm();

            node = reader.getNodeName();

            String value = reader.getValue();

            setValue(realm, node, value);

            reader.moveUp();

            reader.moveDown();

            node = reader.getNodeName();

            value = reader.getValue();

            setValue(realm, node, value);

            reader.moveUp();

            if (reader.hasMoreChildren()) {
                reader.moveDown();

                node = reader.getNodeName();

                value = reader.getValue();

                setValue(realm, node, value);

                reader.moveUp();
            }
            return realm;
        }

        private void setValue(BitbucketSecurityRealm realm, String node, String value) {

            if (node.equalsIgnoreCase("clientid")) {
                realm.setClientID(value);
            } else if (node.equalsIgnoreCase("clientsecret")) {
                realm.setClientSecret(value);
            } else if (node.equalsIgnoreCase("secretclientsecret")) {
                realm.setSecretClientSecret(Secret.fromString(value));
            } else {
                throw new ConversionException("invalid node value = " + node);
            }

        }
    }

    @Extension
    public static final class DescriptorImpl extends Descriptor<SecurityRealm> {

        @Override
        public String getHelpFile() {
            return "/plugin/bitbucket-oauth/help/help-security-realm.html";
        }

        @Override
        public String getDisplayName() {
            return "Bitbucket OAuth Plugin";
        }

        public DescriptorImpl() {
            super();
        }

        public DescriptorImpl(Class<? extends SecurityRealm> clazz) {
            super(clazz);
        }
    }

}

Output:
【漏洞类型】Session Fixation Vulnerability
【严重性】Medium
【漏洞摘要】此代码存在会话固定漏洞。在`doFinishLogin`方法中，如果会话已经存在，代码会直接使用该会话的属性（如`STATE_ATTRIBUTE`和`REFERER_ATTRIBUTE`）而不会重新生成会话ID。攻击者可以通过迫使用户在登录前使用特定的会话ID，然后在用户登录后劫持该会话，从而在会话固定漏洞中获取用户的访问权限。根本原因在于代码在验证`state`参数后，没有重新生成会话ID以防止会话固定。
【关键代码模式】
- `request.getSession(false)`：获取现有会话，如果会话不存在则返回null。
- `request.getSession(true)`：强制创建新的会话，如果会话已经存在则返回现有会话。
- `session.invalidate()`：使会话无效，清除所有会话属性。
【修复方法说明】修复了会话固定漏洞，通过在验证`state`参数后，使现有会话无效，并强制创建新的会话，从而重新生成会话ID，防止攻击者劫持会话。


Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
src/main/java/com/cloudbees/jenkins/plugins/kubernetes_credentials_provider/KubernetesCredentialProvider.java
### 代码内容：
package com.cloudbees.jenkins.plugins.kubernetes_credentials_provider;

import io.fabric8.kubernetes.client.WatcherException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.ExtensionList;
import hudson.model.AdministrativeMonitor;
import hudson.triggers.SafeTimerTask;
import hudson.util.AdministrativeError;
import io.fabric8.kubernetes.api.model.LabelSelector;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretList;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import jenkins.util.Timer;
import org.acegisecurity.Authentication;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import hudson.Extension;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.init.TermMilestone;
import hudson.init.Terminator;
import hudson.model.ItemGroup;
import hudson.model.ModelObject;
import hudson.security.ACL;
import jenkins.model.Jenkins;
import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.common.IdCredentials;

@Extension
public class KubernetesCredentialProvider extends CredentialsProvider implements Watcher<Secret> {

    private static final Logger LOG = Logger.getLogger(KubernetesCredentialProvider.class.getName());

    /** Map of Credentials keyed by their credential ID */
    private ConcurrentHashMap<String, IdCredentials> credentials = new ConcurrentHashMap<>();

    @CheckForNull
    private KubernetesClient client;
    @CheckForNull
    private Watch watch;
    /** Attempt to reconnect k8s client on exception */
    private boolean reconnectClientOnException = Boolean.parseBoolean(System.getProperty(KubernetesCredentialProvider.class.getName() + ".reconnectClientOnException", "true"));
    /** Delay in minutes before attempting to reconnect k8s client */
    private int reconnectClientDelayMins = Integer.getInteger(KubernetesCredentialProvider.class.getName() + ".reconnectClientDelayMins", 5);

    private KubernetesCredentialsStore store = new KubernetesCredentialsStore(this);

    /**
     * Kubernetes label selector expression
     * for matching secrets to manage.
     */
    static final String LABEL_SELECTOR = KubernetesCredentialProvider.class.getName() + ".labelSelector";

    KubernetesClient getKubernetesClient() {
        if (client == null) {
            ConfigBuilder cb = new ConfigBuilder();
            Config config = cb.build();
            // TODO post 2.362 use jenkins.util.SetContextClassLoader
            try (WithContextClassLoader ignored = new WithContextClassLoader(getClass().getClassLoader())) {
                client = new DefaultKubernetesClient(config);
            }
        }
        return client;
    }

    @Initializer(after=InitMilestone.PLUGINS_PREPARED, fatal=false)
    @Restricted(NoExternalUse.class) // only for callbacks from Jenkins
    public void startWatchingForSecrets() {
        final String initAdminMonitorId = getClass().getName() + ".initialize";
        final String labelSelectorAdminMonitorId = getClass().getName() + ".labelSelector";
        String labelSelector = System.getProperty(LABEL_SELECTOR);
        try {
            KubernetesClient _client = getKubernetesClient();
            LOG.log(Level.FINER, "Using namespace: {0}", String.valueOf(_client.getNamespace()));
            LabelSelector selector = LabelSelectorExpressions.parse(labelSelector);
            LOG.log(Level.INFO, "retrieving secrets with selector: {0}, {1}", new String[]{SecretUtils.JENKINS_IO_CREDENTIALS_TYPE_LABEL, Objects.toString(selector)});

            // load current set of secrets into provider
            LOG.log(Level.FINER, "retrieving secrets");
            SecretList list = _client.secrets().withLabelSelector(selector).withLabel(SecretUtils.JENKINS_IO_CREDENTIALS_TYPE_LABEL).list();
            ConcurrentHashMap<String, IdCredentials> _credentials = new  ConcurrentHashMap<>();
            List<Secret> secretList = list.getItems();
            for (Secret s : secretList) {
                LOG.log(Level.FINE, "Secret Added - {0}", SecretUtils.getCredentialId(s));
                addSecret(s, _credentials);
            }
            credentials = _credentials;

            // start watching new secrets before we list the current set of secrets so we don't miss any events
            LOG.log(Level.FINER, "registering watch");
            // XXX https://github.com/fabric8io/kubernetes-client/issues/1014
            // watch(resourceVersion, watcher) is deprecated but there is nothing to say why?
            try {
                watch = _client.secrets().withLabelSelector(selector).withLabel(SecretUtils.JENKINS_IO_CREDENTIALS_TYPE_LABEL).watch(list.getMetadata().getResourceVersion(), this);
            } catch (NoSuchMethodError e) {
                // TODO monkey patching for kubernetes-client 6.x as they broke binary compatibility; Remove once this plugin depends on kubernetes-client 6.x at compilation time.
                Object o = _client.secrets().withLabelSelector(selector).withLabel(SecretUtils.JENKINS_IO_CREDENTIALS_TYPE_LABEL);
                try {
                    Method watchMethod = o.getClass().getMethod("watch", String.class, Watcher.class);
                    watch = (Watch) watchMethod.invoke(o, list.getMetadata().getResourceVersion(), this);
                } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
                    RuntimeException runtimeException = new RuntimeException(ex);
                    runtimeException.addSuppressed(e);
                    throw runtimeException;
                }
            }
            LOG.log(Level.FINER, "registered watch, retrieving secrets");

            // successfully initialized, clear any previous monitors
            clearAdminMonitors(initAdminMonitorId, labelSelectorAdminMonitorId);
        } catch (KubernetesClientException kex) {
            LOG.log(Level.SEVERE, "Failed to initialise k8s secret provider, secrets from Kubernetes will not be available", kex);
            if (reconnectClientOnException) {
                reconnectLater();
            }
            // Only report the latest failure
            clearAdminMonitors(initAdminMonitorId);
            new AdministrativeError(initAdminMonitorId,
                    "Failed to initialize Kubernetes secret provider",
                    "Credentials from Kubernetes Secrets will not be available.", kex);
        } catch (LabelSelectorParseException lex) {
            LOG.log(Level.SEVERE, "Failed to initialise k8s secret provider, secrets from Kubernetes will not be available", lex);
            // Only report the latest failure
            clearAdminMonitors(labelSelectorAdminMonitorId);
            new AdministrativeError(labelSelectorAdminMonitorId,
                    "Failed to parse Kubernetes secret label selector",
                    "Failed to parse Kubernetes secret label selector " +
                            "expression \"" + labelSelector + "\". Secrets from Kubernetes will not be available. ", lex);
        }
    }

    /**
     * Schedule a future task to attempt to reconnect to the kubernetes client.
     * @see #startWatchingForSecrets()
     * @see Timer
     */
    private void reconnectLater() {
        LOG.log(Level.INFO, "Attempting to reconnect Kubernetes client in {0} mins", reconnectClientDelayMins);
        Timer.get().schedule(new SafeTimerTask() {
            @Override
            protected void doRun() throws Exception {
                KubernetesCredentialProvider.this.startWatchingForSecrets();
            }
        }, reconnectClientDelayMins, TimeUnit.MINUTES);
    }

    private void clearAdminMonitors(String... ids) {
        Collection<String> monitorIds = Arrays.asList(ids);
        ExtensionList<AdministrativeMonitor> all = AdministrativeMonitor.all();
        List<AdministrativeMonitor> toRemove = all.stream().filter(am -> monitorIds.contains(am.id)).collect(Collectors.toList());
        all.removeAll(toRemove);
    }

    @Terminator(after=TermMilestone.STARTED)
    @Restricted(NoExternalUse.class) // only for callbacks from Jenkins
    public void stopWatchingForSecrets() {
        if (watch != null) {
            watch.close();
            watch = null;
        }
        if (client != null) {
            client.close();
            client = null;
        }
    }

    @Override
    public <C extends Credentials> List<C> getCredentials(Class<C> type, ItemGroup itemGroup, Authentication authentication) {
        LOG.log(Level.FINEST, "getCredentials called with type {0} and authentication {1}", new Object[] {type.getName(), authentication});
        if (ACL.SYSTEM.equals(authentication)) {
            ArrayList<C> list = new ArrayList<>();
            for (IdCredentials credential : credentials.values()) {
                // is s a type of type then populate the list...
                LOG.log(Level.FINEST, "getCredentials {0} is a possible candidate", credential.getId());
                if (type.isAssignableFrom(credential.getClass())) {
                    LOG.log(Level.FINEST, "getCredentials {0} matches, adding to list", credential.getId());
                    // cast to keep generics happy even though we are assignable..
                    list.add(type.cast(credential));
                } else {
                    LOG.log(Level.FINEST, "getCredentials {0} does not match", credential.getId());
                }
            }
            return list;
        }
        return emptyList();
    }

    @SuppressWarnings("null")
    private final @NonNull <T> List<T> emptyList() {
        // just a separate method to avoid having to suppress "null" for the entirety of getCredentials
        return Collections.emptyList();
    }

    private void addSecret(Secret secret) {
        addSecret(secret, credentials);
    }

    private void addSecret(Secret secret, Map<String, IdCredentials> map) {
        IdCredentials cred = convertSecret(secret);
        if (cred != null) {
            map.put(SecretUtils.getCredentialId(secret), cred);
        }
    }

    @Override
    public void eventReceived(Action action, Secret secret) {
        String credentialId = SecretUtils.getCredentialId(secret);
        switch (action) {
            case ADDED: {
                LOG.log(Level.FINE, "Secret Added - {0}", credentialId);
                addSecret(secret);
                break;
            }
            case MODIFIED: {
                LOG.log(Level.FINE, "Secret Modified - {0}", credentialId);
                addSecret(secret);
                break;
            }
            case DELETED: {
                LOG.log(Level.FINE, "Secret Deleted - {0}", credentialId);
                credentials.remove(credentialId);
                break;
            }
            case ERROR: {
                // XXX  ????
                LOG.log(Level.WARNING, "Action received of type Error. {0}", secret);
                break;
            }
            case BOOKMARK: {
                // TODO handle bookmarks for efficient reconnect
                break;
            }
        }
    }

    @Override
    public void onClose(WatcherException cause) {
        if (cause != null) {
            LOG.log(Level.WARNING, "Secrets watch stopped unexpectedly", cause);
            LOG.log(Level.INFO, "Restating secrets watcher");
            startWatchingForSecrets();
        } else {
            LOG.log(Level.INFO, "Secrets watcher stopped");
        }
    }

    @CheckForNull
    IdCredentials convertSecret(Secret s) {
        String type = s.getMetadata().getLabels().get(SecretUtils.JENKINS_IO_CREDENTIALS_TYPE_LABEL);

        SecretToCredentialConverter lookup = SecretToCredentialConverter.lookup(type);
        if (lookup != null) {
            try {
                return lookup.convert(s);
            } catch (CredentialsConvertionException ex) {
                // do not spam the logs with the stacktrace...
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.log(Level.FINE, "Failed to convert Secret '" + SecretUtils.getCredentialId(s) + "' of type " + type, ex);
                }
                else {
                    LOG.log(Level.WARNING, "Failed to convert Secret ''{0}'' of type {1} due to {2}", new Object[] {SecretUtils.getCredentialId(s), type, ex.getMessage()});
                }
                return null;
            }
        }
        LOG.log(Level.WARNING, "No SecretToCredentialConverter found to convert secrets of type {0}", type);
        return null;
    }

    @Override
    public CredentialsStore getStore(ModelObject object) {
        return object == Jenkins.getInstance() ? store : null;
    }

    @Override
    public String getIconClassName() {
        return "icon-credentials-kubernetes-store";
    }

    private static class WithContextClassLoader implements AutoCloseable {

        private final ClassLoader previousClassLoader;

        public WithContextClassLoader(ClassLoader classLoader) {
            this.previousClassLoader = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(classLoader);
        }

        @Override
        public void close() {
            Thread.currentThread().setContextClassLoader(previousClassLoader);
        }
    }
}

Output:
【漏洞类型】权限绕过漏洞
【严重性】High
【漏洞摘要】原始代码中，`getCredentials` 方法未正确检查 `ItemGroup` 的类型，导致系统级别的 `SYSTEM` 范围的凭据可能被普通 Jenkins 用户访问。具体表现为，在 `getCredentials` 方法中，只有当 `authentication` 为 `ACL.SYSTEM` 时，凭据才会被添加到列表中，但这并没有阻止普通用户通过传入非 `Jenkins` 实例的 `ItemGroup` 来访问 `SYSTEM` 范围的凭据。根本原因是缺乏对 `ItemGroup` 类型的进一步验证，使得 `SYSTEM` 范围的凭据可能在不应该的上下文中被暴露。
【关键代码模式】`getCredentials` 方法、`ACL.SYSTEM`、`ItemGroup`、`Jenkins` 实例。
【修复方法说明】修复方法通过检查 `ItemGroup` 是否为 `Jenkins` 实例来进一步限制 `SYSTEM` 范围凭据的访问权限，从而防止普通用户访问系统级别的凭据。