Java代码审计-sql注入篇

admin 2023年3月9日17:47:25评论97 views字数 6802阅读22分40秒阅读模式
PART 01

SQL注入基础

一、JDBC拼接不当造成SQL注入:

1、执行SQL注入的两种方法:

    PrepareStatement和Statement。
    区别:
    PrepareStatement会对SQL语句进行预编译。
    Statement方法在每次执行时都需要编译,会增大系统开销。

a、Statement:

Java代码审计-sql注入篇
    该方法使用了拼接的方式,将用户的输入id带入SQL语句中,通过创建的Statement对象进行执行。
    但是由于是用的拼接的方式,因此输入1 or 2 = 3就会拼接执行语句,产生SQL注入漏洞。

b、PrepareStatement:

    支持预编译也就是在预编译阶段通过作为占位符,先执行了一遍SQL语句,参数用    占位符代替,目的是为了检查语法是否正确。后续用户输入的字符将被用来代替占位符,但是所有的变量都将被视为字符串,不会拼接关键字或是恶意语法等进入注入语句。

错误的:

Java代码审计-sql注入篇
    虽说PrepareStatement支持预编译,但是上述代码使用了PrepareStatement却未使用预编译的形式,采用了同Statement的拼接形式,因此输入1 or 2 = 3同样会拼接执行语句,产生注入漏洞。

正确的:

Java代码审计-sql注入篇
    正确的PrepareStatement方法,使用占位,可避免SQL注入产生。

二、框架使用不当造成SQL注入:

    在实际的场景下,由于JDBC方式是将SQL语句写在代码块中,不利于后续维护,因此现在的Java项目或多或少会使用对JDBC进行更抽象封装的持久化框架,如MyBatis和Hibernate。通常,框架底层已经实现了对SQL注入的防御,但是如配置不当,同样会存在问题。

1、MyBatis框架:

    MyBatis框架的思想是将SQL语句编入配置文件中,避免SQL语句在Java程序中大量出现,方便后续对SQL语句的修改与配置。
    MyBatis中使用parameterType向SQL语句传参,在SQL引用传参可以使用#{Parameter}和${Parameter}两种方式:

#{Parameter}:

Java代码审计-sql注入篇
    #{Parameter}使用了预编译的格式,因此不存在注入漏洞。

${Parameter}:

Java代码审计-sql注入篇
    ${Parameter}使用的是拼接的格式,因此会存在注入漏洞。

2、Hibernate框架:

    Hibernate框架是Java持久化API(JPA)规范的一种实现方式。Hibernate 将 Java 类映射到数据库表中,从 Java 数据类型映射到 SQL 数据类型。Hibernate是目前主流的Java数据库持久化框架,采用Hibernate查询语言(HQL)注入。
    HQL的语法与SQL类似,但有些许不同。受语法的影响,HQL注入在实际漏洞利用上具有一定的限制。Hibernate是对持久化类的对象进行操作而不是直接对数据库进行操作,因此HQL查询语句由Hibernate引擎进行解析,这意味着产生的错误信息可能来自数据库,也可能来自Hibernate引擎。

HQL:

Java代码审计-sql注入篇
Java代码审计-sql注入篇
Java代码审计-sql注入篇

参数绑定:

    可以使用HQL参数绑定的方式:
位置参数(Positional parameter):
Java代码审计-sql注入篇
命名参数(named parameter):
Java代码审计-sql注入篇
命名参数列表(named parameter list):
Java代码审计-sql注入篇
类实例(JavaBean):
Java代码审计-sql注入篇

执行原生SQL:

    Hibernate支持原生的SQL语句执行,与JDBC的SQL注入相同:
拼接:
Java代码审计-sql注入篇
参数:
Java代码审计-sql注入篇

三、防御不当造成SQL注入:

    SQL注入最主要的成因在于未对用户输入进行严格的过滤,并采取不恰当的方式构造SQL语句。在实际开发的过程中,有些地方难免需要使用拼接构造SQL语句,例如SQL语句中order by后面的参数无法使用预编译赋值。此时应严格检验用户输入的参数类型、参数格式等是否符合程序预期要求。
PART 02

靶场实操


    介绍完基础部分的内容,下面再通过靶场(java-sec-code)的实操来加深对与sql注入代码审计基础的理解。

实例1:jdbc

@RequestMapping("/jdbc/vuln")public String jdbc_sqli_vul(@RequestParam("username") String username) {
StringBuilder result = new StringBuilder();
try { Class.forName(driver); Connection con = DriverManager.getConnection(url, user, password);
if (!con.isClosed()) System.out.println("Connect to database successfully.");
// sqli vuln code Statement statement = con.createStatement(); String sql = "select * from users where username = '" + username + "'"; logger.info(sql); ResultSet rs = statement.executeQuery(sql);
while (rs.next()) { String res_name = rs.getString("username"); String res_pwd = rs.getString("password"); String info = String.format("%s: %sn", res_name, res_pwd); result.append(info); logger.info(info); } rs.close(); con.close();

} catch (ClassNotFoundException e) { logger.error("Sorry,can`t find the Driver!"); } catch (SQLException e) { logger.error(e.toString()); } return result.toString();}
    一看是jdbc的,直接想到之前学的Statement:
Java代码审计-sql注入篇
    其实对于这种简单好定位的代码,直接就能看出来采用的拼接的格式,直接拼接就能注入:
Java代码审计-sql注入篇

实例2:jdbc

@RequestMapping("/jdbc/sec")public String jdbc_sqli_sec(@RequestParam("username") String username) {
StringBuilder result = new StringBuilder(); try { Class.forName(driver); Connection con = DriverManager.getConnection(url, user, password);
if (!con.isClosed()) System.out.println("Connecting to Database successfully.");
// fix code String sql = "select * from users where username = ?"; PreparedStatement st = con.prepareStatement(sql); st.setString(1, username);
logger.info(st.toString()); // sql after prepare statement ResultSet rs = st.executeQuery();
while (rs.next()) { String res_name = rs.getString("username"); String res_pwd = rs.getString("password"); String info = String.format("%s: %sn", res_name, res_pwd); result.append(info); logger.info(info); }
rs.close(); con.close();
} catch (ClassNotFoundException e) { logger.error("Sorry, can`t find the Driver!"); e.printStackTrace(); } catch (SQLException e) { logger.error(e.toString()); } return result.toString();}
    同样的jdbc直接看:
Java代码审计-sql注入篇
    用的preparedstatemen接口,参数化查询,直接G。
    并且查看系统的日志信息,preparedstatemen会将输入参数当成一个字符串,能产生拼接效果的特殊字符会被转译:
Java代码审计-sql注入篇
Java代码审计-sql注入篇

实例3:mybatis

@GetMapping("/mybatis/vuln01")public List<User> mybatisVuln01(@RequestParam("username") String username) {return userMapper.findByUserNameVuln01(username);}

Java代码审计-sql注入篇
    关键方法,往下跟:
Java代码审计-sql注入篇
    用的${},根据之前的知识可知,$采用的是拼接的格式,那就直接拿下:
Java代码审计-sql注入篇

实例4:mybatis

@GetMapping("/mybatis/vuln02")public List<User> mybatisVuln02(@RequestParam("username") String username) {return userMapper.findByUserNameVuln02(username);}

Java代码审计-sql注入篇
    跟踪关键方法,发现并没有写数据库语句。实例3使用的是@Select注解的形式去引用数据库语句,此处则是利用的是在mapper.xml中写语句的形式(mybatis就是通过xml或是注解的形式来进行数据库交互。mybatis此特性与前文servlet、filter所述相似)
Java代码审计-sql注入篇
Java代码审计-sql注入篇
    同样,用的${},存在注入x0;:
Java代码审计-sql注入篇

实例5:orderby

public List<User> mybatisVuln03(@RequestParam("sort") String sort) {return userMapper.findByUserNameVuln03(sort);}
    同上,在xml中找到数据库语句:
Java代码审计-sql注入篇
    orderby无法使用使用预编译,只能使用拼接的形式,直接注入:
Java代码审计-sql注入篇
Java代码审计-sql注入篇

ps:不能用参数化,那要如何修复注入?

    1、直接写死orderby,就不要把orderby参数拿给用户控制。
    2、假预编译:
    如果有一个要排序的表:
Java代码审计-sql注入篇
    修复的方式就是在将列名排序顺序对应为数字,如:
id—— 1 name—— 2 money—— 3 正序—— 1 倒序—— 2
    用户如果需要给id列倒序排序,那传递给后端的参数为【a=1,b=2】,后端就将数字对应的列名排序顺序拿来排序。
    这种方式如果攻击者构造恶意数据的话,后端认不到,就不会被带入数据库语句。

实例6:mybatis安全1

public User mybatisSec01(@RequestParam("username") String username) {return userMapper.findByUserName(username);}
    关键方法:
Java代码审计-sql注入篇
    使用的#{},结合前文的知识,使用的预编译的格式,安全。

实例7:mybatis安全2

public User mybatisSec02(@RequestParam("id") Integer id) {return userMapper.findById(id);}
    同理,找到数据库语句:
Java代码审计-sql注入篇
    用的#,安全。

实例8:orderby安全1

public User mybatisSec03() {return userMapper.OrderByUsername();}
    同理,找到数据库语句:
Java代码审计-sql注入篇
    可以看到此处用的是上文说的修复方案1,直接把排序参数写死,不给用户控制参数的机会。

实例9:orderby安全2

public List<User> mybatisOrderBySec04(@RequestParam("sort") String sort) {return userMapper.findByUserNameVuln03(SecurityUtil.sqlFilter(sort));}

    可以看到,此处涉及到的数据库查询方法同上述实例5相同,但此处的变化就是对用户输入的参数加了一层SecurityUtil.sqlFilter的过滤。
    那就看看过滤是啥样的:
Java代码审计-sql注入篇
    一看是把传入的参数拿去用FILTER_PATTERN这个规则去筛选,不通过的就会返回为null。
    那就再去看看过滤的规则:
Java代码审计-sql注入篇
    正则,仅允许一次或多次匹配字母数字跟部分符号。
    输入参数,尝试发现只有正常参数能被传入数据库语句执行,带有特殊字符的参数会被直接过滤掉:
Java代码审计-sql注入篇
PART 03

项目实操

MCMS

    在靶场中的实操只能加深我们对基础知识的掌握,还是得通过实际项目中的操作来看看在实战中要如何去找到一个注入漏洞。
    在git上找了铭飞CMS,通过复现他的历史注入漏洞,来学习下如何在一个项目中去挖掘sql注入漏洞。

https://gitee.com/mingSoft/MCMS/issues/I5X1U2

    这位师傅上来直接提出sqlwhere存在问题,但是直接在项目中去搜,你是搜不到这个${}。
Java代码审计-sql注入篇
    于是疑惑是如何找到。
    在问题参数处,执行查看debug信息:
Java代码审计-sql注入篇
    在debug信息中看到文件名,去IContenDao中查找sqlwhere:
Java代码审计-sql注入篇
    sqlwhere是定义在IBaseDao中,找到sqlWhere定义的位置,可以看到field是用的${},因此此处存在sql注入漏洞。
Java代码审计-sql注入篇
    但是这是我们通过其他师傅的文章知道sqlWhere参数有问题,才会有后面这些排查过程。那如果是我们单独审计,要如何知道sqlWhere存在问题呢?

1.黑盒发现sqlWhere再白盒审:

    黑+白这是我认为最有可能发现此注入的方式,因为MCMS属于一个开源项目,可能是为了便于下载,他项目中的很多文件是以依赖的形式调用的,想要通过搜索关键词的方式去找到注入点并没有那么容易。通过黑盒发现问题参数,再通过白盒定位问题参数,最后举一反三发现系统其他注入点是我觉得应对此类系统的突破口。
    使用工具确实也能检测到field参数的问题:
Java代码审计-sql注入篇

2.经验:

    找到该参数的师傅根据经验找到了问题参数。例如通过这个例子,以后在项目中搜索就可以不单搜问题函数,可以搜索<include,根据include引入的id去找到定义的数据库语句。尤其是项目还使用的mybatis框架。
Java代码审计-sql注入篇

后续:

    后续又想通过路由直接跟进找到dao层,因为通过debug其实有点属于作弊
    找到问题参数sqlWhere的路由,从路由入手找注入点:
Java代码审计-sql注入篇
    找到定义的地方是在IContentBiz这个接口里x0;:
Java代码审计-sql注入篇
    跟进到接口中,发现没有query,那么就可能是存在于它继承的接口IBaseBizx0;中,往下跟:
Java代码审计-sql注入篇
    跟进去之后找到query,证明调用的这里的query方法:
Java代码审计-sql注入篇
    接口仅会定义该方法,需要查看该方法的实现:
Java代码审计-sql注入篇
    往下去找getDao在哪里定义:
Java代码审计-sql注入篇
    看到了IBaseDao,到dao层了!跟下去:
Java代码审计-sql注入篇
    进入到Dao层了,直接看xml的数据库语句:
Java代码审计-sql注入篇
    就跟前文最开始的方法定位相同了。

小结:

    MCMS其实还有很多历史的sql注入漏洞,也可能还有很多未被发现的sql注入漏洞。这次就不在此进行过多赘述了,各位师傅都比我厉害的多,说不定私下早就索穿了p.p。
PART 04

总结

    讲了这么多其实也就是一点皮毛,审计之路前路漫漫,sql注入的基础知识其实就这么多,剩下的就是去审计挖掘漏洞。通过上述的介绍也能发现,在靶场环境中想要去查找路由跟数据库语句也就是dao层的关系很简单,但是当在具体的项目中,想要找到路由跟dao层的关系就没有那么容易,要一层层跟进分析类与类、类与接口之间的继承实现关系。但这漫漫之路也不是没有尽头,随着遇到的项目越来越多、代码阅读能力越来越强,见多识广之后势必会让审计之路越发的顺利。
监制:船长、铁子   策划:AST   文案:YYGZ   美工:青柠

原文始发于微信公众号(千寻安服):Java代码审计-sql注入篇

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月9日17:47:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java代码审计-sql注入篇https://cn-sec.com/archives/1594062.html

发表评论

匿名网友 填写信息