How does Spring Boot protect against XSS + SQL injection attacks? Finally understand!

Hello everyone, I am Bucai Chen~

1. XSS cross-site scripting attack

① Introduction to XSS vulnerabilities

Cross-site scripting attack (XSS) refers to an attacker inserting malicious script code into a web page. When a user browses the page, the script code embedded in the web page will be parsed and executed, thereby achieving the purpose of maliciously attacking the user. XSS attacks are aimed at user-level attacks!

85ecbab4515049fe65af151b2ce78885.png

② Classification of XSS vulnerabilities

Stored XSS: Stored XSS, persistence, code is stored in the server, such as in personal information or published articles, insert code, if there is no filtering or filtering is not strict, then these codes will be stored in the server, users Trigger code execution when the page is accessed. This kind of XSS is more dangerous, it is easy to cause worms and steal cookies

fdafd9ba6325e382f4aa7a16fe43b806.png

Reflective XSS: Non-persistent, need to deceive users to click on the link to trigger XSS code (there is no such page and content in the server), generally easy to appear on the search page

DOM-type XSS: DOM-XSS vulnerability is a vulnerability based on the Document Object Model (Document Objeet Model, DOM) without going through the backend. DOM-XSS is triggered by passing parameters through url , In fact, it is also a reflected XSS.

③ Protection suggestions

  • Restrict user input, form data specifies the value type, for example, age can only be int, and name is a combination of letters and numbers.

  • Perform html encode processing on the data.

  • Filter or remove special html tags.

  • Tags to filter javascript events.

2. SQL injection attack

① SQL injection vulnerability introduction

SQL Injection (SQLi) is an injection attack that executes malicious SQL statements. It gives an attacker full control over the database server behind a web application by inserting arbitrary SQL code into database queries. Attackers can use SQL injection vulnerabilities to bypass application security measures; they can bypass authentication and authorization of web pages or web applications, and retrieve the contents of the entire SQL database; they can also use SQL injection to add, modify, and delete entries in the database. Record; pay attention to the official account: code ape technology column, reply keywords: 1111 Get Ali’s internal Java performance optimization manual!

SQL injection vulnerabilities can affect any website or web application that uses a SQL database such as MySQL, Oracle, SQL Server or others. Criminals may use it to gain unauthorized access to users’ sensitive data: customer information, personal data, trade secrets, intellectual property, etc. SQL injection attacks are one of the oldest, most popular, and most dangerous web application vulnerabilities.

②Protection advice

Using #{} in mybatis can effectively prevent sql injection.

When using #{}:

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

Print out the executed sql statement, and you will see that sql looks like this:

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

No matter what parameters are entered, the printed sql is like this. This is because mybatis has enabled the pre-compilation function. Before the sql is executed, the above sql will be sent to the database for compilation. When executing, directly use the compiled sql and replace the placeholder “?”. Because sql injection can only work on the compilation process, so it is precompiled like #{}? The way to avoid the problem of sql injection is very good.

How does mybatis achieve sql precompilation?

In fact, at the bottom of the framework, the PreparedStatement class in jdbc works. PreparedStatement is a subclass of Statement that we are very familiar with. Its objects contain compiled SQL statements . This “ready” method can not only improve security, but also improve efficiency when executing a sql multiple times, because the sql has been compiled and there is no need to recompile it when it is executed again.

When using ${}

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

Observe carefully, the format of the inline parameter has changed from “#{xxx}” to ${xxx}. If we assign “id” to the parameter “orderParam“, the sql will be printed out like this:

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

Obviously, SQL injection cannot be prevented in this way, and parameters will directly participate in SQL compilation, so injection attacks cannot be avoided. But when it comes to dynamic table names and column names, only the parameter format “${}” can be used. Therefore, such parameters need to be processed manually in the code to prevent injection.

In fact, these are all answered in the Java interview library applet. If you are planning to change jobs in the near future, it is recommended to write questions on it, covering 2000+ Java interview questions, covering almost all mainstream technical interview questions.

3. How to prevent XSS attacks and sql injection in SpringBoot

For Xss attacks and Sql injections, we can handle them through filters, which can exclude some requests according to business needs

① Create Xss request filter class XssHttpServletRequestWraper

a12e71a38e553364720d8298e04edeb5.png

code show as below:

public class XssHttpServletRequestWraper extends HttpServletRequestWrapper {

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


    public XssHttpServletRequestWraper() {
        super(null);
    }

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

 //Filter the parameters in the @RequestParam annotation in springmvc
    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 purified request: ==========" + as1);
        return as1;
    }

 //Filter the parameters of 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 purified request: ==========" + s2);
            return s2;
        }
    }

 //Filter the request body in json format
    @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 Added
        /**-----------------------start----------------------- ---*/
        src = src. replaceAll("<", " & amp; lt;"). replaceAll(">", " & amp; gt;");
        src = src. replaceAll("\(", " & amp; #40;"). replaceAll("\)", " & amp; #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("""");

        // add script
        src = src.replaceAll("script", "").replaceAll(";", "")
                /*.replaceAll(""", "").replaceAll("@", "")*/
                .replaceAll("0x0d", "").replaceAll("0x0a", "");

        if (!temp. equals(src)) {
            // System.out.println("There is an xss attack in the input information!");
            // System.out.println("Original input information -->" + temp);
            // System.out.println("Processed information -->" + src);

            log.error("xss attack check: the parameter contains illegal attack characters, further access is prohibited!!");
            log.error("Original input information -->" + temp);

            throw new CustomerException("xss attack check: the parameter contains illegal attack characters, further access is prohibited!!");
        }
        return src;
    }

    // output
    public void outputMsgByOutputStream(HttpServletResponse response, String msg) throws IOException {
        ServletOutputStream outputStream = response.getOutputStream(); //Get the output stream
        response.setHeader("content-type", "text/html;charset=UTF-8"); //Control the browser to display data in UTF-8 encoding by setting the response header. If you do not add this sentence, then browse The display will be garbled
        byte[] dataByteArr = msg.getBytes("UTF-8");// Convert characters into byte arrays, specifying UTF-8 encoding for conversion
        outputStream.write(dataByteArr);//Use the OutputStream stream to output the byte array to the client
    }

    // Need to add wildcards to filter case combinations
    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 injection check: there is a SQL attack in the input information!");
            log.error("Original input information -->" + temp);
            log.error("Post-processing information -->" + lowSrc);
            throw new CustomerException("sql injection check: the parameter contains illegal attack characters, further access has been prohibited!!");

        }
        return src;
    }

}

② Add the request filter class XssHttpServletRequestWraper to Filter and inject it into the container

@Component
public class XssFilter implements Filter {

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

    // The url address that ignores the permission check
    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();
        // Get the last two layers of the request url
        String url = req. getServletPath() + pathInfo;
        // Get all the paths after requesting your ip
        String uri = req. getRequestURI();
        //Inject xss filter instance
        XssHttpServletRequestWraper reqW = new XssHttpServletRequestWraper(req);

        //Filter out the addresses of unnecessary Xss checks
        for (String str : excludeUrls) {
            if (uri. indexOf(str) >= 0) {
                arg2. doFilter(arg0, response);
                return;
            }
        }
        //filter
        arg2. doFilter(reqW, response);
    }
    public void destroy() {
    }
    public void init(FilterConfig filterconfig1) throws ServletException {
    }
}

The above code can already complete the filtering of request parameters and JSON request body, but there are other ways to realize the JSON request body. If you are interested, please see the extension below!

Extension: You can also rewrite the MappingJackson2HttpMessageConverter in spring to filter the Json request body

Because the request body will go through a conversion of MappingJackson2HttpMessageConverter when it enters and exits Contoroller to convert the request body into the json format we need, so you can make some modifications here!

@Configuration
public class MyConfiguration {

    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
        //custom converter
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();


        //converter date format setting
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        objectMapper.setDateFormat(smt);
        converter.setObjectMapper(objectMapper);

        //The converter adds a custom Module extension, mainly for XSS filtering here! ! , the others are other businesses, don’t look at them
        SimpleModule simpleModule = new SimpleModule();
        //Add filtering logic class!
        simpleModule. addDeserializer(String. class, new StringDeserializer());
        converter.getObjectMapper().registerModule(simpleModule);

        //Set the Chinese encoding format
        List<MediaType> list = new ArrayList<>();
        list.add(MediaType.APPLICATION_JSON_UTF8);
        converter.setSupportedMediaTypes(list);

        return converter;
    }

}

The real filtering logic class StringDeserializer:

//Check the parameters of the request body
@Component
public class StringDeserializer extends JsonDeserializer<String> {
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        String str = jsonParser.getText().trim();
        //sql injection interception
        if (sqlInject(str)) {
          throw new CustomerException("The parameter contains illegal attack characters, and further access is prohibited!");
        }

        return xssClean(str);

    }

    public boolean sqlInject(String str) {

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

        //Remove the '|"|;|\ character
        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, "", "");

        // convert to lowercase
        str = str.toLowerCase();

        //Invaild symbol
        String[] keywords = {"master", "truncate", "insert", "select", "delete", "update", "declare", "alert", "alter", "drop"};

        //Check if it contains illegal characters
        for (String keyword : keywords) {
            if (str. indexOf(keyword) != -1) {
                return true;
            }
        }
        return false;

    }

    //xss attack interception

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

        //Invaild symbol
        String[] keywords = {"<", ">", "<>", "()", ")", "(", "javascript:", "script", "alter", "''", "'"};
        //Check if it contains illegal characters
        for (String keyword : keywords) {
            if (value. indexOf(keyword) != -1) {
               throw new CustomerException("The parameter contains illegal attack characters, and further access is prohibited!");
            }
        }

        return value;
    }
}

Using this form can also complete the filtering of the json request body, but personally recommend using the form of XssHttpServletRequestWraper to complete the xss filtering! !

One last sentence (don’t prostitute, please pay attention)

Every article by Chen is carefully output. If this article is helpful or inspiring to you, please like, watch, repost, and bookmark. Your support is the biggest motivation for me to persevere!

In addition, Chen’s Knowledge Planet has been opened, and the official account replies to the key words: Knowledge Planet is limited to 30 yuan coupons to join, only 89 yuan, a meal, but the value of the planet’s feedback is huge. Currently, the Spring Family Bucket has been updated. Series, 100 million-level data sub-database sub-table actual combat, DDD micro-service actual combat column, I want to enter the big factory, Spring, Mybatis and other framework source code, architecture actual combat 22 lectures, etc. The price will increase by 20 yuan for each additional column

dff518adfa8400d4bbc6311204700b60.png

Follow the official account: [Code Ape Technology Column], there are awesome fan benefits in the official account, reply: join the group, you can join the technical discussion group, discuss technology with everyone, and brag!