PreparedStatement防止sql注入原理
PreparedStatement類是java的一個類,準確說是jdbc規範中的一個介面,各個資料庫產商的實現不同(即實現類不同),今天我們就以mysql資料庫來說,我已經下載了mysql資料庫的驅動jar包和驅動程式的原始碼.
sql注入的時候,比如,有些使用者就會在介面上輸入anything' OR 'x'='x這種東西,最後sql語句就是如下:
SELECT * FROM users WHERE userName = '令狐沖' AND password = 'anything' OR 'x'='x'
其實
SELECT * FROM users WHERE userName = ? AND password = ?
preparedStatement.setString(1,'XXX');
preparedStatement.setString(2,'XXX');
我們可以看下mysql的底層的setString();方法,其實就是用值代替了?的位置,並且在值的兩邊加上了單引號,然後再把值當中的所有單引號替換成了斜槓單引號,說白了就是把值當中的所有單引號給轉義了!這就達到了防止sql注入的目的,說白了mysql驅動的PreparedStatement實現類的setString();方法內部做了單引號的轉義,而Statement不能防止sql注入,就是因為它沒有把單引號做轉義,而是簡單粗暴的直接拼接字串,所以達不到防止sql注入的目的。
你們可以直接看看打印出來是不是SELECT * FROM users WHERE userName = '令狐沖' AND password = 'anything\' OR\'x\'=\'x'
不好意思,如果你們用的是mysql資料庫的話,你們自己打印出來試一試,我沒試過,但是我知道底層的原理的話,大概猜一下,只要方向猜對了,八九不離十,其實大家寫程式寫多了,寫久了,你們就可以去猜一猜那些大牛或者是框架的底層的原理,只要方向猜對了,基本上八九不離十,所以,有時候還是要大膽的去猜測底層的原理和實現!
如果是sqlserver資料庫的話,
System.out.println(preparedStatement.toString());
好像不會打印出具體的sql語句,因為sqlserver和mysql的preparedStatement實現類不同。至於oracle資料的話,
System.out.println(preparedStatement.toString());
會不會打印出具體的sql語句,我不太清楚,我沒試過,你們可以試一試!
以下是mysql資料庫的PreparedStatement實現類的setString()方法(你們可以去網上下載mysql的驅動程式的原始碼,原始碼裡面有jdbc的PreparedStatement實現類,去看看實現類中的setString()方法吧)
/**
* Set a parameter to a Java String value. The driver converts this to a SQL
* VARCHAR or LONGVARCHAR value (depending on the arguments size relative to
* the driver's limits on VARCHARs) when it sends it to the database.
*
* @param parameterIndex
* the first parameter is 1...
* @param x
* the parameter value
*
* @exception SQLException
* if a database access error occurs
*/
public synchronized void setString(int parameterIndex, String x) throws SQLException {
// if the passed string is null, then set this column to null
if (x == null) {
setNull(parameterIndex, Types.CHAR);
} else {
checkClosed();
int stringLength = x.length();
if (this.connection.isNoBackslashEscapesSet()) {
// Scan for any nasty chars
boolean needsHexEscape = isEscapeNeededForString(x,
stringLength);
if (!needsHexEscape) {
byte[] parameterAsBytes = null;
StringBuffer quotedString = new StringBuffer(x.length() + 2);
quotedString.append('\'');
quotedString.append(x);
quotedString.append('\'');
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(quotedString.toString(),
this.charConverter, this.charEncoding,
this.connection.getServerCharacterEncoding(),
this.connection.parserKnowsUnicode(), getExceptionInterceptor());
} else {
// Send with platform character encoding
parameterAsBytes = StringUtils.getBytes(quotedString.toString());
}
setInternal(parameterIndex, parameterAsBytes);
} else {
byte[] parameterAsBytes = null;
if (!this.isLoadDataQuery) {
parameterAsBytes = StringUtils.getBytes(x,
this.charConverter, this.charEncoding,
this.connection.getServerCharacterEncoding(),
this.connection.parserKnowsUnicode(), getExceptionInterceptor());
} else {
// Send with platform character encoding
parameterAsBytes = StringUtils.getBytes(x);
}
setBytes(parameterIndex, parameterAsBytes);
}
return;
}
String parameterAsString = x;
boolean needsQuoted = true;
if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {
needsQuoted = false; // saves an allocation later
StringBuffer buf = new StringBuffer((int) (x.length() * 1.1));
buf.append('\'');
//
// Note: buf.append(char) is _faster_ than
// appending in blocks, because the block
// append requires a System.arraycopy()....
// go figure...
//
for (int i = 0; i < stringLength; ++i) {
char c = x.charAt(i);
switch (c) {
case 0: /* Must be escaped for 'mysql' */
buf.append('\\');
buf.append('0');
break;
case '\n': /* Must be escaped for logs */
buf.append('\\');
buf.append('n');
break;
case '\r':
buf.append('\\');
buf.append('r');
break;
case '\\':
buf.append('\\');
buf.append('\\');
break;
case '\'':
buf.append('\\');
buf.append('\'');
break;
case '"': /* Better safe than sorry */
if (this.usingAnsiMode) {
buf.append('\\');
}
buf.append('"');
break;
case '\032': /* This gives problems on Win32 */
buf.append('\\');
buf.append('Z');
break;
case '\u00a5':
case '\u20a9':
// escape characters interpreted as backslash by mysql
if(charsetEncoder != null) {
CharBuffer cbuf = CharBuffer.allocate(1);
ByteBuffer bbuf = ByteBuffer.allocate(1);
cbuf.put(c);
cbuf.position(0);
charsetEncoder.encode(cbuf, bbuf, true);
if(bbuf.get(0) == '\\') {
buf.append('\\');
}
}
// fall through
default:
buf.append(c);
}
}
buf.append('\'');
parameterAsString = buf.toString();
}
byte[] parameterAsBytes = null;
if (!this.isLoadDataQuery) {
if (needsQuoted) {
parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString,
'\'', '\'', this.charConverter, this.charEncoding, this.connection
.getServerCharacterEncoding(), this.connection
.parserKnowsUnicode(), getExceptionInterceptor());
} else {
parameterAsBytes = StringUtils.getBytes(parameterAsString,
this.charConverter, this.charEncoding, this.connection
.getServerCharacterEncoding(), this.connection
.parserKnowsUnicode(), getExceptionInterceptor());
}
} else {
// Send with platform character encoding
parameterAsBytes = StringUtils.getBytes(parameterAsString);
}
setInternal(parameterIndex, parameterAsBytes);
this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.VARCHAR;
}
}