xss solution

Posted by iankent on Sat, 25 Dec 2021 09:48:16 +0100

XSS introduction
Cross site scripting attack is abbreviated as XSS in order not to confuse CSS. XSS means that a malicious attacker inserts malicious Script code into a Web page. When a user browses the page, the Script code embedded in the Web will be executed, so as to achieve the purpose of malicious attack on the user. It is similar to sql injection.

XSS attack principle:

Html is a hypertext markup language that distinguishes text and markup by treating some characters specially. For example, the less than symbol (<) is regarded as the beginning of HTML tags,

The characters between and are the title of the page, etc. When the content inserted in the dynamic page contains these special characters, the user browser will mistakenly think that it is inserted with HTML tags. When these HTML tags introduce a JavaScript script, these script programs will be executed in the user browser. Therefore, when these special characters cannot be checked by dynamic pages or errors occur in the check, XSS vulnerabilities will be generated.

AntiSamy introduction
AntiSamy is an open source project of OWASP, which ensures that the input meets the application specifications by checking and cleaning the HTML / CSS / JavaScript and other contents input by users. AntiSamy is widely used in the defense of Web services against storage and reflective XSS.

AntiSamy starter case

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>cn.itcast</groupId>
    <artifactId>antiSamy_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.owasp.antisamy</groupId>
            <artifactId>antisamy</artifactId>
            <version>1.5.7</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

Step 2: create application yml

server:
  port: 9000

Step 3: create a policy file / resources / antisamy test XML, and the contents of the file can be obtained from the jar package of antisamy

Note: AntiSamy's filtering of "malicious code" depends on the policy file. The policy file specifies the processing method of AntiSamy for each tag and attribute. The strict definition of the policy file determines the defense effect of AntiSamy against XSS vulnerabilities. The jar package of AntiSamy contains several common policy files

Step 4: create User entity class

@Data
public class User {
    private int id;
    private String name;
    private int age;
}

Step 5: create UserController

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/save")
    public String save(User user){
        System.out.println("UserController save.... " + user);
        return user.getName();
    }
}

Step 6: create / resources / static / index HTML page

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form method="post" action="/user/save">
            id:<input type="text" name="id"><br>
            name:<input type="text" name="name"><br>
            age:<input type="text" name="age"><br>
            <input type="submit" value="submit">
        </form>
    </body>
</html>

Step 7: create a startup class

@SpringBootApplication
public class AntiSamyApp {
    public static void main(String[] args) {
        SpringApplication.run(AntiSamyApp.class,args);
    }
}

At this time, we can start the project for access, but we have not filtered the parameters, so if we enter any parameters, they can be passed to the Controller normally, which is very unsafe in the actual project. In order to filter and clean the data we input, we need to implement it through filters.

Step 8: create a filter to filter all request parameters submitted to the server

/*
*Filter all request parameters submitted to the server
*/
public class XssFilter implements Filter{
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
                         FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        //Incoming rewritten Request
        filterChain.doFilter(new XssRequestWrapper(request),servletResponse);
    }
}

Note: through the above filter, we can find that we have not directly filtered and cleaned the request parameters in the filter, but directly released them. How can we filter and clean the request parameters? In fact, the filtering and cleaning work is carried out in another class XssRequestWrapper. When the above filter is released, you need to call filterchain Dofilter () method. This method needs to pass in the request object. At this time, we can wrap the current request object, and XssRequestWrapper is the wrapper class of the request object. When the filter is released, the getParameterValues method of the wrapper class will be called automatically. We can filter and clean up the request parameters uniformly in the getParameterValues method of the wrapper class.

Step 9: create the XssRequestWrapper class

public class XssRequestWrapper extends HttpServletRequestWrapper {
    /*
     * The policy file needs to put the policy file to be used under the project resource file path
    */
    private static String antiSamyPath = XssRequestWrapper.class.getClassLoader()
            .getResource( "antisamy-test.xml").getFile();

    public static  Policy policy = null;
    static {
        // Specify policy file
        try {
            policy = Policy.getInstance(antiSamyPath);
        } catch (PolicyException e) {
            e.printStackTrace();
        }
    }

    /**
     * AntiSamy Filter data
     * @param taintedHTML Data to be filtered
     * @return Return filtered data
     * */
    private String xssClean( String taintedHTML){
        try{
            // Filtering with AntiSamy
            AntiSamy antiSamy = new AntiSamy();
            CleanResults cr = antiSamy.scan( taintedHTML, policy);
            taintedHTML = cr.getCleanHTML();
        }catch( ScanException e) {
            e.printStackTrace();
        }catch( PolicyException e) {
            e.printStackTrace();
        }
        return taintedHTML;
    }

    public XssRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public String[] getParameterValues(String name){
        String[] values = super.getParameterValues(name);
        if ( values == null){
            return null;
        }
        int len = values.length;
        String[] newArray = new String[len];
        for (int j = 0; j < len; j++){
            System.out.println("Antisamy Filter cleaning, parameter values before cleaning:" + values[j]);
            // Filter cleaning
            newArray[j] = xssClean(values[j]);
            System.out.println("Antisamy Filter cleaning. Parameter values after cleaning:" + newArray[j]);
        }
        return newArray;
    }
}

Step 10: to make the filter defined above effective, you need to create a configuration class to initialize the filter object

@Configuration
public class AntiSamyConfiguration {
    /**
     * Configure cross site attack filters
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration = 
            new FilterRegistrationBean(new XssFilter());
        filterRegistration.addUrlPatterns("/*");
        filterRegistration.setOrder(1);

        return filterRegistration;
    }
}

Start the project and enter illegal data on the page. You can see that the illegal data has been cleared.

Note: at present, we only process the request parameter filtering in the getParameterValues method of the wrapper class. In the real project, the data submitted by the user may be in the request header or json data submitted by the user. Therefore, if all conditions are considered, we can clean up in multiple methods in the wrapper class, as follows:

public class XssRequestWrapper extends HttpServletRequestWrapper {
    /**
     * The policy file needs to put the policy file to be used under the project resource file path
     * */
    private static String antiSamyPath = XssRequestWrapper.class.getClassLoader()
            .getResource( "antisamy-ebay.xml").getFile();

    public static  Policy policy = null;
    static {
        // Specify policy file
        try {
            policy = Policy.getInstance(antiSamyPath);
        } catch (PolicyException e) {
            e.printStackTrace();
        }
    }

    /**
     * AntiSamy Filter data
     * @param taintedHTML Data to be filtered
     * @return Return filtered data
     * */
    private String xssClean( String taintedHTML){
        try{
            // Filtering with AntiSamy
            AntiSamy antiSamy = new AntiSamy();
            CleanResults cr = antiSamy.scan( taintedHTML, policy);
            taintedHTML = cr.getCleanHTML();
        }catch( ScanException e) {
            e.printStackTrace();
        }catch( PolicyException e) {
            e.printStackTrace();
        }
        return taintedHTML;
    }

    public XssRequestWrapper(HttpServletRequest request) {
        super(request);
    }
    @Override
    public String[] getParameterValues(String name){
        String[] values = super.getParameterValues(name);
        if ( values == null){
            return null;
        }
        int len = values.length;
        String[] newArray = new String[len];
        for (int j = 0; j < len; j++){
            // Filter cleaning
            newArray[j] = xssClean(values[j]);
        }
        return newArray;
    }

    @Override
    public String getParameter(String paramString) {
        String str = super.getParameter(paramString);
        if (str == null) {
            return null;
        }
        return xssClean(str);
    }


    @Override
    public String getHeader(String paramString) {
        String str = super.getHeader(paramString);
        if (str == null) {
            return null;
        }
        return xssClean(str);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> requestMap = super.getParameterMap();
        for (Map.Entry<String, String[]> me : requestMap.entrySet()) {
            String[] values = me.getValue();
            for (int i = 0; i < values.length; i++) {
                values[i] = xssClean(values[i]);
            }
        }
        return requestMap;
    }
}