整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

CSS注入实例

习CSS注入的目的是学习计算机知识,千万不要做违反法律的事情,不然等待你的是法律的严惩。

CSS仅仅只是一种用来表示样式的语言吗?当然不是!CSS就已被安全研究人员运用于渗透测试当中。使用属性选择器和iFrame,并通过CSS注入来窃取敏感数据的方法。但由于该方法需要iFrame,而大多数主流站点都不允许该操作,因此这种攻击方法并不实用。这里为大家详细介绍一种不需要iframe且只需10秒,就能为获得CSRF token的方法。

一、背景

CSS属性选择器开发者可以根据属性标签的值匹配子字符串来选择元素。 这些属性值选择器可以做以下操作:

  • 如果字符串以子字符串开头,则匹配
  • 如果字符串以子字符串结尾,则匹配
  • 如果字符串在任何地方包含子字符串,则匹配

属性选择器能让开发人员查询单个属性的页面HTML标记,并且匹配它们的值。一个实际的用例是将以“https://example.com”开头的所有href属性变为某种特定的颜色。而在实际环境中,一些敏感信息会被存放在HTML标签内。在大多数情况下CSRF token都是以这种方式被存储的:即隐藏表单的属性值中。

可以将CSS选择器与表单中的属性进行匹配,并根据表单是否与起始字符串匹配,加载一个外部资源,例如背景图片,来尝试猜测属性的起始字母。通过这种方式,攻击者可以进行逐字猜解并最终获取到完整的敏感数值。想要解决这个问题受害者可以在其服务器实施内容安全策略(CSP),防止攻击者从外部加载CSS代码。

二、无 iFrames

要做到无iFrame,使用一种方法:创建一个弹窗,然后在设置计时器后更改弹出窗口的位置。使用这种方仍然可以加载受害者的CSS,不再依赖于受害者是否允许iFrame。因为最初的弹出是通过用户事件触发的,没有被浏览器阻止。为了强制重载,在CSS注入间弹出一个虚拟窗口,如下:

但由于CSRF是针对客户端的攻击,因此如果能想出一种不需要服务器的方法,那么就可以节省大量的开销和简化操作。为了接收客户端加载资源,可以利用Service Workers来拦截和读取请求数据。Service Workers目前只适用于同源请求,在演示中受害者和攻击者页面已处于同一源上。

不久后,chrome很可能会合并这个实验性的功能,允许Service Workers拦截跨域请求。这样,就可以确保在客户端的攻击100%的执行,并强制用户在10秒内点击链接执行CSRF攻击,演示如下:

三、Demo

如上所述,因为不想运行一个web服务器,所以使用service workers拦截和模拟服务器端组件。目前,该演示只适用于Chrome浏览器。首先创建了一个易受攻击的目标,它存在一个基于DOM的CSS注入漏洞,并在页面放置了一个敏感token。再对脚本标签添加了一些保护措施,对左尖括号和右尖括号进行了编码。

接下来将强制加载受害者的CSS,并且使用上述方法,可一次窃取(猜解)一个敏感字符。在接收端,定义一个拦截请求的service worker,并通过post-message将它们发送回域,然后将token存储在本地存储中以供后续使用。你也可以想象一个后端Web服务器,通过Web套接字或轮询将CSRF token回发给攻击者域。

如果你的浏览器支持的话,只需点击打开页面任意位置,你将看到CSRF token将逐一被猜解出来。

四、结束语

反射型CSS注入实际上比存储型CSS注入更致命,因为存储型CSS注入需要一个服务器在受害者渲染之前来更新CSS。一段时间以来,CSS注入在严重程度上来回变化。过去IE浏览器是允许用户在CSS中执行Javascript代码的。这个演示也从某种程度上表明了CSS注入,以及渲染不受信任的CSS仍会导致严重的安全问题。所以在设计软件一定要测试,才能及时发现和修复各种漏洞。

让我们看看如下这个web应用示例:

<html>
 <meta http−equiv="Content−Security−Policy"
 content="script−src 'nonce−...' 'unsafe−eval'">
 <div id="template_target"></div>
 <script type="application/template" id="template">
 Hello World! 1 + 1 = {{ 1 + 1 }}
 </script>
 Your search is <?php echo $_GET['q']; ?>
 <script nonce="...">
 let template = document.getElementById('template');
 template_target.innerHTML = template.innerText.replace(/{{(.∗)}}/g,eval)
 </script>
</html>

以上这段简单的HTML代码可能反映了现在渗透测试人员经常碰到的模板化Web应用。某些模板内容存储在Web页面中,然后再转换为HTML代码的一部分。上段代码中含有id为template的HTML元素内容先被读取,然后再执行{{}}括号内的内容,最后在某个单独HTML元素中呈现出来。

Hello World! 1 + 1 = 2
Your search is ........... 

其次,这段代码应用程序会在页面上打印URL中的参数值。这显而易见是一个XSS漏洞,但由于CSP(内容安全策略)的存在,攻击者并不能直接执行javascript。虽然直接运行javascript的路被堵死,但是我们可以找到其他绕过方法。

乍一看,貌似eval函数是一个可以利用的点,我们或许可以直接插入某些特制代码,让eval函数去执行。

为了实现这点,我们必须插入HTML元素中id为template的代码。但在我们插入语句的前面已有id为template的HTML元素,而document.getElementById('template')只会去获取第一个HTML元素,并不是我们所输入的语句。

此刻,我们需要换个角度,看看浏览器是否能出现“意外”,以前就出现过很多浏览器的异常解析所导致的XSS攻击。我把所有能使用的tag都收集起来进行测试,看看是否有惊喜。测试代码如下:

<div id="template">First Tag</div>
 {% for tag in tag_list %}
 <{{tag}} id="template">{{tag}}</{{tag}}>
 {% endfor %}
<script>console.log(document.getElementById('template'));</script>

当程序运行完毕时,我得到一个奇怪的结果:当轮到<html>时,页面结构似乎发生了大变,此时已不再是<div>排在前面。让我们看下当插入<html id="template">时的变化:

此时<html>已排在文档顶部(在我所测试的所有浏览器中都是如此!),现在getElementById('template')将获取<html>中的恶意数据,而不是<div>的内容。

只需简单的?q=<html id="template">{{ alert("xss") }}</html>就可进行攻击

最终,由于浏览器这个“莫名其妙”的特性,我们绕过了CSP成功进行了XSS攻击!

本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场

来源:https://nosec.org/home/detail/2860.html

原文:https://pagedout.institute/download/PagedOut_001_beta1.pdf(该PDF文档的第62页

白帽汇从事信息安全,专注于安全大数据、企业威胁情报。

公司产品:FOFA-网络空间安全搜索引擎、FOEYE-网络空间检索系统、NOSEC-安全讯息平台。

为您提供:网络空间测绘、企业资产收集、企业威胁情报、应急响应服务。

背景

京东SRC(Security Response Center)收录大量外部白帽子提交的sql注入漏洞,漏洞发生的原因多为sql语句拼接和Mybatis使用不当导致。

2 手工检测

2.1 前置知识

mysql5.0以上版本中存在一个重要的系统数据库information_schema,通过此数据库可访问mysql中存在的数据库名、表名、字段名等元数据。information_schema中有三个表成为了sql注入构造的关键。

1)infromation_schema.columns:

  • table_schema 数据库名
  • table_name 表名
  • column_name 列名

2)information_schema.tables

  • table_schema 数据库名
  • table_name 表名

3)information_schema.schemata

  • schema_name 数据库名

SQL注入常用SQL函数

  • length(str) :返回字符串str的长度
  • substr(str, pos, len) :将str从pos位置开始截取len长度的字符进行返回。注意这里的pos位置是从1开始的,不是数组的0开始
  • mid(str,pos,len) :跟上面的一样,截取字符串
  • ascii(str) :返回字符串str的最左面字符的ASCII代码值
  • ord(str) :将字符或布尔类型转成ascll码
  • if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0

2.2 注入类型

2.2.1 参数类型分类

  • 整型注入
    例如?id=1,其中id为注入点,类型为int类型。
  • 字符型注入
    例如?id=”1”,其中id为注入点,类型为字符型,要考虑闭合后端sql语句中的引号。

2.2.2 注入方式分类

  • 盲注
  • 布尔盲注:只能从应用返回中推断语句执行后的布尔值。
  • 时间盲注:应用没有明确的回显,只能使用特定的时间函数来判断,例如sleep,benchmark等。
  • 报错注入:应用会显示全部或者部分的报错信息
  • 堆叠注入:有的应用可以加入 ; 后一次执行多条语句
  • 其他

2.3 手动检测步骤(字符型注入为例)

 // sqli vuln code
            Statement statement = con.createStatement();
            String sql = "select * from users where username = '" + username + "'";
            logger.info(sql);
            ResultSet rs = statement.executeQuery(sql);
// fix code 如果要使用原始jdbc,请采用预编译执行
            String sql = "select * from users where username = ?";
            PreparedStatement st = con.prepareStatement(sql);

使用未预编译原始jdbc作为demo,注意此demo中sql语句参数采用单引号闭合。

2.3.1 确定注入点

对于字符类型注入,通常先尝试单引号,判断单引号是否被拼接到SQL语句中。推荐使用浏览器扩展harkbar作为手工测试工具。https://chrome.google.com/webstore/detail/hackbar/ginpbkfigcoaokgflihfhhmglmbchinc

正常页面应该显示如下:

admin后加单引号导致无信息回显,原因是后端sql执行报错,说明引号被拼接至SQL语句中

select * from users where username = 'admin'  #正常sql
select * from users where username = 'admin'' #admin'被带入sql执行导致报错无法显示信息

2.3.2 判断字段数

mysql中使用order by 进行排序,不仅可以是字段名也可以是字段序号。所以可以用来判断表中字段数,order by 超过字段个数的数字就会报错。

判断字段数

当order by 超过4时会报错,所以此表共四个字段。

后端所执行的sql语句

select * from users where username = 'admin' order by 1-- '

此处我们将原本username的值admin替换为admin’ order by 1 —+,其中admin后的单引号用于闭合原本sql语句中的前引号,—+用于注释sql语句中的后引号。—后的+号主要作用是提供一个空格,sql语句单行注释后需有空格,+会被解码为空格。

2.3.3 确定回显位置

主要用于定位后端sql字段在前端显示的位置,采用联合查询的方式确定。注意联合查询前后字段需一致,这也就是我们为什么做第二步的原因。

通过下图可知,后端查询并回显的字段位置为2,3位。

联合查询后的字段可以随意,本次采用的是数字1到4直观方便。

2.3.4 利用information_schema库实现注入

group_concat()函数用于将查询结果拼接为字符串。

  • 查看存在数据库

  • 查看当前数据库中的表

  • 查看指定表中字段

  • 利用以上获取信息读取users表中username和password

3 自动化检测

3.1 sqlmap 使用

sqlmap兼容python2和python3,可以自动化检测各类注入和几乎所有数据库类型。

3.1.1 常用命令

-u  可能存在注入的url链接
-r读取http数据包
--data 指定post数据
--cookie 指定cookie
--headers 指定http头 如采用token认证的情况下
--threads 指定线程数
--dbms 指定后端的数据库
--os 指定后端的操作系统类型
--current-user 当前用户
--users 所有用户
--is-dba 是否是dba
--sql-shell 交互式的sqlshell
-p指定可能存在注入点的参数
--dbs 穷举系统存在的数据库
-D指定数据库
--tables 穷举存在的表
-T指定表
--column 穷举字段
-C指定字段
--dump dump数据

直接检测
其中—cookie用于指定cookie,—batch 自动化执行,—dbms指定数据库类型

检测结果

读取系统中存在数据库
—dbs读取当前用户下的数据库

读取指定库下的表
-D java_sec_code —tables

dump users表数据
-D java_sec_code -T users —dump

4 进阶

4.1 Mybatis注入

1)$错误使用导致注入

//采用#不会导致sql注入,mybatis会使用预编译执行
    @Select("select * from users where username = #{username}")
    User findByUserName(@Param("username") String username);
//采用$作为入参可导致sql注入
    @Select("select * from users where username = '${username}'")
    List<User> findByUserNameVuln01(@Param("username") String username);

2)模糊查询拼接

//错误写法
  <select id="findByUserNameVuln02" parameterType="String" resultMap="User">
        select * from users where username like '%${_parameter}%'
    </select>

 //正确写法
 <select id="findByUserNameVuln02" parameterType="String" resultMap="User">
        select * from users where username like concat(‘%’,#{_parameter}, ‘%’)  
    </select>

3)order by 注入

order by 后若使用#{}会导致报错,因为#{}默认添加引号会导致找不到字段从而报错。

   //错误写法 
<select id="findByUserNameVuln03" parameterType="String" resultMap="User">
        select * from users
        <if test="order != null">
            order by ${order} asc
        </if>
    </select>
//正确写法 id指字段id 此表字段共四个 所以id为1-4
    <select id="OrderByUsername" resultMap="User">
        select * from users order by id asc limit 1
    </select>

以上测试均在本地进行,请勿未授权进行渗透测试

5 文章及资料推荐

slqmap手册:https://octobug.gitbooks.io/sqlmap-wiki-zhcn/content/Users-manual/Introduction.html
sql注入详解:http://sqlwiki.radare.cn/#/


作者:罗宇(物流安全小分队)