XSS攻击

目录

  • XSS脚本攻击
  • 前端处理
  • 后端处理
  • 参考资料

XSS脚本攻击

跨站脚本攻击(Cross-site scripting,XSS)是一种安全漏洞,攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。若受害者运行这些恶意代码,攻击者就可以突破网站的访问限制并冒充受害者。根据开放式 Web 应用安全项目(OWASP),XSS 在 2017 年被认为 7 种最常见的 Web 应用程序漏洞之一。

如果 Web 应用程序没有部署足够的安全验证,那么,这些攻击很容易成功。浏览器无法探测到这些恶意脚本是不可信的,所以,这些脚本可以任意读取 cookie,session tokens,或者其他敏感的网站信息,或者让恶意脚本重写HTML内容。

在以下 2 种情况下,容易发生 XSS 攻击:

  1. 用户从一个不可靠的链接进入到一个 Web 应用程序。网站本身就存在XSS攻击脚本,当用户无意访问该网页后被攻击;
  2. 没有过滤掉恶意代码的动态内容被发送给 Web 用户。例如攻击者在网站的留言框输入XSS攻击文本后,发布成功后,其他用户浏览后被攻击;

恶意内容一般包括 JavaScript,但是,有时候也会包括 HTML,FLASH 或是其他浏览器可执行的代码。XSS 攻击的形式千差万别,但他们通常都会:将 cookies 或其他隐私信息发送给攻击者,将受害者重定向到由攻击者控制的网页,或是经由恶意网站在受害者的机器上进行其他恶意操作。

XSS 攻击可以分为 3 类:存储型(持久型)、反射型(非持久型)、DOM 型。

  • 存储型 XSS
    注入型脚本永久存储在目标服务器上。当浏览器请求数据时,脚本从服务器上传回并执行。

  • 反射型 XSS
    当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web 服务器将注入脚本,比如一个错误信息,搜索结果等 返回到用户的浏览器上。由于浏览器认为这个响应来自"可信任"的服务器,所以会执行这段脚本。

  • 基于 DOM 的 XSS
    通过修改原始的客户端代码,受害者浏览器的 DOM 环境改变,导致有效载荷的执行。也就是说,页面本身并没有变化,但由于 DOM 环境被恶意修改,有客户端代码被包含进了页面,并且意外执行。

前端处理

后端处理

在微服务架构中,在网关处增加一个全局过滤器,该过滤器会将请求中提交的文本中与xss攻击相关的敏感字符进行删除

  • 工具类
import lombok.extern.slf4j.Slf4j;

import java.util.regex.Pattern;

/**
 * @author samson bruce
 */
@Slf4j
public class XssCleanRuleUtils {

    private static final Pattern[] scriptPatterns = {
            Pattern.compile("", Pattern.CASE_INSENSITIVE),
            Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("", Pattern.CASE_INSENSITIVE),
            Pattern.compile("", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
            Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
    };

    /**
     * GET请求参数过滤
     *
     * @param value
     * @return String
     */
    public static String xssGetClean(String value) {

        //过滤xss字符集
        return xssClean(value);

    }

    /**
     * post请求参数过滤
     *
     * @param value value
     * @return String
     */
    public static String xssPostClean(String value) {

        //过滤xss字符集
        return xssClean(value);

    }

    private static String xssClean(String value) {
        if (value != null) {
            value = value.replaceAll("\0|\n|\r", "");
            for (Pattern pattern : scriptPatterns) {
                value = pattern.matcher(value).replaceAll("");
            }
            value = value.replaceAll("<", "<").replaceAll(">", ">");
        }
        return value;
    }

}
  • 全局过滤器
import io.netty.buffer.ByteBufAllocator;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

/**
 * @author samson bruce、yuqi li
 * @since 2023-07-01
 */
@Slf4j
@Component
public class XssFilter implements GlobalFilter, Ordered {

    @SneakyThrows(Exception.class)
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // grab configuration from Config object
        log.info("----自定义防XSS攻击网关全局过滤器生效----");
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        HttpMethod method = serverHttpRequest.getMethod();
        String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
        URI uri = exchange.getRequest().getURI();

        boolean postFlag = (method == HttpMethod.POST || method == HttpMethod.PUT) &&
                (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_VALUE.equals(contentType));

        //过滤get请求
        if (method == HttpMethod.GET) {

            String rawQuery = uri.getRawQuery();
            if (StringUtils.isBlank(rawQuery)) {
                return chain.filter(exchange);
            }

            log.info("原请求参数为:{}", rawQuery);
            // 执行XSS清理
            rawQuery = XssCleanRuleUtils.xssGetClean(rawQuery);
            log.info("修改后参数为:{}", rawQuery);

            try {
                //重新构造get request
                URI newUri = UriComponentsBuilder.fromUri(uri)
                        .replaceQuery(rawQuery)
                        .build(true)
                        .toUri();

                ServerHttpRequest request = exchange.getRequest().mutate()
                        .uri(newUri).build();
                return chain.filter(exchange.mutate().request(request).build());
            } catch (Exception e) {
                log.error("get请求清理xss攻击异常", e);
                throw new IllegalStateException("Invalid URI query: \"" + rawQuery + "\"");
            }
        }
        //post请求时,如果是文件上传之类的请求,不修改请求消息体
        else if (postFlag) {

            return DataBufferUtils.join(serverHttpRequest.getBody()).flatMap(d -> Mono.just(Optional.of(d))).defaultIfEmpty(
                            Optional.empty())
                    .flatMap(optional -> {
                        // 取出body中的参数
                        String bodyString = "";
                        if (optional.isPresent()) {
                            byte[] oldBytes = new byte[optional.get().readableByteCount()];
                            optional.get().read(oldBytes);
                            bodyString = new String(oldBytes, StandardCharsets.UTF_8);
                        }
                        HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
                        // 执行XSS清理
                        log.info("{} - [URL:{}] XSS处理前参数:{}", method, uri.getPath(), bodyString);
                        bodyString = XssCleanRuleUtils.xssPostClean(bodyString);
                        log.info("{} - [URL:{}] XSS处理后参数:{}", method, uri.getPath(), bodyString);

                        ServerHttpRequest newRequest = serverHttpRequest.mutate().uri(uri).build();

                        // 重新构造body
                        byte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);
                        DataBuffer bodyDataBuffer = toDataBuffer(newBytes);
                        Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);

                        // 重新构造header
                        HttpHeaders headers = new HttpHeaders();
                        headers.putAll(httpHeaders);
                        // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
                        int length = newBytes.length;
                        headers.remove(HttpHeaders.CONTENT_LENGTH);
                        headers.setContentLength(length);
                        headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf8");
                        // 重写ServerHttpRequestDecorator,修改了body和header,重写getBody和getHeaders方法
                        newRequest = new ServerHttpRequestDecorator(newRequest) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return bodyFlux;
                            }

                            @Override
                            public HttpHeaders getHeaders() {
                                return headers;
                            }
                        };

                        return chain.filter(exchange.mutate().request(newRequest).build());
                    });
        } else {
            return chain.filter(exchange);
        }

    }


    /**
     * 自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }


    /**
     * 字节数组转DataBuffer
     *
     * @param bytes 字节数组
     * @return DataBuffer
     */
    private DataBuffer toDataBuffer(byte[] bytes) {
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }

}

参考资料

https://tech.meituan.com/2018/09/27/fe-security.html

你可能感兴趣的:(xss,前端)