IBM Security 扫描解决方案整理

目录

    • 1.SQL盲注
    • 2.已解密的登录请求
      • 解决方案(应用于Shiro):
        • 后端:
          • 1 引入RSA MAVEN 坐标
          • 2 新增/rsa/public接口,用于前端获取加密公钥
          • 3 开放/rsa/public接口的访问权限
          • 4 登陆时加密发送过来的请求参数进行解密
        • 前端:
          • 1 下载并引入jsencrypt.min.js
          • 2 在login.js中增加如下代码:
    • 3.不充分的账户封锁
    • 4.会话标识未更新
    • 5.登录错误消息凭证枚举
    • 6.自动填写未对密码字段禁用的HTML属性
    • 7.HTML注释敏感信息泄露

1.SQL盲注

解决方案:

对相应的参数增加SQL关键字的判断(select update delete),如果参数包含SQL关键字则判定参数失效。

2.已解密的登录请求

解决方案(应用于Shiro):

对登录请求中的敏感信息以RSA加密的方式发送到服务器。

后端:

1 引入RSA MAVEN 坐标

<dependency>
    <groupId>org.bouncycastlegroupId>
    <artifactId>bcprov-jdk16artifactId>
    <version>1.46version>
dependency>
2 新增/rsa/public接口,用于前端获取加密公钥

2.1 新增RSAUtils

public class RSAUtils {
     

    //KeyPair is a simple holder for a key pair.
    private static final KeyPair keyPair = initKey();
    /**
     * 初始化方法,产生key pair,提供provider和random
     * @return KeyPair instance
     */
    private static KeyPair initKey() {
     

        try {
     
            //添加provider
            Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
            Security.addProvider(provider);
            //产生用于安全加密的随机数
            SecureRandom random = new SecureRandom();

            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", provider);
            generator.initialize(1024, random);
            return generator.generateKeyPair();
        } catch(Exception e) {
     
            throw new RuntimeException(e);
        }
    }
    /**
     * 产生public key
     * @return public key字符串
     */
    public static String generateBase64PublicKey() {
     
        PublicKey publicKey = (RSAPublicKey)keyPair.getPublic();

        //encodeBase64(): Encodes binary data using the base64
        //algorithm but does not chunk the output.
        //getEncoded():返回key的原始编码形式
        return new String(Base64.encodeBase64(publicKey.getEncoded()));
    }
    /**
     * 解密数据
     * @param string 需要解密的字符串
     * @return  破解之后的字符串
     */
    public static String decryptBase64(String string) {
     
        //decodeBase64():将Base64数据解码为"八位字节”数据
        return new String(decrypt(Base64.decodeBase64(string.getBytes())));
    }

    private static byte[] decrypt(byte[] byteArray) {
     
        try {
     
            Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
            Security.addProvider(provider);
            //Cipher: 提供加密和解密功能的实例
            //transformation: "algorithm/mode/padding"
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
            PrivateKey privateKey = keyPair.getPrivate();
            //初始化
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            //doFinal(): 加密或者解密数据
            byte[] plainText = cipher.doFinal(byteArray);
            return plainText;
        } catch(Exception e) {
     
            throw new RuntimeException(e);
        }
    }
}

2.2 新增RASController

@Controller
@RequestMapping("/rsa")
public class RasController {
     

    @RequestMapping("/public")
    @ResponseBody
    public Object getRasPublicKey() {
     
        String publicKey = RSAUtils.generateBase64PublicKey();
        return new Result<>(publicKey);
    }

}
3 开放/rsa/public接口的访问权限

3.1 修改配置类

在继承PathMatchingFilterChainResolver的配置类中进行如下配置:

public class CustomPathMatchingFilterChainResolver extends PathMatchingFilterChainResolver {
     

    /** 注册请求 路径映射 pattern. */
    private static final String RSA_FILTER = "/rsa/public";

    private CustomFilterChainManager customDefaultFilterChainManager;

    /** 设置customDefaultFilterChainManager. */
    public void setCustomDefaultFilterChainManager(
                   CustomFilterChainManager customDefaultFilterChainManager) {
     
        this.customDefaultFilterChainManager = customDefaultFilterChainManager;
        setFilterChainManager(customDefaultFilterChainManager);
    }

    @Override
    public FilterChain getChain(ServletRequest request, ServletResponse response,
                                FilterChain originalChain) {
     
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
     
            return null;
        }

        String requestURI = getPathWithinApplication(request);

        List<String> chainNames = new ArrayList<String>();
        for (String pathPattern : filterChainManager.getChainNames()) {
     
            if (pathMatches(pathPattern, requestURI)) {
     
                if (RSA_FILTER.equals(pathPattern)) {
     
                    return customDefaultFilterChainManager.proxy(originalChain, pathPattern);
                }
                chainNames.add(pathPattern);
            }
        }

        if (CollectionUtils.isEmpty(chainNames)) {
     
            return null;
        }

        return customDefaultFilterChainManager.proxy(originalChain, chainNames);
    }
}

3.2 修改security-context.xml配置文件

在id为filterChainManagerbean中,修改name为defaultFilterChainDefinitionspropertyvalue子标签,增加:

/rsa/public = anon
4 登陆时加密发送过来的请求参数进行解密

在继承AuthorizingRealm的类中对token中的参数进行解密,

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                    throws AuthenticationException {
     
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = RSAUtils.decryptBase64(upToken.getUsername().trim());
        String password = "";
        if (upToken.getPassword() != null) {
     
            password =  RSAUtils.decryptBase64(new String(upToken.getPassword()));
        }
        ...

并且在验证登录成功之后修改token中的相应用户参数为非加密状态:

		...
        upToken.setUsername(username);
        upToken.setPassword(password.toCharArray());

        return authenticationInfo;
    }

前端:

1 下载并引入jsencrypt.min.js

下载地址:

https://share.weiyun.com/5SdhNGK

2 在login.js中增加如下代码:
//获取public key
var publicKey = null;
jQuery(document).ready(function() {
     
    ...
    $("#form-submit").bind("click",function(){
     
        getPublicKey();

        var oldUsername = $("#form-username").val();
        var oldPassword = $("#form-password").val();
        convertFormValue();
        event.preventDefault();
        $("form[role='form']").submit();
        resetForm(oldUsername, oldPassword);
    });

});

function getPublicKey(){
     
    var url = window.location.href;
    url = url.substring(0,url.lastIndexOf("/")) + "/rsa/public";
    $.ajax({
     
        url:  url,
        type: "post",
        async: false,
        dataType: "json",
        success: function(data) {
     
            if(data) publicKey = data.data;
        }
    });
}

function convertFormValue() {
     
    if(publicKey != null){
     
        var encrypt = new JSEncrypt();
        encrypt.setPublicKey(publicKey);
        var usrename = encrypt.encrypt($("#form-username").val().trim());
        var password = encrypt.encrypt($("#form-password").val().trim());
        $("#form-username").val(usrename);
        $("#form-password").val(password);

    }
}

function resetForm(username, password) {
     
    $("#form-username").val(username);
    $("#form-password").val(password);
}

3.不充分的账户封锁

解决方法:

在继承AuthorizingRealm的Realm类中增加如下配置:

public class CustomRealm extends AuthorizingRealm {
     

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomRealm.class);
    
    ...

    /**
     * 集群中可能会导致出现验证多过5次的现象,因为AtomicInteger只能保证单节点并发
     */
    private static final String RETRY_CACHE_NAME = "passwordRetryCache";
    private static final Integer MAX_RETRY_COUNT = 5;
    private Cache<String, AtomicInteger> passwordRetryCache;

    /**
     * 线程池
     */
    private static ExecutorService executorService = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));
    
    ...

    /**
     * 验证当前登录的Subject.
     * @param token 验证登录凭证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                    throws AuthenticationException {
     
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = RSAUtils.decryptBase64(upToken.getUsername().trim());
        String password = "";
        if (upToken.getPassword() != null) {
     
            password =  RSAUtils.decryptBase64(new String(upToken.getPassword()));
        }

        try {
     
            doLoginTimeLimits(username);
            ...
        } catch (Exception e) {
     
            ...
        }
        ...
    }
    
    ...

    private void doLoginTimeLimits(String username) {
     
        final String clientUserName = username;
         passwordRetryCache = cacheManager.getCache(RETRY_CACHE_NAME);
        //先查看是否系统中是否已有登录次数缓存
        AtomicInteger retryCount = passwordRetryCache.get(clientUserName);
        // 如果之前没有登录缓存,则创建一个登录次数缓存。
        if (retryCount == null) {
     
            retryCount = new AtomicInteger(0);
        }
        //将缓存记录的登录次数加1
        retryCount.incrementAndGet();
        //如果有且次数已经超过限制,则驳回本次登录请求。
        if (retryCount.get() > MAX_RETRY_COUNT) {
     
            LOGGER.error("登录次数超过限制");
            throw new ExcessiveAttemptsException("用户:" + clientUserName + "登录次数已经超过限制");
        }
        //并将其保存到缓存中
        passwordRetryCache.put(clientUserName, retryCount);
        //debug
        if (LOGGER.isDebugEnabled()) {
     
            LOGGER.debug("用户:{},尝试登录次数:{}", clientUserName, retryCount.get());
        }
        //判断是否登录成功
        boolean isMatcher = false;
        //如果成功则清除缓存
        if (isMatcher) {
     
            passwordRetryCache.remove(clientUserName);
        }
        synchronized (this) {
     
            executorService.execute(() -> {
     
                for (int i = 0; i < 60; i++) {
     
                    try {
     
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                }
                passwordRetryCache.remove(clientUserName);
            });
        }
    }
    ...
}

4.会话标识未更新

解决方法:

public void login(String username,String password){
      
    SecurityUtils.getSubject().logout(); 
    //登录验证 
    ...
}

5.登录错误消息凭证枚举

解决方法:

对每个错误的登录尝试发出相同的错误提示信息(“用户名或密码不正确!”)。

6.自动填写未对密码字段禁用的HTML属性

解决方法:

在密码input标签中增加如下属性:

autocomplete="false"

7.HTML注释敏感信息泄露

解决方法:

将提示出来的相关信息,在html页面中删除掉。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EFLBB935-1579432572730)(?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzMjA0NzA5,size_16,color_FFFFFF,t_70)]

IBM Security 扫描解决方案整理_第1张图片

你可能感兴趣的:(安全,安全)