Java web防护xss/sql注入的正确姿势

Java web防护xss/sql注入的正确姿势

这里以springboot搭建的微服务为例,可以在网关中自定义全局拦截器,对入参进行过滤。防护的方法有很多,这里以黑名单为例,暂定项目中只存在POST和GET两种传参:

自定义防XSS/SQL注入攻击网关全局过滤器

package com.javee.getway.filter;

import com.javee.getway.common.constant.WebBaseConstant;
import com.javee.getway.common.model.ResponseData;
import com.javee.getway.utils.CommonJsonUtil;
import com.javee.getway.utils.CommonStringUtil;
import com.javee.getway.utils.MonoUtil;
import com.javee.getway.utils.XssSqlCleanRuleUtils;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
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.HttpStatus;
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: Javee
 * @Date: 2021/8/15 16:41
 * @Description: 自定义防XSS/SQL注入攻击网关全局过滤器
 */
@Slf4j
@Component
public class O9_XssSqlRequestGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // grab configuration from Config object
        log.debug("----自定义防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)
                    || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.toLowerCase().equalsIgnoreCase(contentType));

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

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

            log.debug("原请求参数为:{}", rawQuery);
            // 执行XSS清理
            rawQuery = XssSqlCleanRuleUtils.xssClean(rawQuery, true);
            log.debug("修改后参数为:{}", rawQuery);

            //    如果存在sql注入,直接拦截请求
            if (rawQuery.contains("forbid")) {
                log.error("请求【" + uri.getRawPath() + uri.getRawQuery() + "】参数中包含不允许sql的关键词, 请求拒绝");
                String msg = CommonJsonUtil.toJson(ResponseData.instance(WebBaseConstant.ResponseCode.FAILD, "系统检测到恶意攻击,已将你的信息进行固定!!!"));
                return MonoUtil.generateMono(exchange.getResponse(),msg, HttpStatus.OK);
            }

            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);
                String msg = CommonJsonUtil.toJson(ResponseData.instance(WebBaseConstant.ResponseCode.FAILD, "系统检测到恶意攻击,已将你的信息进行固定!!!"));
                return MonoUtil.generateMono(exchange.getResponse(),msg, HttpStatus.OK);
            }
        }
        //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();
                        ServerHttpRequest newRequest = serverHttpRequest.mutate().uri(uri).build();
                        // 执行XSS清理
                        log.debug("{} - XSS处理前 [{}:{}]", method, uri.getPath(), bodyString);
                        bodyString = XssSqlCleanRuleUtils.xssClean(bodyString, false);
                        log.info("{} - XSS处理后 [{}:{}] ", method, uri.getPath(), bodyString);

                        //  如果存在sql注入,直接拦截请求
                        if (bodyString.contains("forbid")) {
                            log.error("{} - [{}:{}] 参数:{}, 包含不允许sql的关键词,请求拒绝", method, uri.getPath(), bodyString);
                            // 这里可以自由发挥,ip拉黑之类的都可以
                            String msg = CommonJsonUtil.toJson(ResponseData.instance(WebBaseConstant.ResponseCode.FAILD, "系统检测到恶意攻击,已将你的信息进行固定!!!"));
                            return MonoUtil.generateMono(exchange.getResponse(),msg, HttpStatus.OK);
                        }

                        // 重新构造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 -90;
    }

    /**
     * 字节数组转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;
    }

}

xss过滤/sql注入监测工具类:

package com.javee.getway.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * @Author: Javee
 * @Date: 2021/8/15 16:44
 * @Description: xss过滤/sql注入监测工具
 */
@Slf4j
public class XssSqlCleanRuleUtils {
    private final static Pattern[] scriptPatterns = {
            Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("target[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("<script(.*?)>", 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),
            Pattern.compile("onabort(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onblur(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onchange(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onclick(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("ondbclick(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onerror(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onfocus(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onmousedown(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onmouseup(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onmousemove(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onmouseout(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onmouseover(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onselect(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
            Pattern.compile("onunload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
    };

    private static String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";

    private static Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);//整体都忽略大小写

    /**
     * GET请求参数过滤
     * @param value
     * @return
     */
    public static String xssClean(String value, boolean isGet)  {

        //过滤xss字符集
        if (value != null) {
            value = value.replaceAll("\0|\n|\r", "");
            for (Pattern pattern : scriptPatterns) {
                value = pattern.matcher(value).replaceAll("");
            }
        }

        return cleanSqlKeyWords(value, isGet);

    }

    /**
     * 解析参数SQL关键字
     * @param value
     * @return
     */
    private static String cleanSqlKeyWords(String value, boolean isGet)  {

        //参数需要url编码
        //这里需要将参数转换为小写来处理
        //不改变原值
        //value示例 order=asc&pageNum=1&pageSize=100&parentId=0
        String splitChar = isGet ? "=" : ":";
        String lowerValue = null;
        try {
            lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        //获取到请求中所有参数值-取每个key=value组合第一个等号后面的值
        boolean isContains = Stream.of(lowerValue.split("\\&"))
                .map(kp -> kp.substring(kp.indexOf(splitChar) + 1))
                .parallel()
                .anyMatch(param -> {
                    if (sqlPattern.matcher(param).find())
                    {
                        log.error("参数中包含不允许sql的关键词");
                        return true;
                    }
                    return false;
                });

        return isContains ? "forbid" : value;
    }

}

效果解读:入参中如果存在xss黑名单中的关键词,会直接替换为空字符串,如果入参的sql正则校验不通过(存在sql注入敏感词),则返回给前端,并进行警告(可自定义操作)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 你可以使用Java的JDBC API来读取SQL脚本并执行。以下是一个简单的示例代码: ```java import java.io.BufferedReader; import java.io.FileReader; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class SqlScriptExecutor { public static void main(String[] args) throws Exception { // 读取SQL脚本文件 BufferedReader reader = new BufferedReader(new FileReader("script.sql")); String line = null; StringBuilder sql = new StringBuilder(); while ((line = reader.readLine()) != null) { sql.append(line); sql.append("\n"); } reader.close(); // 连接数据库并执行SQL脚本 Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password"); Statement stmt = conn.createStatement(); stmt.executeUpdate(sql.toString()); stmt.close(); conn.close(); } } ``` 在上面的代码中,我们首先使用`BufferedReader`类读取SQL脚本文件,并将其存储在一个`StringBuilder`对象中。然后,我们使用JDBC API连接到数据库,并使用`Statement`对象执行SQL脚本。最后,我们关闭`Statement`和`Connection`对象以释放资源。 ### 回答2: java读取sql脚本并执行的代码可以使用JDBC(Java Database Connectivity)来实现。首先,我们需要创建一个JDBC连接,连接到数据库。然后,我们使用BufferedReader来读取sql脚本文件中的内容,并将其逐行存储在一个字符串中。接下来,我们可以使用JDBC的Statement或PreparedStatement对象来执行这些sql语句。 下面是一个简单的示例代码: ```java import java.io.BufferedReader; import java.io.FileReader; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class SqlScriptRunner { public static void main(String[] args) { String jdbcUrl = "jdbc:mysql://localhost:3306/mydatabase"; // 数据库连接URL String username = "username"; // 数据库用户名 String password = "password"; // 数据库密码 String scriptPath = "path/to/sql/script.sql"; // sql脚本文件路径 try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password); BufferedReader reader = new BufferedReader(new FileReader(scriptPath)); Statement statement = connection.createStatement()) { StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); sb.append(System.lineSeparator()); // 每行结束加入换行符 } String script = sb.toString(); statement.execute(script); System.out.println("Sql脚本执行成功!"); } catch (Exception e) { e.printStackTrace(); } } } ``` 在上面的代码中,我们使用了try-with-resources语句来确保在使用后自动关闭数据库连接和文件读取器,并通过StringBuilder来构建sql脚本字符串。然后,我们使用Statement对象的execute方法来执行sql脚本。 请注意,这只是一个简单的示例,如果你的sql脚本包含有特殊的语句(如存储过程、触发器、函数等),则可能需要进行额外的处理。此外,为了安全起见,建议对从外部读取的sql脚本进行一定的验证和过滤,以防止SQL注入安全问题的出现。 ### 回答3: 下面是一个用Java编写的读取SQL脚本并执行的代码: ```java import java.io.*; import java.sql.*; public class SQLScriptExecutor { public static void main(String[] args) { try { // 创建数据库连接 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password"); // 读取SQL脚本文件 File sqlScriptFile = new File("script.sql"); BufferedReader reader = new BufferedReader(new FileReader(sqlScriptFile)); // 构建SQL语句 StringBuilder sqlScript = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sqlScript.append(line); sqlScript.append(" "); } // 执行SQL脚本 Statement statement = connection.createStatement(); statement.executeUpdate(sqlScript.toString()); // 关闭数据库连接 statement.close(); connection.close(); System.out.println("SQL脚本执行成功!"); } catch (IOException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } ``` 上述代码首先创建一个数据库连接,并使用`getConnection`方法根据数据库URL、用户名和密码获取连接。然后,打开并读取SQL脚本文件,逐行读取内容,并使用`StringBuilder`构建SQL语句。接下来,通过创建一个`Statement`对象,利用`executeUpdate`方法执行SQL脚本并更新数据库。最后,关闭数据库连接和文件读取器。 请注意,你需要将代码中的`"jdbc:mysql://localhost:3306/mydatabase"`替换为您的实际数据库URL,以及将`"username"`和`"password"`替换为您的数据库用户名和密码。另外,你需要将`"script.sql"`替换为实际的SQL脚本文件路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值