sql注入与数据校验总结

注重java编程规范与安全是Java开发者个人编程一个很好的习惯,以下是对于sql注入的一些学习总结资料,
主要有sql注入的说明,jdbc、存储过程、hibernate、mybatis中防止sql注入,以及简单的数据校验

说明SQL注入是指利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。防止SQL注入的方式主要可以分为两类:

Ÿ 使用参数化查询

Ÿ 对不可信数据进行校验

参数化查询是一种简单有效的防止SQL注入的查询方式,应该被优先考虑使用。

JDBC

错误示例(代码动态构建SQL):

Statement stmt = null;
ResultSet rs = null;
try
{
    String sqlString = "SELECT * FROM t_item WHERE owner='" + userName + "' AND itemName='" + request.getParameter("itemName") + "'";
    stmt = connection.createStatement();
    rs = stmt.executeQuery(sqlString);
}
catch (SQLException se)
{
}

这里将查询字符串常量与用户输入进行拼接来动态构建SQL查询命令。仅当itemName不包含单引号时,这条查询语句的行为才会是正确的。如果一个攻击者以用户名user发起一个请求,并使用以下条目名称参数进行查询:

name' OR '1' = '1'

那么这个查询将变成:

SELECT * FROM t_item WHERE owner = 'user' AND itemname = 'name' OR '1'='1';

此处,额外的OR '1'='1'条件导致整个WHERE子句的值总为真。那么,这个查询便等价于如下非常简单的查询:

SELECT * FROM t_item

这个简化的查询使得攻击者能够绕过原有的条件限制:这个查询会返回items表中所有储存的条目,而不管它们的所有者是谁,而原本应该只返回属于当前已认证用户的条目。

正确示例(使用PreparedStatement进行参数化查询):

PreparedStatement stmt = null
ResultSet rs = null
try
{
    String sqlString = "SELECT * FROM t_item WHERE owner=? AND itemName=?";
    stmt = connection.prepareStatement(sqlString);
    stmt.setString(1, userName);
    stmt.setString(2, itemName);
    rs = stmt.executeQuery();
}
catch (SQLException se)
{
}

如果使用参数化查询,则在SQL语句中使用占位符表示需在运行时确定的参数值。参数化查询使得SQL查询的语义逻辑被预先定义,而实际的查询参数值则等到程序运行时再确定。参数化查询使得数据库能够区分SQL语句中语义逻辑和数据参数,以确保用户输入无法改变预期的SQL查询语义逻辑。在Java中,可以使用java.sql.PreparedStatement来对数据库发起参数化查询。在这个正确示例中,如果一个攻击者将itemName输入为name' OR '1' = '1',这个参数化查询将免受攻击,而是会查找一个itemName匹配name' OR '1' = '1'这个字符串的条目。

JDBC调用存储过程

错误示例(在存储过程中动态构建SQL:

Java代码:

CallableStatement = null
ResultSet results = null;
try
{
    cs = connection.prepareCall("{call sp_queryItem(?,?)}");
    cs.setString(1, userName);
    cs.setString(2, itemName);
    results = cs.executeQuery();
}
catch (SQLException se)
{
}

SQL Server存储过程:

CREATE PROCEDURE sp_queryItem
    @userName varchar(50),
    @itemName varchar(50)
AS
BEGIN
    DECLARE @sql nvarchar(500);
    SET @sql = 'SELECT * FROM t_item
                WHERE owner = ''' + @userName + '''
                AND itemName = ''' + @itemName + '''';
    EXEC(@sql);
END

GO

在存储过程中,通过拼接参数值来构建查询字符串,和在应用程序代码中拼接参数一样,同样是有SQL注入风险的。
正确示例(在存储过程中进行参数化查询):
Java 代码:
CallableStatement = null
ResultSet results = null;
try
{
    cs = connection.prepareCall("{call sp_queryItem(?,?)}");
    cs.setString(1, userName);
    cs.setString(2, itemName);
    results = cs.executeQuery();
}
catch (SQLException se)
{
}
SQL Server存储过程:
CREATE PROCEDURE sp_queryItem
    @userName varchar(50),
    @itemName varchar(50)
AS
BEGIN
    SELECT * FROM t_item  
    WHERE userName = @userName
    AND itemName = @itemName;
END
GO

这个存储过程使用参数化查询,而未包含不安全的动态SQL构建。数据库编译此存储过程时,会生成一个SELECT查询的执行计划,只允许原始的SQL语义被执行。任何参数值,即使是被注入的SQL语句也不会被执行,因为它们不是执行计划的一部分。

Hibernate:

错误示例( 动态构建SQL/HQL):

原生SQL查询:

Query sqlQuery = session.createSQLQuery("select * from t_item where owner = '" + userName + "' and itemName = '" + itemName + "'");
List<Item> rs = (List<Item>) sqlQuery.list();

HQL查询:

Query hqlQuery = session.createQuery("from Item as item where item.owner = '" + userName + "' and item.itemName = '" + itemName + "'");
List<Item> hrs = (List<Item>) hqlQuery.list();

即使是使用Hibernate,如果在动态构建SQL/HQL查询时包含了不可信输入,同样也会面临SQL/HQL注入的问题。

正确示例(参数化查询):

HQL中基于位置的参数化查询:


Query hqlQuery = session.createQuery("from Item as item where item.owner = ? and item.itemName = ?");
hqlQuery.setString(1, userName);
hqlQuery.setString(2, itemName);
List<Item> rs = (List<Item>) hqlQuery.list();

HQL中基于名称的参数化查询:


Query hqlQuery = session.createQuery("from Item as item where item.owner = :owner and item.itemName = :itemName");
hqlQuery.setString("owner", userName);
hqlQuery.setString("itemName", itemName);
List<Item> rs = (List<Item>) hqlQuery.list();

原生参数化查询:


Query sqlQuery = session.createSQLQuery("select * from t_item where owner = ? and itemName = ?");
sqlQuery.setString(0, owner);
sqlQuery.setString(1, itemName);
List<Item> rs = (List<Item>) sqlQuery.list();

Hibernate支持SQL/HQL参数化查询。为了防止SQL注入以及改善性能,以上这些示例使用了参数化绑定的方式来设置查询参数。

Mybatis

正反示例( SQL映射):

SQL映射允许在SQL语句中通过#字符指定动态参数,例如:

<select id="getItems" parameterClass="MyClass" resultClass="Item">
SELECT * FROM t_item WHERE owner = #userName# AND itemName = #itemName#
</select>

#符号括起来的userNameitemName两个参数指示myBATIS在创建参数化查询时将它们替换成占位符:

String sqlString = "SELECT * FROM t_item WHERE owner=? AND itemName=?";
PreparedStatement stmt = connection.prepareStatement(sqlString);
stmt.setString(1, myClassObj.getUserName());
stmt.setString(2, myClassObj.getItemName());
ResultSet rs = stmt.executeQuery();

然而,myBATIS也允许使用$符号指示使用某个参数来直接拼接SQL语句,这种做法是有SQL注入漏洞的

<select id="getItems" parameterClass="MyClass" resultClass="items">
SELECT * FROM t_item WHERE owner = #userName# AND itemName = '$itemName$'
</select>

myBATIS将会为以上SQL映射执行类似下面的代码:

String sqlString = "SELECT * FROM t_item WHERE owner=? AND itemName='" + myClassObj.getItemName() + "'";
PreparedStatement stmt = connection.prepareStatement(sqlString);
stmt.setString(1, myClassObj.getUserName());
ResultSet rs = stmt.executeQuery();
// ... convert results set to Item objects

在这里,攻击者可以利用itemName参数发起SQL注入攻击

不可信数据检验:

正确示例:

public List<Book> queryBooks(List<Expression> queryCondition)
{
    /* ... */
    try
    {
        StringBuilder sb = new StringBuilder("select * from t_book where ");
        Codec oe = new OracleCodec();
        if (queryCondition != null && !queryCondition.isEmpty())
        {
            for (Expression e : queryCondition)
            {
                String exprString = e.getColumn() + e.getOperator() + e.getValue();
                String safeExpr = ESAPI.encoder().encodeForSQL(oe, exprString);
                sb.append(safeExpr).append(" and ");
            }
            sb.append("1=1");
            Statement stat = connection.createStatement();
            ResultSet rs = stat.executeQuery(sb.toString());
            //other omitted code    
        }
    }
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值