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,返回了開發者不想讓我們看到的內容
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注入的防護,並繼續注入。