Web安全头号大敌XSS漏洞解决最佳实践

写在前面

虽然今天是周末,但职位每日推荐不可少,万一有努力找工作的小伙伴因为我一天偷懒导致没找到满意工作那就是我的罪过了,今日职位已放置在文章末尾,请注意查看!

引言

XSS 是目前最普遍的 Web 应用安全漏洞,它带来的危害是巨大的,是 Web 安全的头号大敌。

关键词

1. 什么是 XSS 漏洞?

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 等各种内容。

2. XSS 漏洞攻击原理及攻击手段

HTML 是一种超文本标记语言,通过将一些字符特殊地对待来区别文本和标记,例如,小于符号(<)被看作是 HTML 标签的开始,之间的字符是页面的标题等等。

当动态页面中插入的内容含有这些特殊字符(如<)时,用户浏览器会将其误认为是插入了 HTML 标签,当这些 HTML 标签引入了一段 JavaScript 脚本时,这些脚本程序就将会在用户浏览器中执行。所以,当这些特殊字符不能被动态页面检查或检查出现失误时,就将会产生 XSS 漏洞。

常用的 XSS 攻击手段和目的有:

1、盗用 cookie,获取敏感信息。

2、利用植入 Flash,通过 crossdomain 权限设置进一步获取更高权限;或者利用 Java 等得到类似的操作。

3、利用 iframe、frame、XMLHttpRequest 或上述 Flash 等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。

4、利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。

5、在访问量极大的一些页面上的 XSS 可以攻击一些小型网站,实现 DDoS 攻击的效果。

最简单的 XSS 示例



3. XSS 分类如下:

分类

主要特点

存储型 XSS

经过后端服务处理,数据存储在数据库端

反射型 XSS

经过后端服务处理,不存储数据库

DOM型 XSS

不经过后端服务处理,不存储数据库


4. XSS 漏洞分析

4.1 存储型 XSS

通过网页注入的代码最终会存储在数据库或其他物理文件中,在某个页面中注入的代码会被浏览器成功执行,该类型的漏洞存在持久性的特点。

主要特点:

图解成因分析:

4.2 反射型 XSS

一般是通过 url 的形式注入代码,注入的代码不在服务器端存储,但会在服务器端进行处理然后进行回显,在回显时浏览器会触发漏洞执行注入代码,该类型攻击具有临时性特点。

主要特点:

图解成因分析

4.3 DOM 型 XSS

也是通过 url 的形式注入代码,注入的代码服务器端程序不存储、不处理,而是由浏览器进行处理,该类型攻击也具有临时性特点。主要特点:

图解成因分析


5. 三种XSS漏洞对比

分类

一般表现形式

特点

存储型 XSS

表现为包含表单的页面,post提交后数据存储在数据库,通过其他页面访问触发

存储、持久性

反射型XSS

表现为包含参数的url地址,参数经后端程序程序处理后回显,通过访问 url 触发

url参数、后端处理参数、临时性

DOM型XSS

表现为包含参数的url地址,参数由页面中的JS代码处理,通过访问 url 触发

url参数、JS处理参数、临时性


6. 植入 JS 代码攻击及危害分析

外在表现形式:

基本实现原理

潜在危害:


7. 植入 HTML 代码攻击及危害分析

外在表现形式:

基本实现原理:

潜在危害:


8. XSS 简单预防策略

8.1 对 html 标签进行字符替换:

replaceAll("

存在问题:

大小写问题

优化升级:

正则表达式

存在问题:

反替换

8.2 对 html 字符转义或是半角转全角字符:

{ "<", ">", """}
转义:
{ "<", ">", """})
全角:
{ "<, ">", """}

新场景:

页面中需要根据某个参数生成文字链接

存在问题:

a 标签的 href 属性 javascript:

其他问题:

针对 json 字符串的场景

如何公用的问题


9. XSS 漏洞预防策略最佳实践

9.1 输入环节

9.2 Cookie 防护

9.3 X-Frame-Options 响应头 (是否允许frame、iframe等标记)

9.4 输出环节

thymeleaf

--->

JSP

---> 

9.5 DOM 型 XSS


10. 后端服务编码实践

通过 https://start.spring.io/ 快速创建 springboot 应用:

解压并在 IDEA 导入刚刚创建的 xss-demo 项目

在 pom.xml 添加相关依赖:



     org.owasp.antisamy
     antisamy
     1.5.7




    com.alibaba
    fastjson
    1.2.62

修改 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 包装类编码如下:

/**
 * @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 配置过滤器类编码如下:

/**
 * @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 实体类:

/**
 * @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 测试控制器类编码如下:

/**
 * @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 层处理。


11. 能不能根本上解决问题,即浏览器自动禁止外部注入恶意脚本?

开启 CSP (内容安全策略 Content Security Policy)方法:设置 HTTP 的 头部字段

resp.setHeader("Content-Security-Policy","default-src http: https:");

设置网页的标签

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 方式)

本文转载自码上修行

今日职位推荐:

前端开发主管

要求:

1. 本科及以上学历,软件技术、计算机科学与技术、电子工程等相关专业,5年及以上前端开发经验

2. 精通Vue/JQuery等JS框架,HTML/HTML5/CSS3/JavaScript,熟练使用uniapp

3. 具备浏览器开发调试技能,能分析解决问题

4. 掌握Web前端构建工具,能够运用构建工具

5. 具有基于VUE 低代码开发经验

办公地址:苏州市吴中区

薪资范围:20k-25k

投递方式:Freemen App中定位苏州搜索前端开发主管

页面更新:2024-04-13

标签:攻击者   大敌   表单   头号   过滤器   脚本   漏洞   恶意   策略   参数   代码   文件   网页

1 2 3 4 5

上滑加载更多 ↓
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top