最近笔者最近在进行Java安全SDK的研究编写,过程中对OWASP的ESAPI做了一些研究,收获颇多。
ESAPI (OWASP企业安全应用程序接口)是一个免费、开源的、网页应用程序安全控件库,它使程序员能够更容易写出更低风险的程序。ESAPI接口库被设计来使程序员能够更容易的在现有的程序中引入安全因素。ESAPI库也可以成为作为新程序开发的基础。除了语言方面的差异,所有的OWASP ESAPI版本都具有如下相同的基本设计结构:
1、都有一整套安全控件接口。例如,这些安全接口中定义了发送给不同安全控件的参数类型;
2、每个安全控件都有一个参考实现。 这些实现不是基于特定组织或者特定程序的。例如:基于字符串的输入验证;
3、程序开发者可以有选择的实现自己的安全控件接口。可能这些接口类中的应用逻辑是由您的组织开发的或者为您公司定制的。例如:企业认证。该版本ESAPI下载地址:https://github.com/ESAPI/esapi-java-legacy
SQL注入
该版本的ESAPI中,SQL注入主要通过编码转义的方式进行防御修复。编码的方法主要位于codecs文件夹中。
Codec
在org.owasp.esapi.codecs
包中,提供了Codec
接口。Codec
接口定义了一组用于编码和解码应用程序级编码方法的方法,例如HTML实体编码和URL编码。这些编解码器允许逐字符解码。
- encode
- encodeCharacter
- decode
- decodeCharacter
- getHexForNonALphanumeric
查找任何非字母数字字符的十六进制值 - toOctal
- toHex
- containsCharacter
AbstractCodec
抽象类AbstractCodec
实现了Codec
接口.
- AbstractCodec
类中定义了一个大小为256的hex
字符数组,用来标记要编码的字符。默认构造方法中对数组进行初始化,存储字符的十六进制字符以节省时间。如果不应该对该字符进行编码,则存储null。
public AbstractCodec() {
for ( char c = 0; c < 0xFF; c++ ) {
if ( c >= 0x30 && c <= 0x39 || c >= 0x41 && c <= 0x5A || c >= 0x61 && c <= 0x7A ){
hex[c] = null;
} else {
hex[c] = toHex(c).intern();
}
}
}
- encode
对input
调用encodeCharacter
方法进行逐字符编码- codePointAt
返回字符串中指定索引处的字符的Unicode值 - isBmpCodePoint
确定指定的字符(Unicode代码点)是否在Basic Multilingual Plane(BMP,Unicode中的基于多文种平面)中
- codePointAt
@Overridepublic
String encode(char[] immune, String input) {
StringBuilder sb = new StringBuilder();
for(int offset = 0; offset < input.length(); ) {
final int point = input.codePointAt(offset);
if (Character.isBmpCodePoint(point)) {
//We can then safely cast this to char and maintain legacy behavior.
sb.append(encodeCharacter(immune, new Character((char) point)));
} else {
sb.append(encodeCharacter(immune, point));
}
offset += Character.charCount(point);
}
return sb.toString();
}
- encodeCharacter
这里只是实现了对input
中的字符进行合法性判断
@Overridepublic
String encodeCharacter( char[] immune, int codePoint ) {
String rval = "";
if(Character.isValidCodePoint(codePoint)){
rval = new StringBuilder().appendCodePoint(codePoint).toString();
}
return rval;
}
- decodeCharacter
- getHexForNonAlphanumeric
public String getHexForNonAlphanumeric(char c) {
if(c<0xFF)
return hex[c];
return toHex(c);
}
public String getHexForNonAlphanumeric(int c) {
if (c<0xFF) {
return hex[c];
} else {
return toHex(c);
}
}
- toOctal
- toHex
- containsCharacter
public boolean containsCharacter( char c, char[] array ) {
for (char ch : array) {
if (c == ch) return true;
}
return false;
}
AbstarctCharacterCodec
这个抽象类继承了AbstractCodec
类,重写了decode
方法
@Override
public String decode(String input) {
StringBuilder sb = new StringBuilder();
PushbackSequence<Character> pbs = new PushbackString(input);
while (pbs.hasNext()) {
Character c = decodeCharacter(pbs);
if (c != null) {
sb.append(c);
} else {
sb.append(pbs.next());
}
}
return sb.toString();
}
AbstractIntegerCodec
这个抽象类继承了AbstractCodec
类,重写了decode
方法
@Override
public String decode(String input) {
StringBuilder sb = new StringBuilder();
PushbackSequence<Integer> pbs = new PushBackSequenceImpl(input);
while (pbs.hasNext()) {
Integer c = decodeCharacter(pbs);
if (c != null && Character.isValidCodePoint(c)) {
sb.appendCodePoint(c);
} else {
sb.appendCodePoint(pbs.next());
}
}
return sb.toString();
}
MySQLCodec
该类实现了MySQL数据库中的转义编码器。可以满足MySQL中的两种SQL Mode中的字符编码转义(MySQL中的sql mode设置将会影响MySQL的语法)。
- ANSI
将所有的'(单引号)
替换为''(两个单引号)
- Standard
字符 Ascii码 转义后字符 NUL 0x00 \0 BS 0x08 \b TAB 0x09 \t LF 0x0a \n CR 0x0d \r SUB 0x1a \Z " 0x22 \" % 0x25 % ' 0x27 \' \ 0x5c \\ _ 0x5f \_
所有其他ASCII值小于256的其他非字母数字字符都转为\c
字符
MySQLCodec
类继承于AbstractCharacterCodec
抽象类
- MySQLCodec
根据sql mode初始化MySQL codec
public MySQLCodec( int mode ) { this.mode = Mode.findByKey(mode);}
public MySQLCodec( Mode mode ) { this.mode = mode;}
- encodeCharacter
根据不同的sql mode调用不同的encodeCharacter方法对字符进行编码 - encodeCharacterANSI
ANSI模式下的编码方法
private String encodeCharacterANSI( Character c ) {
if ( c == '\'' )
return "\'\'";
return ""+c;
}
- encodeCharacterMySQL
标准模式下的编码方法
private String encodeCharacterMySQL( Character c ) {
char ch = c.charValue();
if ( ch == 0x00 ) return "\\0";
if ( ch == 0x08 ) return "\\b";
if ( ch == 0x09 ) return "\\t";
if ( ch == 0x0a ) return "\\n";
if ( ch == 0x0d ) return "\\r";
if ( ch == 0x1a ) return "\\Z";
if ( ch == 0x22 ) return "\\\"";
if ( ch == 0x25 ) return "\\%";
if ( ch == 0x27 ) return "\\'";
if ( ch == 0x5c ) return "\\\\";
if ( ch == 0x5f ) return "\\_";
return "\\" + c;
}
- decodeCharacter
根据不同的sql mode调用不同的decodeCharacter方法对字符进行解码 - decodeCharacterANSI
ANSI模式下的字符解码方法 - decodeCharacterMySQL
标准模式下的字符解码方法
OracleCodec
OracleCodec
类继承了AbstractCharacterCodec
类,为Oracle数据库中的字符编码器。这个方法只能对位于单引号中的数据进行安全过滤(写的不全)。
- encodeCharacter
将所有的'(单引号)
替换为''(两个单引号)
public String encodeCharacter( char[] immune, Character c ) {
if ( c.charValue() == '\'' )
return "\'\'";
return ""+c;
}
- decodeCharacter
DB2Codec
DB2Codec
类继承了AbstractCharacterCodec
类,为DB2数据库中的字符编码器,但是只能在有限的情况下进行安全过滤(写的不全)。
- encodeCharacter
将所有的'(单引号)
替换为''(两个单引号)
;将;(分号)
转为.(点号)
public String encodeCharacter(char[] immune, Character c) {
if (c.charValue() == '\'')
return "\'\'";
if (c.charValue() == ';')
return ".";
return "" + c;
}
- decodeCharacter