通過某大學生的的畢業設計複習java-sql審計

sql注入原理:業務端代碼從客戶端接收到惡意payload之後沒有進行過濾直接進行sql語句拼接並且執行造成sql注入

本人正在拜讀一本代碼審計的書感覺非常的棒,剛剛好室友在挑戰自己,就順便整理一下知識點!

1.原生jdbc連接無過濾造成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,返回了開發者不想讓我們看到的內容

2.原生jdbc預編譯開發失誤導致sql注入

上面第一種存在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();
      }

3.分析mybatis框架類sql注入的情況

使用mybatis的好處是將sql整合到一個地方避免代碼中出現大量的sql語句並且其接近原生sql。比較靈活,但是當xml裏的sql語句是用$做佔位符時,sql語句依舊是拼接而成的,這時候便會存在sql注入

select * from user1 where name = ${name}

下圖可以看到傳進來的值已經被執行了

4.審計室友的畢業設計

搭好室友的畢業設計之後,簡單看了一下,這是一個商城後臺管理系統,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注入的防護,並繼續注入。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章