CSRF攻击与防御

CSRF是什么

CS福睿斯F在百度百科中是这么说的:“CS宝马X5F(Cross-site request forgery跨站恳求伪造,也被叫做“one click attack”可能session riding,平常缩写为CSOdysseyF也许XSCRUISERF,是一种对网址的黑心使用。就算听上去像跨站脚本(XSS),但它与XSS特别例外,并且攻击格局大约相左。XSS利用站点内的信赖顾客,而CS帕杰罗F则通过伪装来自受信任顾客的央浼来行使受信任的网址。与XSS攻击比较,CS大切诺基F攻击往往一点都不大流行(由此对其进展防守的财富也格外罕见)和麻烦防备,所以被认为比XSS更具危慢性。”。

 

CS奥德赛F攻击原理

图片 1

从上海体育场面能够见到,要到位二遍CSOdysseyF攻击,受害者必需逐项实现2个步骤

1.登陆受信赖网址A,并在本土生成Cookie。

2.在不登出A的图景下,访谈危急网址B (这几个历程做到攻击)

 

CSRF 举例

CS奥迪Q3F 攻击能够在被害人毫不知情的情景下以事主名义冒领伏乞发送给受攻击站点,进而在未曾授权的气象下试行在权力珍重之下的操作。

举个例子说,受害者 Bob 在银行有一笔积蓄,通过对银行的网址发送必要 Bob 把 一千000 的储蓄转到 bob2 的账号下。

平常来说情状下,该乞请发送到网址后,服务器会先验证该央浼是不是来自二个合法的 session,並且该 session 的客户 鲍伯 已经打响登入。

红客 Mallory 本身在该积储所也许有账户,他明白上文中的 U哈弗L 能够把钱进行转帐操作。

Mallory 能够谐和发送一个呼吁给银行: Mallory 而非 Bob,他无法透过平安认证,因而该必要不会起成效。

那时候,Mallory 想到利用 CSENVISIONF 的攻击方式,他先自身做八个网址,在网址中放入如下代码:<img src=” ” />,並且经过广告等诱惑 Bob 来访问他的网址。当 鲍伯访谈该网址时,上述 url 就能从 鲍伯 的浏览器发向银行,而以此央浼会顺便 Bob 浏览器中的 cookie 一同发向银行服务器。大多数场地下,该央求会战败,因为他须求 鲍伯的注明音信。

唯独,假诺 Bob 那时候恰巧刚访谈他的银行后赶紧,他的浏览器与银行网址之间的 session 尚未过期,浏览器的 cookie 之中含有 鲍伯 的印证消息。

那时候,正剧爆发了,那一个 url 央浼就能够拿走响应,钱将从 鲍勃 的账号转移到 Mallory 的账号,而 Bob 那时毫不知情。等今后 鲍勃开掘账户钱少了,即便他去银行查询日志,他也只能开掘确实有一个源于于他作者的官方乞请退换了本金,未有另外被口诛笔伐的印迹。而 Mallory 则能够获得钱后逍遥法外。

 

CSRF 防御

对此Web应用来讲能够有以下二种方案:

1)验证 HTTP Referer 字段

  遵照 HTTP 合同,在 HTTP 头中有三个字段叫 Referer,它记录了该 HTTP 须求的发源地址。在平时状态下,访谈一个安然无事受限页面包车型地铁伸手来自于同三个网址,比方需要会见 bank.example,然后通过点击页面上的按键来触发转账事件。那时,该转帐央求的 Referer 值就能够是转载按键所在的页面包车型客车 U瑞虎L,日常是以 bank.example 域名最早的地点。而只要黑客要对银行网址进行 CS路虎极光F 攻击,他不得不在他本身的网址组织乞求,当顾客通过黑客的网址发送央浼到银行时,该央浼的 Referer 是指向黑客自个儿的网址。因而,要守护 CS酷威F 攻击,银行网址只必要对此每叁个转折央浼验证其 Referer 值,要是是以 bank.example 初阶的域名,则证实该诉求是源于银行网址自个儿的央求,是官方的。纵然 Referer 是别的网址的话,则有希望是红客的 CS索罗德F 攻击,拒绝该央浼。

  这种措施的明显的好处正是简单易行,网址的日常开采职员没有供给担心CSPRADOF 的尾巴,只必要在终极给持有安全敏感的呼吁统一扩张三个拦截器来检查 Referer 的值就足以。特别是对此近来现成的体系,无需更动近些日子系统的别的已有代码和逻辑,未有危机,极度简便。

  但是,这种方法毫无百步穿杨。Referer 的值是由浏览器提供的,就算 HTTP 公约上有显明的需要,然而各样浏览器对于 Referer 的切实落实只怕有反差,并不可能确定保障浏览器自戊子有安全漏洞。使用验证 Referer 值的办法,正是把安全性都正视于第三方(即浏览器)来维持,从理论上来说,那样并不安全。事实上,对于一些浏览器,举个例子IE6 或 FF2,前段时间一度有部分艺术能够篡改 Referer 值。借使 bank.example 网址帮助 IE6 浏览器,骇客完全能够把顾客浏览器的 Referer 值设为以 bank.example 域名开端的地点,那样就足以因而认证,进而实行 CS哈弗F 攻击。

  即正是利用最新的浏览器,黑客不能够篡改 Referer 值,这种艺术照旧极度。因为 Referer 值会记录下客户的访问来源,某些客商感到那样会凌犯到她们友善的隐秘权,极其是有些组织怀想Referer 值会把组织内网中的某个音讯败露到外网中。由此,客商本身能够安装浏览器使其在发送诉求时不再提供 Referer。当他俩平常访谈银行网址时,网址会因为央求未有 Referer 值而认为是 CSOdysseyF 攻击,拒绝法定客商的会见。

2)在伸手地址中增添 token 并表达

  CS奥迪Q5F 攻击之所以能够得逞,是因为黑客能够完全伪造客商的央浼,该央求中有所的客商验证新闻都是存在于 cookie 中,由此骇客能够在不知底那个注脚消息的景色下直接动用顾客本身的 cookie 来通过平安认证。要对抗 CSGL450F,关键在于在伸手中放入hacker所不可能伪造的音信,并且该新闻不设有于 cookie 之中。能够在 HTTP 央求中以参数的样式进入五个随机产生的 token,并在劳动器端建构一个拦截器来评释那几个 token,如若诉求中平素不 token 只怕 token 内容不得法,则感觉大概是 CSSportageF 攻击而拒绝该恳求。

  这种办法要比检查 Referer 要安全一些,token 能够在客户登录后发生并放于 session 之中,然后在每便央浼时把 token 从 session 中拿出,与央浼中的 token 进行比对,但这种措施的难关在于怎么着把 token 以参数的款型步向央求。对于 GET 乞请,token 将附在乞请地址然后,那样 UWranglerL 就产生 而对于 POST 供给来说,要在 form 的结尾加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,那样就把 token 以参数的花样步入诉求了。然而,在三个网址中,能够承受必要的地点相当多,要对于每二个央求都抬高 token 是很麻烦的,何况很轻松漏掉,平时使用的方法就是在历次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中全体的 a 和 form 标签后投入 token。那样能够缓慢解决大部分的乞请,然则对于在页面加载之后动态变化的 html 代码,这种艺术就从未效果与利益,还索要程序员在编码时手动增加 token。

  该方法还也可以有一个劣点是难以保障 token 本人的安全。非常是在一些论坛之类支持客户自个儿发布内容的网址,红客能够在下面宣布本身个人网址的地点。由于系统也会在这些地点前面加上 token,黑客能够在融洽的网址上获得这一个 token,并登时就能够发动 CS翼虎F 攻击。为了防止那点,系统能够在丰裕 token 的时候扩充贰个剖断,即便这几个链接是链到本身本站的,就在前边增多token,若是是向阳外网则不加。可是,固然那个 csrftoken 不以参数的款式附加在呼吁之中,黑客的网址也一直以来能够因而 Referer 来收获这些 token 值以动员 CSOdysseyF 攻击。这也是部分客商心爱手动关闭浏览器 Referer 成效的原因。

 

3)在 HTTP 头中自定义属性并证实

  这种方法也是选用 token 并开展表明,和上一种艺术区别的是,这里并非把 token 以参数的款型置于 HTTP 伏乞之中,而是把它放到 HTTP 头中自定义的脾气里。通过 XMLHttpRequest 那一个类,能够一遍性给全体此类要求加上 csrftoken 那些 HTTP 头属性,并把 token 值归入个中。那样消除了上种方法在呼吁中参与 token 的艰苦,同有时常间,通过 XMLHttpRequest 央浼的地点不会被记录到浏览器的地址栏,也不用忧虑 token 会透过 Referer 走漏到别的网址中去。

  但是这种艺术的局限性非常的大。XMLHttpRequest 央浼经常用于 Ajax 方法中对此页面局地的异步刷新,并不是全部的央求都契合用这些类来倡导,而且经过此类诉求得到的页面不能够被浏览器所记录下,进而举办发展,后退,刷新,收藏等操作,给顾客带来困难。别的,对于从未进展 CSPRADOF 防护的残存系统的话,要运用这种措施来开展防止,要把全数乞求都改为 XMLHttpRequest 诉求,那样差相当少是要重写整个网址,那代价无疑是不能够经受的。

 

Tomcat中的CsrfPreventionFilter

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.catalina.filters;

import java.io.IOException;
import java.io.Serializable;
import java.security.SecureRandom;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;

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 javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

/**
 * Provides basic CSRF protection for a web application. The filter assumes
 * that:
 * <ul>
 * <li>The filter is mapped to /*</li>
 * <li>{@link HttpServletResponse#encodeRedirectURL(String)} and
 * {@link HttpServletResponse#encodeURL(String)} are used to encode all URLs
 * returned to the client
 * </ul>
 */
public class CsrfPreventionFilter extends FilterBase {

    private static final Log log =
        LogFactory.getLog(CsrfPreventionFilter.class);

    private String randomClass = SecureRandom.class.getName();

    private Random randomSource;

    private final Set<String> entryPoints = new HashSet<String>();

    private int nonceCacheSize = 5;

    @Override
    protected Log getLogger() {
        return log;
    }

    /**
     * Entry points are URLs that will not be tested for the presence of a valid
     * nonce. They are used to provide a way to navigate back to a protected
     * application after navigating away from it. Entry points will be limited
     * to HTTP GET requests and should not trigger any security sensitive
     * actions.
     * 
     * @param entryPoints   Comma separated list of URLs to be configured as
     *                      entry points.
     */
    public void setEntryPoints(String entryPoints) {
        String values[] = entryPoints.split(",");
        for (String value : values) {
            this.entryPoints.add(value.trim());
        }
    }

    /**
     * Sets the number of previously issued nonces that will be cached on a LRU
     * basis to support parallel requests, limited use of the refresh and back
     * in the browser and similar behaviors that may result in the submission
     * of a previous nonce rather than the current one. If not set, the default
     * value of 5 will be used.
     * 
     * @param nonceCacheSize    The number of nonces to cache
     */
    public void setNonceCacheSize(int nonceCacheSize) {
        this.nonceCacheSize = nonceCacheSize;
    }

    /**
     * Specify the class to use to generate the nonces. Must be in instance of
     * {@link Random}.
     * 
     * @param randomClass   The name of the class to use
     */
    public void setRandomClass(String randomClass) {
        this.randomClass = randomClass;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // Set the parameters
        super.init(filterConfig);

        try {
            Class<?> clazz = Class.forName(randomClass);
            randomSource = (Random) clazz.newInstance();
        } catch (ClassNotFoundException e) {
            ServletException se = new ServletException(sm.getString(
                    "csrfPrevention.invalidRandomClass", randomClass), e);
            throw se;
        } catch (InstantiationException e) {
            ServletException se = new ServletException(sm.getString(
                    "csrfPrevention.invalidRandomClass", randomClass), e);
            throw se;
        } catch (IllegalAccessException e) {
            ServletException se = new ServletException(sm.getString(
                    "csrfPrevention.invalidRandomClass", randomClass), e);
            throw se;
        }
    }


    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        ServletResponse wResponse = null;

        if (request instanceof HttpServletRequest &&
                response instanceof HttpServletResponse) {

            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse res = (HttpServletResponse) response;

            boolean skipNonceCheck = false;

            if (Constants.METHOD_GET.equals(req.getMethod())) {
                String path = req.getServletPath();
                if (req.getPathInfo() != null) {
                    path = path + req.getPathInfo();
                }

                if (entryPoints.contains(path)) {
                    skipNonceCheck = true;
                }
            }

            HttpSession session = req.getSession(false);

            @SuppressWarnings("unchecked")
            LruCache<String> nonceCache = (session == null) ? null
                    : (LruCache<String>) session.getAttribute(
                            Constants.CSRF_NONCE_SESSION_ATTR_NAME);
       // 对请求进行验证
            if (!skipNonceCheck) {
                String previousNonce =
                    req.getParameter(Constants.CSRF_NONCE_REQUEST_PARAM);

                if (nonceCache == null || previousNonce == null ||
                        !nonceCache.contains(previousNonce)) {
                    res.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }
            }

            if (nonceCache == null) {
                nonceCache = new LruCache<String>(nonceCacheSize);
                if (session == null) {
                    session = req.getSession(true);
                }
                session.setAttribute(
                        Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
            }
            // 为下一次请求,生成一个随机数放在URL中
            String newNonce = generateNonce();
            // 同时将随机数存储在session中
            nonceCache.add(newNonce);

            wResponse = new CsrfResponseWrapper(res, newNonce);
        } else {
            wResponse = response;
        }

        chain.doFilter(request, wResponse);
    }


    @Override
    protected boolean isConfigProblemFatal() {
        return true;
    }


    /**
     * Generate a once time token (nonce) for authenticating subsequent
     * requests. This will also add the token to the session. The nonce
     * generation is a simplified version of ManagerBase.generateSessionId().
     * 
     */
    protected String generateNonce() {
        byte random[] = new byte[16];

        // Render the result as a String of hexadecimal digits
        StringBuilder buffer = new StringBuilder();

        randomSource.nextBytes(random);

        for (int j = 0; j < random.length; j++) {
            byte b1 = (byte) ((random[j] & 0xf0) >> 4);
            byte b2 = (byte) (random[j] & 0x0f);
            if (b1 < 10)
                buffer.append((char) ('0' + b1));
            else
                buffer.append((char) ('A' + (b1 - 10)));
            if (b2 < 10)
                buffer.append((char) ('0' + b2));
            else
                buffer.append((char) ('A' + (b2 - 10)));
        }

        return buffer.toString();
    }

    protected static class CsrfResponseWrapper
            extends HttpServletResponseWrapper {

        private String nonce;

        public CsrfResponseWrapper(HttpServletResponse response, String nonce) {
            super(response);
            this.nonce = nonce;
        }

        @Override
        @Deprecated
        public String encodeRedirectUrl(String url) {
            return encodeRedirectURL(url);
        }

        @Override
        public String encodeRedirectURL(String url) {
            return addNonce(super.encodeRedirectURL(url));
        }

        @Override
        @Deprecated
        public String encodeUrl(String url) {
            return encodeURL(url);
        }

        @Override
        public String encodeURL(String url) {
            return addNonce(super.encodeURL(url));
        }

        /**
         * Return the specified URL with the nonce added to the query string. 
         *
         * @param url URL to be modified
         * @param nonce The nonce to add
         */
        private String addNonce(String url) {

            if ((url == null) || (nonce == null))
                return (url);

            String path = url;
            String query = "";
            String anchor = "";
            int pound = path.indexOf('#');
            if (pound >= 0) {
                anchor = path.substring(pound);
                path = path.substring(0, pound);
            }
            int question = path.indexOf('?');
            if (question >= 0) {
                query = path.substring(question);
                path = path.substring(0, question);
            }
            StringBuilder sb = new StringBuilder(path);
            if (query.length() >0) {
                sb.append(query);
                sb.append('&');
            } else {
                sb.append('?');
            }
            sb.append(Constants.CSRF_NONCE_REQUEST_PARAM);
            sb.append('=');
            sb.append(nonce);
            sb.append(anchor);
            return (sb.toString());
        }
    }

    protected static class LruCache<T> implements Serializable {

        private static final long serialVersionUID = 1L;

        // Although the internal implementation uses a Map, this cache
        // implementation is only concerned with the keys.
        private final Map<T,T> cache;

        public LruCache(final int cacheSize) {
            cache = new LinkedHashMap<T,T>() {
                private static final long serialVersionUID = 1L;
                @Override
                protected boolean removeEldestEntry(Map.Entry<T,T> eldest) {
                    if (size() > cacheSize) {
                        return true;
                    }
                    return false;
                }
            };
        }

        public void add(T key) {
            synchronized (cache) {
                cache.put(key, null);
            }
        }

        public boolean contains(T key) {
            synchronized (cache) {
                return cache.containsKey(key);
            }
        }
    }
}

 

本文由银河网址发布于银河网址,转载请注明出处:CSRF攻击与防御

您可能还会对下面的文章感兴趣: