1. 程式人生 > >PreparedStatement防止sql注入原理

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注入的目的。

所以,不管是mysql資料庫產商的PreparedStatement實現類還是sqlserver資料庫產商的PreparedStatement實現類,其實底層的原理都差不多,當然啦,我們直接在java程式中把那些單引號直接轉義也行,只不過比較繁瑣一點罷了,既然各個資料庫產商的PreparedStatement實現類的setString()方法中已經做了轉義,我們何必在java程式中自己去做轉義的這部分工作呢?

你如果想看最後在資料庫中執行的sql語句是什麼的話,可以打印出來看看,如果是mysql資料庫的話,直接System.out.println(preparedStatement.toString());

你們可以直接看看打印出來是不是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;
		}
	}