写在前面
市面上关于java代码审计的课程和教程比较少,所以我们打算开设一个新的专栏,在这个专栏里面免费和大家分享一些从0到1的关于java代码审计相关的知识
随着国内网络空间安全技术的发展,最初的一些只会使用黑客工具的“脚本小子”逐渐被能够进行代码分析、编写脚本的专业安全人员所代替。可以说,一个优秀的安全人员,必须要懂编程。但是懂编程仅仅是第一步,懂编程的人不胜其数,但将编程能力演变成代码审计能力,并且利用代码审计能力去挖掘漏洞的人却少之又少
1.注入漏洞简介
注入漏洞,是指攻击者可以通过HTTP请求将payload注入某种代码中,导致payload被当作代码执行的漏洞。例如SQL注入漏洞,攻击者将SQL注入payload插入SQL语句中,并且被SQL引擎解析成SQL代码,影响原SQL语句的逻辑,形成注入。同样,文件包含漏洞、命令执行漏洞、代码执行漏洞的原理也类似,也可以看作代码注入漏洞。
2.SQL注入漏洞简介
SQL注入(SQL Injection)是因为程序未能正确对用户的输入进行检查,将用户的输入以拼接的方式带入SQL语句中,导致了SQL注入的产生。黑客通过SQL注入可直接窃取数据库信息,造成信息泄露。
3.JDBC拼接不当造成SQL注入
JDBC有两种方法执行SQL语句,分别为PrepareStatement
和Statement
。两个方法的区别在于PrepareStatement
会对SQL语句进行预编译,而Statement
方法在每次执行时都需要编译,会增大系统开销。理论上PrepareStatement
的效率和安全性会比Statement
要好,但并不意味着使用PrepareStatement
就绝对安全,不会产生SQL注入。
第一关:使用Statement
小渣是一名菜鸡程序员,这天他收到了一封匿名黑客邮件,黑客声称已经获取了小渣数据库中所有用户的详细信息,并要求小渣对此负责,于是小渣委托同事大牛上机排查,锁定了下面这几行代码:
String sql = "select * from user where id = " + req.getParameter("id");
out.println(sql);
try {
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(sql);
while (rs.next()) {
out.println("<br> id: " + rs.getObject("id"));
out.println("<br> name: " + rs.getObject("name"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
大牛🐂:该代码使用拼接的方式将用户输入的参数“id”带入SQL语句中,存在明显的sql注入,例如:经过拼接构造后,最终在数据库执行的语句可能为“select * from user where id = 1 or 1=2
”,改变了程序想要查询“id=1
”的语义
大牛🐂:还有,真正的程序员从来不用select *
小渣:咦?为什么呢?
大牛🐂:自己去悟!
第二关:使用PrepareStatement
程序员小渣知道了Statement方法的危害,于是他决定在之后的Coding中都采用PrepareStatement方法
小渣:熬了一通宵,项目中所有的Statement都改成PrepareStatement啦!😊
String sql = "select * from user where id = " + req.getParameter("id");
out.println(sql);
try {
PreparedStatement pstt = con.prepareStatement(sql);
ResultSet rs = pstt.executeQuery();
while (rs.next()) {
out.println("<br> id: " + rs.getObject("id"));
out.println("<br> name: " + rs.getObject("name"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
大牛🐂:。。。你这个还是采取拼接的方式构造SQL语句,此时进行预编译则无法阻止SQL注入的产生,没什么卵用
小渣:啊。。。那我要怎么写才能保证不出现sql注入呢?😟
大牛🐂:可以考虑这样写,PrepareStatement方法支持使用‘?
’对变量位进行占位,在预编译阶段填入相应的值构造出完整的SQL语句,此时可以避免SQL注入的产生
PrintWriter out = resp.getWriter();
String sql = "select * from user where id = ?";
out.println(sql);
try {
PreparedStatement pstt = con.prepareStatement(sql);
pstt.setInt(1, Integer.parseInt(req.getParameter("id")));
ResultSet rs = pstt.executeQuery();
while (rs.next()){
out.println("<br> id: "+rs.getObject("id"));
out.println("<br> name: "+rs.getObject("name"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
小渣:哎。。又要熬夜了┭┮﹏┭┮
4.框架使用不当造成SQL注入
在实际的代码开发工作中,JDBC方式是将SQL语句写在代码块中,不利于后续维护。如今的Java项目或多或少会使用对JDBC进行更抽象封装的持久化框架,如MyBatis和Hibernate。通常,框架底层已经实现了对SQL注入的防御,但在研发人员未能恰当使用框架的情况下,仍然可能存在SQL注入的风险。
第三关:使用MyBatis框架
小渣最近学了MyBatis框架,于是赶紧跑来和大牛炫耀
小渣:我用了MyBatis,将SQL语句编入配置文件中,避免SQL语句在Java程序中大量出现,方便后续对SQL语句的修改与配置。正确使用MyBatis框架可以有效地阻止SQL注入的产生O(∩_∩)O
大牛🐂:那你是怎么写的呢?(略带嘲讽的语气)
小渣:MyBatis中使用parameterType向SQL语句传参,在SQL引用传参可以使用#{Parameter}
和${Parameter}
两种方式,我个人比较喜欢${Parameter}
这种方式,因为它前面有美元🐱🐉
<select id="getUsername" resultType="com.z1ng.bean.User">
select id,name,age from user where name = #{name}
</select>
大牛🐂:你测试过你的代码了吗。。。
小渣:当然,不做单元测试的程序员不是好程序员!当输入的“name”值为“genius”时,成功查询到结果,Debug的回显如下
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc. onnectionImpl@6d3af739]
==> Preparing: select id,name,age from user where name = 'genius'
==> Parameters:
<== Columns: id, name, age
<== Row: 1, z1ng, 18
<== Total: 1
User{id=1, name='genius', age=18}
大牛🐂:你再传一个'aaaa' or 1=1
试试
小渣:貌似还是把用户爆出来了。。😳
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc. onnectionImpl@6d3af739]
==> Preparing: select id,name,age from user where name = 'aaaa' or 1=1
==> Parameters:
<== Columns: id, name, age
<== Row: 1, genius, 18
<== Total: 1
User{id=1, name='genius', age=18}
大牛🐂:在底层构造完整SQL语句时,MyBatis的两种传参方式所采取的方式不同。#{Parameter}
采用预编译的方式构造SQL,避免了SQL注入的产生。而${Parameter}
采用拼接的方式构造SQL,在对用户输入过滤不严格的前提下,此处很可能存在SQL注入
小渣:那我还是乖乖用#
吧。。
第四关:Hibernate框架
小渣最近的项目组使用到了Hibernate框架,于是他赶紧跑来问问大牛
大牛🐂:Hibernate框架是Java持久化API(JPA)规范的一种实现方式。Hibernate 将Java 类映射到数据库表中,从 Java 数据类型映射到 SQL 数据类型。Hibernate是目前主流的Java数据库持久化框架,采用Hibernate查询语言(HQL)注入
Hibernate是对持久化类的对象进行操作而不是直接对数据库进行操作,因此HQL查询语句由Hibernate引擎进行解析,这意味着产生的错误信息可能来自数据库,也可能来自Hibernate引擎
小渣:懂了,我这就回去写代码(信心满满)
两天后。。。
小渣:哎。写的代码又有sql注入了/(ㄒoㄒ)/~~
factory = new Configuration().configure().buildSessionFactory();
Session session = factory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
String parameter = " zaaaa' or '1'='1 ";
List user = session.createQuery("FROM User where name='"+ parameter+"'",User.class).getResultList();
for (Iterator iterator =
user.iterator(); iterator.hasNext();){
User user1 = (User) iterator.next();
System.out.println(user1.toString());
}
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
大牛🐂:别灰心,我来教你几招
正确使用以下几种HQL参数绑定的方式可以有效避免注入的产生
位置参数:
String parameter = "z1ng";
Query<User> query = session.createQuery("from com.z1ng.bean.Userwhere name = ?1", User.class);
query.setParameter(1, parameter);
命名参数:
Query<User> query = session.createQuery("from com.z1ng.bean.Userwhere name = ?1", User.class);String parameter = "z1ng";
Query<User> query = session.createQuery("from com.z1ng.bean.Userwhere name = :name", User.class);
query.setParameter("name", parameter);
命名参数列表:
List<String> names = Arrays.asList("z1ng", "z2ng");
Query<User> query = session.createQuery("from com.z1ng.bean.User where name in (:names)", User.class);
query.setParameter("names", names);
类实例:
user1.setName("z1ng");
Query<User> query = session.createQuery("from com.z1ng.bean.User where name =:name", User.class);
query.setProperties(user1);
Hibernate还支持原生的SQL语句执行,与JDBC的SQL注入相同,直接拼接构造SQL语句会导致安全隐患的产生,应采用参数绑定的方式构造SQL语句
拼接构造如下:
Query<User> query = session.createNativeQuery("select * from user where name = '"+parameter+"'");
参数绑定如下:
Query<User> query = session.createNativeQuery("select * from user where name = :name");
query.setParameter("name",parameter);
小渣:不愧是大牛👍
结语
在实际开发的过程中,有些地方难免需要使用拼接构造SQL语句,例如SQL语句中order by后面的参数无法使用预编译赋值。此时应严格检验用户输入的参数类型、参数格式等是否符合程序预期要求。
黑夜给了我黑色的眼睛,我却用它寻找漏洞👀
昆仑云安全实验室系列文章仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担!
昆仑云安全实验室拥有对此文章的修改、删除和解释权限,如转载或传播此文章,需保证文章的完整性,未经允许,禁止转载!
原文始发于微信公众号(昆仑云安全):浅析java代码审计中的sql注入漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论