sql注入原理:业务端代码从客户端接收到恶意payload之后没有进行过滤直接进行sql语句拼接并且执行造成sql注入
本人正在拜读一本代码审计的书感觉非常的棒,刚刚好室友在挑战自己,就顺便整理一下知识点!
我们看下面这段代码,首先从客户端接收传进来的id的值拼接成sql语句,然后Statement去编译拼接的sql语句,将结果传给rs之后读出,这里没有对传进来的值进行任何过滤,尝试去构造sql语句造成注入
String sql = "select * from user where id ="+req.getParameter("id"); PrintWriter out = resp.getWriter(); out.println("Statement Demo"); out.println("SQL: "+sql); try { Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql); while (rs.next()){ out.println("<br>Result: "+ rs.getObject("name")); } } catch (SQLException throwables) { throwables.printStackTrace(); }
正常访问
构造测试payload进行测试,可以看到这边是执行了构造过的payload,返回了开发者不想让我们看到的内容
上面第一种存在sql注入的情况是因为每次执行都会将sql语句进行编译在数据库中执行,为了防止sql注入,可以使用prepareStatement进行预编译sql语句,使用?占位符来传可改变的值,但是因为sql语句已经编译过,所以按道理来说这里传进来的值只会被当作字符串数据处理不作为sql语句的一部分,传进来的值不参与编译也就是不会在sql里执行,但是开发者也可能出错就是在使用prepareStatement时仍然使用sql拼接而不是用占位符或者在预编译之后再次执行sql语句!
我们首先看一下不存在sql注入的代码,使用问号占位符,预编译sql语句,从下面第二张图可以看到这时候sql语句是一个问号而我们传进去的值不在数据库中运行并且没有返回结果的!可以比较好的防止sql注入,这时候我们来讲讲为什么预编译可以防止sql注入,当我们sql执行的时候大致会经历几个阶段分别是编译--优化--缓存--执行,当使用prepareStatement时,他是将上述的步骤已经执行过了,将结果放到了缓存当中,用户的输入只作为数据进行填充而不是sql的一部分,然后服务器从缓存中获得已经编译之后的语句,替换掉用户输入的数据执行以达到防止sql注入的目的!
String sql = "select * from user where id = ?"; PrintWriter out = resp.getWriter(); out.println("prepareStatement Demo"); out.println("SQL: "+sql); try { PreparedStatement pst = conn.prepareStatement(sql); pst.setString(1,req.getParameter("id")); ResultSet rs = pst.executeQuery(); while (rs.next()){ out.println("<br>Result: "+ rs.getObject("id")); } image-20211107193812360
看了安全代码,我们看以下预编译依旧存在问题的代码
虽然使用预编译但是sql语句依旧是拼接的!就会造成sql注入,看第一行,开发者忘记使用占位符导致sql语句依旧是拼接进去的
String sql = "select * from user where id ="+req.getParameter("id"); PrintWriter out = resp.getWriter(); out.println("prepareStatement Demo"); out.println("SQL: "+sql); try { PreparedStatement pst = conn.prepareStatement(sql); ResultSet rs = pst.executeQuery(); while (rs.next()){ out.println("<br>Result: "+ rs.getObject("id")); } } catch (SQLException throwables) { throwables.printStackTrace(); }
使用mybatis的好处是将sql整合到一个地方避免代码中出现大量的sql语句并且其接近原生sql。比较灵活,但是当xml里的sql语句是用$做占位符时,sql语句依旧是拼接而成的,这时候便会存在sql注入
select * from user1 where name = ${name}
下图可以看到传进来的值已经被执行了
搭好室友的毕业设计之后,简单看了一下,这是一个商城后台管理系统,SSM框架的,然后找了一下入口点,发现只有登录界面是开放的入口。其他的地方权限都做了token验证,然后看他登录是怎么写的
@RequestMapping("/login") public String login(User user) { if (userService.login(user)==1) { return "head"; } else { return "login"; } }
看看上面写的,就很简单粗暴,然后就跟进去看server层里看看写了什么判断逻辑没有。
@Override public int login(User user) { return userMapper.login(user); }
看了一下也没问题,继续往下走,发现室友mybatis里的sql全部是使用$拼接的!这不就成了么
<!-- 登陆验证--> <select id="login" parameterType="user" resultType="java.lang.Integer"> select count(*) from user where user_name = '${userName}' and password = '${password}' </select>
根据一开始controller里写的,是判断返回值等于1,然后就可以登录后台,这时候就可以构造sql让返回值等于1!
然后他所有的sql都是使用$,存在大量的sql注入,以此说明了读书还是要认真!不要老听老头过时的技术!自己要有思考!
推荐实操:SQL注入进阶
PC端练习地址:http://mrw.so/6eK95U
在掌握了基本的注入手段后。我们尝试绕过各种针对SQL注入的防护,并继续注入。
本文作者:合天网安实验室
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/170444.html