安全的Redis
2015年11月,全球數萬個Redis節點遭受到了攻擊,所有數據都會被清除了,只有一個叫crackit的鍵存在,這個鍵的值很像一個公鑰。
數據丟失對于很多Redis的開發者來說是致命的,經過相關機構的調查發現,被攻擊的Redis有以下特點:
Redis所在的機器有外網IP
Redis以默認端口6379為啟動端口,并且是對外網開放的。
Redis是以root用戶啟動的
Redis沒有設置密碼
Redis的bind設置為0.0.0.0或者""
攻擊者充分利用Redis的dir和dbfilename兩個配置可以利用config set動態設置,以及RDB持久化的特性,將自己的公鑰寫入到目標機器的/root/.ssh/authotriezed_keys文件中,從而實現了對目標機器的攻陷。
假設機器A是攻擊者的機器(內網IP:10.10.xx.192),機器B是被攻擊者機器(外網IP:123.16.xx.182),上面部署著一個滿足上述五個特性的Redis,下面我們來模擬整個攻擊過程:
1)首先確認當前(攻擊前)機器A不能通過SSH訪問機器B,因為沒有權限。
2)由于機器B的外網對外開通了Redis的6379端口,所以可以直接連接到Redis上執行flushall操作,注意此事破壞性已經很大了。
3)在機器A生成公鑰,并將公鑰保存到一個文件my.pub中。
4)將鍵crackit的值設置為公鑰。
5)將Redis的dir設置為/root/.ssh目錄,dbfilename設置為authorized_keys,執行save命令生成RDB文件。此時機器B的/roor/.ssh/authorized_keys包含了攻擊者的公鑰,之后攻擊者就可以“為所欲為”了。
6)此時機器A再通過SSH協議訪問機器B,發現可以順利登錄,登錄后可以觀察/root/.ssh/authorized_keys,可以發現它就是RDB文件。
誰也不想自己的Redis以及機器就這樣被攻擊吧?本節我們來介紹如何讓Redis足夠安全。
Redis的設計目標是一個在內網運行的輕量級高性能鍵值服務,因為是在內網運行,所以對于安全方面沒有做太多的工作,Redis值提供了簡單的密碼機制,并且沒有做用戶權限的相關劃分。那么,在日常對于Redis的開發和運維中要注意哪些方面才能讓Redis服務不僅能提供高效穩定的服務,還能保證在一個足夠安全的網絡環境下運行呢?下面將從7個方面進行介紹。
-
Redis密碼機制
-
簡單的密碼機制
Redis提供了requirepass配置為Redis提供密碼功能,如果添加這個配置,客戶端就不能通過redis-cli -h {ip} -p {port}來執行命令。例如下面啟動一個密碼為hello_redis-devops的redis:
redis-server --requirepass hello_redis_devpos
此時通過redis-cli執行命令會受到沒有權限的提示:
# redis-cli 127.0.0.1:6379> ping (error) NOAUTH Authentication required.
Redis提供了兩種方式訪問配置了密碼的Redis:
- redis-cli -a參數。使用redis-cli連接Redis時,添加-a加密碼的參數,如果密碼正確就可以正常訪問Redis了,具體操作如下:
# redis-cli -h 127.0.0.1 -p 6379 -a hello_redis_devops 127.0.0.1:6379> ping PONG
- auth命令。通過redis-cli連接后,執行auth加密碼命令,如果密碼正確就可以正常訪問訪問Redis了,具體操作如下:
# redis-cli 127.0.0.1:6379> auth hello_redis-devops OK 127.0.0.1:6379> ping PONG
-
運維建議
這種密碼機制能在一定程度上保護Redis的安全,但是在使用requirepass時候要注意以下幾點:
密碼要足夠復雜(64個字節以上),因為Redis的性能很高,如果密碼比較單間,完全是可以在一段時間內通過暴力破解來破譯密碼。
如果是主從結構的Redis,不要忘記在從節點的配置中加入masterauth(master的密碼)配置,否則會造成主從節點同步失效。
auth是通過明文進行傳輸的,所以也不是100%可靠,如果被攻擊者劫持也相當危險。
-
-
偽裝危險命令
-
引入rename-command
Redis中包含了很多“危險”命令,一旦錯誤使用或者誤操作,后果不堪設想,例如如下命令:
keys:如果鍵值較多,存在阻塞Redis的可能性。
flushall/flushdb:數據全部被清除。
save:如果鍵值較多,存在阻塞Redis的可能性。
debug:例如debug reload會重啟Redis。
config:config應該交給管理員使用。
shutdown:停止Redis。
理論上這些命令不應該給普通開發人員使用,那有沒有什么好的方法能夠防止這些危險命令被隨意執行呢?Redis提供了rename-conmmand配置解決這個問題。下面直接用一個例子說明rename-command的作用。例如當前Redis包含了10000個鍵值對,現使用flushall將全部是數據清除:
127.0.0.1:6379> flushall OK `` 例如Redis添加如下配置: `rename-command flushall flushalltest` 那么執行flushall命令的話,會收到Redis不認識flushall的錯誤提示,說明我們成功地使用rename-command對flushall命令做了偽裝:
127.0.0.1:6379> flushall
(error) ERR unknonwn command 'flushall'而如果執行flushalltest,那么就可以實現flushall的功能了,這就是rename-command的作用,管理員可以對認為比較危險的命令做rename-command處理。
-
沒有免費的午餐
rename-command雖然對Redis的安全有一定幫助,但是天下并沒有免費的午餐。使用了rename-command時可能會帶來如下麻煩:
管理員要對自己的客戶端進行修改,例如jedis.flushall()操作內部使用的是flushalll命令,如果用rename-command后需要修改為新的命令,有一定的開發和維護成本。
rename-command配置不支持config set,所以啟動前一定要確定哪些命令需要使用rename-command。
如果AOF和RDB文件包含了rename-command之前的命令,Redis將無法啟動,因為此時它識別不了rename-command之前的命令。
Redis源碼中有一些命令是寫死的,rename-command可能造成Redis無法正常工作。例如Sentinel節點在修改配置時直接使用了config命令,如果對config使用rename-command,會造成Redis Sentinel無法正常工作。
-
最佳實踐
在使用rename-command的相關配置時,需要注意以下幾點:
對于一些危險的命令(例如flushall),不管是內網還是外網,一律使用rename-command配置。
建議第一次配置Redis時,就應該配置rename-command,因為rename-command不支持config set。
如果涉及主從關系,一定要保持主從節點配置一致性,否則存在主從數據不一致的可能性。
-
-
防火墻
可以使用防火墻限制輸入和輸出的IP或者IP范圍、端口或者端口范圍,在比較成熟的公司都會對有waiwangIP的服務器做一些端口的限制,例如只允許80端口對外開放。因為一般來說,開放外網IP的服務器中Web服務器比較多,但通常存儲服務器的端口無序對外開放,防火墻是一個限制外網訪問Redis的必殺技。
-
bind
-
對于bing的錯誤認識
很多開發者在一開始看到bind這個配置是都是這么認為的:指定Redis只接收來自于某個網段IP的客戶端請求。
但事實上bing指定的是Redis和哪個網卡進行綁定,和客戶端是什么網段沒有關系。ifconfig命令獲取網卡信息包含了三個IP地址:
內網地址:10.10.xx.192
外網地址:220.181.xx.123
回環地址:127.0.0.1
如果當前Redis配置了bind 10.10.xx.192,那么Redis訪問只能通過10.10.xx.192這塊網卡進入,通過redis-cli -h 220.181.xx.123 -p 6379和本機redis-cli -h 128.0.0.1 -p 6379都無法連接到Redis。只能通過10.10.xx.192作為redis-cli的參數。
bind參數可以設置多個,例如下面配置表示當前Redis只接收來自10.10.xx.192和127.0.0.1的網絡流量:
bind 10.10.xx.192 127.0.0.1
運維提示:Redis3.0中bind默認值為"",也就是不限制網卡的訪問,但是Redis3.2中必須顯示的配置bind 0.0.0.0才可以達到這種效果。
-
建議
經過上面的實驗以及對于bind的認識,可以得出如下結論:
如果機器有外網IP,但是部署的Redis是給內部使用的,建議去掉外網網卡或者使用bind配置限制流量從外網進入。
如果客戶端和Redis部署在一臺機器上,可以使用回環地址127.0.0.1。
bind配置不支持config set,所以盡可能在第一次啟動前配置好。
如果當前Redis沒有配置密碼,沒有配置bind,那么只允許來自本機的訪問,也就是相當于配置了bind 127.0.0.1。
-
-
定期備份數據
天有不測風云,假如有一天Redis真的被攻擊了(清理了數據,關閉了進程),那么定期備份的數據能夠在一定程度挽回一些損失,定期備份持久化數據是一個比較好的習慣。
-
不使用默認端口
Redis的默認端口是6379,不使用默認端口從一定程度上可降低被入侵者發現的可能性,因為入侵者通常本身也是一些攻擊程序,對目標服務器進行端口掃描,例如MySQL的默認端口是3306、Memcache的默認端口11211、Jetty的默認端口8080等都會被設置成攻擊目標,Redis作為一款較為致知名的NoSQL服務,6379必然也在端口的掃毛的列表中,雖然不設置默認端口還是有可能被攻擊者入侵,但是能夠在一定程度上降低被攻擊的概率。
-
使用非root用戶啟動
root用戶作為管理員,權限非常大。如果被入侵者獲取root權限后,就可以在這臺機器以及相關機器上“為所欲為”了。筆者建議在啟動Redis服務的使用使用非root用戶啟動。事實上許多服務,例如Resin、Jetty、HBase、Hadoop都建議使用非root啟動。