例2 中的错误实现。此函数严格遵循反模式,还通过抛出包含传入(未过滤)数据(即用户名)的异常而执行了另外一项重要的 no-no 操作。如果以响应的形式为用户呈现此数据,您就很可能遇到某些恶意利用,特别是可能遭遇跨站脚本攻击。
public void validateUser(String user, String pwd, Connection db)
throws InvalidUserException
{
Statement stmt = null;
ResultSet rs = null;
try
{
// Create the statement
stmt = db.createStatement();
String sql = "select id from users where user=''" + user +
"'' and pwd=''" + pwd + "''";
// Execute it, process the result
rs = stmt.executeQuery(sql);
if( rs == null || rs.next() == null )
throw new InvalidUserException(user);
}
catch( SQLException e )
{
throw new InvalidUserException(user);
}
finally
{
try { if( rs != null ) rs.close(); } catch( Exception e ) { }
try { if( stmt != null ) stmt.close(); } catch( Exception e ) { }
}
}
示例2 错误实现。
为了修正此代码,不应动态构建 SQL 查询,而是直接构建准备好的语句,并使用它来代替传入参数。
我们将准备的语句会为参数保留空间,并且不易受此类攻击利用,原因就在于它的词汇方面并不像字符串串联那样脆弱。
考虑以下语句(准备该语句的目的与前面提到的串联字符串相同):
SELECT ID FROM USERS WHERE USER=?
AND PWD=?
我使用这个准备好的语句代入了 user 和 pwd 参数的传入数据。如果我们将之前被利用的字符串作为输入,结果将是查询代入过程出错,因为不能将包含单引号等特殊字符的参数提供给准备好的查询。
其他可能出现的利用也能在不同阶段捕捉到,但如示例3 所示,新实现的创建与原实现一样简单,但安全性要高得多(我们也从抛出的异常中删除了用户名,这样可以避免在未经过滤的情况下将其公开给调用方的危险)。
public void validateUser(String user, String pwd, Connection db)
throws InvalidUserException
{
PreparedStatement stmt = null;
ResultSet rs = null;
try
{
// Prepare the statement, rather than concatenating it
String sql = "select id from users where user=? and pwd=?");
stmt = db.prepareStatement(sql);
// Substitute our incoming parameters into the query
stmt.setString(1, user);
stmt.setString(2, pwd);
// Execute the query and process the results as before
rs = stmt.executeQuery();
if( rs == null || rs.next() == null )
throw new InvalidUserException();
}
catch( SQLException e )
{
throw new InvalidUserException();
}
finally
{
try { if( rs != null ) rs.close(); } catch( Exception e ) { }
try { if( stmt != null ) stmt.close(); } catch( Exception e ) { }
}
}
示例3 示例2 的较为安全的版本。
总体而言,无论是处理查询还是DML,在处理来自最终用户的数据时,始终应使用准备好的语句来利用数据库本身内置的过滤和解析功能。
跨站点脚本攻击(XSS)
在早期的浏览器版本中,对于JavaScript 施加的第一项限制就是为页面内容建立一种边界,使一个站点提供的一个框架内执行的脚本无法访问其他站点提供的框架中的内容。因而,跨站点脚本攻击这种攻击模式关注的是使来自一个站点(攻击者站点)的脚本能够访问其他站点的内容(例如,用户的银行账户站点)。
为此,用户通常必然要访问一个恶意或不可信的网站,而社会工程的众多试验已经显示,用户可能会被最古怪的站点吸引。
在此类漏洞中,最常见的形式就是简单的反射漏洞,在一次服务器请求中将未经过滤的 HTML 参数 |