JAVA安全及编码规范

我是程序汪

共 4931字,需浏览 10分钟

 · 2022-01-22

目录

  • 1、引用java security library

  • 2、SQL注入防护

  • 3、xss防护

  • 4、url重定向防护

  • 5、SSRF防护

  • 6、CSRF防护

  • 7、readObject反序列化漏洞防护

  • 8、xxe防护

  • 9、AES加解密

  • 10、RSA加解密

  • 11、ECDSA 加签验签

1、引用java security library

环境需求

  • Java 8

  • Maven 3

a、编译jar包:

    mvn -Dmaven.test.skip=true clean install

b、引入java security library:

在target目录中找到target/rhizobia_J-1.0.jar,导入工程中

需要在自己的maven工程pom.xml中加入如下依赖

    
log4j
log4j
1.2.17


commons-codec
commons-codec
1.11

2、SQL注入防护

a、确认是连接的是哪种数据库,选择对应的数据库编码,目前支持数据库包括:MySQL Oracle DB2:

    import com.immomo.rhizobia.rhizobia_J.sqli.MysqlSanitiser;
//import com.immomo.rhizobia.rhizobia_J.sqli.OracleSanitiser;
//import com.immomo.rhizobia.rhizobia_J.sqli.DB2Sanitiser;

MysqlSanitiser sqlTool = MysqlSanitiser.getInstance();

b、对sql语句中拼接的参数进行转义:

    String id = "1' or '1'='1' #";
String idEncode = sqlTool.mysqlSanitise(id);
String query = "SELECT NAME FROM users WHERE id = '" + idEncode + "'";

使用order by、group by等需要转换列名时,需使用带boolean参数

    //保证列名中的下划线不被转义
String columnName = "user_name";
String columnNameEncode = sqlTool.mysqlSanitise(columnName, true);
query = "SELECT NAME FROM users order by " + columnNameEncode ;

c、转义前后对比:

    转义前:SELECT NAME FROM users WHERE id = '1' or '1'='1' #'
转义后:SELECT NAME FROM users WHERE id = '1\' or \'1\'\=\'1\' \#'

d、表名列名转义前后对比:

    转义前:SELECT NAME FROM users order by user_name
转义后:SELECT NAME FROM users order by user_name
转义前:SELECT NAME FROM users order by user-name
转义后:SELECT NAME FROM users order by user\-name

3、xss防护

a、调用XssSanitiser单例:

    import com.immomo.rhizobia.rhizobia_J.xss.XssSanitiser;

XssSanitiser xssFilter = XssSanitiser.getInstance();

b、如果输出到html body:

    String ret = xssFilter.encodeForHTML(oriString);

过滤前后对比:

    过滤前:<script> alert('xss') </script>
过滤后:&lt;script&gt;alert&#x28;&#x27;xss&#x27;&#x29;&lt;&#x2f;script&gt;

c、如果输出到html标签的属性(多了对空字符的过滤):

    String ret = xssFilter.encodeForHTMLAttribute(oriString);

过滤前后对比:

    过滤前:<script> alert('xss') </script>
过滤后:&lt;script&gt;&#x20;alert&#x28;&#x27;xss&#x27;&#x29;&#x20;&lt;&#x2f;script&gt;

d、如果输出到JavaScript代码块中:

    String ret = xssFilter.encodeForJavaScript(oriString);

过滤前后对比:

    过滤前:alert('xss');
过滤后:alert\x28\x27xss\x27\x29\x3B

4、url重定向防护

a、调用UrlRedirectionWhiteChecher单例:

    import com.immomo.rhizobia.rhizobia_J.urlredirection.UrlRedirectionWhiteChecher;

UrlRedirectionWhiteChecher urlChecker = UrlRedirectionWhiteChecher.getInstance();

b、自定义白名单:

    List<String> whitelist = new ArrayList<String>();
String white1=".trust1.com";
String white2=".trust2.com";

//setWhiteList会先清空原有白名单列表
//在原有基础上新增白名单,使用addWhiteList(whitelist)
urlChecker.setWhiteList(whitelist);

c、校验url:

    try{
boolean isWhite = urlChecker.verifyURL(url.trim());
} catch (Exception e) {
...
}

5、SSRF防护

a、调用SSRFWhiteChecker单例,与前面url重定向类似:

    import com.immomo.rhizobia.rhizobia_J.ssrf.SSRFWhiteChecker;

SSRFWhiteChecker ssrfChecker = SSRFWhiteChecker.getInstance();

b、自定义白名单:

    List<String> whitelist = new ArrayList<String>();
String white1=".trust1.com";
String white2=".trust2.com";

//setWhiteList会先清空原有白名单列表
//在原有基础上新增白名单,使用addWhiteList(whitelist)
ssrfChecker.setWhiteList(whitelist);

c、校验url:

    try{
boolean isWhite = ssrfChecker.verifyURL(url.trim());
} catch (Exception e) {
...
}

6、CSRF防护

a、随机算出csrf token,并且每次生成随机值都不一样(实测结果连续生成1000亿次无重复):

    import com.immomo.rhizobia.rhizobia_J.csrf.CSRFTokenUtils;

CSRFTokenUtils csrfInstance = CSRFTokenUtils.getInstance();
String token = csrfInstance.resetCsrfToken(32);

b、后端保存生成的token,以待校验(可以采用数据库、分布存储等任意存储手段)

c、前端页面加上hidden字段

form中加入csrf token的hidden字段:

    <input name="${(_csrf.parameterName)!}" value="${(_csrf.token)!}" type="hidden">

ajax中加入csrf头

    xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");

d、当前端向后端发送请求时,请求header中携带token,后端收到后与之前存储的token进行校验

7、readObject反序列化漏洞防护

a、选择适当的构造函数初始化,自定义白名单:

使用SecureObjectInputStream中适当的构造函数,增加自定义的白名单

    import com.immomo.rhizobia.rhizobia_J.deserialization.SecureObjectInputStream;

SecureObjectInputStream(InputStream in, String[] classlist)
SecureObjectInputStream(InputStream in, List<String> classlist)

b、使用安全的类SecureObjectInputStream,恢复非白名单中类的对象时会抛出异常:

    List<String> classlist = new ArrayList<String>();
classlist.add(SafeClass.class.toString());

try{
//考虑如果白名单为空时会影响正常判断逻辑,所以此处会抛出异常
SecureObjectInputStream ois = new SecureObjectInputStream(fis, classlist);

//使用安全的SecureObjectInputStream恢复对象时会抛出exception
UnsafeClass objectFromDisk = (UnsafeClass)ois.readObject();
} catch (Exception e) {
...
}

8、xxe防护

8.1、解析xml内容为Document

a、初始化时注意xml编码格式:

    import com.immomo.rhizobia.rhizobia_J.xxe.XmlUtils;
//如果xml格式包含外部实体,会抛异常
try{
Document doc = XmlUtils.getInstance().newDocument(xmlFile, "utf-8");
} catch (Exception e) {
...
}

b、使用生成的Document对象:

    Node notifyNode = doc.getFirstChild();
NodeList list = notifyNode.getChildNodes();
for (int i = 0, length = list.getLength(); i < length; i++) {
Node n = list.item(i);
String nodeName = n.getNodeName();
String nodeContent = n.getTextContent();
System.out.println(nodeName.toString() + " " + nodeContent.toString());
}

8.2、解析xml内容为Bean

a、自定义TestBean,然后调用converyToJavaBean解析:

    import com.immomo.rhizobia.rhizobia_J.xxe.XmlUtils;

//如果xml格式包含外部实体,会抛异常
XmlUtils xmlParser = XmlUtils.getInstance();
try {
TestBean testbean = (TestBean)xmlParser.converyToJavaBean(xmlFile, TestBean.class);
} catch (Exception e) {
...
}

b、使用生成的bean对象:

    testbean.getTo()
testbean.getFrom()
testbean.getHeading()
testbean.getBody()

9、AES加解密

知识点1:oracle官方已经在如下版本去除了aes-256的限制,6u181,7u171,8u161,9 b148,openjdk7u
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8170157

知识点2:之所以AES、RSA没有封装base64或16进制编码处理,是因为在使用base64编码后的内容中,可能存在'+'字符,
'+'字符返回给前端后再返回给后端时,如果不经过处理,会变为' '空格字符,
所以请用户自行选择base64编码或16进制编码
并在对加密内容进行base64编码时,请注意'+'字符

a、调用AESUtils:

    import com.immomo.rhizobia.rhizobia_J.crypto.AESUtils;

AESUtils aesInstance = AESUtils.getInstance(String aesKey, String secretKey, String aesMode);
/**
参数说明:
aesKey: 用于生成密钥的原始字符串,推荐使用session/id,具有唯一性
secretKey: 加解密双方约定的secret
aesMode: 值为null时,默认采用"AES/CBC/PKCS5Padding"
*/
AESUtils aesInstance = AESUtils.getInstance("843739488","TcmEqGzSpH5S2VgoUix7HJ9cwqCofoUD",null);

b、加密

    String orginText = "10000";

byte[] ciphertext = aesInstance.Encrypt(orginText);
//由于返回是byte流,所以如果需要base64编码或转换成Hex,需另做处理
String encryptRet = new BASE64Encoder().encode(ciphertext);

c、解密

    //同样,如果加密内容用base64编码或转换成Hex,解密时需另做处理
byte[] encrypted = new BASE64Decoder().decodeBuffer(encryptRet);
String DeString = aesInstance.Decrypt(encrypted);

10、RSA加解密 加签验签

知识点1:RSA加解密时,明文是有长度限制的,明文字符串限制长度 = 密钥长度(byte) - padding占用大小(byte)
padding大小如下:
RSA/ECB/PKCS1Padding or RSA : 11
RSA/ECB/OAEPWithSHA-1AndMGF1Padding : 42
RSA/ECB/OAEPWithSHA-256AndMGF1Padding : 66

例如:RSA密钥长度为1024(bit)/8 = 128(byte),keyPairGenerator.initialize(1024),
在RSA/ECB/OAEPWithSHA-1AndMGF1Padding模式下,
被加密的明文字符串长度不能超过 128-42 = 86

知识点2:同前一节aes的知识点2

知识点3:源码所用的signature类中,已经封装了摘要算法,所以可以不必再生成摘要,当然自已多生成摘要也没有问题

感谢LeadroyaL的issue

10.1、加解密

a、创建RSAUtils单例

  1. 如密钥在文件中:

    import com.immomo.rhizobia.rhizobia_J.crypto.RSAUtils;

/**
参数说明:目前证书支持 PEM 格式
priKeyPath: openssl生成的私钥地址
pubKeyPath: openssl生成的公钥地址
*/
String priKeyPath = "/tmp/pri.key";
String pubKeyPath = "/tmp/pub.key";
RSAUtils rsaInstance = RSAUtils.getInstance(priKeyPath, pubKeyPath);
  1. 由于文件头尾格式有多种,根据需要调用set方法修改头尾后生成公私钥

   RSAUtils rsaInstance = RSAUtils.getInstance();
rsaInstance.setPemPriHead("-----BEGIN PRIVATE KEY-----\n");
rsaInstance.setPemPriEnd("-----END PRIVATE KEY-----");
rsaInstance.setPemPubHead("-----BEGIN PUBLIC KEY-----\n");
rsaInstance.setPemPubEnd("-----END PUBLIC KEY-----");
rsaInstance.setPrivateKey(rsaInstance.getPrivateKey(priKeyPath));
rsaInstance.setPublicKey(rsaInstance.getPublicKey(pubKeyPath));
  1. 如果已有公私钥,也可直接用已有密钥生成单例

    //如下为生成公私钥的例子,用户可任意选用其他方法生成公私钥
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(512);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey rsaPublicKey = (PublicKey) keyPair.getPublic();
PrivateKey rsaPrivateKey = (PrivateKey) keyPair.getPrivate();

//生成单例
RSAUtils rsaInstance = RSAUtils.getInstance(rsaPrivateKey, rsaPublicKey);

b、加密

    String plaintext = "123";

//如知识点1,如果明文长度超过最大长度,可以用rsaInstance.encryptWithouLimit
//或者自行将明文字符串拆分成对应的限制长度的块
byte[] ciphertext = rsaInstance.encrypt(plaintext);

//与aes一样返回是byte流,所以如果需要base64编码或转换成Hex,需另做处理
String encryptRet = new BASE64Encoder().encode(ciphertext);

c、解密

    //同样,如果加密内容用base64编码或转换成Hex,解密时需另做处理
byte[] encrypted = new BASE64Decoder().decodeBuffer(encryptRet);

//如知识点1,如果使用了encryptWithouLimit加密,对应使用rsaInstance.decryptWithoutLimit进行解密
String plaintext = rsaInstance.decrypt(ciphertext);

10.2、加签验签(更推荐使用ECDSA来实现加签验签,参考)

a、加签

    //原文
String plaintext = "123";

byte[] sigintext = rsaInstance.sign(plaintext);
//与aes一样返回是byte流,所以如果需要base64编码或转换成Hex,需另做处理
String signtRet = new BASE64Encoder().encode(sigintext);

b、验签

    //同样,如果加密内容用base64编码或转换成Hex,解密时需另做处理
byte[] verified = new BASE64Decoder().decodeBuffer(signtRet);
boolean ifPass = rsaInstance.verify(verified, plaintext);

11、ECDSA 加签验签

a、创建ECDSAUtils单例

与前面一节RSA类似,也有三种获取单例的方法

    import com.immomo.rhizobia.rhizobia_J.crypto.ECDSAUtils;

String priKeyPath = "/tmp/ECDSAPrivateKey.key";
String pubKeyPath = "/tmp/ECDSAPublicKey.key";
ECDSAUtils ecInstance = ECDSAUtils.getInstance(priKeyPath, pubKeyPath);

//ECDSAUtils.getInstance()或ECDSAUtils.getInstance(ecPrivateKey, ecPublicKey)参照前面RSA章节

b、加签

    String plaintext = "123";

byte[] sigintext = ecInstance.sign(plaintext);
//与aes一样返回是byte流,所以如果需要base64编码或转换成Hex,需另做处理
String signtRet = new BASE64Encoder().encode(sigintext);

c、验签

//同样,如果加密内容用base64编码或转换成Hex,解密时需另做处理
byte[] verified = new BASE64Decoder().decodeBuffer(signtRet);

boolean ifPass = ecInstance.verify(verified, plaintext);


来源:https://github.com/momosecurity/rhizobia_J

程序汪资料链接

程序汪接的7个私活都在这里,经验整理

Java项目分享  最新整理全集,找项目不累啦 04版

堪称神级的Spring Boot手册,从基础入门到实战进阶

卧槽!字节跳动《算法中文手册》火了,完整版 PDF 开放下载!

卧槽!阿里大佬总结的《图解Java》火了,完整版PDF开放下载!

字节跳动总结的设计模式 PDF 火了,完整版开放下载!

欢迎添加程序汪个人微信 itwang008  进粉丝群或围观朋友圈


浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报