最近筆者最近在進行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