當前位置: 妍妍網 > 碼農

Spring Boot 如何防護 XSS + SQL 隱碼攻擊 ?終於懂了!

2024-03-28碼農

👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 贈書福利

全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 http://116.62.199.48/ 新計畫正在醞釀中 。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了239小節,累計38w+字,講解圖:1645張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,

1. XSS跨站指令碼攻擊

① XSS漏洞介紹

跨站指令碼攻擊XSS是指攻擊者往Web頁面裏插入惡意Script程式碼,當使用者瀏覽該頁之時,嵌入其中Web裏面的Script程式碼會被解析執行,從而達到惡意攻擊使用者的目的。XSS攻擊針對的是使用者層面的攻擊!

圖片

② XSS漏洞分類

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

圖片

反射型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註入來添加,修改和刪除資料庫中的記錄;

    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

    ![圖片](https://mmbiz.qpic.cn/mmbiz_png/19cc2hfD2rDk4RBjVAbd2MVMGXnZEaSpOsT5Rv3V5mGYAUH8ic8vEmV4AewXdbeSsO8lhwX7DcA0s69WtHPeTxw/640?wx_fmt=png&tp=wxpic&wxfrom=5&wx_lazy=1&wx_co=1)

    程式碼如下:

    public class XssHttpServletRequestWraper extends HttpServletRequestWrapper {
    Logger log = LoggerFactory.getLogger(this.get class());

    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() {
    returnfalse;
    }
    @Override
    public boolean isReady() {
    returnfalse;
    }
    @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.get class());
    // 忽略許可權檢查的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)) {
    returnfalse;
    }
    //去掉'|"|;|\字元
    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) {
    returntrue;
    }
    }
    returnfalse;
    }
    //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過濾!!

    👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 贈書福利

    全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 http://116.62.199.48/ 新計畫正在醞釀中 。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了239小節,累計38w+字,講解圖:1645張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,


    1. 

    2. 

    3. 

    4. 

    最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。

    獲取方式:點「在看」,關註公眾號並回復 Java 領取,更多內容陸續奉上。

    PS:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下在看,加個星標,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。

    「在看」支持小哈呀,謝謝啦