前言
工作中渗透测试检测出,存储型XSS漏洞进行修复的记录。一、存储型XSS是什么?
应用程序通过Web请求获取不可信赖的数据,在未检验数据是否存在XSS代码的情况下,便将其存入数据库。当下一次从数据库中获取该数据时程序也未对其进行过滤,页面再次执行XSS代码,存储型XSS可以持续攻击用户。
二、给出的修复建议
遍历排查各文本交互位置(文本框)对以及URL中的参数(含有特殊字符<、>、‘、’等)进行过滤或者编码。要考虑用户输入通过应用可能用到的所有路径。例如,如果数据是由用户输入的,存储在数据库中,然后再重新显示,就必须要确保在每次检索的时候都能正确编码。如果必须允许自由格式文本输入(如在消息板中),而又希望允许使用一些HTML格式,则可以通过仅明确允许很小的安全标签列表来安全的处理这种情况。实施安全编程技术确保正确过滤用户提供的数据,并编码所有用户提供的数据以防以可执行的格式向终端用户发送注入的脚本。可通过仔细验证所有输入和正确编码所有输出来防范跨站脚本攻击。可使用标准的ASP.NET验证控件或直接在代码中实施验证,要尽可能使用严格的模版。输出编码要确保在将内容发送给客户端之前对任何可脚本化的内容都进行了正确的HTML编码。
三、修复过程
1、准备过滤器
public class RestServiceFilter implements Filter {
private final Logger logger = LogManager.getLogger(RestServiceFilter.class);
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String method = request.getMethod();
String contentType = request.getHeader("content-type");
if ("POST".equalsIgnoreCase(method) && (contentType.contains("application/json") || contentType.contains ("text/json"))) {
ServletRequest requestWrapper = new RestServiceRequestWrapper((HttpServletRequest) servletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
}
2、准备RestServiceRequestWrapper
public class RestServiceRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody = null;
public RestServiceRequestWrapper(HttpServletRequest request) {
super(request);
try {
StringBuilder sb = new StringBuilder();
BufferedReader br = request.getReader();
String str;
while ((str = br.readLine()) != null) {
sb.append(str);
}
br.close();
String body = sb.toString();
body = filterString(body);
requestBody = body.getBytes("UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* inputSteam
*
* @return
* @throws IOException
*/
@Override
public ServletInputStream getInputStream() throws IOException {
if (requestBody == null) {
requestBody = new byte[0];
}
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
}
/**
* getReader
*
* @return
* @throws IOException
*/
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
/**
* :
*
* @param s
* @return
*/
private String filterString(String s) {
String s1 = s.replace("<script", "").replace("<img", "");
return s1;
}
}
3、注册过滤器
@Bean
public FilterRegistrationBean<RestServiceFilter> restServiceFilterBean() {
FilterRegistrationBean<RestServiceFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new RestServiceFilter());
registrationBean.addUrlPatterns("/*"); // 这里配置需要过滤的URL
registrationBean.setName("restServiceFilter");
registrationBean.setOrder(4); // 设置过滤器的执行顺序,数字越小越优先
return registrationBean;
}