Java xss攻击拦截,Java CSRF跨站点伪造请求拦截
一、CSRF
1.CSRF的基本概念、缩写、全称
CSRF(Cross-site request forgery):跨站请求伪造。
PS:中文名一定要记住。英文全称,如果记不住也拉倒。
2.CSRF的攻击原理
用户是网站A的注册用户,且登录进去,于是网站A就给用户下发cookie。
从上图可以看出,要完成一次CSRF攻击,受害者必须满足两个必要的条件:
(1)登录受信任网站A,并在本地生成Cookie。(如果用户没有登录网站A,那么网站B在诱导的时候,请求网站A的api接口时,会提示你登录)
(2)在不登出A的情况下,访问危险网站B(其实是利用了网站A的漏洞)。
温馨提示一下,cookie保证了用户可以处于登录状态,但网站B其实拿不到 cookie。
3、CSRF如何防御
方法一、Token 验证:(用的最多)
(1)服务器发送给客户端一个token;
(2)客户端提交的表单中带着这个token。
(3)如果这个 token 不合法,那么服务器拒绝这个请求。
方法二:隐藏令牌:
把 token 隐藏在 http 的 head头中。
方法二和方法一有点像,本质上没有太大区别,只是使用方式上有区别。
方法三、Referer 验证:
Referer 指的是页面请求来源。意思是,只接受本站的请求,服务器才做响应;如果不是,就拦截。
二、XSS
1、XSS的基本概念
XSS(Cross Site Scripting):跨域脚本攻击。
XSS的攻击原理
XSS攻击的核心原理是:不需要你做任何的登录认证,它会通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等)。
最后导致的结果可能是:
盗用Cookie破坏页面的正常结构,插入广告等恶意内容D-doss攻击
XSS的攻击方式
1、反射型
发出请求时,XSS代码出现在url中,作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,最后浏览器解析执行XSS代码。这个过程像一次反射,所以叫反射型XSS。
2、存储型存
储型XSS和反射型XSS的差别在于,提交的代码会存储在服务器端(数据库、内存、文件系统等),下次请求时目标页面时不用再提交XSS代码。
XSS的防范措施主要有三个:
1、编码:
对用户输入的数据进行
HTML Entity 编码。
如上图所示,把字符转换成 转义字符。
Encode的作用是将
$var
等一些字符进行转化,使得浏览器在最终输出结果上是一样的。
比如说这段代码:
<script>alert(1)</script>
若不进行任何处理,则浏览器会执行alert的js操作,实现XSS注入。
进行编码处理之后,L在浏览器中的显示结果就是
<script>alert(1)</script>
,实现了将$var作为纯文本进行输出,且不引起JavaScript的执行。
2、过滤:
移除用户输入的和事件相关的属性。如onerror可以自动触发攻击,还有onclick等。(总而言是,过滤掉一些不安全的内容)移除用户输入的Style节点、Script节点、Iframe节点。(尤其是Script节点,它可是支持跨域的呀,一定要移除)。
3、校正
避免直接对HTML Entity进行解码。使用DOM Parse转换,校正不配对的DOM标签。备注:我们应该去了解一下
DOM Parse
这个概念,它的作用是把文本解析成DOM结构。
比较常用的做法是,通过第一步的编码转成文本,然后第三步转成DOM对象,然后经过第二步的过滤。
还有一种简洁的答案:
首先是encode,如果是富文本,就白名单。
三、Java xss攻击拦截
XssFilter过滤器
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class XssFilter implements Filter{
FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XssWrapper((HttpServletRequest) request), response);
}
}
XssWrapper类:
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import com.szpl.csgx.utils.JsonUtil;
public class XssWrapper extends HttpServletRequestWrapper {
public static final String JSON_TYPE = "application/json";
public static final String CONTENT_TYPE = "Content-Type";
public static final String CHARSET = "UTF-8";
private String mBody;
HttpServletRequest originalRequest = null;
public XssWrapper(HttpServletRequest request) throws IOException {
super(request);
// 将body数据存储起来
originalRequest = request;
setRequestBody(request.getInputStream());
}
/**
* 获取最原始的request。已经被getInputStream()了。
*
* @return
*/
public HttpServletRequest getOrgRequest() {
return originalRequest;
}
/**
* 获取最原始的request的静态方法。已经被getInputStream()了。
*
* @return
*/
public static HttpServletRequest getOriginalRequest(HttpServletRequest req) {
if (req instanceof XssWrapper) {
return ((XssWrapper) req).getOrgRequest();
}
return req;
}
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if(StringUtils.isBlank(value)) {
return value;
}
return StringEscapeUtils.escapeHtml4(value);
}
@Override
public String getQueryString() {
return StringUtils.isBlank(super.getQueryString()) ? "" : StringEscapeUtils.escapeHtml4(super.getQueryString());
}
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if(StringUtils.isBlank(value)) {
return value;
}
return StringEscapeUtils.escapeHtml4(value);
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) {
return values;
}
for (int i=0; i < values.length; i++) {
values[i] = StringEscapeUtils.escapeHtml4(values[i]);
}
return values;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> map = new LinkedHashMap<String, String[]>();
Map<String, String[]> parameterMap = super.getParameterMap();
if(parameterMap == null) {
return super.getParameterMap();
}
for (String key : parameterMap.keySet()) {
String[] values = parameterMap.get(key);
if(values != null && values.length > 0) {
for (int i = 0; i < values.length; i++) {
values[i] = StringEscapeUtils.escapeHtml4(values[i]);
}
}
map.put(key, values);
}
return map;
}
private void setRequestBody(InputStream stream) {
String line = "";
StringBuilder body = new StringBuilder();
// 读取POST提交的数据内容
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.forName(CHARSET)));
try {
while ((line = reader.readLine()) != null) {
body.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
mBody = body.toString();
if(StringUtils.isBlank(mBody)) {//为空时,直接返回
return;
}
@SuppressWarnings("unchecked")
Map<String,Object> map= JsonUtil.string2Obj(mBody, Map.class);
Map<String,Object> resultMap=new HashMap<>(map.size());
for(String key : map.keySet()){
Object val = map.get(key);
if(map.get(key) instanceof String){
resultMap.put(key, StringEscapeUtils.escapeHtml4(val.toString()));
}else{
resultMap.put(key, val);
}
}
mBody = JsonUtil.obj2String(resultMap);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
if(!JSON_TYPE.equalsIgnoreCase(super.getHeader(CONTENT_TYPE))) {//非json类型,直接返回
return super.getInputStream();
}
if(StringUtils.isBlank(mBody)) {//为空时,直接返回
return super.getInputStream();
}
final ByteArrayInputStream bais = new ByteArrayInputStream(mBody.getBytes(CHARSET));
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
}
}
四、Java CSRF跨站点伪造请求拦截(这里用的是Referer )
import java.io.IOException;
import java.net.URL;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import cn.hutool.json.JSONUtil;
/**
* CSRF跨站点伪造请求拦截
*/
@Component
public class CsrfFilter implements Filter {
//后台日志打印
private Logger log = LoggerFactory.getLogger(CsrfFilter.class);
//跨站点请求白名单,通过英文逗号分隔。在application.properties配置
@Value("${csrf.white.paths}")
private String[] csrfWhitePaths;
//跨站点请求域名白名单,通过英文逗号分隔。在application.properties配置
@Value("${csrf.white.domains}")
private String[] csrfWhiteDomains;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("init……");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String referer = req.getHeader("Referer");
if (!StringUtils.isBlank(referer)) {
//log.info("referer = " + referer);
URL refererUrl = new URL(referer);
String refererHost = refererUrl.getHost();
int refererPort = refererUrl.getPort();
String refererHostAndPort;
if(refererPort == -1) {
refererHostAndPort = refererHost;
}else {
refererHostAndPort = refererHost + ":" + refererPort;
}
//log.info("refererHostAndPort = " + refererHostAndPort);
//log.info("refererHost = " + refererHost);
String requestURL = req.getRequestURL().toString();
//log.info("requestURL = " + requestURL);
URL urlRequest = new URL(requestURL);
String requestHost = urlRequest.getHost();
int requestPort = urlRequest.getPort();
String requestHostAndPort;
if(requestPort == -1) {
requestHostAndPort = requestHost;
}else {
requestHostAndPort = requestHost + ":" + requestPort;
}
//log.info("requestHost = " + requestHost);
if(requestHostAndPort.equalsIgnoreCase(refererHostAndPort)) {//同域名和同端口,即同一个域的系统,通过
filterChain.doFilter(request, response);
}else {
if(isCsrfWhiteDomains(refererHostAndPort)) {//域名白名单
filterChain.doFilter(request, response);
return;
}
String path = urlRequest.getPath();
log.info("path = " + path);
String actionPath = path.replaceAll(request.getServletContext().getContextPath(), "");
log.info("actionPath = " + actionPath);
if(isCsrfWhitePaths(actionPath)) {//访问路径白名单
filterChain.doFilter(request, response);
return;
}
log.warn("csrf跨站点伪造请求已经被拦截:");
log.warn("requestURL = " + requestURL);
log.warn("referer = " + referer);
res.sendRedirect(req.getContextPath() + "/illegal");
return;
}
}else{
filterChain.doFilter(request, response);
}
}
@Override
public void destroy() {
log.info("destroy……");
}
/**
* 本系统不拦截的路径白名单
* @param path
* @return
*/
private boolean isCsrfWhitePaths(String path) {
if(csrfWhitePaths != null && csrfWhitePaths.length > 0) {
for (String csrfWhitePath : csrfWhitePaths) {
if(!StringUtils.isBlank(csrfWhitePath)) {
if(csrfWhitePath.equals(path)) {
log.info("跨站点请求所有路径白名单:csrfWhitePaths = " + JSONUtil.toJsonStr(csrfWhitePaths));
log.info("符合跨站点请求路径白名单:path = " + path);
return true;
}
}
}
}
return false;
}
/**
* 不拦截外部系统的域名(可带端口)白名单
* @param path
* @return
*/
private boolean isCsrfWhiteDomains(String refererHostAndPort) {
if(csrfWhiteDomains != null && csrfWhiteDomains.length > 0) {
for (String csrfWhiteDomain : csrfWhiteDomains) {
if(!StringUtils.isBlank(csrfWhiteDomain)) {
if(csrfWhiteDomain.equals(refererHostAndPort)) {
log.info("跨站点请求所有【域名】]白名单:csrfWhiteDomains = " + JSONUtil.toJsonStr(csrfWhiteDomains));
log.info("符合跨站点请求【域名】白名单:refererHost = " + refererHostAndPort);
return true;
}
}
}
log.info("跨站点请求非法【域名】:refererHost = " + refererHostAndPort);
}
return false;
}
}
配置文件(我这里以本地为主):
csrf:
white:
paths: #跨站点请求路径白名单,通过英文逗号分隔。如(/illegal,/illegal2,/illegal3)
domains: 127.0.0.1:9003,127.0.0.1:9528 #跨站点请求域名白名单,通过英文逗号分隔。如(abc.com:9010,abc.org:9010)
五、SpringBoot注册过滤器(可以不要,根据项目而定,本次项目未使用)
import javax.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.szpl.csgx.security.CsrfFilter;
import com.szpl.csgx.security.XssFilter;
/**
* 使用配置方式开发Filter,否则其中的自动注入无效
*
*/
@Configuration
public class HttpFilterConfig {
/**
* xss攻击过滤器
* @return
*/
@Bean
public FilterRegistrationBean<Filter> xssFilter() {
FilterRegistrationBean<Filter> XssBean = new FilterRegistrationBean<>(new XssFilter());
XssBean.setName("xssFilter");
XssBean.addUrlPatterns("/*");
XssBean.setOrder(4);
return XssBean;
}
/**
* csrf跨站点欺骗过滤器
*/
@Bean
public FilterRegistrationBean<Filter> csrfFilterRegistrationBean(CsrfFilter csrfFilter) {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<Filter>();
registration.setFilter(csrfFilter);//这里不能直接使用New,因为直接New出来的东西,CsrfFilter不受spring管理,不能通过@value注入变量
registration.addUrlPatterns("/*");
registration.setName("csrfFilter");
registration.setOrder(0);
return registration;
}
}