Springboot3巧妙运用拦截器阻断xss攻击

Springboot3巧妙运用拦截器阻断xss攻击

  • 什么是xss
    • 跨站脚本攻击
    • 类型
    • 简单示例
    • 解决方法
      • 拦截器
        • 代码
        • 使用demo

什么是xss

人们经常将跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。

跨站脚本攻击

是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。

类型

  1. 持久型跨站 : 危害最大,跨站脚本存储在服务器的数据持久层中—数据库;
  2. 非持久型跨站 : 攻击者伪造反射型跨站脚本连接,直接访问该连接返回跨站代码;
  3. DOM跨站 :通常是客户端处理文档对象模型出现的逻辑安全问题

简单示例

我自己做了一个存在xss漏洞的简单web网站
我需要的正常访问结果
正常访问的结果
意料之外的异常结果
非法访问的结果
发现我们可以在连接后面拼接js等脚本,这很有可能就给攻击者留了后门

解决方法

拦截器

每次请求我们先看看内容存不存在非法内容

代码

代码结构
核心代码

package com.zxs.xss;

import com.zxs.xss.filter.XssFilter;
import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;



@Configuration
@EnableConfigurationProperties({XssProperties.class})
public class XssAutoConfiguration {

	@Autowired
	XssProperties xssProperties;

	@Bean
	@ConditionalOnProperty(prefix = XssProperties.XSS, name = "enable", havingValue = "true")
	public FilterRegistrationBean xssFilterRegistration() {
		FilterRegistrationBean registration = new FilterRegistrationBean();
		registration.setDispatcherTypes(DispatcherType.REQUEST);
		registration.setFilter(new XssFilter());
		registration.addUrlPatterns(xssProperties.getUrlPatterns());
		registration.setName(xssProperties.getName());
		registration.setOrder(xssProperties.getOrder());
		System.out.println("拦截器启动 :"+xssProperties.getName()+" 状态");
		return registration;
	}
}

package com.zxs.xss.filter;

import com.zxs.xss.servlet.XssHttpServletRequestWrapper;
import com.zxs.xss.util.HtmlFilterKit;
import jakarta.servlet.*;

import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;

public class XssFilter implements Filter {

	@Override
	public void init(FilterConfig config) throws ServletException {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request,new HtmlFilterKit());
		System.out.println("拦截到:"+((HttpServletRequest) request).getMethod());
		chain.doFilter(xssRequest, response);
	}

	@Override
	public void destroy() {
	}
}

package com.zxs.xss.servlet;

import com.zxs.xss.util.HtmlFilterKit;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;


public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
	public static final String APPLICATION_JSON_VALUE = "application/json";
	public static final String CONTENT_TYPE = "Content-Type";

	/**
	 * 没被包装过的HttpServletRequest(特殊场景,需要自己过滤)
	 */
	HttpServletRequest orgRequest;
	/**
	 * html过滤
	 */
	private final  HtmlFilterKit htmlFilter;


	public XssHttpServletRequestWrapper(HttpServletRequest request,HtmlFilterKit htmlFilter) {
		super(request);
		this.htmlFilter = htmlFilter;
		orgRequest = request;
	}

	@Override
	public ServletInputStream getInputStream() throws IOException {
		//非json类型,直接返回
		if (!APPLICATION_JSON_VALUE.equalsIgnoreCase(super.getHeader(CONTENT_TYPE))) {
			return super.getInputStream();
		}

		//为空,直接返回
		String json = StreamUtils.copyToString(super.getInputStream(), Charset.forName("UTF-8"));
		if (StringUtils.isEmpty(json)) {
			return super.getInputStream();
		}

		//xss过滤
		json = xssEncode(json);
		final ByteArrayInputStream bis = new ByteArrayInputStream(json.getBytes("UTF-8"));
		return new ServletInputStream() {
			@Override
			public boolean isFinished() {
				return true;
			}

			@Override
			public boolean isReady() {
				return true;
			}

			@Override
			public void setReadListener(ReadListener readListener) {
			}

			@Override
			public int read() throws IOException {
				return bis.read();
			}
		};
	}

	@Override
	public String getParameter(String name) {
		String value = super.getParameter(xssEncode(name));
		if (!StringUtils.isEmpty(value)) {
			value = xssEncode(value);
		}
		return value;
	}

	@Override
	public String[] getParameterValues(String name) {
		String[] parameters = super.getParameterValues(name);
		if (parameters == null || parameters.length == 0) {
			return null;
		}

		for (int i = 0; i < parameters.length; i++) {
			parameters[i] = xssEncode(parameters[i]);
		}
		return parameters;
	}

	@Override
	public Map<String, String[]> getParameterMap() {
		Map<String, String[]> map = new LinkedHashMap<>();
		Map<String, String[]> parameters = super.getParameterMap();
		for (String key : parameters.keySet()) {
			String[] values = parameters.get(key);
			for (int i = 0; i < values.length; i++) {
				values[i] = xssEncode(values[i]);
			}
			map.put(key, values);
		}
		return map;
	}

	@Override
	public String getHeader(String name) {
		String value = super.getHeader(xssEncode(name));
		if (!StringUtils.isEmpty(value)) {
			value = xssEncode(value);
		}
		return value;
	}

	private String xssEncode(String input) {
		return htmlFilter.filter(input);
	}

	/**
	 * 获取最原始的request
	 */
	public HttpServletRequest getOrgRequest() {
		return orgRequest;
	}

	/**
	 * 获取最原始的request
	 */
	public static HttpServletRequest getOrgRequest(HttpServletRequest request) {
		if (request instanceof XssHttpServletRequestWrapper) {
			return ((XssHttpServletRequestWrapper) request).getOrgRequest();
		}
		return request;
	}
}

package com.zxs.xss.util;

public interface CharacterFilter {

    String filter(String input);
}

package com.zxs.xss.util;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class HtmlFilterKit implements CharacterFilter {

	/**
	 * regex flag union representing /si modifiers in php
	 **/
	private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL;
	private static final Pattern P_COMMENTS = Pattern.compile("<!--(.*?)-->", Pattern.DOTALL);
	private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI);
	private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL);
	private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI);
	private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI);
	private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI);
	private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI);
	private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI);
	private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?");
	private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?");
	private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?");
	private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))");
	private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL);
	private static final Pattern P_END_ARROW = Pattern.compile("^>");
	private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)");
	private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)");
	private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)");
	private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)");
	private static final Pattern P_AMP = Pattern.compile("&");
	private static final Pattern P_QUOTE = Pattern.compile("<");
	private static final Pattern P_LEFT_ARROW = Pattern.compile("<");
	private static final Pattern P_RIGHT_ARROW = Pattern.compile(">");
	private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>");

	/**
	 * @xxx could grow large... maybe use sesat's ReferenceMap
	 */
	private static final ConcurrentMap<String, Pattern> P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<String, Pattern>();
	private static final ConcurrentMap<String, Pattern> P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<String, Pattern>();

	/**
	 * set of allowed html elements, along with allowed attributes for each element
	 **/
	private final Map<String, List<String>> vAllowed;
	/**
	 * counts of open tags for each (allowable) html element
	 **/
	private final Map<String, Integer> vTagCounts = new HashMap<String, Integer>();

	/**
	 * html elements which must always be self-closing (e.g. "<img />")
	 **/
	private final String[] vSelfClosingTags;
	/**
	 * html elements which must always have separate opening and closing tags (e.g. "<b></b>")
	 **/
	private final String[] vNeedClosingTags;
	/**
	 * set of disallowed html elements
	 **/
	private final String[] vDisallowed;
	/**
	 * attributes which should be checked for valid protocols
	 **/
	private final String[] vProtocolAtts;
	/**
	 * allowed protocols
	 **/
	private final String[] vAllowedProtocols;

	private final String[] vRemoveBlanks;
	/**
	 * entities allowed within html markup
	 **/
	private final String[] vAllowedEntities;
	/**
	 * flag determining whether comments are allowed in input String.
	 */
	private final boolean stripComment;
	private final boolean encodeQuotes;
	private boolean vDebug = false;

	private final boolean alwaysMakeTags;

	/**
	 * Default constructor.
	 */
	public HtmlFilterKit() {
		vAllowed = new HashMap<>();

		final ArrayList<String> a_atts = new ArrayList<String>();
		a_atts.add("href");
		a_atts.add("target");
		vAllowed.put("a", a_atts);

		final ArrayList<String> img_atts = new ArrayList<String>();
		img_atts.add("src");
		img_atts.add("width");
		img_atts.add("height");
		img_atts.add("alt");
		vAllowed.put("img", img_atts);

		final ArrayList<String> no_atts = new ArrayList<String>();
		vAllowed.put("b", no_atts);
		vAllowed.put("strong", no_atts);
		vAllowed.put("i", no_atts);
		vAllowed.put("em", no_atts);

		vSelfClosingTags = new String[]{"img"};
		vNeedClosingTags = new String[]{"a", "b", "strong", "i", "em"};
		vDisallowed = new String[]{};
		vAllowedProtocols = new String[]{"http", "mailto", "https"}; // no ftp.
		vProtocolAtts = new String[]{"src", "href"};
		vRemoveBlanks = new String[]{"a", "b", "strong", "i", "em"};
		vAllowedEntities = new String[]{"amp", "gt", "lt", "quot"};
		stripComment = true;
		encodeQuotes = true;
		alwaysMakeTags = true;
	}

	/**
	 * Set debug flag to true. Otherwise use default settings. See the default constructor.
	 *
	 * @param debug turn debug on with a true argument
	 */
	public HtmlFilterKit(final boolean debug) {
		this();
		vDebug = debug;

	}

	/**
	 * Map-parameter configurable constructor.
	 *
	 * @param conf map containing configuration. keys match field names.
	 */
	public HtmlFilterKit(final Map<String, Object> conf) {
		assert conf.containsKey("vAllowed") : "configuration requires vAllowed";
		assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags";
		assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags";
		assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed";
		assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols";
		assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts";
		assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks";
		assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities";

		vAllowed = Collections.unmodifiableMap((HashMap<String, List<String>>) conf.get("vAllowed"));
		vSelfClosingTags = (String[]) conf.get("vSelfClosingTags");
		vNeedClosingTags = (String[]) conf.get("vNeedClosingTags");
		vDisallowed = (String[]) conf.get("vDisallowed");
		vAllowedProtocols = (String[]) conf.get("vAllowedProtocols");
		vProtocolAtts = (String[]) conf.get("vProtocolAtts");
		vRemoveBlanks = (String[]) conf.get("vRemoveBlanks");
		vAllowedEntities = (String[]) conf.get("vAllowedEntities");
		stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true;
		encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true;
		alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true;
	}

	private void reset() {
		vTagCounts.clear();
	}

	private void debug(final String msg) {
		if (vDebug) {
			Logger.getAnonymousLogger().info(msg);
		}
	}

	//---------------------------------------------------------------
	// my versions of some PHP library functions
	public static String chr(final int decimal) {
		return String.valueOf((char) decimal);
	}

	public static String htmlSpecialChars(final String s) {
		String result = s;
		result = regexReplace(P_AMP, "&amp;", result);
		result = regexReplace(P_QUOTE, "&quot;", result);
		result = regexReplace(P_LEFT_ARROW, "&lt;", result);
		result = regexReplace(P_RIGHT_ARROW, "&gt;", result);
		return result;
	}

	@Override
	public String filter(final String input) {
		reset();
		String s = input;

		debug("************************************************");
		debug("              INPUT: " + input);

		s = escapeComments(s);
		debug("     escapeComments: " + s);

		s = balanceHTML(s);
		debug("        balanceHTML: " + s);

		s = checkTags(s);
		debug("          checkTags: " + s);

		s = processRemoveBlanks(s);
		debug("processRemoveBlanks: " + s);

		s = validateEntities(s);
		debug("    validateEntites: " + s);

		debug("************************************************\n\n");
		return s;
	}

	public boolean isAlwaysMakeTags() {
		return alwaysMakeTags;
	}

	public boolean isStripComments() {
		return stripComment;
	}

	private String escapeComments(final String s) {
		final Matcher m = P_COMMENTS.matcher(s);
		final StringBuffer buf = new StringBuffer();
		if (m.find()) {
			final String match = m.group(1); //(.*?)
			m.appendReplacement(buf, Matcher.quoteReplacement("<!--" + htmlSpecialChars(match) + "-->"));
		}
		m.appendTail(buf);

		return buf.toString();
	}

	private String balanceHTML(String s) {
		if (alwaysMakeTags) {
			//
			// try and form html
			//
			s = regexReplace(P_END_ARROW, "", s);
			s = regexReplace(P_BODY_TO_END, "<$1>", s);
			s = regexReplace(P_XML_CONTENT, "$1<$2", s);

		} else {
			//
			// escape stray brackets
			//
			s = regexReplace(P_STRAY_LEFT_ARROW, "&lt;$1", s);
			s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2&gt;<", s);

			//
			// the last regexp causes '<>' entities to appear
			// (we need to do a lookahead assertion so that the last bracket can
			// be used in the next pass of the regexp)
			//
			s = regexReplace(P_BOTH_ARROWS, "", s);
		}

		return s;
	}

	private String checkTags(String s) {
		Matcher m = P_TAGS.matcher(s);

		final StringBuffer buf = new StringBuffer();
		while (m.find()) {
			String replaceStr = m.group(1);
			replaceStr = processTag(replaceStr);
			m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr));
		}
		m.appendTail(buf);

		s = buf.toString();

		// these get tallied in processTag
		// (remember to reset before subsequent calls to filter method)
		for (String key : vTagCounts.keySet()) {
			for (int ii = 0; ii < vTagCounts.get(key); ii++) {
				s += "</" + key + ">";
			}
		}

		return s;
	}

	private String processRemoveBlanks(final String s) {
		String result = s;
		for (String tag : vRemoveBlanks) {
			if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) {
				P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?></" + tag + ">"));
			}
			result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result);
			if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) {
				P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>"));
			}
			result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result);
		}

		return result;
	}

	private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) {
		Matcher m = regex_pattern.matcher(s);
		return m.replaceAll(replacement);
	}

	private String processTag(final String s) {
		// ending tags
		Matcher m = P_END_TAG.matcher(s);
		if (m.find()) {
			final String name = m.group(1).toLowerCase();
			if (allowed(name)) {
				if (!inArray(name, vSelfClosingTags)) {
					if (vTagCounts.containsKey(name)) {
						vTagCounts.put(name, vTagCounts.get(name) - 1);
						return "</" + name + ">";
					}
				}
			}
		}

		// starting tags
		m = P_START_TAG.matcher(s);
		if (m.find()) {
			final String name = m.group(1).toLowerCase();
			final String body = m.group(2);
			String ending = m.group(3);

			//debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" );
			if (allowed(name)) {
				String params = "";

				final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body);
				final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body);
				final List<String> paramNames = new ArrayList<String>();
				final List<String> paramValues = new ArrayList<String>();
				while (m2.find()) {
					paramNames.add(m2.group(1)); //([a-z0-9]+)
					paramValues.add(m2.group(3)); //(.*?)
				}
				while (m3.find()) {
					paramNames.add(m3.group(1)); //([a-z0-9]+)
					paramValues.add(m3.group(3)); //([^\"\\s']+)
				}

				String paramName, paramValue;
				for (int ii = 0; ii < paramNames.size(); ii++) {
					paramName = paramNames.get(ii).toLowerCase();
					paramValue = paramValues.get(ii);

//          debug( "paramName='" + paramName + "'" );
//          debug( "paramValue='" + paramValue + "'" );
//          debug( "allowed? " + vAllowed.get( name ).contains( paramName ) );

					if (allowedAttribute(name, paramName)) {
						if (inArray(paramName, vProtocolAtts)) {
							paramValue = processParamProtocol(paramValue);
						}
						params += " " + paramName + "=\"" + paramValue + "\"";
					}
				}

				if (inArray(name, vSelfClosingTags)) {
					ending = " /";
				}

				if (inArray(name, vNeedClosingTags)) {
					ending = "";
				}

				if (ending == null || ending.length() < 1) {
					if (vTagCounts.containsKey(name)) {
						vTagCounts.put(name, vTagCounts.get(name) + 1);
					} else {
						vTagCounts.put(name, 1);
					}
				} else {
					ending = " /";
				}
				return "<" + name + params + ending + ">";
			} else {
				return "";
			}
		}

		// comments
		m = P_COMMENT.matcher(s);
		if (!stripComment && m.find()) {
			return "<" + m.group() + ">";
		}

		return "";
	}

	private String processParamProtocol(String s) {
		s = decodeEntities(s);
		final Matcher m = P_PROTOCOL.matcher(s);
		if (m.find()) {
			final String protocol = m.group(1);
			if (!inArray(protocol, vAllowedProtocols)) {
				// bad protocol, turn into local anchor link instead
				s = "#" + s.substring(protocol.length() + 1, s.length());
				if (s.startsWith("#//")) {
					s = "#" + s.substring(3, s.length());
				}
			}
		}

		return s;
	}

	private String decodeEntities(String s) {
		StringBuffer buf = new StringBuffer();

		Matcher m = P_ENTITY.matcher(s);
		while (m.find()) {
			final String match = m.group(1);
			final int decimal = Integer.decode(match).intValue();
			m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
		}
		m.appendTail(buf);
		s = buf.toString();

		buf = new StringBuffer();
		m = P_ENTITY_UNICODE.matcher(s);
		while (m.find()) {
			final String match = m.group(1);
			final int decimal = Integer.valueOf(match, 16).intValue();
			m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
		}
		m.appendTail(buf);
		s = buf.toString();

		buf = new StringBuffer();
		m = P_ENCODE.matcher(s);
		while (m.find()) {
			final String match = m.group(1);
			final int decimal = Integer.valueOf(match, 16).intValue();
			m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal)));
		}
		m.appendTail(buf);
		s = buf.toString();

		s = validateEntities(s);
		return s;
	}

	private String validateEntities(final String s) {
		StringBuffer buf = new StringBuffer();

		// validate entities throughout the string
		Matcher m = P_VALID_ENTITIES.matcher(s);
		while (m.find()) {
			final String one = m.group(1); //([^&;]*)
			final String two = m.group(2); //(?=(;|&|$))
			m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two)));
		}
		m.appendTail(buf);

		return encodeQuotes(buf.toString());
	}

	private String encodeQuotes(final String s) {
		if (encodeQuotes) {
			StringBuffer buf = new StringBuffer();
			Matcher m = P_VALID_QUOTES.matcher(s);
			while (m.find()) {
				final String one = m.group(1); //(>|^)
				final String two = m.group(2); //([^<]+?)
				final String three = m.group(3); //(<|$)
				m.appendReplacement(buf, Matcher.quoteReplacement(one + regexReplace(P_QUOTE, "&quot;", two) + three));
			}
			m.appendTail(buf);
			return buf.toString();
		} else {
			return s;
		}
	}

	private String checkEntity(final String preamble, final String term) {

		return ";".equals(term) && isValidEntity(preamble)
				? '&' + preamble
				: "&amp;" + preamble;
	}

	private boolean isValidEntity(final String entity) {
		return inArray(entity, vAllowedEntities);
	}

	private static boolean inArray(final String s, final String[] array) {
		for (String item : array) {
			if (item != null && item.equals(s)) {
				return true;
			}
		}
		return false;
	}

	private boolean allowed(final String name) {
		return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed);
	}

	private boolean allowedAttribute(final String name, final String paramName) {
		return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName));
	}
}

package com.zxs.xss;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = XssProperties.XSS)
public class XssProperties {
	public static final String XSS = "xss";

	/**
	 * xss 是否生效
	 */
	boolean enable = false;
	/**
	 * xss过滤器的名字
	 */
	String name = "xssFilter";
	/**
	 * xss过滤器需要匹配的路径
	 */
	String[] urlPatterns = {"/*"};

	/**
	 * 过滤器的优先级,值越小优先级越高
	 */
	int order = 0;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String[] getUrlPatterns() {
		return urlPatterns;
	}

	public void setUrlPatterns(String[] urlPatterns) {
		this.urlPatterns = urlPatterns;
	}

	public int getOrder() {
		return order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	public boolean isEnable() {
		return enable;
	}

	public void setEnable(boolean enable) {
		this.enable = enable;
	}
}

package com.zxs.xss.exception;

public class XSSException extends RuntimeException {
    private String msg;
    private int code = 500;

    public XSSException(String msg) {
        super(msg);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}

com.zxs.xss.XssAutoConfiguration
<?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>

    <groupId>com.zxs</groupId>
    <artifactId>zxs-xss-starter</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>

    <name>zxs-xss-starter</name>
    <description>为SpringbootWeb应用打造的Xss防护工具</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.0</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>21</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <optional>true</optional>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

使用demo

将上面的插件包打包引入到我们的demo包里面

   <dependency>
            <groupId>com.bizihang</groupId>
            <artifactId>zxs-xss-starter</artifactId>
            <version>0.0.1</version>
        </dependency>
# 开启xss防护
xss.enable=true
# 设置xss防护的url拦截路径
xss.url-patterns=/zxs/noxss-json
# 设置xss防护过滤器的优先级,值越小优先级越高
xss.order=0
package com.example.demo.controller;

import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.tags.Param;

@RequestMapping("/zxs")
@RestController
public class XSSTestController {


    @GetMapping("/xss-string")
    public Object xssFormTest(String value) {
        System.out.println(value);
        return value;
    }

    @PostMapping("/noxss-json")
    public Object xssJsonTest(@RequestBody Param param ) {
        return param;
    }
}

启动之后就可以了

通过例子可以看到我这边实现了一个存在xss的api与一个被拦截器保护的api

在这里插入图片描述
可以看到被保护的api是会过滤掉我们代码里面定义的非法字符

没被保护的api还是正常的出现xss

在这里插入图片描述
然后我们把
xss.url-patterns=/* 全保护起来,再次进入之前存在xss的api
在这里插入图片描述
发现已经被拦截了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/951482.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DAY39|动态规划Part07|LeetCode:198.打家劫舍、213.打家劫舍II、337.打家劫舍III

目录 LeetCode:198.打家劫舍 基本思路 C代码 LeetCode:213.打家劫舍II 基本思路 C代码 LeetCode:337.打家劫舍III 基本思路 C代码 LeetCode:198.打家劫舍 力扣题目链接 文字讲解&#xff1a;LeetCode:198.打家劫舍 视频讲解&#xff1a;动态规划&#xff0c;偷不偷这个…

数据结构——栈的实现

今天&#xff0c;我们来写一下关于栈的博文。 1.首先我们先了解一下什么是栈&#xff1f; 一&#xff1a;概念&#xff1a; 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端称为栈顶&#xff0c;另…

uniapp 的uni.getRecorderManager() 录音功能小记

官网上明确说的是全局唯一并且只是获取对象&#xff0c;所以会导致一个问题就是&#xff0c;当你多个页面要用到这个对象的时候&#xff0c;会发现 onStop 方法会被覆盖&#xff0c;导致调用结果不是自己想要的 解决办法也简单粗暴&#xff0c;在需要用到的界面重新覆盖onStop…

Unity:删除注册表内的项目记录

然后WinR按键输入regedit 打开注册表 在注册表 HKEY CURRENT USER—>SOFTWARE—>Unity—>UnityEditor—>DefaultCompany —>language_Test 中&#xff0c;删除我们的之前存储的语言环境数据。在 “ 三、文本调用和替换 ” 测试时已经将语言环境存储到注册表中了…

标准应用 | 2025年网络安全服务成本度量实施参考

01 网络安全服务成本度量依据相关新变化 为了解决我国网络安全服务产业发展中面临的服务供需两方对于服务成本组成认知偏差较大、网络安全服务成本度量缺乏依据的问题&#xff0c;中国网络安全产业联盟&#xff08;CCIA&#xff09;组织北京赛西科技发展有限责任公司、北京安…

微信小程序map组件所有markers展示在视野范围内

注意&#xff1a;使用include-points属性不生效&#xff0c;要通过createMapContext实现 <template><view class"map-box"><map id"map" class"map" :markers"markers" :enable-traffic"true" :enable-poi&…

PLC实现HTTP协议JSON格式数据上报对接的参数配置说明

IGT-SER系列PLC通讯智能网关支持HTTP协议GET和POST、PUT请求模式。支持JSON格式的文件&#xff0c;也可以实现WebService的调用。 通常智能网关是HTTP协议的客户端&#xff0c;也可以同时作为HTTP的服务端。相关案例 作为客户端时支持触发、周期、混合等多种工…

微信小程序——创建滑动颜色条

在微信小程序中&#xff0c;你可以使用 slider 组件来创建一个颜色滑动条。以下是一个简单的示例&#xff0c;展示了如何实现一个颜色滑动条&#xff0c;该滑动条会根据滑动位置改变背景颜色。 步骤一&#xff1a;创建小程序项目 首先&#xff0c;使用微信开发者工具创建一个新…

Improving Language Understanding by Generative Pre-Training GPT-1详细讲解

Improving Language Understanding by Generative Pre-Training 2018.06 GPT-1 0.有监督、半监督、无监督 CV&#xff1a;ImageNet pre-trained model NLP&#xff1a;pre-trained model? 在计算机视觉中任务包含分类、检测、分割&#xff0c;任务类别数少&#xff0c;对应…

sql server cdc漏扫数据

SQL Server的CDC指的是“变更数据捕获”&#xff08;Change Data Capture&#xff09;。这是SQL Server数据库提供的一项功能&#xff0c;能够跟踪并记录对数据库表中数据所做的更改。这些更改包括插入、更新和删除操作。CDC可以捕获这些变更的详细信息&#xff0c;并使这些信息…

如何在 Ubuntu 22.04 上安装 Caddy Web 服务器教程

简介 Caddy 是一个开源的 Web 服务器&#xff0c;它支持静态和现代 Web 应用程序&#xff0c;使用预定义的配置规则&#xff0c;并为所有链接的域名自动启用 HTTPS。Caddy 使用 GO 语言编写&#xff0c;提供了用户友好的配置指令&#xff0c;使你既可以将其用作 Web 服务器&am…

《机器学习》——贝叶斯算法

贝叶斯简介 贝叶斯公式&#xff0c;又称贝叶斯定理、贝叶斯法则&#xff0c;最初是用来描述两个事件的条件概率间的关系的公式&#xff0c;后来被人们发现具有很深刻的实际意义和应用价值。该公式的实际内涵是&#xff0c;支持某项属性的事件发生得愈多&#xff0c;则该属性成…

边缘计算网关在机床设备数据采集中的应用

边缘计算网关是连接边缘设备和云端的一个中间节点&#xff0c;负责在边缘设备和云服务器之间进行数据传输和处理。它具备数据采集、数据处理、协议转换、数据存储、安全功能及远程管理等多种能力&#xff0c;是边缘计算系统中不可或缺的关键设备。 一、功能与优势 数据采集&a…

腾讯二面:MySQL的半同步是什么?不是MySQL的两阶段提交,那是什么?

前言 年后在进行腾讯二面的时候&#xff0c;写完算法的后问的第一个问题就是&#xff0c;MySQL的半同步是什么&#xff1f;我当时直接懵了&#xff0c;我以为是问的MySQL的两阶段提交的问题呢&#xff1f;结果确认了一下后不是两阶段提交&#xff0c;然后面试官看我连问的是啥都…

云计算基础,虚拟化原理

文章目录 一、虚拟化1.1 什么是虚拟化1.2 虚拟化类型 二 、存储虚拟化2.1 存储指标2.2 存储类型2.3 存储协议2.4 RAID 三、内存 i/O虚拟化3.1 内存虚拟化基本概念地址空间转换原理内存共享与隔离原理 3.2 I/O 虚拟化基本概念模拟&#xff08;Emulation&#xff09;方式半虚拟化…

【网络协议】IPv4 地址分配 - 第二部分

前言 在第 1 部分中&#xff0c;我们学习了 IPv4 地址的分配方式&#xff0c;了解了各种类型的 IPv4 地址&#xff0c;并进行了基础的子网划分&#xff08;Subnetting&#xff09;。在第 2 部分中&#xff0c;我们将继续学习子网划分&#xff0c;并引入一些新的概念。 【网络…

JAVA 使用apache poi实现EXCEL文件的输出;apache poi实现标题行的第一个字符为红色;EXCEL设置某几个字符为别的颜色

设置输出文件的列宽&#xff0c;防止文件过于丑陋 Sheet sheet workbook.createSheet(FileConstants.ERROR_FILE_SHEET_NAME); sheet.setColumnWidth(0, 40 * 256); sheet.setColumnWidth(1, 20 * 256); sheet.setColumnWidth(2, 20 * 256); sheet.setColumnWidth(3, 20 * 25…

Cursor 实战技巧:好用的提示词插件Cursor Rules

你好啊&#xff0c;见字如面。感谢阅读&#xff0c;期待我们下一次的相遇。 最近在小红书发现了有人分享这款Cursor提示词的插件&#xff0c;下面给各位分享下使用教程。简单来说Cursor Rules就是可以为每一个我们自己的项目去配置一个系统级别的提示词&#xff0c;这样在我们…

【简博士统计学习方法】第1章:3. 统计学习方法的三要素

3. 统计学习方法的三要素 3.1 监督学习的三要素 3.1.1 模型 假设空间&#xff08;Hypothesis Space&#xff09;&#xff1a;所有可能的条件概率分布或决策函数&#xff0c;用 F \mathcal{F} F表示。 若定义为决策函数的集合&#xff1a; F { f ∣ Y f ( X ) } \mathcal{F…

60.在 Vue 3 中使用 OpenLayers 绘制自由线段、自由多边形

前言 在现代 Web 开发中&#xff0c;地图功能已经成为许多应用的重要组成部分。OpenLayers 是一个强大的开源地图库&#xff0c;支持多种地图源和地图操作。结合 Vue 3 的响应式特性&#xff0c;我们可以轻松实现地图的交互功能。本文将详细介绍如何在 Vue 3 中使用 OpenLayer…