4.2 XSS漏洞

XSS学名为跨站脚本攻击(Cross Site Scriptings),在Web漏洞中XSS是出现最多的漏洞,没有之一。这种漏洞有两种情况,一种是通过外部输入然后直接在浏览器端触发,即反射型XSS;还有一种则是先把利用代码保存在数据库或文件中,当Web程序读取利用代码并输出在页面上时触发漏洞,也就是存储型XSS。XSS攻击在浏览器端触发,大家对其危害认识往往停留在可以窃取cookie、修改页面钓鱼,等等。用一句话来说明该漏洞的危害就是:前端页面能做的事它都能做。

4.2.1 挖掘经验

挖掘XSS漏洞的关键在于寻找没有被过滤的参数,且这些参数传入到输出函数,常用的输出函数列表如下:print、print_r、echo、printf、sprintf、die、var_dump、var_export,所以我们只要寻找带有变量的这些函数即可。另外在代码审计中,XSS漏洞在浏览器环境对利用的影响非常大,所以最重要的还要掌握各种浏览器容错、编码等特性和数据协议。关于XSS漏洞的东西可以再写一本厚厚的书,由于篇幅问题,这些东西就不在这里详细介绍了,推荐阅读邱永华的《XSS跨站脚本攻击剖析与防御》和余弦的《Web前端黑客技术揭秘》。

XSS漏洞比SQL注入更多,而且在满足业务需求的情况下更加难防御。XSS漏洞经常出现在文章发表、评论回复、留言以及资料设置等地方,特别是在发文章的时候,因为这里大多都是富文本,有各种图片引用、文字格式设置等,所以经常出现对标签事件过滤不严格导致的XSS,同样,评论回复以及留言也是。其次在资料设置的地方,比如用户昵称、签名等,有的应用可能不只一处设置资料的地方,像在注册的地方可以设置、修改资料的地方可以设置,这时候要多留意,不一定所有设置这个资料的地方都过滤严格了。我们在通读代码挖掘的时候可以重点关注这几个地方,这几个地方的XSS也通常都是存储型的。

4.2.1.1 反射型XSS

反射型XSS也就是我们在描述里面说直接通过外部输入然后在浏览器端输出触发的类型,这种类型的漏洞比较容易通过扫描器黑盒直接发现,只需要将尖括号、单双引号等提交到Web服务器,检查返回的HTML页面里面有没有保留原来的特殊字符即可判断。但是白盒审计中,我们只需要寻找带有参数的输出函数,然后根据输出函数对输出内容回溯输入参数,观察有没有经过过滤。

举例一个反射型XSS漏洞的大致形式,代码如下:


// 以下是 QQ 私密接口

if
$_GET["openid"] {

      //
授权成功后,会返回用户的 openid

      //
检查返回的 openid 是否是合法 id

      //echo $_GET["oauth_signature"]


      if
(! is_valid_openid $_GET["openid"] $_GET["timestamp"] $_GET["oauth_signature"] ))       {      showerr 'API 帐号有误! ' );       //demo 对错误简单处理       echo "###invalid openid\n"       echo "sig ".$_GET["oauth_signature"]."\n"       exit

}

代码中echo"sig:".$_GET["oauth_signature"]."\n";直接将$_GET["oauth_signature"]的值输出到浏览器中,则可以直接用GET方式注入代码。

4.2.1.2 存储型XSS

存储型XSS,顾名思义也就是需要先把利用代码保存在比如数据库或文件中,当Web程序读取利用代码并输出在页面上时执行利用代码,它的原理图流程图如图4-5所示。

图 4-5

存储型XSS比反射型要容易利用得多,不用考虑绕过浏览器的过滤,另外在隐蔽性上面也要好得多,特别是在社交网络中的存储型XSS蠕虫能造成大面积的传播,影响非常大,曾经在新浪微博和百度贴吧都爆发过大规模的XSS蠕虫。

同样,要挖掘存储型XSS也是要寻找未过滤的输入点和未过滤的输出函数,这个最终的输出点可能跟输入点完全不在一个业务流上,对于这类可以根据当前代码功能去猜,或者老老实实去追哪里有操作过这个数据,使用表名、字段名去代码里面搜索。

下面的经典案例分析将讲述一个存储型XSS的挖掘过程。

4.2.1.3 骑士cms存储型XSS分析

这里笔者临时找了一个叫骑士cms的程序看了下,在后台申请友情链接的地方存在XSS漏洞,常规的特殊字符(如尖括号)和标签的事件(如onerror等)大多被过滤,漏洞挖掘过程如下。

安装好骑士cms后,在后台看到一个友情链接管理如图4-6所示。

图 4-6

前台有一个申请友情链接,根据经验这个申请友情链接的地方应该是一个payload输入的地方,我们先看看/admin/admin_link.php的代码:


$act = empty $_GET['act'] trim $_GET['act'] 'list'

$smarty->assign
'pageheader' " 友情链接 " );

if
$act == 'list'

{      get_token
();       check_permissions $_SESSION['admin_purview'] "link_show" );       require_once QISHI_ROOT_PATH.'include/page.class.php' );       $oederbysql=" order BY l.show_order DESC"

这里是判断访问admin_link.php这个文件的时候有没有act参数,没有就给$act变量赋值为list,即进入到输出友情链接列表的代码:


$offset= $currenpage-1 *$perpage $link = get_links $offset $perpage $joinsql.$wheresql.$oederbysql ); $smarty->assign 'link' $link ); $smarty->assign 'page' $page->show 3 )); $smarty->assign 'upfiles_dir' $upfiles_dir ); $smarty->assign 'get_link_category' get_link_category ()); $smarty->assign 'navlabel' "list" ); $smarty->display 'link/admin_link.htm' );

get_links()函数代码如下:


function get_links $offset $perpage $get_sql= ''

{

      global $db


      $row_arr = array
();

      $limit=" LIMIT ".$offset.'
'.$perpage

      $result = $db->query
"SELECT l.* c.categoryname FROM ".table 'link' ." AS l ".$get_sql.$limit );

      while
$row = $db->fetch_array $result ))

      {

      $row_arr[] = $row


      }

      return $row_arr
     

}

很清楚地看到,这是一个从数据库读取友情链接列表的功能:


$link = get_links $offset $perpage $joinsql.$wheresql.$oederbysql );

后面的代码则是将读取的内容以link/admin_link.htm为模板显示出来。跟进模板页看看,有一个关键的代码片段如下:

其中:


<spanduokan-code-cn">:#FF6600" title="<img src={#$list.link_logo#} border=0/>" class="vtip">[logo]</span>

这段代码是有问题的,这里直接把显示logo的img标签放在span标签的title里面,当鼠标滑过的时候会调用事件执行显示title即执行img标签,这里的利用点是{#$list.link_logo#}可以是HTML实体编码,从而绕过骑士cms的安全检查。目前我们已经找到一个输出点了,输入点也根据当前代码功能猜到是在前台申请链接的地方,利用过程如下,在前台申请友情链接页面 http://localhost/74cms/link/add_link.php 的logo字段输入


1 oner&#114 or=ale&#114 t 1

来构造代码如下:


<spanduokan-code-cn">:#FF6600" title="<img src=1 oner&#114 or=ale&#114 t 1 border=0/>" class="vtip">[logo]</span>

执行结果如图4-7所示。

图 4-7

当管理员在后台查看链接时触发漏洞执行代码,如图4-8所示。

图 4-8

4.2.2 漏洞防范

由于XSS漏洞在不同浏览器下有不同的利用方式,而且特别是业务上有需求使用富文本编辑器的时候,防御起来就更加复杂,所以在XSS防御这块应该从多个方面入手,尽量减少XSS漏洞。

4.2.2.1 特殊字符HTML实体转码

一般的XSS漏洞都是因为没过滤特殊字符,导致可以通过注入单双引号以及尖括号等字符利用漏洞,比如一个图片标签如下<img src="$_GET['a']"/>,则可以通过输入双引号来闭合第一个单引号利用漏洞,防御这类的XSS漏洞只需要过滤掉相关的特殊字符即可,特殊字符列表如下:

1)单引号(')

2)双引号(")

3)尖括号(<>)

4)反斜杠(\)

5)冒号(:)

6)and符(&)

7)#号(#)

还有两个问题,这些字符应该怎么过滤,什么时候过滤?为了保证数据原始性,最好的过滤方式是在输出和二次调用的时候进行如HTML实体一类的转码,防止脚本注入的问题。

4.2.2.2 标签事件属性黑白名单

上面我们提到过滤特殊字符来防止XSS漏洞,实际上即使过滤了也同样可能会被绕过,比如利用跟宽字节注入一样的方式来吃掉反斜杠,再利用标签的事件来执行js代码,面对这样的情况,我们还得加标签事件的黑名单或者白名单,这里更推荐用白名单的方式,实现规则可以直接用正则表达式来匹配,如果匹配到的事件不在白名单列表,就直接拦截掉,而不是替换为空。