月度归档:2020年03月

Hazelcast数据分发和集群平台管理中心

Hazelcast简介

是Hazelcast公司开源的一款分布式内存数据库产品,提供弹性可扩展、高性能的分布式内存计算。基于Apache v2许可证发布,可用于实现分布式数据存储、数据缓存、消息代理器以及分布式计算平台。Hazelcast强调高速访问分布式数据(通常是分布式的缓存)、分布式计算和分布式消息。
Github地址:https://github.com/hazelcast/hazelcast
官方网站:https://hazelcast.org/
API文档:https://docs.hazelcast.org/docs/latest/manual/html-single/index.html

Hazelcast特性:

1.强大的分布式计算:允许任意业务逻辑执行位置引用,并支持跨集群的分布式扩展。作为完全内存数据存储,Hazelcast IMDG可以在几微秒内转换和提取数据,提供吞吐量和查询。在Hazelcast里所有的数据结构都是分布式的,把多个节点当作一个节点使用,最常用的是map,其它的有Queue(队列),MultiMap,Set,List,跟java里的是一样的,因为实现了java里的泛型接口。
2.支持多种语言:Java, C++, .NET/C#, Python, Scala, and NodeJs
3.持久化:Hazelcast主要是解决访问分布式数据和进行分布式计算时保持低延迟。默认情况下,Hazelcast不涉及磁盘或其它持久化的存储。
4.简易性: Hazelcast 功能只需引用一个jar包,除此之外,不依赖任何第三方包。因此可以非常便捷高效的将其嵌入到各种应用服务器中,而不必担心带来额外的问题(jar包冲突、类型冲突等等)。他仅仅提供一系列分布式功能,而不需要绑定任何框架来使用,因此适用于任何场景。扩展,故障处理和恢复都是自动化的,以便于操作管理
5.自治集群(无中心化):Hazelcast 没有任何中心节点,在运行的过程中,它自己选定集群中的某个节点作为中心点来管理所有的节点

Hazelcast客户端(管理中心)下载

下载地址:https://hazelcast.org/download/

 

下载完解压即可,双击start.bat即可启动

 

这个是管理中心的登录URL

需要先设置账户密码,密码需要字母大小写还有数字

 

登录页面

与springboot进行集成

SpringBoot官方明确表示支持Hazelcast

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-hazelcast.html
引入依赖

<dependency>
       <groupId>com.hazelcast</groupId>
       <artifactId>hazelcast</artifactId>
       <version>3.11</version>
</dependency>

引入Hazelcast.xml配置文件

模板可以在Github上找的:https://github.com/hazelcast/hazelcast/blob/master/hazelcast/src/main/resources/hazelcast-default.xml

<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.hazelcast.com/schema/config
           http://www.hazelcast.com/schema/config/hazelcast-config-3.11.xsd">

    <group>
        <name>dev</name>
        <password>dev-pass</password>
    </group>
    <management-center enabled="true">http://localhost:8080/hazelcast-mancenter</management-center>
    <network>
        <port auto-increment="true" port-count="100">5701</port>
        <outbound-ports>
            <ports>0</ports>
        </outbound-ports>
        <join>
            <multicast enabled="true">
                <multicast-group>224.2.2.3</multicast-group>
                <multicast-port>54327</multicast-port>
            </multicast>
            <tcp-ip enabled="false">
                <interface>127.0.0.1</interface>
                <member-list>
                    <member>127.0.0.1</member>
                </member-list>
            </tcp-ip>
            <aws enabled="false">
                <access-key>my-access-key</access-key>
                <secret-key>my-secret-key</secret-key>
                <region>us-west-1</region>
                <host-header>ec2.amazonaws.com</host-header>
                <security-group-name>hazelcast-sg</security-group-name>
                <tag-key>type</tag-key>
                <tag-value>hz-nodes</tag-value>
            </aws>
            <gcp enabled="false">
                <zones>us-east1-b,us-east1-c</zones>
            </gcp>
            <azure enabled="false">
                <client-id>CLIENT_ID</client-id>
                <client-secret>CLIENT_SECRET</client-secret>
                <tenant-id>TENANT_ID</tenant-id>
                <subscription-id>SUB_ID</subscription-id>
                <cluster-id>HZLCAST001</cluster-id>
                <group-name>GROUP-NAME</group-name>
            </azure>
            <kubernetes enabled="false">
                <namespace>MY-KUBERNETES-NAMESPACE</namespace>
                <service-name>MY-SERVICE-NAME</service-name>
                <service-label-name>MY-SERVICE-LABEL-NAME</service-label-name>
                <service-label-value>MY-SERVICE-LABEL-VALUE</service-label-value>
            </kubernetes>
            <eureka enabled="false">
                <self-registration>true</self-registration>
                <namespace>hazelcast</namespace>
            </eureka>
            <discovery-strategies>
            </discovery-strategies>
        </join>
        <interfaces enabled="false">
            <interface>10.10.1.*</interface>
        </interfaces>
        <ssl enabled="false"/>
        <socket-interceptor enabled="false"/>
        <symmetric-encryption enabled="false">
            <algorithm>PBEWithMD5AndDES</algorithm>
            <salt>thesalt</salt>
            <password>thepass</password>
            <iteration-count>19</iteration-count>
        </symmetric-encryption>
        <failure-detector>
            <icmp enabled="false"/>
        </failure-detector>
    </network>
    <partition-group enabled="false"/>
    <executor-service name="default">
        <pool-size>16</pool-size>
        <queue-capacity>0</queue-capacity>
    </executor-service>
    <security>
        <client-block-unmapped-actions>true</client-block-unmapped-actions>
    </security>
    <queue name="default">
        <max-size>0</max-size>
        <backup-count>1</backup-count>
        <async-backup-count>0</async-backup-count>
        <empty-queue-ttl>-1</empty-queue-ttl>
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </queue>
    <map name="default">
        <in-memory-format>BINARY</in-memory-format>
        <backup-count>1</backup-count>
        <async-backup-count>0</async-backup-count>
        <time-to-live-seconds>0</time-to-live-seconds>
        <max-idle-seconds>0</max-idle-seconds>
        <eviction-policy>NONE</eviction-policy>
        <max-size policy="PER_NODE">0</max-size>
        <eviction-percentage>25</eviction-percentage>
        <min-eviction-check-millis>100</min-eviction-check-millis>
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
        <cache-deserialized-values>INDEX-ONLY</cache-deserialized-values>
    </map>
    <event-journal enabled="false">
        <mapName>mapName</mapName>
        <capacity>10000</capacity>
        <time-to-live-seconds>0</time-to-live-seconds>
    </event-journal>

    <event-journal enabled="false">
        <cacheName>cacheName</cacheName>
        <capacity>10000</capacity>
        <time-to-live-seconds>0</time-to-live-seconds>
    </event-journal>
    <merkle-tree enabled="false">
        <mapName>mapName</mapName>
        <depth>10</depth>
    </merkle-tree>

    <multimap name="default">
        <backup-count>1</backup-count>
        <value-collection-type>SET</value-collection-type>
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </multimap>

    <replicatedmap name="default">
        <in-memory-format>OBJECT</in-memory-format>
        <async-fillup>true</async-fillup>
        <statistics-enabled>true</statistics-enabled>
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </replicatedmap>

    <list name="default">
        <backup-count>1</backup-count>
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </list>

    <set name="default">
        <backup-count>1</backup-count>
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </set>

    <jobtracker name="default">
        <max-thread-size>0</max-thread-size>
        <!-- Queue size 0 means number of partitions * 2 -->
        <queue-size>0</queue-size>
        <retry-count>0</retry-count>
        <chunk-size>1000</chunk-size>
        <communicate-stats>true</communicate-stats>
        <topology-changed-strategy>CANCEL_RUNNING_OPERATION</topology-changed-strategy>
    </jobtracker>

    <semaphore name="default">
        <initial-permits>0</initial-permits>
        <backup-count>1</backup-count>
        <async-backup-count>0</async-backup-count>
    </semaphore>

    <reliable-topic name="default">
        <read-batch-size>10</read-batch-size>
        <topic-overload-policy>BLOCK</topic-overload-policy>
        <statistics-enabled>true</statistics-enabled>
    </reliable-topic>

    <ringbuffer name="default">
        <capacity>10000</capacity>
        <backup-count>1</backup-count>
        <async-backup-count>0</async-backup-count>
        <time-to-live-seconds>0</time-to-live-seconds>
        <in-memory-format>BINARY</in-memory-format>
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </ringbuffer>

    <flake-id-generator name="default">
        <prefetch-count>100</prefetch-count>
        <prefetch-validity-millis>600000</prefetch-validity-millis>
        <id-offset>0</id-offset>
        <node-id-offset>0</node-id-offset>
        <statistics-enabled>true</statistics-enabled>
    </flake-id-generator>

    <atomic-long name="default">
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </atomic-long>

    <atomic-reference name="default">
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </atomic-reference>

    <count-down-latch name="default"/>

    <serialization>
        <portable-version>0</portable-version>
    </serialization>

    <services enable-defaults="true"/>

    <lite-member enabled="false"/>

    <cardinality-estimator name="default">
        <backup-count>1</backup-count>
        <async-backup-count>0</async-backup-count>
        <merge-policy batch-size="100">HyperLogLogMergePolicy</merge-policy>
    </cardinality-estimator>

    <scheduled-executor-service name="default">
        <capacity>100</capacity>
        <durability>1</durability>
        <pool-size>16</pool-size>
        <merge-policy batch-size="100">com.hazelcast.spi.merge.PutIfAbsentMergePolicy</merge-policy>
    </scheduled-executor-service>

    <crdt-replication>
        <replication-period-millis>1000</replication-period-millis>
        <max-concurrent-replication-targets>1</max-concurrent-replication-targets>
    </crdt-replication>

    <pn-counter name="default">
        <replica-count>2147483647</replica-count>
        <statistics-enabled>true</statistics-enabled>
    </pn-counter>
</hazelcast>

<management-center>中enabled属性需要设置为true才能开启Hazelcast管理中心,默认为false
<outbound-ports>表示允许连接到其他节点的端口范围。 0或*表示使用系统提供的端口。
<network>是非常重要的元素,指定Hazelcast的网络环境。
<port>指定Hazelcast将用于在集群成员之间进行通信的端口。
<multicast >组播,不建议将生成多播机制用于生产,因为UDP通常在生产环境中被阻止,而其他发现机制更明确。
<tcp-ip>TCP / IP群集
<queue name="default">队列名
<max-size>队列的最大大小。当JVM的本地队列大小达到最大值时,所有put / offer操作都将被阻塞,直到JVM的队列大小低于最大值。 0到Integer.MAX_VALUE之间的任何整数。
<backup-count>备份数量。例如,如果将1设置为备份计数,则映射的所有条目都将复制到另一个JVM以实现故障安全。 0表示没有备份
<async-backup-count>异步备份的数量。 0表示没有备份
具体的配置说明可以参考:

https://docs.hazelcast.org/docs/latest/manual/html-single/#understanding-configuration

测试代码

@Component
public class HazelcastGetStartServerMaster {
    private HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance();

    @PostConstruct
    public void put(){
        Map map = hazelcastInstance.getMap("hello");
        map.put("hello","world");
    }
}

启动测试之前需要先启动Hazelcast管理中心

 

启动成功会显示管理中心的URL

在管理中心可以看到

 
来源:https://www.jianshu.com/p/f32a24771d17

 

Java中使用AntiSamy开源项目防御XSS攻击

之前公司有接了一个国土的项目,虽然是内部小项目,但是可能是zhengfu项目竟然找软件测试公司大概测了一下。。。然后出现了以下三种问题:sql注入,XSS攻击,接口访问频率。下面是解决XSS攻击。

研究:

一开始的想法是,弄个过滤器把那些关键字过滤掉不就好了,例如script、eval、document等等,确实网上也有这些例子。可参考此博客https://www.cnblogs.com/myyBlog/p/8890365.html

但是人家可是请专业的软件测试工程师测试的,我这么混过去肯定是不行滴,上面的主要只是简单地过滤掉用户的输入参数,而且过滤得非常的不全面,可能还有很多方面的攻击呢?然后继续的百度找找有没有此方面的开源项目。结果真的找到了一个,就是AntiSamy,OWASP的一个开源项目,通过对用户输入的 HTML / CSS / JavaScript 等内容进行检验和清理,确保输入符合应用规范。AntiSamy被广泛应用于Web服务对存储型和反射型XSS的防御中。这个就相当的强大了啊。

方案:

参考上面的博客的做法:弄一个过滤器XSSFilter,拦截所有路径。再使用HttpServletRequestWrapper来装饰一下HttpServletRequest,已达到XSS清洗来防御XSS攻击。嗯,就这么大概了,网友真强大~

开工:

1、引入Maven依赖:

<!-- OWASP AntiSamy 防止XSS攻击-->
<dependency>
<groupId>org.owasp.antisamy</groupId>
<artifactId>antisamy</artifactId>
<version>1.5.5</version>
</dependency>
2、策略文件:

Maven下载下来的依赖里面是带有策略文件,之前试过前一点的版本例如1.5.3好像是没有的。下面我使用的策略文件将是antisamy-ebay.xml,毕竟eBay 是当下最流行的在线拍卖网站之一,防御策略肯定是非常好的。关于AntiSamy的策略文件的详解大家可以去搜一下,毕竟可能以后要改里面的处理规则呢。

3、怎么使用AntiSamy:

想当的简单,首先要指定策略文件生成Policy,然后新建AntiSamy,再对需要进行XSS清洗的内容进行扫面即可。

String xsshtml = "hyf<script>alert(1)</script>";
Policy policy = Policy.getInstance("antiSamyPath");
AntiSamy antiSamy = new AntiSamy();
CleanRequests cr = antiSamy.scan(xsshtml,policy);
xsshtml = cr.getCleanHTML(); //清洗完的
4、创建XssFilter实现Filter接口然后重新doFilter方法:

因为我们对用户的每次请求都进行拦截清洗,清洗完才能真正地调用Controller的方法,所以我们用到的是过滤器而不是拦截器。因为Filter是一个典型的过滤链,可以用来对 HttpServletRequest 进行预处理,或者是对 HttpServlerResponse 进行后处理。

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
*
*@author howinfun
*@date 2018/10/23
*@company DM
*@version 1.0
*/
public class XssFilter implements Filter {

private FilterConfig filterConfig;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
/* filterChain.doFilter(new XssHttpServletRequestWrapper2(request),servletResponse);*/
filterChain.doFilter(new XssHttpServletRequestWrapper(request),servletResponse); //包装request,在里头进行XSS清洗
}

@Override
public void destroy() {
this.filterConfig = null;
}
}
5、新建一个自定义 HttpServletRequest 包装类 XssHttpServletRequestWrapper ,继承 HttpServletRequestWrapper 类:

对getParameter( String param)、getParameterValues( String param)以及 getHeader( String param) 等方法进行重写,在重写里头都进行一遍全方位清洗。

import org.apache.commons.lang.StringEscapeUtils;
import org.jeecgframework.web.appconfig.util.StringUtils;
import org.owasp.validator.html.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Iterator;
import java.util.Map;

/**
* @desc 基于AntiSamy的XSS防御
* @author howinfun
* @date 2018/10/23
* @company DM
* @version 1.0
*/
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

//AntiSamy使用的策略文件
private static Policy policy = null;
static {
//指定策略文件,策略文件记得放在classpath下
String antiSamyPath = XssHttpServletRequestWrapper.class.getClassLoader().getResource("antisamy-ebay.xml").getFile();
System.out.println("policy_filepath:"+antiSamyPath);
if(antiSamyPath.startsWith("file")){
antiSamyPath = antiSamyPath.substring(6);
}
try {
policy = Policy.getInstance(antiSamyPath);
} catch (PolicyException e) {
e.printStackTrace();
}
}

public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}

/**
* @desc Header为空直接返回,不然进行XSS清洗
* @author howinfun
* @date 2018/10/24
*
*/
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
if(StringUtils.isEmpty(value)){
return value;
}
else{
String newValue = cleanXSS(value);
return newValue;
}

}

/**
* @desc Parameter为空直接返回,不然进行XSS清洗
* @author howinfun
* @date 2018/10/24
*
*/
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if(StringUtils.isEmpty(value)){
return value;
}
else{
value = cleanXSS(value);
return value;
}
}

/**
* @desc 对用户输入的参数值进行XSS清洗
* @author howinfun
* @date 2018/10/24
*
*/
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values != null) {
int length = values.length;
String[] escapseValues = new String[length];
for (int i = 0; i < length; i++) {
escapseValues[i] = cleanXSS(values[i]);
}
return escapseValues;
}
return super.getParameterValues(name);
}

@SuppressWarnings("rawtypes")
public Map<String,String[]> getParameterMap(){
Map<String,String[]> request_map = super.getParameterMap();
Iterator iterator = request_map.entrySet().iterator();
System.out.println("request_map"+request_map.size());
while(iterator.hasNext()){
Map.Entry me = (Map.Entry)iterator.next();
//System.out.println(me.getKey()+":");
String[] values = (String[])me.getValue();
for(int i = 0 ; i < values.length ; i++){
System.out.println(values[i]);
values[i] = cleanXSS(values[i]);
}
}
return request_map;
}

/**
* @desc AntiSamy清洗数据
* @author howinfun
* @date 2018/10/24
*
*/
private String cleanXSS(String taintedHTML) {
try{
AntiSamy antiSamy = new AntiSamy();
CleanResults cr = antiSamy.scan(taintedHTML, policy);
taintedHTML = cr.getCleanHTML();
return taintedHTML ;

}catch( ScanException e) {
e.printStackTrace();
}catch( PolicyException e) {
e.printStackTrace();
}
return taintedHTML;
}
}
6、在web.xml对新建的Filter进行注册。

<!-- 防止XSS攻击 -->
<filter>
<filter-name>XSSFilter</filter-name>
<filter-class>org.jeecgframework.core.filter.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XSSFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
7、接下来进行测试:

简单的测试是否能过滤掉script标签及里面的内容。这个上传的gif不动的,我就不放了,结果肯定是可以过滤掉滴。

8、继续测试:

我继续测试其他接口,测试了一个之前自己做的通用API,发现传回来的JSON数据出现了问题,原来的双引号被转换为&quot。还测试到&nbsp;被转成乱码了。。。所以我们必须在清洗完后进行进一步处理,将这两个问题给自己解决掉。

/**
* @desc AntiSamy清洗数据
* @author howinfun
* @date 2018/10/24
*
*/
private String cleanXSS(String taintedHTML) {
try{
AntiSamy antiSamy = new AntiSamy();
final CleanResults cr = antiSamy.scan(taintedHTML,policy);
//AntiSamy会把“&nbsp;”转换成乱码,把双引号转换成"&quot;" 先将&nbsp;的乱码替换为空,双引号的乱码替换为双引号
String str = StringEscapeUtils.unescapeHtml(cr.getCleanHTML());
str = str.replaceAll(antiSamy.scan("&nbsp;",policy).getCleanHTML(),"");
str = str.replaceAll(antiSamy.scan("\"",policy).getCleanHTML(),"\"");
return str;

}catch( ScanException e) {
e.printStackTrace();
}catch( PolicyException e) {
e.printStackTrace();
}
return taintedHTML;
}

 

原文链接:https://blog.csdn.net/Howinfun/article/details/83414262

antisamy的策略文件使用详解

1 前言

Antisamy是OWASP(open web application security project)的一个开源项目,其能够对用户输入的html/css/javascript 脚本进行过滤,确保输入满足规范,无法提交恶意脚本。Antisamy被应用在web服务中对存储型和反射性的xss防御,尤其是在存在富文本输入的场景,antisamy能够很好的解决用户输入体验和安全要求之间的冲突。

  • Antisamy的对包含非法字符的用户输入的过滤依赖于策略文件,策略文件规定了antisamy对各个标签、属性的处理方法。策略文件定义的严格与否,决定了antisamy对xss漏洞的防御效果。
  • OWASP中antisamy有java和.net两个项目,这篇文档中仅对java项目进行介绍。从安装、使用及策略文件的定制几个方面讲解如何将antisamy应用到实际项目当中。

2 工具安装

  •  Antisamy的官方下载路径:++https://code.google.com/archive/p/owaspantisamy/downloads++。下载列表中包含antisamy的jar包,同时也包含了几个常用的策略文件。官方下载的链接需要翻墙,为方便使用,将最新版本的antisamy和策略文件放到文档的附件中(见附件1)。
  • Antisamy直接导入到java的工程即可,但是其运行依赖另外三个库:

xercesImpl.jar    http://xerces.apache.org/mirrors.cgi#binary

batik.jar              http://xmlgraphics.apache.org/batik/download.cgi

nekohtml.jar       http://sourceforge.net/projects/nekohtml/

  • 可以通过链接下载,也可以从marven中直接下载。测试过程中使用的是从marven中下载,使用的版本信息如下:

 <orderEntry type=”library” name=”xerces:xercesImpl:2.11.0″ level=”project” />

<orderEntry type=”library” name=”com.nrinaudo:kantan.xpath-nekohtml_2.12:0.1.9″ level=”project” />

<orderEntry type=”library” name=”org.apache.xmlgraphics:batik-script:1.8″ level=”project” />

  • 在导入完成后,在java工程中新建一个类,输入如下代码进行测试,确认安装是否正确。
    1. public class AntiSamyApplication {
    2.     public static void main(String[] args)
    3.     {
    4.         AntiSamy as = new AntiSamy();
    5.         try{
    6.             Policy policy = Policy.getInstance("\\antisamy\\antisamy-tinymce-1.4.4.xml");
    7.             CleanResults cr = as.scan("<div>wwwww<script>alert(1)</script></div>", policy);
    8.             System.out.print(cr.getCleanHTML());
    9.         }
    10.         catch(Exception ex) {
    11.         } ;
    12.     }
    13. }

如果输出结果如下结果,说明安装正确,可以正常使用antisamy了。

  1. "C:\Program Files (x86)\Java\jdk1.8.0_121\bin\java"
  2.   <div>wwwwwdddd</div>
  3.   Process finished with exit code 0

3 使用方法

Antisamy存在两种使用方法,一种是从系统层面,重写request处理相关的功能函数,对用户输入的每一个参数均作过滤验证;另外一种是仅对富文本使用过滤验证;

4 策略文件

4.1 策略文件结构

一个简易的Antisamy的策略文件主体结构如所示:

Antisamy的策略文件为xml格式的,除去xml文件头外,可以为7个部分,下面对各个部分的功能做简单的介绍。

  1. <directives>
  2.     <directive name="omitXmlDeclaration" value="true" />
  3.     <directive name="omitDoctypeDeclaration" value="true" />
  4. </directives>

对应上图中标注为1的部分,主要为全局性配置,对antisamy的过滤验证规则、输入及输出的格式进行全局性的控制。具体字段的意义在后面继续文档中详细介绍。

  1. <common-regexps>
  2.     <regexp name="htmlTitle" value="[a-zA-Z0-9\s\-_',:\[\]!\./\\\(\)&amp;]*" />
  3.     <regexp name="onsiteURL" value="([\p{L}\p{N}\p{Zs}/\.\?=&amp;\-~])+" />
  4.     <regexp name="offsiteURL" value="(\s)*((ht|f)tp(s?)://|mailto:)[A-Za-z0-9]+[~a-zA-Z0-9-_\.@\#\$%&amp;;:,\?=/\+!\(\)]*(\s)*" />
  5. </common-regexps>

对应上图中标注为2的部分,将规则文件中需要使用到的正则表达式相同的部分归总到这,会在后续中需要正则的时候通过name直接引用;

  1. <common-attributes>
  2.     <attribute name="title" description="The 'title' attribute provides text that shows up in a 'tooltip' when a user hovers their mouse over the element">
  3.         <regexp-list>
  4.         <regexp name="htmlTitle" />
  5.         </regexp-list>
  6.     </attribute>
  7. </common-attributes>

对应上图中标注为3的部分,这部分定义了通用的属性需要满足的输入规则,其中包括了标签和css的属性;在后续的tag和css的处理规则中会引用到上述定义的属性。

  1. <global-tag-attributes>
  2.     <attribute name="title" />
  3. </global-tag-attributes>

对应上图中标注为4的部分,这部分定义了所有标签的默认属性需要遵守的规则;需要验证如果标签中为validate,有属性但是不全的场景

  1. <tags-to-encode>
  2.    <tag>g</tag>
  3.    <tag>grin</tag>
  4. </tags-to-encode>

对应上图中标注为5的部分,这部分定义了需要进行编码处理的标签;

  1. <tag-rules>
  2.    <!--  Remove  -->
  3.    <tag name="script" action="remove" />
  4.    <!--  Truncate  -->
  5.    <tag name="br" action="truncate" />
  6.    <!--  Validate -->
  7.    <tag name="p" action="validate">
  8.    <attribute name="align" />
  9.    </tag>
  10. </tag-rules>

对应上图中标注为6的部分,这部分定义了tag的处理规则,共有三种处理方式:

remove:对应的标签直接删除如script标签处理规则为删除,当输入为:

<div><script>alert(1);</script></div>

输出为:

<div>ddd</div>

truncate:对应的标签进行缩短处理,直接删除所有属性,只保留标签和值;如标签dd处理规则为truncate,当输入为:

<dd id='dd00001' align='left'>test</test>

validate:对应的标签的属性进行验证,如果tag中定义了属性的验证规则,按照tag中的规则执行;如果标签中未定义属性,则按照 \<global-tag-attributes\> 中定义的处理;

  1. <css-rules>
  2.     <property name="text-decoration" default="none" description="">
  3.         <category-list>
  4.             <category value="visual" />
  5.         </category-list>
  6.         <literal-list>
  7.             <literal value="underline" />
  8.             <literal value="overline" />
  9.             <literal value="line-through" />
  10.         </literal-list>
  11.     </property>
  12. </css-rules>

对应上图中中标注为7的部分,这部分定义了css的处理规则;

4.2 策略文件详解

4.2.1 策略文件指令[<directives></directives>]

策略文件指令包含13个参数,下面对其参数的意义逐一介绍。

  1. <directives>
  2.     <directive name="useXHTML" value="false"/>
  3.     <directive name="omitXMLDeclaration" value="true"/>
  4.     <directive name="omitDoctypeDeclaration" value="true"/>
  5.     <directive name="formatOutput" value="false"/>
  6.     <directive name="maxInputSize" value="100000"/>
  7.     <directive name="embedStyleSheets" value="false"/>
  8.     <directive name="maxStyleSheetImports" value="1"/>
  9.     <directive name="connectionTimeout" value="1000"/>
  10.     <directive name="preserveComments" value="false"/>
  11.     <directive name="nofollowAnchors" value="false"/>
  12.     <directive name="validateParamAsEmbed" value="false"/>
  13.     <directive name="preserveSpace" value="false"/>
  14.     <directive name="onUnknownTag" value="remove"/>
  15. </directives>

下面的介绍中使用到的样例均以antisamy-myspace-1.4.4.xml为基础进行修改

useXHML>

类型:boolean

默认值: false

功能:开启后,antisamy的结果将按照XHTML的格式输出;默认使用HTML;

注:XHTML和HTML的主要区别在于XHMTL格式更加严格,元素必须正确的嵌套,必须闭合,标签名要小写,必须要有根元素;

在测试过程中,发现该参数实际上对输出的结果没什么影响。

omitXMLDeclaration>

类型:boolean

默认值:true
功能:按照文档描述,在开启时antisamy自动添加xml的头

测试时发现该参数对输出没啥影响。

omitDoctypeDeclaration>

类型: boolean

默认值:true

功能:在值为false时,antisamy在输出结果中自动添加html头

当输入为:

<div class='navWrapper'><p>sec_test<script>alert(1);</script></p></div>

输出结果为:

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  2.                       "http://www.w3.org/TR/html4/strict.dtd">
  3. <div class="navWrapper">
  4.   <p>sec_test</p>
  5. </div>

formatOutput>

类型:boolean

默认值:true

功能:开启后,antisamy会将输出结果格式化,使可读性更好;

默认是开启的,在关闭后,当输入为:

<div class='navWrapper'><p>sec_test<script>alert(1);</script></p></div>

输出结果为:

<div class="navWrapper"><p>sec_test</p></div>

注:可以和omitDoctypeDeclaration的结果对比着看。

maxInputSize>

类型:int
默认值:100000

功能:用于验证的最长字符串长度,单位bytes;

embedStyleSheets>

类型:boolean

默认值:false

功能:在开启css过滤功能后,该指令确认是否将引用的css文件下载下来并导入到用户输入一起作为检查的输入;

maxStyleSheetImports>

类型:int
默认值:1

功能:配合embedStyleSheets使用,指定在输入中可以下载css文件的个数;

connecttionTimeout>

类型:int

默认值:1000

功能:配合embedStyleSheets使用,指定在下载css文件时的超时时长,单位为毫秒;

preserveComments>

类型:boolean

默认值:false

功能:开启后,保留输入中的注释行;

nofollowAnchors>

类型:boolean

默认值:falsle

功能:开启后,在锚点标签(<a>)后添加rel=“nofollow”属性,防止跳转到其他页面

开启后当输入为:

<div><a href='www.baid.com'>click</a></div>

输出结果为:

  1. <div>
  2.   <a href="www.baid.com" rel="nofollow">click</a></div>

validateParamAsEmbed>

类型:booblean

默认值:false

功能:开启后,antisamy将<embed>标签的属性和嵌入到embed标签内的<param>标签的属性相同的规则处理,主要用于在需要用户输入的视频站点。

preserveSpace>

类型:boolean

默认值:false

功能:开启后,保留输入中的空格;

onUnknowTag>

类型:String

默认值:remove

功能:对未知tag的处理规则,默认为删除,可以修改为encode;

4.2.2 通用正则表达式 [<common-regexps> </common-regexps>]

其定义的格式为:

<regexp name="htmlId" value="[a-zA-Z0-9\:\-_\.]+"/>

htmlId为正则的名称,通过名称被引用

value后面是具体的正则表达式的内容  该部分可以参考antisamy中样例规则文件编写;

如有特殊需要,正则表达式可以参考网上其他资料进行编写。

4.2.3 通用属性 [<common-attributes> </common-attributes>]

通用属性义如下:

  1. <attribute name="media">
  2.     <regexp-list>
  3.         <regexp value="[a-zA-Z0-9,\-\s]+"/>
  4.         <regexp name="htmlId"/>
  5.     </regexp-list>
  6.     <literal-list>
  7.         <literal value="screen"/>
  8.         <literal value="tty"/>
  9.         <literal value="tv"/>
  10.     </literal-list>
  11. </attribute>

“attribute name”为标签的名称,与html的tag名称保持一致;

“regexp name”为标签需要满足的正则表达式的名称,其在<common-regexps>中定义;

“regexp value”为标签需要满足的正则表达式;

可以通过literal直接指定属性的值;如media的值可以满足上面两个的正则表达式外,也可以为screen、tty、tv中的一个

注:<regexp-list><literal-list>中的值均可以为多个

4.2.4 全局tag属性<global-tag-attributes>

其定义和4.2.3中通用属性的定义无区别,只能功能不同;

具体功能在后面tag的规则介绍(4.2.7)中展示;

4.2.5 编码处理tag<tag-to-encode>

此处标识的tag将在输出中进行编码;

其定义的格式为:

  1. <tags-to-encode>
  2.    <tag>g</tag>
  3.    <tag>grin</tag>
  4. </tags-to-encode>

但是实际测试的时候,并未生效。

4.2.6 tag处理规则<tag-rules>

tag-rules的定义规则如下:

  1. <tag name="button" action="validate">
  2.     <attribute name="name"/>
  3.     <attribute name="value">
  4.    <regexp-list>
  5.    <regexp name="anything"/>
  6.    </regexp-list>
  7.     </attribute>
  8.     <attribute name="type">
  9.         <literal-list>
  10.             <literal value="submit"/>
  11.             <literal value="reset"/>
  12.             <literal value="button"/>
  13.         </literal-list>
  14.     </attribute>
  15. </tag>

tagc-rule的action有三种:remove、validate、truncate,各个动作的功能在4.1中介绍过,不再赘述.

其中有一种场景需要注意:

<tag name="h1" action="validate"/>

这种标签中action是validate,但是标签的属性需要遵守的正则却没有标识出来;在这个时候,4.2.4中全局tag属性中定义的属性就起作用了。上面这种类型的标签就需要遵守4.2.4中定义的全局属性;

如:h1标签处理规则如下:

<tag name="h1" action="validate"/>

输入为:

<div><h1 id='h111' align='center'>h1 title</h1></div>

输出为:

  1. <div>
  2.   <h1 id="h111">h1 title</h1></div>

global-tag-attribute中有”id”, “style”,”title”,”class”,”lang”这5个标签,不包括align,所以在输出中就被过滤掉了。

4.2.7 css处理规则<css-rules>

css-rules定义如下:

  1. <property name="background-color" description="This property sets the background color of an element, either a &lt;color&gt; value or the keyword 'transparent', to make the underlying colors shine through.">
  2.     <literal-list>
  3.         <literal value="transparent"/>
  4.         <literal value="inherit"/>
  5.     </literal-list>
  6.     <regexp-list>
  7.         <regexp name="colorName"/>
  8.         <regexp name="colorCode"/>
  9.         <regexp name="rgbCode"/>
  10.         <regexp name="systemColor"/>
  11.     </regexp-list>
  12. </property>

从上面可以看出,css过滤规格的定义和tag的基本相同;但是css有一些特殊的字段,如:

  1. <property name="background" description="The 'background' property is a shorthand property for setting the individual background properties (i.e., 'background-color', 'background-image', 'background-repeat', 'background-attachment' and 'background-position') at the same place in the style sheet.">
  2.     <literal-list>
  3.         <literal value="inherit"/>
  4.     </literal-list>
  5.     <shorthand-list>
  6.         <shorthand name="background-color"/>
  7.         <shorthand name="background-image"/>
  8.         <shorthand name="background-repeat"/>
  9.         <shorthand name="background-attachment"/>
  10.         <shorthand name="background-position"/>
  11.     </shorthand-list>
  12. </property>

相比tag的过滤规则,css增加了shorthand-list,为css的自有语法。意味着如果background有多个值,说明使用了css的缩写,同时需要满足shorthand中规定的属性的过滤规则。

4.3 通用策略文件

5 Antisamy代码简介

antisamy对html进行扫描的主要流程如下:

其中

recursiveValidateTag(tmp, currentStackDepth);

为antisamy最终执行扫描的函数,其通过递归调用对html中每一个标签根据规则文件的定义进行处理。

来源:https://blog.csdn.net/RayChiu757374816/article/details/79016101

XSS的防御

简介

XSS 的防御很复杂,并不是一套防御机制就能就解决的问题,它需要具体业务具体实现。

目前来说,流行的浏览器内都内置了一些 XSS 过滤器,但是这只能防御一部分常见的 XSS,而对于网站来说,也应该一直寻求优秀的解决方案,保护网站及用户的安全,我将阐述一下网站在设计上该如何避免 XSS 的攻击。

HttpOnly

HttpOnly 最早是由微软提出,并在 IE 6 中实现的,至今已经逐渐成为一个标准,各大浏览器都支持此标准。具体含义就是,如果某个 Cookie 带有 HttpOnly 属性,那么这一条 Cookie 将被禁止读取,也就是说,JavaScript 读取不到此条 Cookie,不过在与服务端交互的时候,Http Request 包中仍然会带上这个 Cookie 信息,即我们的正常交互不受影响。

Cookie 是通过 http response header 种到浏览器的,我们来看看设置 Cookie 的语法:

Set-Cookie: <name>=<value>[; <Max-Age>=<age>][; expires=<date>][; domain=<domain_name>][; path=<some_path>][; secure][; HttpOnly]

复制代码

第一个是 name=value 的键值对,然后是一些属性,比如失效时间,作用的 domainpath ,最后还有两个标志位,可以设置为 secureHttpOnly

栗子:

// 利用 express 这个轮子设置cookie
res.cookie('myCookie', 'test', {
  httpOnly: true
})
res.cookie('myCookie2', 'test', {
  httpOnly: false
})
复制代码

然后回到浏览器查看:

这个时候我们试着在控制台输出:

我们发现,只有没有设置 HttpOnlymyCookie2 输出了出来,这样一来, javascript 就读取不到这个 Cookie 信息了。

HttpOnly 的设置过程十分简单,而且效果明显,不过需要注意的是,所有需要设置 Cookie 的地方,都要给关键的 Cookie 都加上 HttpOnly ,若有遗漏则会功亏一篑。

但是, HttpOnly 不是万能的,添加了 HttpOnly 不等于解决了 XSS 问题。

严格的说,HttpOnly 并非为了对抗 XSSHttpOnly 解决的是 XSS 后的 Cookie 劫持问题,但是 XSS 攻击带来的不仅仅是 Cookie 劫持问题,还有窃取用户信息,模拟身份登录,操作用户账户等一系列行为。

使用 HttpOnly 有助于缓解 XSS 攻击,但是仍然需要其他能够解决 XSS 漏洞的方案。

输入检查

记住一点:不要相信任何输入的内容。

无论是不是做了安全校验,都必须进行过滤操作,而且需要后台配合过滤,如果后端的检查校验还做得不好,那就可能被攻破。

输入检查在更多的时候被用于格式检验,例如用户名只能以字母和数字组合,手机号码只能有 11 位且全部为数字,否则即为非法。

这些格式检查类似于白名单效果,限制输入允许的字符,让一下特殊字符的攻击失效。

目前网上有很多开源的 XSS Filter ,这些 XSS Filter 目前来说还是有些效果的,能只能检验输入内容,高级一点的还会匹配 XSS 特征,例如内容是否包含了 <script>javascript 等敏感字符,但是这些 XSS Filter 只是获取到了用户的输入内容,并不了解其上下文含义,很多时候会误过滤。

例如:

用户输入昵称:<|无敌是多么鸡毛|>,对于 XSS Filter 来说,<> 就是特殊字符,需要过滤然后过滤成为 |无敌是多么鸡毛| ,直接改变了用户的昵称。

所以,我们不能完全信赖开源的 XSS Filter ,很多场景需要我们自己配置规则,进行过滤。

输出检查

不要以为在输入的时候进行过滤就万事大吉了,恶意攻击者们可能会层层绕过防御机制进行 XSS攻击,一般来说,所有需要输出到 HTML 页面的变量,全部需要使用编码或者转义来防御。

HTMLEncode

针对 HTML 代码的编码方式是 HTMLEncode,它的作用是将字符串转换成 HTMLEntities

目前来说,为了对抗 XSS ,以下转义内容是必不可少的:

特殊字符实体编码
&&amp ;
<&lt ;
>&gt ;
"&quot ;
'&#x27 ;
/&#x2F ;

PS. ; 是必须的,而且要和前面的字符连接起来,我这边分开是因为,markdown 就是 HTML 语言,我连上就直接转义成前面的特殊字符了,/(ㄒoㄒ)/~~

来看看效果:

可以看到,这些编码在 HTML 上已经成功转成了对应的符号。

当然,上面的只是最基本而且是最必要的,HTMLEncode 还有很多很多,我这边列举了一些(请允许我用代码的形式写出来,这样就不会转义了):

const HtmlEncode = (str) => {
    // 设置 16 进制编码,方便拼接
    const hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
    // 赋值需要转换的HTML
    const preescape = str;
    let escaped = "";
    for (let i = 0; i < preescape.length; i++) {
        // 获取每个位置上的字符
        let p = preescape.charAt(i);
        // 重新编码组装
        escaped = escaped + escapeCharx(p);
    }

    return escaped;
    // HTMLEncode 主要函数
    // original 为每次循环出来的字符
    function escapeCharx(original) {
        // 默认查到这个字符编码
        let found = true;
        // charCodeAt 获取 16 进制字符编码
        const thechar = original.charCodeAt(0);
        switch (thechar) {
            case 10: return "<br/>"; break; // 新的一行
            case 32: return "&nbsp;"; break; // space
            case 34: return "&quot;"; break; // "
            case 38: return "&amp;"; break; // &
            case 39: return "&#x27;"; break; // '
            case 47: return "&#x2F;"; break; // /
            case 60: return "&lt;"; break; // <
            case 62: return "&gt;"; break; // >
            case 198: return "&AElig;"; break; // Æ
            case 193: return "&Aacute;"; break; // Á
            case 194: return "&Acirc;"; break; // Â
            case 192: return "&Agrave;"; break; // À
            case 197: return "&Aring;"; break; // Å
            case 195: return "&Atilde;"; break; // Ã
            case 196: return "&Auml;"; break; // Ä
            case 199: return "&Ccedil;"; break; // Ç
            case 208: return "&ETH;"; break; // Ð
            case 201: return "&Eacute;"; break; // É
            case 202: return "&Ecirc;"; break;
            case 200: return "&Egrave;"; break;
            case 203: return "&Euml;"; break;
            case 205: return "&Iacute;"; break;
            case 206: return "&Icirc;"; break;
            case 204: return "&Igrave;"; break;
            case 207: return "&Iuml;"; break;
            case 209: return "&Ntilde;"; break;
            case 211: return "&Oacute;"; break;
            case 212: return "&Ocirc;"; break;
            case 210: return "&Ograve;"; break;
            case 216: return "&Oslash;"; break;
            case 213: return "&Otilde;"; break;
            case 214: return "&Ouml;"; break;
            case 222: return "&THORN;"; break;
            case 218: return "&Uacute;"; break;
            case 219: return "&Ucirc;"; break;
            case 217: return "&Ugrave;"; break;
            case 220: return "&Uuml;"; break;
            case 221: return "&Yacute;"; break;
            case 225: return "&aacute;"; break;
            case 226: return "&acirc;"; break;
            case 230: return "&aelig;"; break;
            case 224: return "&agrave;"; break;
            case 229: return "&aring;"; break;
            case 227: return "&atilde;"; break;
            case 228: return "&auml;"; break;
            case 231: return "&ccedil;"; break;
            case 233: return "&eacute;"; break;
            case 234: return "&ecirc;"; break;
            case 232: return "&egrave;"; break;
            case 240: return "&eth;"; break;
            case 235: return "&euml;"; break;
            case 237: return "&iacute;"; break;
            case 238: return "&icirc;"; break;
            case 236: return "&igrave;"; break;
            case 239: return "&iuml;"; break;
            case 241: return "&ntilde;"; break;
            case 243: return "&oacute;"; break;
            case 244: return "&ocirc;"; break;
            case 242: return "&ograve;"; break;
            case 248: return "&oslash;"; break;
            case 245: return "&otilde;"; break;
            case 246: return "&ouml;"; break;
            case 223: return "&szlig;"; break;
            case 254: return "&thorn;"; break;
            case 250: return "&uacute;"; break;
            case 251: return "&ucirc;"; break;
            case 249: return "&ugrave;"; break;
            case 252: return "&uuml;"; break;
            case 253: return "&yacute;"; break;
            case 255: return "&yuml;"; break;
            case 162: return "&cent;"; break;
            case '\r': break;
            default: found = false; break;
        }
        if (!found) {
            // 如果和上面内容不匹配且字符编码大于127的话,用unicode(非常严格模式)
            if (thechar > 127) {
                let c = thechar;
                let a4 = c % 16;
                c = Math.floor(c / 16);
                let a3 = c % 16;
                c = Math.floor(c / 16);
                let a2 = c % 16;
                c = Math.floor(c / 16);
                let a1 = c % 16;
                return "&#x" + hex[a1] + hex[a2] + hex[a3] + hex[a4] + ";";
            } else {
                return original;
            }
        }
    }
}
复制代码

emmmm……作者比较懒,剩下的注释自己补充,这应该是比较全的 HTMLEncode 编码转换了,大家可以直接拿去用(可以给个赞不~),来让我们测试一下:

<div id="id"></div>
复制代码
// 当我们输入:
document.querySelector('#id').innerHTML = '<img onerror=alert(1) src=1/>'
复制代码

页面不可避免的发生了 XSS 注入:

// 当我们利用 HTMLEncode 之后
document.querySelector('#id').innerHTML = HtmlEncode('<img onerror=alert(1) src=1/>')
console.log(HtmlEncode('<img onerror=alert(1) src=1/>'))
复制代码

发现页面将输入的内容完全呈现了:

JavaScriptEncode

JavaScriptEncodeHTMLEncode 的编码方式不同,它需要用 \ 对特殊字符进行转义。

在对抗 XSS 时,还要求输出的变量必须在引号内部,以免造成安全问题,可是很多开发者并没有这种习惯,这样只能使用更为严格的 JavaScriptEncode 来保证数据安全:除了数字,字符之外的所有字符,小于127的字符编码都使用十六进制 \xHH 的方式进行编码,大于用unicode(非常严格模式)。

同样是代码的方式展现出来:

//使用“\”对特殊字符进行转义,除数字字母之外,小于127使用16进制“\xHH”的方式进行编码,大于用unicode(非常严格模式)。
// 大部分代码和上面一样,我就不写注释了
const JavaScriptEncode = function (str) {
    const hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
    const preescape = str;
    let escaped = "";
    for (let i = 0; i < preescape.length; i++) {
        escaped = escaped + encodeCharx(preescape.charAt(i));
    }
    return escaped;
    // 小于127转换成十六进制
    function changeTo16Hex(charCode) {
        return "\\x" + charCode.charCodeAt(0).toString(16);
    }
    function encodeCharx(original) {
        let found = true;
        const thecharchar = original.charAt(0);
        const thechar = original.charCodeAt(0);
        switch (thecharchar) {
            case '\n': return "\\n"; break; //newline
            case '\r': return "\\r"; break; //Carriage return
            case '\'': return "\\'"; break;
            case '"': return "\\\""; break;
            case '\&': return "\\&"; break;
            case '\\': return "\\\\"; break;
            case '\t': return "\\t"; break;
            case '\b': return "\\b"; break;
            case '\f': return "\\f"; break;
            case '/': return "\\x2F"; break;
            case '<': return "\\x3C"; break;
            case '>': return "\\x3E"; break;
            default: found = false; break;
        }
        if (!found) {
            if (thechar > 47 && thechar < 58) { //数字
                return original;
            }
            if (thechar > 64 && thechar < 91) { //大写字母
                return original;
            }
            if (thechar > 96 && thechar < 123) { //小写字母
                return original;
            }
            if (thechar > 127) { //大于127用unicode
                let c = thechar;
                let a4 = c % 16;
                c = Math.floor(c / 16);
                let a3 = c % 16;
                c = Math.floor(c / 16);
                let a2 = c % 16;
                c = Math.floor(c / 16);
                let a1 = c % 16;
                return "\\u" + hex[a1] + hex[a2] + hex[a3] + hex[a4] + "";
            } else {
                return changeTo16Hex(original);
            }
        }
    }
}
复制代码

除了 HTMLEncodeJavaScript 外,还有许多用于各种情况的编码函数,比如 XMLEncodeJSONEncode 等。

编码函数需要在适当的情况下用适当的函数,需要注意的是,编码之后数据长度发生改变,如果文件对数据长度有所限制的话,可能会影响到某些功能。我们在使用编码函数时,一定要注意这个细节,以免产生不必要的 bug

正确的防御 XSS

上面说了两种转义只是为了设计个人能更好的 XSS 防御方案,但是我们需要认清 XSS 产生的本质原因。

XSS 的本质还是一种 HTML 注入,用户的数据被当成了 HTML 代码一部分来执行,从而混淆了原本的语意,产生了新的语意。

如果网站使用了 MVC(MVVM) 结构,那么 XSS 就会发生在 View 层,也就是变量拼接到页面时产生的,所以在用户提交数据的时候进行输入检查,并不是真正在被攻击的地方做防御,而是预防攻击,下面,我将总结一些 XSS 发生的场景,再一一解决。

HTML 标签中输出

HTML 标签中直接输出变量,没有做任何处理,会导致 XSS

<a href=# ><img src=1 onerror=alert(1)></a>
复制代码

这种方式的解决方案是,所有需要输出到页面的元素全部通过 HTMLEncode

HTML 属性中输出

在和 HTML 标签中输出攻击方式类似,只不过输出的内容会自动闭合标签。

<a href="我是变量" ></a>
<!-- 我是变量: "><img src=1 onerror=alert(1)><" -->
<!-- 插入之后变为 -->
<a href=""><img src=1 onerror=alert(1)><""></a>
复制代码

这种方式的防御方法仍然是 HTMLEncode

<script> 标签中输出

假设我们的变量都在引号内部:

let a = "我是变量"
// 我是变量 = ";alert(1);//
a = "";alert(1);//"
复制代码

攻击者只需要闭合标签就能实行攻击,目前的防御方法为 JavaScriptEncode

CSS 中输出

CSS 中或者 style 标签或者 style attribute 中形成的攻击花样非常多,总体上类似于下面几个例子:

<style>@import url('http:xxxxx')</style>
<style>@import 'http:xxxxx'</style>
<style>li {list-style-image: url('xxxxxx')}</style>
<style>body {binding:url('xxxxxxxxxx')}</style>
<div style='background-image: url(xxxx)'></div>
<div style='width: expression(xxxxx)'></div>
复制代码

要解决 CSS 的攻击问题,一方面要严格控制用户将变量输入style 标签内,另一方面不要引用未知的 CSS 文件,如果一定有用户改变 CSS 变量这种需求的话,可以使用 OWASP ESAPI 中的encodeForCSS() 函数。

一个很典型的第三方 CSS 库攻击的案例:

input[type="password"][value$="0"]{ background-image: url("http://localhost:3000/0") }
input[type="password"][value$="1"]{ background-image: url("http://localhost:3000/1") }
input[type="password"][value$="2"]{ background-image: url("http://localhost:3000/2") }
input[type="password"][value$="3"]{ background-image: url("http://localhost:3000/3") }
input[type="password"][value$="4"]{ background-image: url("http://localhost:3000/4") }
input[type="password"][value$="5"]{ background-image: url("http://localhost:3000/5") }
input[type="password"][value$="6"]{ background-image: url("http://localhost:3000/6") }
input[type="password"][value$="7"]{ background-image: url("http://localhost:3000/7") }
input[type="password"][value$="8"]{ background-image: url("http://localhost:3000/8") }
input[type="password"][value$="9"]{ background-image: url("http://localhost:3000/9") }
...
复制代码

剩下的就不写了,就是将所有键盘能输入的字符都写进去。

input[type="password"] 是css选择器,作用是选择密码输入框,[value$="0"]表示匹配输入的值是以 0 结尾的。

所以如果你在密码框中输入 0 ,就去请求 http://localhost:3000/0 接口,但是浏览器默认情况下是不会将用户输入的值存储在 value 属性中,但是有的框架会同步这些值,例如React

我们模拟同步 value 值:

<body>
  <input type="password" value="" id="pwd">
</body>
<script>
  const pwd = document.querySelector('#pwd');
  pwd.oninput = (e) => {
    pwd.attributes.value.value = e.target.value
  }
</script>
复制代码

然后我们看看效果:

Jietu20180927-150717-HD

看!你的密码都被发送到远程了,所以输 CSS 也是 XSS 攻击的手段之一,只有想不到,没有做不到~

URL 中输出

在地址张输出也比较复杂。一般来说 URLpath 或者 search 中进行攻击直接使用 URLEncode即可。URLEncode 会将字符串转换为 %HH 的形式,类似空格就是 %20

可能的攻击方法就是:

<!-- 原始 URL -->
<a href="http://localhost:3000/?test=我是变量"></a>
<!-- 攻击 URL -->
<a href="http://localhost:3000/?test=" onclick=alert(1)""></a>
<!-- URLEncode -->
<a href="http://localhost:3000/?test=%22%20onclick%3balert%281%29%22"></a>
复制代码

但是是否用了 URLEncode 就万事大吉了呢?

不不不

如果整个 URL 被用户控制,那么前面的 http://localhost:3000 等部分被转义不就乱套了,这些部分是不能被转义的。

一个 URL 的组成如下:

[Protocal][Host][Path][Search][Hash]

栗子:

http://localhost:3000/a/b/c?search=123#666aaa

[Protocal] 对应 http://

[Host] 对应 localhost:3000

[Path] 对应 /a/b/c

[Search] 对应 ?search=123

[Hash] 对应 #666aaa

一般来说,如果变量是整个 URL ,则应该先检查变量是否以 http 开头,在此之后再对里面的变量进行 URLEncode

富文本处理

在一些网站,网站允许用户富含 HTML 标签的代码,比如文本里面要有图片、视频之类,这些文本展现出来全都是依靠 HTML 代码来实现。

那么,我们需要如何区分安全的 富文本XSS 攻击呢?

我正好在华为做过相关的富文本过滤操作,基本的思想就是:

  1. 首先进行输入检查,保证用户输入的是完整的 HTML 代码,而不是有拼接的代码
  2. 通过 htmlParser 解析出 HTML 代码的标签、属性、事件
  3. 富文本事件 肯定要被禁止,因为富文本 并不需要 事件 这种东西,另外一些危险的标签也需要禁止,例如: <iframe><script><base><form>
  4. 利用白名单机制,只允许安全的标签嵌入,例如:<a><img>div等,白名单不仅仅适用于标签,也适用于属性
  5. 过滤用户 CSS ,检查是否有危险代码

小结

理论上来说,XSS 漏洞虽然复杂,但是却是可以彻底解决掉的,在设计 XSS 解决方案时,要结合目前的业务需求,从业务风险角度定义每个 XSS 漏洞,针对不同的场景使用不同的方法,同时,很多开源的项目可以借鉴参考,完善自己的 XSS 解决方案。

最后不好意思推广一下我基于 Taro 框架写的组件库:MP-ColorUI

可以顺手 star 一下我就很开心啦,谢谢大家。

点这里是文档

点这里是 GitHUb 地址

关注下面的标签,发现更多相似文章

XSS 防御方法总结

1. XSS攻击原理

XSS原称为CSS(Cross-Site Scripting),因为和层叠样式表(Cascading Style Sheets)重名,所以改称为XSS(X一般有未知的含义,还有扩展的含义)。XSS攻击涉及到三方:攻击者,用户,web server。用户是通过浏览器来访问web server上的网页,XSS攻击就是攻击者通过各种办法,在用户访问的网页中插入自己的脚本,让其在用户访问网页时在其浏览器中进行执行。攻击者通过插入的脚本的执行,来获得用户的信息,比如cookie,发送到攻击者自己的网站(跨站了)。所以称为跨站脚本攻击。XSS可以分为反射型XSS和持久性XSS,还有DOM Based XSS。(一句话,XSS就是在用户的浏览器中执行攻击者自己定制的脚本。)

1.1 反射型XSS

反射性XSS,也就是非持久性XSS。用户点击攻击链接,服务器解析后响应,在返回的响应内容中出现攻击者的XSS代码,被浏览器执行。一来一去,XSS攻击脚本被web server反射回来给浏览器执行,所以称为反射型XSS。

特点:

1> XSS攻击代码非持久性,也就是没有保存在web server中,而是出现在URL地址中;

2> 非持久性,那么攻击方式就不同了。一般是攻击者通过邮件,聊天软件等等方式发送攻击URL,然后用户点击来达到攻击的;

1.2 持久型XSS

区别就是XSS恶意代码存储在web server中,这样,每一个访问特定网页的用户,都会被攻击。

特点:

1> XSS攻击代码存储于web server上;

2> 攻击者,一般是通过网站的留言、评论、博客、日志等等功能(所有能够向web server输入内容的地方),将攻击代码存储到web server上的;

有时持久性XSS和反射型XSS是同时使用的,比如先通过对一个攻击url进行编码(来绕过xss filter),然后提交该web server(存储在web server中), 然后用户在浏览页面时,如果点击该url,就会触发一个XSS攻击。当然用户点击该url时,也可能会触发一个CSRF(Cross site request forgery)攻击。

1.3 DOM based XSS

基于DOM的XSS,也就是web server不参与,仅仅涉及到浏览器的XSS。比如根据用户的输入来动态构造一个DOM节点,如果没有对用户的输入进行过滤,那么也就导致XSS攻击的产生。过滤可以考虑采用esapi4js

参见:http://www.freebuf.com/articles/web/29177.html , http://www.zhihu.com/question/26628342/answer/33504799

2. XSS 存在的原因

XSS 存在的根本原因是,对URL中的参数,对用户输入提交给web server的内容,没有进行充分的过滤。如果我们能够在web程序中,对用户提交的URL中的参数,和提交的所有内容,进行充分的过滤,将所有的不合法的参数和输入内容过滤掉,那么就不会导致“在用户的浏览器中执行攻击者自己定制的脚本”。

但是,其实充分而完全的过滤,实际上是无法实现的。因为攻击者有各种各样的神奇的,你完全想象不到的方式来绕过服务器端的过滤,最典型的就是对URL和参数进行各种的编码,比如escape, encodeURI, encodeURIComponent, 16进制,10进制,8进制,来绕过XSS过滤。那么我们如何来防御XSS呢?

3. XSS 攻击的防御

XSS防御的总体思路是:对输入(和URL参数)进行过滤,对输出进行编码

也就是对提交的所有内容进行过滤,对url中的参数进行过滤,过滤掉会导致脚本执行的相关内容;然后对动态输出到页面的内容进行html编码,使脚本无法在浏览器中执行。虽然对输入过滤可以被绕过,但是也还是会拦截很大一部分的XSS攻击

3.1 对输入和URL参数进行过滤(白名单和黑名单)

下面贴出一个常用的XSS filter的实现代码:

public class XssFilter implements Filter {

    public void init(FilterConfig config) throws ServletException {}

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest)request);
        chain.doFilter(xssRequest, response);
    }

    public void destroy() {}
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    HttpServletRequest orgRequest = null;

    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        orgRequest = request;
    }
    /**
     * 覆盖getParameter方法,将参数名和参数值都做xss过滤。<br/>
     * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取<br/>
     * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
     */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(xssEncode(name));
        if (value != null) {
            value = xssEncode(value);
        }
        return value;
    }
    /**
     * 覆盖getHeader方法,将参数名和参数值都做xss过滤。<br/>
     * 如果需要获得原始的值,则通过super.getHeaders(name)来获取<br/>
     * getHeaderNames 也可能需要覆盖
     */
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(xssEncode(name));
        if (value != null) {
            value = xssEncode(value);
        }
        return value;
    }
    /**
     * 将容易引起xss漏洞的半角字符直接替换成全角字符
     *
     * @param s
     * @return
     */
    private static String xssEncode(String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }
        StringBuilder sb = new StringBuilder(s.length() + 16);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
            case '>':
                sb.append('>');// 全角大于号
                break;
            case '<':
                sb.append('<');// 全角小于号
                break;
            case '\'':
                sb.append('‘');// 全角单引号
                break;
            case '\"':
                sb.append('“');// 全角双引号
                break;
            case '&':
                sb.append('&');// 全角
                break;
            case '\\':
                sb.append('\');// 全角斜线
                break;
            case '#':
                sb.append('#');// 全角井号
                break;
            case '%':    // < 字符的 URL 编码形式表示的 ASCII 字符(十六进制格式) 是: %3c
                processUrlEncoder(sb, s, i);
                break;
            default:
                sb.append(c);
                break;
            }
        }
        return sb.toString();
    }
    public static void processUrlEncoder(StringBuilder sb, String s, int index){
        if(s.length() >= index + 2){
            if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'c' || s.charAt(index+2) == 'C')){    // %3c, %3C
                sb.append('<');
                return;
            }
            if(s.charAt(index+1) == '6' && s.charAt(index+2) == '0'){    // %3c (0x3c=60)
                sb.append('<');
                return;
            }            
            if(s.charAt(index+1) == '3' && (s.charAt(index+2) == 'e' || s.charAt(index+2) == 'E')){    // %3e, %3E
                sb.append('>');
                return;
            }
            if(s.charAt(index+1) == '6' && s.charAt(index+2) == '2'){    // %3e (0x3e=62)
                sb.append('>');
                return;
            }
        }
        sb.append(s.charAt(index));
    }
    /**
     * 获取最原始的request
     *
     * @return
     */
    public HttpServletRequest getOrgRequest() {
        return orgRequest;
    }
    /**
     * 获取最原始的request的静态方法
     *
     * @return
     */
    public static HttpServletRequest getOrgRequest(HttpServletRequest req) {
        if (req instanceof XssHttpServletRequestWrapper) {
            return ((XssHttpServletRequestWrapper) req).getOrgRequest();
        }
        return req;
    }
}

然后在web.xml中配置该filter:

    <filter>
        <filter-name>xssFilter</filter-name>
        <filter-class>com.xxxxxx.filter.XssFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>xssFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

主要的思路就是将容易导致XSS攻击的边角字符替换成全角字符。< 和 > 是脚本执行和各种html标签需要的,比如 <script>,& 和 # 以及 % 在对URL编码试图绕过XSS filter时,会出现。我们说对输入的过滤分为白名单和黑名单。上面的XSS filter就是一种黑名单的过滤,黑名单就是列出不能出现的对象的清单,一旦出现就进行处理。还有一种白名单的过滤,白名单就是列出可被接受的内容,比如规定所有的输入只能是“大小写的26个英文字母和10个数字,还有-和_”,所有其他的输入都是非法的,会被抛弃掉。很显然如此严格的白名单是可以100%拦截所有的XSS攻击的。但是现实情况一般是不能进行如此严格的白名单过滤的。

对于输入,处理使用XSS filter之外,对于每一个输入,在客户端和服务器端还要进行各种验证,验证是否合法字符,长度是否合法,格式是否正确。在客户端和服务端都要进行验证,因为客户端的验证很容易被绕过。其实这种验证也分为了黑名单和白名单。黑名单的验证就是不能出现某些字符,白名单的验证就是只能出现某些字符。尽量使用白名单。

3.2 对输出进行编码

在输出数据之前对潜在的威胁的字符进行编码、转义是防御XSS攻击十分有效的措施。如果使用好的话,理论上是可以防御住所有的XSS攻击的。

对所有要动态输出到页面的内容,通通进行相关的编码和转义。当然转义是按照其输出的上下文环境来决定如何转义的。

1> 作为body文本输出,作为html标签的属性输出:

比如:<span>${username}</span>, <p><c:out value="${username}"></c:out></p>

<input type="text" value="${username}" />

此时的转义规则如下:

< 转成 &lt;

> 转成 &gt;

& 转成 &amp;

" 转成 &quot;

' 转成 &#39

2> javascript事件

<input type="button" onclick='go_to_url("${myUrl}");' />

除了上面的那些转义之外,还要附加上下面的转义:

\ 转成 \\

/ 转成 \/

; 转成 ;(全角;)

3> URL属性

如果 <script>, <style>, <imt> 等标签的 src 和 href 属性值为动态内容,那么要确保这些url没有执行恶意连接。

确保:href 和 src 的值必须以 http://开头,白名单方式;不能有10进制和16进制编码字符。

4. HttpOnly 与 XSS防御

XSS 一般利用js脚步读取用户浏览器中的Cookie,而如果在服务器端对 Cookie 设置了HttpOnly 属性,那么js脚本就不能读取到cookie,但是浏览器还是能够正常使用cookie。(下面的内容转自:http://www.oschina.net/question/12_72706)

一般的Cookie都是从document对象中获得的,现在浏览器在设置 Cookie的时候一般都接受一个叫做HttpOnly的参数,跟domain等其他参数一样,一旦这个HttpOnly被设置,你在浏览器的 document对象中就看不到Cookie了,而浏览器在浏览的时候不受任何影响,因为Cookie会被放在浏览器头中发送出去(包括ajax的时 候),应用程序也一般不会在js里操作这些敏感Cookie的,对于一些敏感的Cookie我们采用HttpOnly,对于一些需要在应用程序中用js操作的cookie我们就不予设置,这样就保障了Cookie信息的安全也保证了应用。

如果你正在使用的是兼容 Java EE 6.0 的容器,如 Tomcat 7,那么 Cookie 类已经有了 setHttpOnly 的方法来使用 HttpOnly 的 Cookie 属性了。

1
cookie.setHttpOnly(true);

设置完后生成的 Cookie 就会在最后多了一个 ;HttpOnly

另外使用 Session 的话 jsessionid 这个 Cookie 可通过在 Context 中使用 useHttpOnly 配置来启用 HttpOnly,例如:

1
2
<Context path="" docBase="D:/WORKDIR/oschina/webapp"
    reloadable="false" useHttpOnly="true"/>

也可以在 web.xml 配置如下:

1
2
3
4
5
<session-config>
 <cookie-config>
  <http-only>true</http-only>
 </cookie-config>
<session-config>

对于 不支持 HttpOnly 的低版本java ee,可以手动设置(比如在一个过滤器中):

String sessionid = request.getSession().getId();  
response.setHeader("SET-COOKIE", "JSESSIONID=" + sessionid + "; HttpOnly");

对于 .NET 2.0 应用可以在 web.config 的 system.web/httpCookies 元素使用如下配置来启用 HttpOnly?

1
<httpCookies httpOnlyCookies="true" …>

而程序的处理方式如下:

C#:

1
2
3
HttpCookie myCookie = new HttpCookie("myCookie");
myCookie.HttpOnly = true;
Response.AppendCookie(myCookie);

VB.NET:

1
2
3
Dim myCookie As HttpCookie = new HttpCookie("myCookie")
myCookie.HttpOnly = True
Response.AppendCookie(myCookie)

.NET 1.1 只能手工处理:

1
Response.Cookies[cookie].Path += ";HttpOnly";

PHP 从 5.2.0 版本开始就支持 HttpOnly

1
session.cookie_httponly = True

PS: 实际测试在 Tomcat 8.0.21 中在 web.xml 和 context中设置 HttpOnly 属性不起作用。只有cookie.setHttpOnly(true); 和

 response.setHeader("SET-COOKIE", "JSESSIONID=" + sessionid+ "; HttpOnly"); 起作用

参考链接:https://www.owasp.org/index.php/HttpOnly#Using_Java_to_Set_HttpOnly

5. 总结下

XSS攻击访问方法:对输入(和URL参数)进行过滤,对输出进行编码;白名单和黑名单结合;

--------------------------------------------分割线----------------------------------------------------

6. 使用 OWASP AntiSamy Project 和 OWASP ESAPI for Java 来防御 XSS(还有客户端的esapi4js: esapi.js)

上面说到了防御XSS的一些原理和思想,对输入进行过滤,对输出进行编码。那么OWASP组织中项目 AntiSamy 和 ESAPI 就恰恰能够帮助我们。其中 AntiSamy 提供了 XSS Filter 的实现,而 ESAPI 则提供了对输出进行编码的实现。(samy是一个人名,他第一次在MySpace上实现第一个XSS工具蠕虫,所以AntiSamy项目就是反XSS攻击的意思; ESAPI就是enterprise security api的意思;owasp: Open Web Application Securtiy Project)

使用maven可以引入依赖包:

    <dependency>
        <groupId>org.owasp.antisamy</groupId>
        <artifactId>antisamy</artifactId>
        <version>1.5.3</version>
    </dependency>
    <dependency>
        <groupId>org.owasp.esapi</groupId>
        <artifactId>esapi</artifactId>
        <version>2.1.0</version>
    </dependency>

使用AntiSamy构造XSS Filter的方法如下:

 XssFilter
 XssRequestWrapper

然后在web.xml中配置:

 web.xml

上面我们不仅对输入进行扫描过滤,而且设置了response中sessionId的httponly属性,我们看下httponly的实际效果:

我们看到baidu使用了httponly=true,我们的localhost的httponly=true也其作用了。

ESAPI 编码输出,使用方法入下:

        ESAPI.encoder().encodeForHTML(String input);
        ESAPI.encoder().encodeForHTMLAttribute(String input);
        ESAPI.encoder().encodeForJavaScript(String input);
        ESAPI.encoder().encodeForCSS(String input);
        ESAPI.encoder().encodeForURL(String input);
        
        MySQLCodec codec = new MySQLCodec(Mode.STANDARD);
        ESAPI.encoder().encodeForSQL(codec, String input);

对应上面 3.2 中说到的编码输出。encodeForSQL 用于 防御 MySQL 的 sql 注入。

(压缩版的esapi-compressed.js,大小为51K。)

-----------------------------------------------------

低危漏洞- X-Frame-Options Header未配置

X-Frame-Options 响应头
X-Frame-Options HTTP 响应头是用来给浏览器指示允许一个页面可否在 <frame>, </iframe> 或者 <object> 中展现的标记。网站可以使用此功能,来确保自己网站的内容没有被嵌到别人的网站中去,也从而避免了点击劫持 (clickjacking) 的攻击。

使用 X-Frame-Options
X-Frame-Options 有三个值:

DENY
表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
SAMEORIGIN
表示该页面可以在相同域名页面的 frame 中展示。
ALLOW-FROM uri
表示该页面可以在指定来源的 frame 中展示。
换一句话说,如果设置为 DENY,不光在别人的网站 frame 嵌入时会无法加载,在同域名页面中同样会无法加载。另一方面,如果设置为 SAMEORIGIN,那么页面就可以在同域名页面的 frame 中嵌套。

配置 Apache

配置 Apache 在所有页面上发送 X-Frame-Options 响应头,需要把下面这行添加到 ‘site’ 的配置中:

Header always append X-Frame-Options SAMEORIGIN

配置 nginx

配置 nginx 发送 X-Frame-Options 响应头,把下面这行添加到 ‘http’, ‘server’ 或者 ‘location’ 的配置中:

add_header X-Frame-Options SAMEORIGIN;

配置 IIS

配置 IIS 发送 X-Frame-Options 响应头,添加下面的配置到 Web.config 文件中:

<system.webServer>
  ...

  <httpProtocol>
    <customHeaders>
      <add name="X-Frame-Options" value="SAMEORIGIN" />
    </customHeaders>
  </httpProtocol>

  ...
</system.webServer>

结果

在 Firefox 尝试加载 frame 的内容时,如果 X-Frame-Options 响应头设置为禁止访问了,那么 Firefox 会用 about:blank 展现到 frame 中。也许从某种方面来讲的话,展示为错误消息会更好一点。

Tomcat处理:

定义一个过滤器filter,在HTTP请求头中加入x-frame-options: SAMEORIGIN即可。

最后补充代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * 安全过滤器
 * @author digdeep@126.com
 */
public class SecureFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse resp = (HttpServletResponse)response;
        
        String sessionid = req.getSession().getId(); 
        resp.setHeader("SET-COOKIE""JSESSIONID=" + sessionid + "; HttpOnly");
        resp.setHeader("x-frame-options","SAMEORIGIN"); //X-Frame-Options
        
        chain.doFilter(request, response);
    }
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        
    }
    
    @Override
    public void destroy() {
    }
}

进行配置:

1
2
3
4
5
6
7
8
<filter>
  <filter-name>SecureFilter</filter-name>
  <filter-class>com.diantu.hemr.filter.SecureFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>SecureFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

进行验证生效:

转自https://www.cnblogs.com/digdeep/p/4695348.html