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()方法吧)

mysql驱动程序和源代码

 

/**
	 * 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;
		}
	}

 

 

 

  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值