Danh sách bài viết

Bài 112: Protected Mode & Bind — Lớp Phòng Thủ Đầu Tiên

Redis không có authentication layer mặc định ở các phiên bản cũ, và chỉ cần một Redis instance bị expose ra Internet mà không có mật khẩu là dữ liệu hoàn toàn bị truy cập hoặc xóa. bind directive kiểm soát interface nào Redis lắng nghe; protected-mode là safety net chặn kết nối từ xa khi cấu hình thiếu sót. Bài này đi qua cách hai directive này hoạt động, sự thay đổi default giữa các phiên bản, pattern cấu hình an toàn cho production và container, cách xác minh bằng netstat, và các anti-pattern dẫn đến Redis bị expose.

01/06/2026
0 lượt xem
1

Mục Tiêu Bài Học

  • Hiểu bind directive quyết định interface nào Redis lắng nghe TCP connection, và ý nghĩa thực tế của từng giá trị (127.0.0.1, IP cụ thể, 0.0.0.0).
  • Nắm sự thay đổi default bind giữa Redis < 3.2 và Redis ≥ 3.2, tại sao điều đó quan trọng về security.
  • Hiểu protected-mode hoạt động như thế nào, khi nào nó trigger, và điều kiện để nó kích hoạt.
  • Biết pattern cấu hình an toàn cho production server, Docker container và Kubernetes pod.
  • Xác minh cấu hình bằng netstat và biết ký hiệu nào là "tốt" hay "nguy hiểm".
  • Nhận diện các anti-pattern dẫn đến Redis bị expose ngoài ý muốn.
2

bind Directive — Interface Nào Redis Lắng Nghe

Một server thường có nhiều network interface: loopback (127.0.0.1), internal NIC (10.0.x.x hay 192.168.x.x), public NIC (203.x.x.x). bind trong redis.conf chỉ định Redis chỉ chấp nhận kết nối TCP đến trên các interface (địa chỉ IP) được liệt kê. Kết nối đến interface không có trong bind sẽ bị từ chối ngay ở tầng kernel, trước khi Redis thậm chí nhìn thấy request.

# Chỉ localhost — process trên cùng máy mới kết nối được
bind 127.0.0.1

# Một IP nội bộ cụ thể — chỉ traffic đến interface đó
bind 192.168.1.10

# Tất cả interface — bao gồm cả public NIC (NGUY HIỂM nếu không có firewall)
bind 0.0.0.0

# Nhiều địa chỉ cùng lúc — localhost + internal
bind 127.0.0.1 192.168.1.10

# Redis ≥ 3.2 default (localhost IPv4 + IPv6 loopback, dấu - = optional nếu IPv6 không có)
bind 127.0.0.1 -::1

Dấu - trước địa chỉ (ví dụ -::1) có nghĩa là Redis sẽ không fail khi khởi động nếu interface đó không tồn tại — đây là syntax từ Redis 6.2+.

Khi bind không có trong redis.conf hoặc bị comment out, hành vi phụ thuộc phiên bản — xem mục tiếp theo.

3

Default bind Thay Đổi Qua Các Phiên Bản

Đây là thay đổi security quan trọng nhất trong lịch sử cấu hình Redis:

Phiên bản Default khi không có bind Hậu quả
Redis < 3.2 Bind tất cả interface (tương đương 0.0.0.0) Redis lắng nghe trên cả public IP nếu có; rất nhiều instance bị compromise vì không ai cấu hình bind
Redis ≥ 3.2 bind 127.0.0.1 -::1 Chỉ chấp nhận kết nối từ localhost; an toàn mặc định

Redis 3.2 được release năm 2016. Trước đó, nhiều Redis instance bị expose Internet do admin không thêm bind vào config, đặc biệt trên VPS cloud. Shodan từng index hàng chục nghìn Redis instance công khai truy cập được mà không cần mật khẩu.

Nếu bạn đang vận hành Redis < 3.2 (hiếm nhưng vẫn tồn tại trong legacy system), cần kiểm tra bind ngay. Redis 7.x (phiên bản hiện tại tính đến 2025) giữ default 127.0.0.1 -::1.

4

protected-mode — Safety Net Khi bind Sai

protected-mode được thêm vào Redis 3.2 cùng lúc với việc đổi default bind. Nó là một lớp kiểm tra logic: khi Redis thấy kết nối đến từ địa chỉ không phải loopback, nó sẽ kiểm tra ba điều kiện:

  1. Có bind cụ thể không? (không phải 0.0.0.0 / mọi interface)
  2. requirepass (mật khẩu AUTH) không?
  3. Có TLS không?

Nếu cả ba đều không, protected-mode yes sẽ từ chối kết nối với error message rõ ràng. Logic này bảo vệ trường hợp ai đó vô tình cấu hình bind 0.0.0.0 mà quên mất AUTH.

# Default từ Redis 3.2+
protected-mode yes

Lưu ý: protected-mode không thay thế bind. Nó chỉ là kiểm tra phụ khi bind không được cấu hình chặt chẽ. Khi Redis đã bind đúng interface nội bộ và có AUTH, protected-mode trở thành lớp dư nhưng vẫn nên để yes.

5

Error Message Khi protected-mode Trigger

Khi bạn thử kết nối Redis từ xa và protected-mode chặn, bạn sẽ nhận được:

$ redis-cli -h 203.0.113.10
203.0.113.10:6379> ping
(error) DENIED Redis is running in protected mode because protected mode is enabled,
no bind address was specified, no authentication password is requested to clients.
In this mode connections are only accepted from the loopback interface.
If you want to connect from external computers to Redis you may adopt one of the
following solutions:
1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface
   by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly
   accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent.
2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting
   the protected mode option to 'no', and then restarting the server.
3) If you started the server manually just for testing, restart it with the '--protected-mode no' parameter.
4) Setup a bind address or an authentication password. NOTE: You only need to do one of the following:
   - Add a bind address: bind 127.0.0.1
   - Or set an authentication password: requirepass <password>

Error message này rất rõ ràng và gợi ý đúng hướng. Điểm cần chú ý: message khuyên thêm bind 127.0.0.1 hoặc requirepass, không khuyên tắt protected-mode mà không làm gì khác. Nếu bạn đọc message kỹ, giải pháp 1 và 2 (disable protected-mode) được kèm cảnh báo "MAKE SURE Redis is not publicly accessible".

6

Khi Nào Tắt protected-mode

Có thể set protected-mode no một cách an toàn khi đã có ít nhất một trong các biện pháp sau:

  • Bind specific interface: bind 10.0.1.50 (không phải 0.0.0.0) — Redis chỉ nghe trên internal IP.
  • AUTH password: requirepass StrongPassword123! — mọi kết nối đều phải authenticate.
  • TLS: tls-port 6379 với cert/key — kết nối không xác thực không thể dùng được.

Trong thực tế production, bạn thường có cả ba. Khi đó protected-mode không còn thêm giá trị thực sự, nhưng vẫn nên để yes như một safety net phòng trường hợp vô tình tháo một trong ba lớp kia.

# Không nên tắt protected-mode như thế này:
protected-mode no
# (không có bind cụ thể, không AUTH, không TLS — Redis hoàn toàn mở)

# Ổn nếu:
protected-mode no
bind 10.0.1.50          # chỉ nghe internal
requirepass Str0ngP@ss  # AND có password
7

Pattern Cấu Hình An Toàn Cho Production

Một production server thường có hai network interface: public NIC (eth0) và private/VPC NIC (eth1). Redis chỉ nên nghe trên private NIC:

# redis.conf — production server trong VPC

# Listen only on internal VPC IP
bind 10.0.1.50

# Protected mode safety net (giữ yes dù đã bind specific)
protected-mode yes

# Mandatory AUTH — bài 113 đi sâu hơn
requirepass StrongPassword123!

# Standard port (có firewall ở tầng OS/cloud phía trước)
port 6379

# Disable dangerous commands (bài sau trong module này)
# rename-command CONFIG ""
# rename-command FLUSHALL ""

Với cấu hình này:

  • Traffic từ public IP (eth0) bị từ chối ở tầng OS — Redis không nghe trên interface đó.
  • Ngay cả khi firewall misconfigured, protected-mode + requirepass tạo thêm hai rào cản.
  • App server cùng VPC kết nối qua 10.0.1.50:6379 bình thường.
8

Multi-Interface Binding

Một số trường hợp Redis cần nghe trên nhiều interface cùng lúc:

  • App traffic: app server kết nối qua internal VPC subnet (10.0.1.0/24).
  • Replica traffic: replica kết nối qua cùng internal subnet.
  • Monitoring: Prometheus Redis exporter chạy trên management VLAN riêng (10.0.2.0/24).
# Listen trên cả app subnet VIP và management subnet VIP
bind 10.0.1.50 10.0.2.50

# Không cần 0.0.0.0 — vẫn không expose public interface

Cần đảm bảo cả hai subnet đều được kiểm soát bởi firewall/Security Group. Management VLAN thường restricted hơn app subnet: chỉ IP của monitoring server mới được phép kết nối vào port 6379 trên management interface.

9

Docker & Kubernetes — Binding Trong Container

Container thay đổi cách tư duy về bind: trong một Docker container, network namespace độc lập. bind 0.0.0.0 bên trong container chỉ có nghĩa là "tất cả interface của container đó", không phải tất cả interface của host. Việc container có bị expose ra ngoài hay không phụ thuộc vào cấu hình Docker network và port mapping, không phải Redis bind.

Docker Compose

# docker-compose.yml
services:
  redis:
    image: redis:7
    command: redis-server --requirepass ${REDIS_PASSWORD} --bind 0.0.0.0
    networks:
      - app-internal   # Docker internal overlay network
    # Không có ports: mapping → container không expose ra host
    # App service cùng network kết nối được qua tên service "redis"

  app:
    image: myapp:latest
    environment:
      REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379
    networks:
      - app-internal

networks:
  app-internal:
    driver: bridge

Điểm mấu chốt: không có dòng ports: "6379:6379". Nếu không map port ra host, Redis chỉ tiếp cận được từ container cùng Docker network. bind 0.0.0.0 ở đây an toàn vì network isolation do Docker cung cấp. Dù vậy, requirepass vẫn cần thiết.

Kubernetes NetworkPolicy

Trong K8s, bind 0.0.0.0 là bình thường vì Pod IP thay đổi liên tục và không biết trước. Security thay vào đó được đặt ở tầng NetworkPolicy:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: redis-allow-app
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: redis
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - protocol: TCP
          port: 6379

NetworkPolicy này chỉ cho phép Pod có label app: web kết nối vào Redis port 6379. Mọi Pod khác, kể cả trong cùng namespace, bị từ chối ở tầng Kubernetes network plugin (Calico, Cilium, v.v.). Redis vẫn cần requirepass vì NetworkPolicy không thay thế authentication.

10

Xác Minh Với netstat

Sau khi cấu hình, luôn verify Redis đang listen đúng interface bằng netstat hoặc ss:

# netstat (cũ hơn, có thể cần apt install net-tools)
netstat -tlnp | grep 6379

# ss (hiện đại hơn, có sẵn trên hầu hết Linux)
ss -tlnp | grep 6379

Đọc output:

# BAD — Redis nghe trên tất cả interface (bao gồm public IP)
tcp   0.0.0.0:6379   0.0.0.0:*   LISTEN   1234/redis-server

# GOOD — chỉ localhost
tcp   127.0.0.1:6379  0.0.0.0:*  LISTEN   1234/redis-server

# GOOD — chỉ internal VPC IP
tcp   10.0.1.50:6379  0.0.0.0:*  LISTEN   1234/redis-server

# GOOD — localhost + internal
tcp   127.0.0.1:6379  0.0.0.0:*  LISTEN   1234/redis-server
tcp   10.0.1.50:6379  0.0.0.0:*  LISTEN   1234/redis-server

Ngoài netstat, có thể test từ bên ngoài để xác nhận kết nối bị từ chối:

# Từ máy ngoài — phải fail
redis-cli -h 203.0.113.10 -p 6379 ping
# → Could not connect to Redis at 203.0.113.10:6379: Connection refused

# Từ trong server (internal IP) — phải thành công
redis-cli -h 10.0.1.50 -p 6379 -a StrongPassword123! ping
# → PONG
11

port Directive, Unix Socket & TLS Port

port directive

port 6379   # Default Redis port
port 0      # Disable TCP hoàn toàn (chỉ dùng Unix socket hoặc TLS)

Đôi khi có ý kiến đổi port sang non-standard (ví dụ 6380, 16379) để giảm scan từ script kiddies. Điều này có tác dụng rất hạn chế: các port scanner như nmap sẽ vẫn tìm ra. Đổi port không phải security, chỉ là thay đổi nhỏ trong ops. Không nên coi đây là biện pháp bảo mật.

Unix socket — chỉ local

Khi app và Redis cùng chạy trên một máy (ví dụ: Redis Sentinel + Redis instance, hoặc PHP-FPM + Redis trên cùng VM), Unix domain socket loại bỏ hoàn toàn network layer:

# redis.conf
unixsocket /var/run/redis/redis.sock
unixsocketperm 700   # Chỉ owner (redis user) đọc/ghi được
port 0               # Disable TCP — chỉ socket

# Kết nối qua socket
redis-cli -s /var/run/redis/redis.sock ping

File permission (700, 770) kiểm soát ai được kết nối: chỉ process chạy với cùng user/group mới mở được file socket. Phù hợp cho Sentinel-to-Redis local communication.

TLS port (Redis 6+)

port 0           # Disable plain TCP
tls-port 6379    # Chỉ chấp nhận TLS connection
tls-cert-file /etc/redis/redis.crt
tls-key-file  /etc/redis/redis.key
tls-ca-cert-file /etc/redis/ca.crt

TLS được đề cập chi tiết ở bài 115. Điểm cần biết ở đây: khi dùng TLS, connection không có cert hợp lệ bị từ chối ở tầng TLS handshake, trước khi Redis process xử lý command.

12

IPv6 Considerations

Nếu server có IPv6, cần chú ý:

# IPv6 localhost only
bind ::1

# All IPv6 interfaces (tương đương 0.0.0.0 cho IPv4)
bind ::

# Mix IPv4 localhost + IPv6 localhost
bind 127.0.0.1 ::1

# Redis default từ 3.2 (dấu - = optional — không fail nếu IPv6 không available)
bind 127.0.0.1 -::1

Một lỗi phổ biến: cấu hình bind 127.0.0.1 nhưng quên IPv6, sau đó app kết nối qua ::1 (IPv6 loopback) và không được chấp nhận. Nếu không dùng IPv6, tắt hẳn IPv6 trên OS hoặc chỉ bind 127.0.0.1 và đảm bảo app connect qua IPv4.

13

Defense In Depth — Không Chỉ Dựa Vào bind

bind chỉ là một lớp. Một hệ thống production cần nhiều lớp bảo vệ đồng thời:

Lớp Cơ chế Phạm vi
Redis-level bind + protected-mode Redis process quyết định nghe interface nào
OS-level iptables / ufw Kernel filter traffic trước khi đến process
Cloud-level Security Group (AWS/GCP) / NSG (Azure) Network-level filter trước khi vào VM
Authentication requirepass / ACL (bài 113–114) Ngay cả khi có kết nối, phải authenticate
Encryption TLS (bài 115) Traffic không bị nghe lén trên wire

Chỉ dựa vào một lớp là sai lầm. Ví dụ: chỉ có firewall mà không có bind — nếu firewall rule bị misconfigured, Redis ngay lập tức bị expose. Chỉ có bind mà không có AUTH — kẻ tấn công từ bên trong VPC vẫn kết nối được tự do.

# Ví dụ iptables: chỉ cho phép 10.0.1.0/24 kết nối port 6379
iptables -A INPUT -p tcp --dport 6379 -s 10.0.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROP

# ufw
ufw allow from 10.0.1.0/24 to any port 6379
ufw deny 6379
14

Anti-patterns & Common Mistakes

Các lỗi thường gặp dẫn đến Redis bị expose:

  1. Xóa dòng bind mặc định trong redis.conf
    Nhiều người comment out hoặc xóa dòng bind 127.0.0.1 -::1 mà không thêm bind mới. Trên Redis < 3.2, hậu quả là bind all interfaces. Trên Redis ≥ 3.2, protected-mode sẽ chặn nhưng không phải giải pháp đúng.
  2. bind 0.0.0.0 "cho tiện" rồi để đó
    Thường xảy ra khi dev set up local test, sau đó cấu hình này bị push lên production. 0.0.0.0 kết hợp với Security Group misconfigured là công thức để Redis bị expose.
  3. Chỉ dựa vào bind mà không có firewall
    Nếu Redis process bị exploit và attacker có thể thay đổi cấu hình runtime (CONFIG SET bind), bind không còn là biện pháp bảo vệ nữa. Cần có firewall độc lập ở tầng OS/cloud.
  4. Tắt protected-mode để "fix" kết nối trong test rồi quên re-enable
    CONFIG SET protected-mode no hoặc thêm protected-mode no vào config khi debug, sau đó config này được commit và deploy production.
  5. Bind 127.0.0.1 nhưng muốn remote kết nối được
    Đây không phải lỗi bảo mật, nhưng là lỗi ops phổ biến: sau đó tắt protected-mode mà không thêm internal IP vào bind, khiến remote vẫn không kết nối được.
  6. Public cloud + bind 0.0.0.0 + Security Group "mở tạm"
    Security Group rule 0.0.0.0/0:6379 "mở tạm để test" rồi quên đóng lại. Kết hợp với bind 0.0.0.0, Redis hoàn toàn public.
15

Tổng Kết & Quiz

Hai directive bindprotected-mode là lớp đầu tiên kiểm soát ai có thể mở TCP connection đến Redis. bind quyết định interface — nên luôn chỉ định explicit, không dựa vào default. protected-mode là safety net khi bind chưa đủ chặt. Cả hai cần kết hợp với firewall và AUTH để có bảo mật thực sự.

Checklist trước khi deploy Redis lên production:

  • bind đặt explicit với internal IP (không phải 0.0.0.0, trừ container với network isolation).
  • protected-mode yes.
  • requirepass có giá trị mạnh (bài 113).
  • Firewall OS và cloud Security Group chỉ cho phép app subnet đến port 6379.
  • Verify bằng netstat -tlnp | grep 6379 và test connection từ ngoài.

Quiz

  1. Redis được cấu hình bind 0.0.0.0, protected-mode yes, không có requirepass. Một client từ IP bên ngoài kết nối vào. Chuyện gì xảy ra?
  2. Redis ≥ 3.2 không có dòng bind nào trong redis.conf. Default là gì? Điều này khác gì so với Redis < 3.2?
  3. Trong Docker Compose, Redis được cấu hình bind 0.0.0.0 nhưng không có dòng ports:. Redis có bị expose ra máy host không?
  4. Giải thích tại sao "đổi port Redis sang 16379 để tăng bảo mật" không phải biện pháp security thực sự.
  5. Bạn có một server với hai NIC: eth0 (public 203.0.113.10) và eth1 (internal 10.0.1.50). Viết cấu hình bind + protected-mode + requirepass đúng.

Đáp án gợi ý

  1. Connection bị từ chối với error "DENIED Redis is running in protected mode". Điều kiện trigger: bind all interfaces + không có AUTH + không có TLS → protected-mode chặn kết nối non-loopback.
  2. Default là bind 127.0.0.1 -::1 (chỉ loopback). Redis < 3.2 không có dòng bind → bind tất cả interfaces (tương đương 0.0.0.0), dẫn đến nhiều instance bị expose Internet trước khi Redis 3.2 ra mắt năm 2016.
  3. Không. Không có ports: mapping nghĩa là port chỉ mở trong Docker network nội bộ. Container từ service khác cùng network kết nối được, máy host và bên ngoài không thể kết nối trực tiếp vào container.
  4. Port scanner (nmap, masscan) quét toàn bộ port range, không chỉ 6379. Port 16379 sẽ bị phát hiện với banner Redis như bình thường. Đổi port chỉ giảm noise từ scanner amateur, không ngăn được targeted attack. Security thực sự đến từ firewall, authentication và encryption, không phải port number.
  5. bind 10.0.1.50 (chỉ internal NIC, không bind 203.0.113.10), protected-mode yes, requirepass StrongPassword123!. Tùy nhu cầu có thể thêm bind 10.0.1.50 127.0.0.1 nếu cần local tool (redis-cli) trên máy chủ đó kết nối qua localhost.

Bài tiếp theo

Bài 113 đi vào AUTH password: requirepass, masterauth cho replication, giới hạn của single-password và ACL (Redis 6+).

Tham khảo