前言

前幾天在 Linode 上開了一台 VPS 部署我的程式,裏頭用到 JWT token 與 Redis 作登入登出的機制,本來想說這個 VM 是做為測試用途,就沒有把 Redis 加密,也沒有限定 ip 連線,意外導致 redis 對外開放,受到挖礦病毒的攻擊,查了一下網路上的心得,發現蠻多人像我一樣一時疏忽而中招,因此寫了這篇文章來簡單紀錄一下過程。

事出必有因

整件事情的起頭是這樣,我在 VPS 上用 docker-compose 部署三個服務,分別是我的 api server、PostgreSQL、Redis,前端則是部署在 Netlify 上。

剛開始一切運行都很正常,但測試了一下馬上就發現有個小問題,我前端登入之後,沒多久 cookie 就失效被強制登出,由於我的 expire 設定 24 小時,沒道理這麼快就過期,所以直覺地去查了一下 Redis 內存的 session,看了一下記憶體用量也沒有發現什麼異常現象,就暫時沒有追蹤下去。

隔天我再測試,發現前端已經無法登入了,再次連到 VM 上看一下 api-server 的 log,發現源源不絕的 error 從 Redis 那邊不斷噴出來

1:S 15 May 2023 20:47:34.995 * Non blocking connect for SYNC fired the event.
1:S 15 May 2023 20:47:36.398 # Failed to read response from the server: No error information
1:S 15 May 2023 20:47:36.398 # Master did not respond to command during SYNC handshake
1:S 15 May 2023 20:47:36.758 * Connecting to MASTER 194.38.20.11:8886
1:S 15 May 2023 20:47:36.758 * MASTER <-> REPLICA sync started
...

當下看到 MASTER 時滿頭問號,因為我根本沒有配置 Redis cluster,理論上應該只有一個 MASTER 才對(就是我啟動的 Redis 服務),怎麼會說嘗試把資料 sync 到遠端的 Replica 呢? 再來看到一個未知的 IP 時就覺得事情不太妙了,直覺的再想或許有被駭的可能,但由於環境變數與金鑰都是測試的假資料,所以就先想說找一下 root cause,假如真的是被駭客攻擊,再重建一個 VM 就好。

接著我連進去 redis 的服務內看一下存的資料

sudo sh docker exec -it redis redis-cli

意外發現幾個奇怪的 keys

hack

看到 backup 時還以為是 Redis 定期備份,但查了一下 value 就發現幾個奇怪的 scripts,上網 google 一下發現大事不妙,是挖礦病毒的攻擊

網路上有人 逐行解析這幾個 scripts 的用途,甚至索性開一台 VM 給他中看看病毒,真的是佩服他們追究問題的精神

確定自己中了挖礦病毒之後,就趕緊把 Redis 先停掉,並清查一下是否有奇怪的 ssh key,一查就發現有一筆 ssh key 很奇怪,名字甚至還偽裝成跟我登入的 ssh key 一樣,但 ssh 的金鑰根本跟網路上 中毒分享的文章所寫的一樣,明顯就是行之有年的挖礦病毒

問題與解決方案

再來,為什麼我在 AWS 的機器上沒有遇到這個問題,但在 Linode 上卻有這個問題,本來以為是環境的問題,但兩者都是 Ubuntu 22.04,後來發現原因是,AWS 預設所有 port 都關閉,你需要開放哪些 port 得自己開啟,但是 Linode 剛好相反,預設是所有 port 全開,需要關閉哪些 port 得自己關閉(這個設計滿不解的)。

而我的 docker-compose 運行時為了開發方便,把 network 設定為 host,就間接地把 Redis 的 6379 port 對外,再因為 http 80 port 沒有關閉,又因為測試使用,Redis 就沒有設密碼,一連串的失誤就造成這次攻擊的入口。

解決方法很簡單,就是替 Redis 加上密碼保護,並且限定 Redis 連線的 ip 就好

但 docker 部署的 Redis 網路必須對外開放,否則任何服務都無法和他聯繫,所以必須在 redis.conf 內做以下調整:

# 這行需註解掉
# bind 127.0.0.1 -::1

# 加上密碼
requirepass <your-password>

最後,如果是用 docker-compose 啟動服務,Redis 和 PostgreSQL 不需要對外公開 port,僅在 docker-compose 的 network 內和 api-server 溝通就好,所以移除掉 network=host 後,就可以完全杜絕 Redis 對外的風險

倘若非不得已,因為業務需求或管理流程上的原因,docker-compose 啟動的容器需要和 docker run 啟動的容器做溝通,network 使用 <host_port>:<container_port> 與 host machine 對接,這時候 mapping 到 host machine 的 port 可以修改,例如:Redis 6379 port 改成其他 port,就可以避免被偵測到有 port 對外開放,讓駭客找到入口嘗試對 Redis 做攻擊,這時候如果密碼強度不夠或甚至根本沒設定,就有可能被攻破

結論

以上就是這次攻擊事件的全貌,經過這次經驗後,對網路環境與 docker 配置又更了解一點,算是一次血淋淋的教訓,好險機敏性資料沒有外洩,否則後果不堪設想

另外,還沒發現被攻擊前,我以為是 memory 的用量達標,導致 Redis 出問題,我還查了一下怎麼把 slaveof 的設定關閉

redis-cli -h 127.0.0.1 -p 6379 slaveof no one

沒想到過一陣子之後,slavof 就又被設定成那組未知的 IP,雖然到現在我還是不知道駭客是怎麼修改的。..。總之,如果有遇到以上種種現象,很有可能是中了挖礦病毒,只能說網路世界真的很可怕,到處都是流彈 QQ