赞
踩
XSS 是目前最普遍的 Web 应用安全漏洞,它带来的危害是巨大的,是 Web 安全的头号大敌。
跨站
脚本(JavaScript、Java、 VBScript、ActiveX、 Flash 或者 HTML)
注入
执行
XSS 攻击:跨站脚本攻击(Cross Site Scripting),为不和 前端层叠样式表(Cascading Style Sheets)CSS 混淆,故将跨站脚本攻击缩写为 XSS。
XSS(跨站脚本攻击)是指恶意攻击者往 Web 页面里插入恶意 Script 代码,当用户浏览该页时,嵌入其中 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。类似于 sql 注入。是目前最普遍的 Web 应用安全漏洞,也是 Web 攻击中最常见的攻击方式之一。
XSS( 跨站脚本攻击)攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是 JavaScript,但实际上也可以包括 Java、 VBScript、ActiveX、 Flash 或者甚至是普通的 HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和 cookie 等各种内容。
HTML 是一种超文本标记语言,通过将一些字符特殊地对待来区别文本和标记,例如,小于符号(<)被看作是 HTML 标签的开始,之间的字符是页面的标题等等。
当动态页面中插入的内容含有这些特殊字符(如<)时,用户浏览器会将其误认为是插入了 HTML 标签,当这些 HTML 标签引入了一段 JavaScript 脚本时,这些脚本程序就将会在用户浏览器中执行。所以,当这些特殊字符不能被动态页面检查或检查出现失误时,就将会产生 XSS 漏洞。
常用的 XSS 攻击手段和目的有:
- 1、盗用 cookie,获取敏感信息。
- 2、利用植入 Flash,通过 crossdomain 权限设置进一步获取更高权限;或者利用 Java 等得到类似的操作。
- 3、利用 iframe、frame、XMLHttpRequest 或上述 Flash 等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
- 4、利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
- 5、在访问量极大的一些页面上的 XSS 可以攻击一些小型网站,实现 DDoS 攻击的效果。
最简单的 XSS 示例:
- <script>alert('hello world')</script>
- <script src='http://www.smart4j.cn/xxx.js'></script>
- <input type="button" value="评论"></input>
分类 | 主要特点 |
存储型 XSS | 经过后端服务处理,数据存储在数据库端 |
反射型 XSS | 经过后端服务处理,不存储数据库 |
DOM型 XSS | 不经过后端服务处理,不存储数据库 |
通过网页注入的代码最终会存储在数据库或其他物理文件中,在某个页面中注入的代码会被浏览器成功执行,该类型的漏洞存在持久性的特点。
主要特点:
存储
持久性
图解成因分析:
一般是通过 url 的形式注入代码,注入的代码不在服务器端存储,但会在服务器端进行处理然后进行回显,在回显时浏览器会触发漏洞执行注入代码,该类型攻击具有临时性特点。
主要特点:
回显
临时性
图解成因分析
也是通过 url 的形式注入代码,注入的代码服务器端程序不存储、不处理,而是由浏览器进行处理,该类型攻击也具有临时性特点。主要特点:
不存储、不处理
浏览器
临时性
图解成因分析
分类 | 一般表现形式 | 特点 |
存储型 XSS | 表现为包含表单的页面,post提交后数据存储在数据库,通过其他页面访问触发 | 存储、持久性 |
反射型XSS | 表现为包含参数的url地址,参数经后端程序程序处理后回显,通过访问 url 触发 | url参数、后端处理参数、临时性 |
DOM型XSS | 表现为包含参数的url地址,参数由页面中的JS代码处理,通过访问 url 触发 | url参数、JS处理参数、临时性 |
外在表现形式:
直接注入 JavaScript 代码
引用外部 JS 文件
基本实现原理:
通过 img 标签的 src 发送数据
构造表单诱导用户输入账密
构造隐藏的 form 表单自动提交
页面强制跳转
植入文字链接、图片链接
潜在危害:
获取管理员或者其他用户 Cookie,冒充用户身份登录
构造表单诱导用户输入账号、密码,获取账密
跳转到其他网站,网站流量被窃取
植入广告、外链等
通过隐藏友链提升其他网站百度权重(SEO 黑帽)
外在表现形式:
构造 img 标签
构造 a 标签
构造 iframe
构造其他 HTML 标签
基本实现原理:
通过 img 标签的 src 发送数据
通过 img 的 onerror 触发脚本代码
通过 a 标签被动触发脚本代码 href/onclick
通过 iframe 引入第三方页面
直接构造文字链接或图片链接
style 属性嵌入脚本代码 background-image:url("javascript:…");(浏览器已可防范)
潜在危害:
获取管理员或者其他用户 Cookie,冒充用户身份登录
构造表单诱导用户输入账号、密码,获取账密
植入广告、外链等
通过隐藏友链提升其他网站百度权重(SEO 黑帽)
- replaceAll("<script", "");
- ...
存在问题:
大小写问题
优化升级:
正则表达式
存在问题:
反替换<scr<scriptipt
——> <script
- { "<", ">", "\""}
- 转义:
- { "<", ">", """})
- 全角:
- { "<, ">", "\""}
新场景:
页面中需要根据某个参数生成文字链接
存在问题:
a 标签的 href 属性 javascript:
其他问题:
针对 json 字符串的场景
如何公用的问题
页面限制输入长度、特殊字符限制,后端代码限制输入长度、处理特殊字符
Filter 过滤器统一处理(自定义处理规则、使用 Apache Text、使用 Owasp AntiSamy)
cookie 设置 httponly,一般 servlet 容器默认 httponly 为 true
resp.setHeader("SET-COOKIE", "JSESSIONID=" + request.getSession().getId()+ "; HttpOnly");
DENY 不允许、SAMEORIGIN 可在相同域名页面的 frame 中展示、ALLOW-FROM uri 可在指定页的 frame 中展示
add_header X-Frame-Options SAMEORIGIN; //在nginx的 http 或 server 节点中配置即可
也可通过 Filter 设置 resp.setHeader("x-frame-options","SAMEORIGIN");
OWASP ESAPI for Java
显示时对字符进行转义处理,各种模板都有相关语法,注意标签的正确使用
示例如下:
thymeleaf
- <span th:utext="${ result }"></span>---><span th:text="${ result }"></span>
- <!-- utext 与 text 区别 -->
JSP
<c:out value=" ${ content }" escapeXml="false" />---><c:out value=" ${ content }"/> <!-- escapeXml默认true -->
避免 .innerHTML、.outerHTML、document.write() 的使用,应使用 .textContent、.setAttribute() 等。
Vue/React 技术栈,避免使用 v-html/dangerouslySetInnerHTML
尤其注意 onclick、onerror、onload、onmouseover 、eval()、setTimeout()、setInterval() 以及 a 标签的 href
可使用 OWASP esapi4js : esapi.js
通过 https://start.spring.io/ 快速创建 springboot 应用:
解压并在 IDEA 导入刚刚创建的 xss-demo 项目
在 pom.xml 添加相关依赖:
- <!--防止XSS攻击的antiSamy-->
- <dependency>
- <groupId>org.owasp.antisamy</groupId>
- <artifactId>antisamy</artifactId>
- <version>1.5.7</version>
- </dependency>
-
- <!--fastjson依赖-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.62</version>
- </dependency>
修改 xss-demo 工程包结构如下:
XSSFilter 编码如下:
/** * @program: xss-demo * @author: Mr.Zhang * @create: 2021-02-21 15:45 **/public class XssFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletRequest req = (HttpServletRequest) request; String path = req.getServletPath(); //注解配置的是urlPatterns="/*"(过滤所有请求),所以这里对不需要过滤的静态资源url,作忽略处理(大家可以依照具体需求配置) String[] exclusionsUrls = {".js", ".gif", ".jpg", ".png", ".css", ".ico"}; for (String str : exclusionsUrls) { if (path.contains(str)) { chain.doFilter(request, response); return; } } chain.doFilter(new XssRequestWrapper(httpServletRequest), response); }}
XssRequestWrapper 包装类编码如下:
- import com.alibaba.fastjson.JSON;
- import lombok.extern.slf4j.Slf4j;
- import org.owasp.validator.html.AntiSamy;
- import org.owasp.validator.html.CleanResults;
- import org.owasp.validator.html.Policy;
- import org.owasp.validator.html.PolicyException;
- import org.owasp.validator.html.ScanException;
-
- import javax.servlet.ReadListener;
- import javax.servlet.ServletInputStream;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletRequestWrapper;
- import java.io.BufferedReader;
- import java.io.ByteArrayInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.util.Map;
-
- /**
- * @program: xss-demo
- * @author: Mr.Zhang
- * @create: 2021-02-21 15:46
- **/
- @Slf4j
- public class XssRequestWrapper extends HttpServletRequestWrapper {
- public XssRequestWrapper(HttpServletRequest request) {
- super(request);
- }
-
- /**
- * 获取策略文件,直接使用jar中自带的ebay策略文件
- */
- private static InputStream inputStream = XssRequestWrapper.class.getClassLoader()
- .getResourceAsStream("antisamy-ebay.xml");
- private static Policy policy = null;
-
- static {
- try {
- // 使用静态代码块处理策略对象的创建
- policy = Policy.getInstance(inputStream);
- } catch (PolicyException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * 使用AntiSamy进行过滤数据
- * @param html
- * @return
- */
- private String xssClean(String html) {
- String cleanHTML = "";
- try {
- AntiSamy antiSamy = new AntiSamy();
- CleanResults scan = antiSamy.scan(html, policy);
- cleanHTML = scan.getCleanHTML();
- } catch (ScanException e) {
- e.printStackTrace();
- } catch (PolicyException e) {
- e.printStackTrace();
- }
- return cleanHTML;
- }
-
- /**
- * 重写处理请求参数的方法
- * @param name
- * @return
- */
- @Override
- public String[] getParameterValues(String name) {
- String[] values = super.getParameterValues(name);
-
- // 判断参数有值,如果没有值,直接返回
- if (values == null) {
- return null;
- }
-
- // 遍历参数数组,使用AntiSamy进行过滤
- int len = values.length;
- String[] newValues = new String[len];
- for (int i = 0; i < len; i++) {
- // 过滤前的数据
- log.info("使用AntiSamy进行过滤清理,过滤清理之前的数据:{}", values[i]);
- // 进行过滤
- newValues[i] = xssClean(values[i]);
- // 过滤后的数据
- log.info("使用AntiSamy进行过滤清理,过滤清理之后的数据:{}", newValues[i]);
- }
-
- //返回过滤后的结果
- return newValues;
- }
-
- /**
- * 重写处理json数据的方法
- * @return
- * @throws IOException
- */
- @Override
- public ServletInputStream getInputStream() throws IOException {
- // 读取流
- BufferedReader reader = new BufferedReader(
- new InputStreamReader(super.getInputStream(), "UTF-8"));
-
- // 获取json格式的数据
- StringBuilder sb = new StringBuilder();
- String inputStr;
- while ((inputStr = reader.readLine()) != null) {
- sb.append(inputStr);
- }
-
- // 把json转为map
- Map map = JSON.parseObject(sb.toString(), Map.class);
-
- // 过滤前
- log.info("json过滤前:{}", sb.toString());
- // 对map中的value值进行AntiSamy的过滤
- map.keySet().forEach(k -> {
- map.put(k, xssClean(map.get(k).toString()));
- });
- // 过滤后
- String json = JSON.toJSONString(map);
- log.info("json过滤后:{}", json);
-
- // 把json数据转为流的格式进行返回
- ByteArrayInputStream bais = new ByteArrayInputStream(json.getBytes());
-
- return new ServletInputStream() {
- @Override
- public boolean isFinished() {
- return false;
- }
-
- @Override
- public boolean isReady() {
- return false;
- }
-
- @Override
- public void setReadListener(ReadListener listener) { }
-
- @Override
- public int read() throws IOException {
- return bais.read();
- }
- };
-
- }
- }
AntiSamyConfig 配置过滤器类编码如下:
- import cn.smart4j.xssdemo.filter.XssFilter;
- import org.springframework.boot.web.servlet.FilterRegistrationBean;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- /**
- * @program: xss-demo
- * @author: Mr.Zhang
- * @create: 2021-02-21 15:58
- **/
- @Configuration
- public class AntiSamyConfig {
-
- /**
- * 配置xss过滤器
- * @return
- */
- @Bean
- public FilterRegistrationBean create() {
- FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new XssFilter());
- filterRegistrationBean.addUrlPatterns("/*");
- filterRegistrationBean.setOrder(1);
- return filterRegistrationBean;
- }
- }
User 实体类:
- import lombok.Data;
-
- /**
- * @program: xss-demo
- * @author: Mr.Zhang
- * @create: 2021-02-21 15:42
- **/
- @Data
- public class User {
- private int id;
- private String name;
- private int age;
- }
UserController 测试控制器类编码如下:
- import cn.smart4j.xssdemo.domain.User;
- import com.alibaba.fastjson.JSON;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
-
- /**
- * @program: xss-demo
- * @author: Mr.Zhang
- * @create: 2021-02-21 15:43
- **/
- @Slf4j
- @RestController
- @RequestMapping("user")
- public class UserController {
- /**
- * 表单
- * @param user
- * @return
- */
- @PostMapping("save")
- public String save(User user) {
- log.info("name={}, age={}", user.getName(), user.getAge());
- return JSON.toJSONString(user);
- }
-
- /**
- * json数据格式请求体
- * @param user
- * @return
- */
- @PostMapping("json")
- public String saveJson(@RequestBody User user) {
- log.info("user={}", user.toString());
- return JSON.toJSONString(user);
- }
- }
application.properties 配置文件为空,运行启动类后默认端口号8080。
Postman 模拟表单数据请求及响应效果如下:
后端程序控制台日志打印如下:
Postman 模拟 json 数据请求及响应效果如下:
后端程序控制台日志打印如下:
以上两个情况,请求参数中隐藏的 xss 攻击代码被过滤器过滤后再进入 Contrlloer 层处理。
开启 CSP (内容安全策略 Content Security Policy)方法:设置 HTTP 的 头部字段
resp.setHeader("Content-Security-Policy","default-src http: https:");
设置网页的<meta>
标签
<meta http-equiv="Content-Security-Policy" content="form-action 'self';">
CSP 常见可选策略设置如下:
策略 | 含义 |
default-src http: https: ; | 只能通过外联的方式引用 js 和 css |
default-src 'self' http://smart4j.cn/; | 只能在指定的域下加载文件(包含 img) |
form-action 'self''; | form 表单的只能在指定域提交 |
script-src 'self'; | 只限制 js 文件在同域加载文件 |
report-uri /report; | 向指定uri发送违规报告(不支持 meta 方式) |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。