JAVA安全(一)JWT安全及预编译CASE注入

JWT安全

关于jwt安全之前专门写过一篇jwt专题文章:

非常的详细 不在补充

JWT伪造的那些事-Drton1博客 (yanyang.ink)

预编译

聊到JAVA安全绕不开的就是java的sql预编译,JAVA中JDBC编程中数据库查询方式常常分为两种,第一种是Statement 这种方式就是完全把查询语句给写死,不会与用户进行交互,让用户控制参数,这种基本没有sql注入的问题,第二种就是PreparedStatement 这种方式就是预编译,这就是今天的主角,这两种方式的具体内容如下 :

  • Statement  该对象用于执行静态的 SQL 语句,并且返回执行结果。 此处的SQL语句必须是完整的,有明确的数据指示。查的是哪条记录?改的是哪条记录?都要指示清楚。     通过调用 Connection 对象的 createStatement 方法创建该对象 查询:ResultSet excuteQuery(String sql)——返回查询结果的封装对象ResultSet. 用next()遍历结果集,getXX()获取记录数据。修改、删除、增加:int excuteUpdate(String sql)——返回影响的数据表记录数.
  • PreparedStatement  SQL 语句被预编译并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。     可以通过调用 Connection 对象的 preparedStatement() 方法获取 PreparedStatement 对象     PreparedStatement 对象所执行的 SQL 语句中,参数用问号(?)来表示,调用 PreparedStatement 对象的 setXXX() 方法来设置这些参数. setXXX() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值,注意用setXXX方式设置时,需要与数据库中的字段类型对应,例如mysql中字段为varchar,就需要使用setString方法,如果为Date类型,就需要使用setDate方法来设置具体sql的参数。简单来说就是,预编译的SQL语句不是有具体数值的语句,而是用(?)来代替具体数据,然后在执行的时候再调用setXX()方法把具体的数据传入。同时,这个语句只在第一次执行的时候编译一次,然后保存在缓存中。之后执行时,只需从缓存中抽取编译过了的代码以及新传进来的具体数据,即可获得完整的sql命令。这样一来就省下了后面每次执行时语句的编译时间。
例如:
select * from test where id= ’ + x + ’
x= 1’;delete from 'test
这样进入数据库就变成了
select * from test where id= ’ 1 ';
delete from 'test’
会被数据库解析成两条sql
但是预编译会让数据库跳过编译阶段,也就无法就进行词法分析,关键字不会被拆开,
所有参数 直接 变成字符串 进入 数据库执行器执行。
可能数据库执行的sql是这样(推测)
select * from test where id= ’ 1 delete from test ’
(没有词法分析 所有关键字都成为了字符串的一部分)
也就失去了sql注入的能力

预编译作用

主要有两个作用: 预编译阶段可以优化 sql 的执行 预编译之后的 sql 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的sql,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。可以提升性能。 防止SQL注入 使用预编译,而其后注入的参数将不会再进行SQL编译。也就是说其后注入进来的参数,系统将不会认为它会是一条SQL语句,而默认其是一个参数,参数中的or或者and 等就不是SQL语法保留字了。

预编译的局限

like语句 查询过程中的like语句,比如查询名字中带有”D”的用户,可能会采用如下语句查询

select * from user where name like '%D%';

此时如果注入语句为%,执行语句会变为

select * from user where name like '%%%'

这种情况可以通过预编译查询,能返回user的所有内容

order by注入突破预编译防御

原理:

SQL中:ORDER BY执行排序后面需要指定列名,该列名是不能被引号包含的否则就会被认为是一个字符串

图片[1]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

而PrepareStatment使用占位符传递参数时,会用单引号包裹参数,因此不能使用预编译

如下:

图片[2]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

所以使用ORDER BY要搭配字符串拼接上列名

图片[3]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

而字符串拼接就可能会造成SQL注入

图片[4]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

所以本质还是拼接导致的sql注入,产生的原因就是order by后的参数不能通过预编译出来 需要进行拼接。

WebGoat 预编译order by注入靶场

打开靶场: 我们的目标是寻找主机名为:webgoat-prd 的ip地址。 并且知道 bcd段 我们只用找出a段就可以了。

图片[5]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

发现可以通过主机名,知识产权,地位等对数据进行排序,于是进行抓包:

图片[6]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

点击hostname排序 发现hostname变成colume参数

这里猜测 column就是拼接在order by 后的参数 所以此处可能存在注入点

我们把culumn随便放入一个不存在的数据:

图片[7]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

发现返回值 中存在那几个列名,证实我们的想法 确实是直接拼接order by 后面的 我们发现aa被拼接到order by 后

破题关键:

select * from users order by (case when (true) then lastname else firstname)

case then 就是 if else 其实

图片[8]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

我们现在能控制order by 后的字段 根据靶场提示我们知道我们需要找的webgoat-prd的a段ip

那么 hostname=”webgoat-prd” 肯定是存在的

那么我们构造语句:

column=(case when (substring((select ip from servers where hostname=’webgoat-prd’),1,1)=1) then hostname else id end)

如果 a段 第一位是1 就 按 hostname 排序 反之 则不 我们就可以根据排序的结果来进行判断了。

我们先看看按照hostname排序的结果是:

图片[9]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

然后替换column:

(case%20when%20((select%20substring(ip,1,1)%20from%20servers%20where%20hostname=%27webgoat-prd%27)=1)%20then%20hostname%20else%20id%20end) 判断第一位是不是1:

结果:

图片[10]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

可以看到结果是按照hostname进行排序的 所以a段第一位就是1

我们再判断第一位是不是2 :

(case%20when%20((select%20substring(ip,1,1)%20from%20servers%20where%20hostname=%27webgoat-prd%27)=2)%20then%20hostname%20else%20id%20end)

图片[11]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

可以看到这是正常排序 所以第一位不是2

我们同理去判断第二位第三位

最终得到a段为:104

图片[12]-JAVA安全(一)JWT安全及预编译CASE注入-Drton1博客

防御措施

关键字符串过滤

限制拼接内容

使用列号排序

非得用PrepareStatment预编译就用列号进行排序

而列号就是数字 就不用考虑引号问题了 就没有上述问题

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论