本节重点在于让大家熟悉各种SQL注入在JAVA当中的表现,本想带点ORM框架实例,但是与其几乎无意,最近在学习MongoDb,挺有意思的,后面有机会给大家补充相关。
0x00 JDBC和ORM
JDBC:
JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问。
JPA:
JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。是一个ORM规范。Hibernate是JPA的具体实现。但是Hibernate出现的时间早于JPA(因为Hibernate作者很狂,sun看不惯就叫他去制定JPA标准去了哈哈)。
ORM:
对象关系映射(ORM)目前有Hibernate、OpenJPA、TopLink、EclipseJPA等实现。
JDO:
JDO(Java Data Object )是Java对象持久化的新的规范,也是一个用于存取某种数据仓库中的对象的标准化API。没有听说过JDO没有关系,很多人应该知道PDO,ADO吧?概念一样。
关系:
JPA可以依靠JDBC对JDO进行对象持久化,而ORM只是JPA当中的一个规范,我们常见的Hibernate、Mybatis和TopLink什么的都是ORM的具体实现。
概念性的东西知道就行了,能记住最好。很多东西可能真的是会用,但是要是让你去定义或者去解释的时候发现会有些困难。
重点了解JDBC是个什么东西,知道Hibernate和Mybatis是ORM的具体的实现就够了。
Object:
在Java当中Object类(java.lang.object)是所有Java类的祖先。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。所以在认识Java之前应该有一个对象的概念。
关系型数据库和非关系型数据库:
数据库是按照数据结构来组织、存储和管理数据的仓库。
关系型数据库,是建立在关系模型基础上的数据库。关系模型就是指二维表格模型,因而一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织。当前主流的关系型数据库有Oracle、DB2、Microsoft SQL Server、Microsoft Access、MySQL等。
NoSQL,指的是非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
1、High performance - 对数据库高并发读写的需求。
2、Huge Storage - 对海量数据的高效率存储和访问的需求。
3、High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求。
常见的非关系型数据库:Membase、MongoDB、Hypertable、Apache Cassandra、CouchDB等。
常见的NoSQL数据库端口:
MongoDB:27017、28017、27080
CouchDB:5984
Hbase:9000
Cassandra:9160
Neo4j:7474
Riak:8098
在引入这么多的概念之后我们今天的故事也就要开始了,概念性的东西后面慢慢来。引入这些东西不只仅仅是为了讲一个SQL注入,后面很多地方可能都会用到。
传统的JDBC大于要经过这么些步骤完成一次查询操作,java和数据库的交互操作:
准备JDBC驱动
加载驱动
获取连接
预编译SQL
执行SQL
处理结果集
依次释放连接
sun只是在JDBC当中定义了具体的接口,而JDBC接口的具体的实现是由数据库提供厂商去写具体的实现的, 比如说Connection对象,不同的数据库的实现方式是不同的。
使用传统的JDBC的项目已经越来越少了,曾经的model1和model2已经被MVC给代替了。如果用传统的JDBC写项目你不得不去管理你的数据连接、事物等。而用ORM框架一般程序员只用关心执行SQL和处理结果集就行了。比如Spring的JdbcTemplate、Hibernate的HibernateTemplate提供了一套对dao操作的模版,对JDBC进行了轻量级封装。开发人员只需配置好数据源和事物一般仅需要提供一个SQL、处理SQL执行后的结果就行了,其他的事情都交给框架去完成了。
?
0x01 经典的JDBC的Sql注入
Sql注入产生的直接原因是拼凑SQL,绝大多数程序员在做开发的时候并不会去关注SQL最终是怎么去运行的,更不会去关注SQL执行的安全性。因为时间紧,任务重完成业务需求就行了,谁还有时间去管你什么SQL注入什么?还不如喝喝茶,看看妹子。正是有了这种懒惰的程序员SQL注入一直没有消失,而这当中不乏一些大型厂商。有的人可能心中有防御Sql注入意识,但是在面对复杂业务的时候可能还是存在侥幸心理,最近还是被神奇路人甲给脱裤了。为了处理未知的SQL注入攻击,一些大厂商开始采用SQL防注入甚至是使用某些厂商的WAF。
JDBCSqlInjectionTest.java类:
package org.javaweb.test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCSqlInjectionTest {
/**
* sql注入测试
* @param id
*/
public static void sqlInjectionTest(String id){
String MYSQLDRIVER = "com.mysql.jdbc.Driver";//MYSQL驱动
//Mysql连接字符串
String MYSQLURL = "jdbc:mysql://localhost:3306/wooyun?user=root&password=caonimei&useUnicode=true&characterEncoding=utf8&autoReconnect=true";
String sql = "SELECT * from corps where id = "+id;//查询语句
try {
Class.forName(MYSQLDRIVER);//加载MYSQL驱动
Connection conn = DriverManager.getConnection(MYSQLURL);//获取数据库连接
PreparedStatement pstt = conn.prepareStatement(sql);
ResultSet rs = pstt.executeQuery();
System.out.println("SQL:"+sql);//打印SQL
while(rs.next()){//结果遍历
System.out.println("ID:"+rs.getObject("id"));//ID
System.out.println("厂商:"+rs.getObject("corps_name"));//输出厂商名称
System.out.println("主站"+rs.getObject("corps_url"));//厂商URL
}
rs.close();//关闭查询结果集
pstt.close();//关闭PreparedStatement
conn.close();//关闭数据连接
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
sqlInjectionTest("2 and 1=2 union select version(),user(),database(),5 ");//查询id为2的厂商
}
}
现在有以下Mysql数据库结构(后面用到的数据库结构都是一样): ?
看下图代码是一个取数据和显示数据的过程。
第20行就是典型的拼SQL导致SQL注入,现在我们的注入将围绕着20行展开: ?
当传入正常的参数”2”时输出的结果正常:
?
当参数为2 and 1=1去查询时,由于1=1为true所以能够正常的返回查询结果:
?
当传入参数2 and 1=2时查询结果是不存在的,所以没有显示任何结果。
Tips:在某些场景下可能需要在参数末尾加注释符--,使用“--”的作用在于注释掉从当前代码末尾到SQL末尾的语句。
--在oracle和mssql都可用,mysql可以用# /**。
?
执行order by 4正常显示数据order by 5错误说明查询的字段数是4。 ?
Order by 5执行后直接爆了一个SQL异常: ?
用联合查询执行:2 and 1=2 union select version(),user(),database(),5 ?
小结论:
通过控制台执行SQL注入可知SQL注入跟平台无关、跟开发语言关系也不大,而是跟数据库有关。 知道了拼SQL肯定是会造成SQL注入的,那么我们应该怎样去修复上面的代码去防止SQL注入呢?其实只要把参数经过预编译就能够有效的防止SQL注入了,我们已经依旧提交SQL注入语句会发现之前能够成功注入出数据库版本、用户名、数据库名的语句现在无法带入数据库查询了:
0x02 PreparedStatement实现防注入
SQL语句被预编译并存储在PreparedStatement对象中。然后可以使用此对象多次高效地执行该语句。
Class.forName(MYSQLDRIVER);//加载MYSQL驱动
Connection conn = DriverManager.getConnection(MYSQLURL);//获取数据库连接
String sql = "SELECT * from corps where id = ? ";//查询语句
PreparedStatement pstt = conn.prepareStatement(sql);//获取预编译的PreparedStatement对象
pstt.setObject(1, id);//使用预编译SQL
ResultSet rs = pstt.executeQuery();
?
从Class.forName反射去加载MYSQL启动开始,到通过DriverManager去获取一个本地的连接数据库的对象。而拿到一个数据连接以后便是我们执行SQL与事物处理的过程。当我们去调用PreparedStatement的方法如:executeQuery或executeUpdate等都会通过mysql的JDBC实现对Mysql数据库做对应的操作。Java里面连接数据库的方式一般来说都是固定的格式,不同的只是实现方式。所以只要我们的项目中有加载对应数据库的jar包我们就能做相应的数据库连接。而在一个Web项目中如果/WEB-INF/lib下和对应容器的lib下只有mysql的数据库连接驱动包,那么就只能连接MYSQL了,这一点跟其他语言有点不一样,不过应该容易理解和接受,假如php.ini不开启对mysql、mssql、oracle等数据库的支持效果都一样。修复之前的SQL注入的方式显而易见了,用“?”号去占位,预编译SQL的时候会自动根据pstt里的参数去处理,从而避免SQL注入。
?
1
2
3
4
String sql = "SELECT * from corps where id = ? ";
pstt = conn.prepareStatement(sql);//获取预编译的PreparedStatement对象
pstt.setObject(1, id);//使用预编译SQL
ResultSet rs = pstt.executeQuery();
在通过conn.prepareStatement去获取一个PreparedStatement便会以预编译去处理查询SQL,而使用conn.createStatement得到的只是一个普通的Statement不会去预编译SQL语句,但Statement执行效率和速度都比prepareStatement要快前者是后者的父类。
从类加载到连接的关闭数据库厂商根据自己的数据库的特性实现了JDBC的接口。类加载完成之后才能够继续调用其他的方法去获取一个连接对象,然后才能过去执行SQL命令、返回查询结果集(ResultSet)。
Mysql的Driver:
public class Driver extends NonRegisteringDriver implements java.sql.Driver{}
在加载驱动处下断点(22行),可以跟踪到mysql的驱动连接数据库到获取连接的整个过程。
F5进入到Driver类: ?
驱动加载完成后我们会得到一个具体的连接的对象Connection,而这个Connection包含了大量的信息,我们的一切对数据库的操作都是依赖于这个Connection的:
conn.prepareStatement(sql);
在获取PreparedStatement对象的时进入会进入到Connection类的具体的实现类ConnectionImpl类。
然后调用其prepareStatement方法。 ?
而nativeSQL方法调用了EscapeProcessor类的静态方法escapeSQL进行转意,返回的自然是转意后的SQL。
预编译默认是在客户端的用com.mysql.jdbc.PreparedStatement本地SQL拼完SQL,最终mysql数据库收到的SQL是已经替换了“?”后的SQL,执行并返回我们查询的结果集。 从上而下大概明白了预编译做了个什么事情,并不是用了PreparedStatement这个对象就不存在SQL注入而是跟你在预编译前有没有拼凑SQL语句,
String sql = “select * from xxx where id = ”+id//这种必死无疑。
Web中绕过SQL防注入:
Java中的JSP里边有个特性直接request.getParameter("Parameter");去获取请求的数据是不分GET和POST的,而看过我第一期的同学应该还记得我们的Servlet一般都是两者合一的方式去处理的,而在SpringMVC里面如果不指定传入参数的方式默认是get和post都可以接受到。
SpringMvc如:
?
1
2
3
4
5
@RequestMapping(value="/index.aspx",method=RequestMethod.GET)
public String index(HttpServletRequest request,HttpServletResponse response){
System.out.println("------------");
return "index";
}
上面默认只接收GET请求,而大多数时候是很少有人去制定请求的方式的。说这么多其实就是为了告诉大家我们可以通过POST方式去绕过普通的SQL防注入检测!
Web当中最容易出现SQL注入的地方:
常见的文章显示、分类展示。
用户注册、用户登录处。
关键字搜索、文件下载处。
数据统计处(订单查询、上传下载统计等)经典的如select下拉框注入。
逻辑略复杂处(密码找回以及跟安全相关的)。
关于注入页面报错:
如果发现页面抛出异常,那么得从两个方面去看问题,传统的SQL注入在页面报错以后肯定没法直接从页面获取到数据信息。如果报错后SQL没有往下执行那么不管你提交什么SQL注入语句都是无效的,如果只是普通的错误可以根据错误信息进行参数修改之类继续SQL注入。 假设我们的id改为int类型:
int id = Integer.parseInt(request.getParameter("id")); ?
程序在接受参数后把一个字符串转换成int(整型)的时候发生异常,那么后面的代码是不会接着执行的哦,所以SQL注入也会失败。
Spring中如何安全的拼SQL(JDBC同理):
对于常见的SQL注入采用预编译就行了,但是很多时候条件较多或较为复杂的时候很多人都想偷懒拼SQL。
?写了个这样的多条件查询条件自动匹配:
public static String SQL_FORUM_CLASS_SETTING = "SELECT * from bjcyw_forum_forum where 1=1 ";
? public List
阅读(4144) | 评论(0) | 转发(0) |