天天看點

Spring Boot 如何防護 XSS + SQL 注入攻擊 ?終于懂了!

作者:Java小蟲

1. XSS跨站腳本攻擊

① XSS漏洞介紹

跨站腳本攻擊XSS是指攻擊者往Web頁面裡插入惡意Script代碼,當使用者浏覽該頁之時,嵌入其中Web裡面的Script代碼會被解析執行,進而達到惡意攻擊使用者的目的。XSS攻擊針對的是使用者層面的攻擊!

Spring Boot 如何防護 XSS + SQL 注入攻擊 ?終于懂了!

② XSS漏洞分類

存儲型XSS: 存儲型XSS,持久化,代碼是存儲在伺服器中的,如在個人資訊或發表文章等地方,插入代碼,如果沒有過濾或過濾不嚴,那麼這些代碼将儲存到伺服器中,使用者通路該頁面的時候觸發代碼執行。這種XSS比較危險,容易造成蠕蟲,盜竊cookie

Spring Boot 如何防護 XSS + SQL 注入攻擊 ?終于懂了!

反射型XSS: 非持久化,需要欺騙使用者自己去點選連結才能觸發XSS代碼(伺服器中沒有這樣的頁面和内容),一般容易出現在搜尋頁面

DOM型XSS: 不經過後端,DOM-XSS漏洞是基于文檔對象模型(Document Objeet Model,DOM)的一種漏洞,DOM-XSS是通過url傳入參數去控制觸發的,其實也屬于反射型XSS。

③ 防護建議

  • 限制使用者輸入,表單資料規定值得類型,例如年齡隻能是int,name為字母數字組合。
  • 對資料進行html encode處理。
  • 過濾或移除特殊的html标簽。
  • 過濾javascript事件的标簽。

2. SQL注入攻擊

① SQL注入漏洞介紹

SQL注入(SQLi)是一種注入攻擊,可以執行惡意SQL語句。它通過将任意SQL代碼插入資料庫查詢,使攻擊者能夠完全控制Web應用程式後面的資料庫伺服器。攻擊者可以使用SQL注入漏洞繞過應用程式安全措施;可以繞過網頁或Web應用程式的身份驗證和授權,并檢索整個SQL資料庫的内容;還可以使用SQL注入來添加,修改和删除資料庫中的記錄;關注公衆号:碼猿技術專欄,回複關鍵詞:1111 擷取阿裡内部Java性能優化手冊!

SQL注入漏洞可能會影響使用SQL資料庫(如MySQL,Oracle,SQL Server或其他)的任何網站或Web應用程式。犯罪分子可能會利用它來未經授權通路使用者的敏感資料:客戶資訊,個人資料,商業機密,知識産權等。SQL注入攻擊是最古老,最流行,最危險的Web應用程式漏洞之一。

②防護建議

使用mybatis中#{}可以有效防止sql注入。

使用#{}時:

<select id="getBlogById" resultType="Blog" parameterType=”int”>
       select id,title,author,content
       from blog where id=#{id}
</select>
           

列印出執行的sql語句,會看到sql是這樣的:

select id,title,author,content from blog where id = ?
           

不管輸入什麼參數,列印出的sql都是這樣的。這是因為mybatis啟用了預編譯功能,在sql執行前,會先将上面的sql發送給資料庫進行編譯,執行時,直接使用編譯好的sql,替換占位符“?”就可以了。因為sql注入隻能對編譯過程起作用,是以像#{}這樣預編譯成?的方式就很好地避免了sql注入的問題。

mybatis是如何做到sql預編譯的呢?

其實在架構底層,是jdbc中的PreparedStatement類在起作用,PreparedStatement是我們很熟悉的Statement的子類,它的對象包含了編譯好的sql語句。這種“準備好”的方式不僅能提高安全性,而且在多次執行一個sql時,能夠提高效率,原因是sql已編譯好,再次執行時無需再編譯。

使用${}時

<select id="orderBlog" resultType="Blog" parameterType=”map”>
       select id,title,author,content
       from blog order by ${orderParam}
</select>
           

仔細觀察,内聯參數的格式由“#{xxx}”變為了${xxx}。如果我們給參數“orderParam”指派為”id”,将sql列印出來,是這樣的:

select id,title,author,contet from blog order by id
           

顯然,這樣是無法阻止sql注入的,參數會直接參與sql編譯,進而不能避免注入攻擊。但涉及到動态表名和列名時,隻能使用“${}”這樣的參數格式,是以,這樣的參數需要我們在代碼中手工進行處理來防止注入。

其實,這些都在Java面試庫小程式上都有答案,如果你近期準備面試跳槽,建議在上面刷題,涵蓋 2000+ 道 Java 面試題,幾乎覆寫了所有主流技術面試題。

3. SpringBoot中如何防止XSS攻擊和sql注入

對于Xss攻擊和Sql注入,我們可以通過過濾器來搞定,可根據業務需要排除部分請求

① 建立Xss請求過濾類XssHttpServletRequestWraper

Spring Boot 如何防護 XSS + SQL 注入攻擊 ?終于懂了!

代碼如下:

public class XssHttpServletRequestWraper extends HttpServletRequestWrapper {

    Logger log = LoggerFactory.getLogger(this.getClass());


    public XssHttpServletRequestWraper() {
        super(null);
    }

    public XssHttpServletRequestWraper(HttpServletRequest httpservletrequest) {
        super(httpservletrequest);
    }

 //過濾springmvc中的 @RequestParam 注解中的參數
    public String[] getParameterValues(String s) {

        String str[] = super.getParameterValues(s);
        if (str == null) {
            return null;
        }
        int i = str.length;
        String as1[] = new String[i];
        for (int j = 0; j < i; j++) {
            //System.out.println("getParameterValues:"+str[j]);
            as1[j] = cleanXSS(cleanSQLInject(str[j]));
        }
        log.info("XssHttpServletRequestWraper淨化後的請求為:==========" + as1);
        return as1;
    }

 //過濾request.getParameter的參數
    public String getParameter(String s) {
        String s1 = super.getParameter(s);
        if (s1 == null) {
            return null;
        } else {
            String s2 = cleanXSS(cleanSQLInject(s1));
            log.info("XssHttpServletRequestWraper淨化後的請求為:==========" + s2);
            return s2;
        }
    }

 //過濾請求體 json 格式的
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(inputHandlers(super.getInputStream ()).getBytes ());

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) { }
        };
    }

 
    public   String inputHandlers(ServletInputStream servletInputStream){
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (servletInputStream != null) {
                try {
                    servletInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return  cleanXSS(sb.toString ());
    }

    public String cleanXSS(String src) {
        String temp = src;

        src = src.replaceAll("<", "<").replaceAll(">", ">");
        src = src.replaceAll("\\(", "(").replaceAll("\\)", ")");
        src = src.replaceAll("'", "'");
        src = src.replaceAll(";", ";");
        //bgh 2018/05/30  新增
        /**-----------------------start--------------------------*/
        src = src.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
        src = src.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41");
        src = src.replaceAll("eval\\((.*)\\)", "");
        src = src.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        src = src.replaceAll("script", "");
        src = src.replaceAll("link", "");
        src = src.replaceAll("frame", "");
        /**-----------------------end--------------------------*/
        Pattern pattern = Pattern.compile("(eval\\((.*)\\)|script)",
                Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(src);
        src = matcher.replaceAll("");

        pattern = Pattern.compile("[\\\"\\'][\\s]*javascript:(.*)[\\\"\\']",
                Pattern.CASE_INSENSITIVE);
        matcher = pattern.matcher(src);
        src = matcher.replaceAll("\"\"");

        // 增加腳本
        src = src.replaceAll("script", "").replaceAll(";", "")
                /*.replaceAll("\"", "").replaceAll("@", "")*/
                .replaceAll("0x0d", "").replaceAll("0x0a", "");

        if (!temp.equals(src)) {
            // System.out.println("輸入資訊存在xss攻擊!");
            // System.out.println("原始輸入資訊-->" + temp);
            // System.out.println("處理後資訊-->" + src);

            log.error("xss攻擊檢查:參數含有非法攻擊字元,已禁止繼續通路!!");
            log.error("原始輸入資訊-->" + temp);

            throw new CustomerException("xss攻擊檢查:參數含有非法攻擊字元,已禁止繼續通路!!");
        }
        return src;
    }

    //輸出
    public void outputMsgByOutputStream(HttpServletResponse response, String msg) throws IOException {
        ServletOutputStream outputStream = response.getOutputStream(); //擷取輸出流
        response.setHeader("content-type", "text/html;charset=UTF-8"); //通過設定響應頭控制浏覽器以UTF-8的編碼顯示資料,如果不加這句話,那麼浏覽器顯示的将是亂碼
        byte[] dataByteArr = msg.getBytes("UTF-8");// 将字元轉換成位元組數組,指定以UTF-8編碼進行轉換
        outputStream.write(dataByteArr);// 使用OutputStream流向用戶端輸出位元組數組
    }

    // 需要增加通配,過濾大小寫組合
    public String cleanSQLInject(String src) {
        String lowSrc = src.toLowerCase();
        String temp = src;
        String lowSrcAfter = lowSrc.replaceAll("insert", "forbidI")
                .replaceAll("select", "forbidS")
                .replaceAll("update", "forbidU")
                .replaceAll("delete", "forbidD").replaceAll("and", "forbidA")
                .replaceAll("or", "forbidO");

        if (!lowSrcAfter.equals(lowSrc)) {
            log.error("sql注入檢查:輸入資訊存在SQL攻擊!");
            log.error("原始輸入資訊-->" + temp);
            log.error("處理後資訊-->" + lowSrc);
            throw new CustomerException("sql注入檢查:參數含有非法攻擊字元,已禁止繼續通路!!");

        }
        return src;
    }

}
           

② 把請求過濾類XssHttpServletRequestWraper添加到Filter中,注入容器

@Component
public class XssFilter implements Filter {

    Logger log  = LoggerFactory.getLogger(this.getClass());

    // 忽略權限檢查的url位址
    private final String[] excludeUrls = new String[]{
            "null"
    };

    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;

        String pathInfo = req.getPathInfo() == null ? "" : req.getPathInfo();
        //擷取請求url的後兩層
        String url = req.getServletPath() + pathInfo;
        //擷取請求你ip後的全部路徑
        String uri = req.getRequestURI();
        //注入xss過濾器執行個體
        XssHttpServletRequestWraper reqW = new XssHttpServletRequestWraper(req);

        //過濾掉不需要的Xss校驗的位址
        for (String str : excludeUrls) {
            if (uri.indexOf(str) >= 0) {
                arg2.doFilter(arg0, response);
                return;
            }
        }
        //過濾
        arg2.doFilter(reqW, response);
    }
    public void destroy() {
    }
    public void init(FilterConfig filterconfig1) throws ServletException {
    }
}
           

上述代碼已經可以完成 請求參數、JSON請求體 的過濾,但對于json請求體還有其他的方式實作,有興趣的請看下面的擴充!

擴充:還可以重寫spring中的MappingJackson2HttpMessageConverter來過濾Json請求體

因為請求體在進出Contoroller時,會經過MappingJackson2HttpMessageConverter的一個轉換,把請求體轉換成我們需要的json格式,是以可以在這裡邊做一些修改!

@Configuration
public class MyConfiguration {

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
        //自定義轉換器
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();


        //轉換器日期格式設定
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        objectMapper.setDateFormat(smt);
        converter.setObjectMapper(objectMapper);

        //轉換器添加自定義Module擴充,主要是在這裡做XSS過濾的!!,其他的是其他業務,不用看
        SimpleModule simpleModule = new SimpleModule();
        //添加過濾邏輯類!
        simpleModule.addDeserializer(String.class,new StringDeserializer());
        converter.getObjectMapper().registerModule(simpleModule);

        //設定中文編碼格式
        List<MediaType> list = new ArrayList<>();
        list.add(MediaType.APPLICATION_JSON_UTF8);
        converter.setSupportedMediaTypes(list);

        return converter;
    }

}
           

真正的過濾邏輯類StringDeserializer:

//檢驗請求體的參數
@Component
public class StringDeserializer extends JsonDeserializer<String> {
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        String str = jsonParser.getText().trim();
        //sql注入攔截
        if (sqlInject(str)) {
          throw new CustomerException("參數含有非法攻擊字元,已禁止繼續通路!");
        }

        return xssClean(str);

    }

    public boolean sqlInject(String str) {

        if (StringUtils.isEmpty(str)) {
            return false;
        }

        //去掉'|"|;|\字元
        str = org.apache.commons.lang3.StringUtils.replace(str, "'", "");
        str = org.apache.commons.lang3.StringUtils.replace(str, "\"", "");
        str = org.apache.commons.lang3.StringUtils.replace(str, ";", "");
        str = org.apache.commons.lang3.StringUtils.replace(str, "\\", "");

        //轉換成小寫
        str = str.toLowerCase();

        //非法字元
        String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alert","alter", "drop"};

        //判斷是否包含非法字元
        for (String keyword : keywords) {
            if (str.indexOf(keyword) != -1) {
                return true;
            }
        }
        return false;

    }

    //xss攻擊攔截

    public String xssClean(String value) {
        if (value == null || "".equals(value)) {
            return value;
        }

        //非法字元
        String[] keywords = {"<", ">", "<>", "()", ")", "(", "javascript:", "script","alter", "''","'"};
        //判斷是否包含非法字元
        for (String keyword : keywords) {
            if (value.indexOf(keyword) != -1) {
               throw new CustomerException("參數含有非法攻擊字元,已禁止繼續通路!");
            }
        }

        return value;
    }
}
           

使用這種形式也可以完成json請求體的過濾,但個人更推薦使用XssHttpServletRequestWraper的形式來完成xss過濾!!

最後說一句(别白嫖,求關注)

每一篇文章都是精心輸出,如果這篇文章對你有所幫助,或者有所啟發的話,幫忙點贊、關注、轉發,你的支援就是我堅持下去的最大動力!

繼續閱讀