JAVA jdbc(数据库连接池)学习笔记(二) SQL注入

PS:今天偶然间发现了 SQL 的注入... 所以就简单的脑补了一下,都是一些简单的例子... 这篇写的不怎么样... 由于自己没有进行很深的研究...

学习内容:

1.SQL 注入的概念...

  所谓 SQL 注入,就是通过把 SQL 命令插入到 Web 表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令,比如先前的很多影视网站泄露 VIP 会员密码大多就是通过 WEB 表单递交查询字符暴出的,这类表单特别容易受到 SQL 注入式攻击.当应用程序使用输入内容来构造动态 sql 语句以访问数据库时,会发生 sql 注入攻击。如果代码使用存储过程,而这些存储过程作为包含未筛选的用户输入的字符串来传递,也会发生 sql 注入。 黑客通过 SQL 注入攻击可以拿到网站数据库的访问权限,之后他们就可以拿到网站数据库中所有的数据,恶意的黑客可以通过 SQL 注入功能篡改数据库中的数据甚至会把数据库中的数据毁坏掉。

2.SQL 注入产生的原因...

  sql 注入攻击是利用是指利用设计上的漏洞,在目标服务器上运行 Sql 语句以及进行其他方式的攻击,动态生成 Sql 语句时没有对用户输入的数据进行验证是 Sql 注入攻击得逞的主要原因。对于 java 数据库连接 JDBC 而言,SQL 注入攻击只对 Statement 有效,对 PreparedStatement 是无效的,这是因为 PreparedStatement 不允许在不同的插入时间改变查询的逻辑结构。

3.SQL 注入原理...

  SQL 注射能使攻击者绕过认证机制,完全控制远程服务器上的数据库。 SQL 是结构化 查询语言的简称,它是访问数据库的事实标准。目前,大多数 Web 应用都使用 SQL 数据库来存放应用程序的数据。几乎所有的 Web 应用在后台 都使用某种 SQL 数据库。跟大多数语言一样,SQL 语法允许数据库命令和用户数据混杂在一起的。如果开发人员不细心的话,用户数据就有可能被解释成命令, 这样的 话,远程用户就不仅能向 Web 应用输入数据,而且还可以在数据库上执行任意命令了。

 SQL 注入式攻击的主要形式有两种。一是直接将代码插入到与 SQL 命令串联 在一起并使得其以执行的用户输入变量。上面笔者举的例子就是采用了这种方法。由于其直接与 SQL 语句捆绑,故也被称为直接注入式攻击法。二是一种间接的攻 击方法,它将恶意代码注入要在表中存储或者作为原书据存储的字符串。在存储的字符串中会连接到一个动态的 SQL 命令中,以执行一些恶意的 SQL 代码。注入 过程的工作方式是提前终止文本字符串,然后追加一个新的命令。如以直接注入式攻击为例。就是在用户输入变量的时候,先用一个分号结束当前的语句。然后再插 入一个恶意 SQL 语句即可。由于插入的命令可能在执行前追加其他字符串,因此攻击者常常用注释标记“—”来终止注入的字符串。执行时,系统会认为此后语句 位注释,故后续的文本将被忽略,不背编译与执行。

4. 举例说明

  这里我只是使用简单的语句来实现 SQL 的注入,只是为了让大家简单的看一下注入后的效果... 都是一些简单的例子,一般黑客是不可能用我这种太简单方式来实现注入的,一般都是很复杂的东西... 由于自己也不打算过深的研究这个东西,所以就上一些简单的例子..

package JDBC_4_SQL_injection;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.sql.SQLException;
import java.sql.ResultSet;
public class injection_2 {
    static final String DB_URL="jdbc:mysql://localhost/java_mysql";
    static final String USER="root";
    static final String PAS="49681888";
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Connection cn =null;
        Statement pt=null;
        ResultSet rs=null;
        String sql="select id,name,age from test where name=''or 1 or''";//这里的 or 1 or 表示的是永真式... 无论后面有多少语句, 都会被忽略掉...
        try {
            Class.forName("com.mysql.jdbc.Driver");
            cn=DriverManager.getConnection(DB_URL,USER,PAS);
            pt=cn.createStatement();
            rs=pt.executeQuery(sql);
            //由于实现了 SQL 注入,因此我就能够获取, 数据库中所有的数据信息, 这样数据库里的数据就造成了数据泄露...
            while(rs.next())
            {System.out.println(rs.toString());
                System.out.println(rs.getObject(2));}
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();}finally{
            try {cn.close();
                pt.close();
                rs.close();} catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();}    
        }
}

}

  很简单的实现了注入...这里使用了 Statement 来保存我们的因为 sql 语句中把 1 认为是 true, 又因为是或的关系,所以将所有的数据查询出来了,这个就是 sql 注入,因为 Statement 会把传递进来的参数进行一下转化操作,用引号包含一下,所以会出现这个问题,那么我们该怎么解决呢?有的 同学说我们可以添加一句过滤的代码,将传递的参数取出单引号,这个方法是可行的的,但是这个只能解决那些使用单引号的数据库,可能有的数据库使用的是双引 号包含内容,那就不行了,所以应该想一个全套的方法,那么这里我们就是用一个叫做:PreparedStatement 类,这个类是 Statement 类 的子类.. 可以防止这种情况的发生...

package JDBC_4_SQL_injection;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.ResultSet;
public class injection_2 {
    static final String DB_URL="jdbc:mysql://localhost/java_mysql";
    static final String USER="root";
    static final String PAS="49681888";
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Connection cn =null;
        PreparedStatement pt=null;
        ResultSet rs=null;
        String sql="select id,name,age from test where name=?";
        try {
            Class.forName("com.mysql.jdbc.Driver");
            cn=DriverManager.getConnection(DB_URL,USER,PAS);
            pt=cn.prepareStatement(sql);
            pt.setString(1,"'or 1 or'");//在这里进行传递参数...
            rs=pt.executeQuery();
            while(rs.next())
            {System.out.println(rs.toString());
                System.out.println(rs.getObject(2));}//这里就不会有查询结果了...
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();}finally{
            try {cn.close();
                pt.close();
                rs.close();} catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();}    
        }
}

}

正如上文所描述的,SQL 漏洞危害非常的巨大,但我相信国内很多中小站点还普遍存在着这样的漏洞。因此建议:

    1、代码要对输入的参数做到充分的过滤,并尽可能得考虑极端情况
  2、错误信息尽可能的少,否则无关的人看不懂而有心的人就会提起兴趣
  3、不要以管理员的身份运行服务器进程
  4、某些情况下,net 命令对于攻击者而言就是“微软牌”的木马
  5、严格控制远程登录访问者的来源
  6、如果可能的情况下,不是很推荐使用 Windows 作为服务器操作系统

/*另一个注入例子...
 * 使用几个查询语句...
 * */
package JDBC_4_SQL_injection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.*;
public class injection_1 {
    static final String DB_URL="jdbc:mysql://localhost:3306/java_mysql?allowMultiQueries=true";//?allowMultiQueries=true 这句话的目的是允许使用多个 sql 语句进行对数据库的操作...
    static final String USER="root";
    static final String PAS="49681888";
    public static void main(String[] args) {
        // TODO Auto-generated method stub    
        String name="clearlove';delete from test;select * from test where name='clearlove";
    String sql</span>=<span style="color: rgba(0, 0, 0, 1)">createSQL(name);
    System.out.println(sql);
    Connection cn</span>=<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    Statement st</span>=<span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;    
    </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
        Class.forName(</span>"com.mysql.jdbc.Driver"<span style="color: rgba(0, 0, 0, 1)">);
        cn</span>=<span style="color: rgba(0, 0, 0, 1)">DriverManager.getConnection(DB_URL,USER,PAS);
        st</span>=cn.createStatement();<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">实现了注入...这样我们的数据库信息将全部删除掉...</span>

st.execute(sql);
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
try {
cn.close();
st.close();
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

    }
    
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> String createSQL(String name){
    String sql</span>="select id, name, age from test "<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(name!=<span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; name.length()!=0<span style="color: rgba(0, 0, 0, 1)">){
        sql</span>+= "where name='"+name+"'"<span style="color: rgba(0, 0, 0, 1)">;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sql;
}

}

上述代码完成了注入,直接将数据库里面的数据进行了删除...

/*
 * */
package JDBC_4_SQL_injection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.*;
public class injection_1 {
    static final String DB_URL="jdbc:mysql://localhost:3306/java_mysql?allowMultiQueries=true";
    static final String USER="root";
    static final String PAS="49681888";
    public static void main(String[] args) {
        // TODO Auto-generated method stub    
        String sql="select id,name,age from test where name=?";
        Connection cn=null;
        PreparedStatement st=null;    
        try {
            Class.forName("com.mysql.jdbc.Driver");
            cn=DriverManager.getConnection(DB_URL,USER,PAS);
            st=cn.prepareStatement(sql);
            st.setString(1, "clearlove';delete from test;select * from test where name='clearlove");} catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();}finally{
            try {cn.close();
                st.close();} catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();}
    }
    
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> String createSQL(String name){
    String sql</span>="select id, name, age from test "<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(name!=<span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; name.length()!=0<span style="color: rgba(0, 0, 0, 1)">){
        sql</span>+= "where name='"+name+"'"<span style="color: rgba(0, 0, 0, 1)">;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sql;
}

}

这里也使用了 PreparedStatement 来解决了这个问题的发生....

  虽然使用 PreparedStatement 可以避免 sql 的注入,但是并不意味着我们在任何时候都使用 PreparedStatement,有很多时候也必须要使用 Statement... 这个要具体情况具体分析...

小结

SQL 注入的手法相当灵活,在注入的时候会碰到很多意外的情况。能不能根据具体情况进行分析,构造巧妙的 SQL 语句,从而成功获取想要的数据,是高手与“菜鸟”的根本区别。