Khắc phục lỗ hổng bảo mật Docker và UFW mà không vô hiệu hóa Iptables

Tác giả sysadmin, T.Một 25, 2024, 05:39:07 CHIỀU

« Chủ đề trước - Chủ đề tiếp »

0 Thành viên và 1 Khách đang xem chủ đề.

Khắc phục lỗ hổng bảo mật Docker và UFW mà không vô hiệu hóa Iptables


UFW là giao diện người dùng iptables phổ biến trên Ubuntu giúp dễ dàng quản lý các quy tắc tường lửa. Nhưng khi Docker được cài đặt, Docker bỏ qua các quy tắc UFW và các cổng đã xuất bản có thể được truy cập từ bên ngoài. Vấn đề là:

  • UFW được bật trên máy chủ cung cấp dịch vụ bên ngoài và tất cả các kết nối đến không được phép sẽ bị chặn theo mặc định.
  • Chạy vùng chứa Docker trên máy chủ và sử dụng tùy -pchọn xuất bản cổng cho vùng chứa đó trên tất cả các địa chỉ IP. Ví dụ: docker run -d --name httpd -p 0.0.0.0:8080:80 httpd:alpine, lệnh này sẽ chạy dịch vụ httpd và xuất bản cổng 80 của container lên cổng 8080 của máy chủ.
  • UFW sẽ không chặn tất cả các yêu cầu bên ngoài truy cập vào cổng 8080. Ngay cả lệnh cũng ufw deny 8080sẽ không ngăn cản quyền truy cập từ bên ngoài vào cổng này.
  • Vấn đề này thực sự khá nghiêm trọng, có nghĩa là một cổng ban đầu được dự định cung cấp dịch vụ nội bộ sẽ được đưa ra mạng công cộng.

Tìm kiếm "ufw docker" trên web có thể tìm thấy rất nhiều cuộc thảo luận, hầu như tất cả các giải pháp này đều tương tự nhau. Nó yêu cầu phải tắt chức năng iptables của docker trước, nhưng điều này cũng có nghĩa là chúng ta từ bỏ chức năng quản lý mạng của docker. Điều này khiến các container sẽ không thể truy cập vào mạng bên ngoài. Một số bài viết cũng đề cập rằng bạn có thể thêm thủ công một số quy tắc vào tệp cấu hình UFW, chẳng hạn như -A POSTROUTING ! -o docker0 -s 172.17.0.0/16 -j MASQUERADE. Nhưng điều này chỉ cho phép các container thuộc mạng 172.17.0.0/16có thể truy cập ra bên ngoài. Nếu chúng ta tạo một mạng docker mới, chúng ta phải thêm các quy tắc iptables tương tự như vậy cho mạng mới theo cách thủ công.

1. Mục tiêu dự kiến

Các giải pháp mà chúng ta có thể tìm thấy trên internet rất giống nhau và không tinh tế, tôi hy vọng một giải pháp mới có thể:

  • Không cần phải tắt iptables của Docker và để Docker quản lý mạng của nó. Chúng tôi không cần duy trì thủ công các quy tắc iptables cho bất kỳ mạng Docker mới nào và tránh các tác dụng phụ tiềm ẩn sau khi tắt iptables trong Docker.
  • Mạng công cộng không thể truy cập các cổng do Docker xuất bản. Ngay cả khi cổng được xuất bản trên tất cả các địa chỉ IP bằng tùy chọn như -p 8080:80. Các container và mạng nội bộ có thể truy cập lẫn nhau một cách bình thường. Mặc dù có thể yêu cầu Docker xuất bản cổng của vùng chứa tới địa chỉ IP riêng của máy chủ nhưng cổng sẽ không được truy cập trên mạng công cộng. Tuy nhiên, máy chủ này có thể có nhiều địa chỉ IP riêng và những địa chỉ IP riêng này cũng có thể thay đổi.
  • Một cách rất thuận tiện để cho phép/từ chối các mạng công cộng truy cập vào các cổng container mà không cần phần mềm bổ sung và cấu hình bổ sung. Cũng giống như việc sử dụng lệnh ufw allow 8080cho phép truy cập bên ngoài vào cổng 8080, sau đó sử dụng lệnh ufw delete allow 8080từ chối các mạng công cộng truy cập vào cổng 8080.

2. Cách thực hiện

2.1. Thu hồi sửa đổi ban đầu

Nếu bạn đã sửa đổi máy chủ của mình theo giải pháp hiện tại mà chúng tôi tìm thấy trên internet, vui lòng khôi phục những thay đổi này trước, bao gồm:

  • Kích hoạt tính năng iptables của Docker. Xóa tất cả các thay đổi như --iptables=false, kể cả tệp cấu hình /etc/docker/daemon.json.
  • Quy tắc FORWARD mặc định của UFW thay đổi trở lại DROP mặc định thay vì ACCEPT.
  • Loại bỏ các quy tắc liên quan đến mạng Docker trong tệp cấu hình UFW /etc/ufw/after.rules.
  • Nếu bạn đã sửa đổi các tệp cấu hình Docker, trước tiên hãy khởi động lại Docker. Chúng tôi sẽ sửa đổi cấu hình UFW sau và sau đó chúng tôi có thể khởi động lại nó.

2.2. Giải quyết các vấn đề về UFW và Docker

Giải pháp này chỉ cần sửa đổi một tệp cấu hình UFW, tất cả các tùy chọn và cấu hình Docker vẫn giữ nguyên mặc định.

Sửa đổi tệp cấu hình UFW /etc/ufw/after.rulesvà thêm các quy tắc sau vào cuối tệp:

Mã nguồn [Chọn]
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN

-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN

-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP

COMMIT
# END UFW AND DOCKER

Sử dụng lệnh sudo systemctl restart ufw hoặc sudo ufw reload khởi động lại UFW sau khi thay đổi tập tin. Bây giờ mạng công cộng không thể truy cập bất kỳ cổng docker nào được xuất bản, vùng chứa và mạng riêng có thể truy cập lẫn nhau một cách bình thường và các vùng chứa cũng có thể truy cập mạng bên ngoài từ bên trong. Có thể có một số lý do chưa xác định khiến quy tắc UFW sẽ không có hiệu lực sau khi khởi động lại UFW, vui lòng khởi động lại máy chủ.

Ví dụ: nếu bạn muốn cho phép các mạng công cộng truy cập vào các dịch vụ do bộ chứa Docker cung cấp thì cổng dịch vụ của bộ chứa là 80. Chạy lệnh sau để cho phép các mạng công cộng truy cập dịch vụ này:

Mã nguồn [Chọn]
ufw route allow proto tcp from any to any port 80
Điều này cho phép mạng công cộng truy cập vào tất cả các cổng được xuất bản có cổng container là 80.

Lưu ý: Nếu chúng tôi xuất bản một cổng bằng cách sử dụng tùy chọn -p 8080:80, chúng tôi nên sử dụng cổng container 80chứ không phải cổng máy chủ 8080.

Nếu có nhiều container có cổng dịch vụ là 80, nhưng chúng ta chỉ muốn mạng bên ngoài truy cập vào một container nhất định. Ví dụ: nếu địa chỉ riêng của vùng chứa là 172.17.0.2, hãy sử dụng lệnh sau:

Mã nguồn [Chọn]
ufw route allow proto tcp from any to 172.17.0.2 port 80
Nếu giao thức mạng của một dịch vụ là UDP, ví dụ như dịch vụ DNS, bạn có thể sử dụng lệnh sau để cho phép mạng bên ngoài truy cập tất cả các dịch vụ DNS được xuất bản:

Mã nguồn [Chọn]
ufw route allow proto udp from any to any port 53
Tương tự, nếu chỉ dành cho một container cụ thể, chẳng hạn như địa chỉ IP 172.17.0.2:

Mã nguồn [Chọn]
ufw route allow proto udp from any to 172.17.0.2 port 53
3. Làm thế nào nó hoạt động?

Các quy tắc sau đây cho phép các mạng riêng có thể truy cập lẫn nhau. Thông thường, mạng riêng được tin cậy hơn mạng công cộng.

Mã nguồn [Chọn]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

Các quy tắc sau đây cho phép UFW quản lý xem các mạng công cộng có được phép truy cập các dịch vụ do bộ chứa Docker cung cấp hay không. Để chúng tôi có thể quản lý tất cả các quy tắc tường lửa ở một nơi.

Mã nguồn [Chọn]
-A DOCKER-USER -j ufw-user-forward
Ví dụ: chúng tôi muốn chặn tất cả các kết nối gửi đi từ bên trong một vùng chứa có địa chỉ IP là 172.17.0.9, nghĩa là chặn vùng chứa này truy cập Internet hoặc mạng bên ngoài. Sử dụng lệnh sau:

Mã nguồn [Chọn]
ufw route deny from 172.17.0.9 to any
Các quy tắc sau chặn các yêu cầu kết nối do tất cả các mạng công cộng khởi tạo nhưng cho phép mạng nội bộ truy cập vào mạng bên ngoài. Đối với giao thức TCP, nó ngăn cản việc chủ động thiết lập kết nối TCP từ các mạng công cộng. Đối với giao thức UDP, tất cả quyền truy cập vào các cổng nhỏ hơn 32767 đều bị chặn. Tại sao lại là cổng này? Vì giao thức UDP không có trạng thái nên không thể chặn tín hiệu bắt tay khởi tạo yêu cầu kết nối như TCP. Đối với GNU/Linux, chúng ta có thể tìm thấy phạm vi cổng cục bộ trong tệp /proc/sys/net/ipv4/ip_local_port_range. Phạm vi mặc định là 32768 60999. Khi truy cập dịch vụ giao thức UDP từ một container đang chạy, cổng cục bộ sẽ được chọn ngẫu nhiên một cổng từ phạm vi cổng và máy chủ sẽ trả dữ liệu về cổng ngẫu nhiên này. Do đó, chúng ta có thể giả định rằng cổng nghe của giao thức UDP bên trong tất cả các vùng chứa ít hơn 32768. Đây là lý do chúng tôi không muốn các mạng công cộng truy cập vào các cổng UDP ít hơn 32768.

Mã nguồn [Chọn]
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN

Nếu bộ chứa docker không tuân theo cài đặt của HĐH khi nhận dữ liệu, nghĩa là số cổng tối thiểu nhỏ hơn 32768. Ví dụ: chúng tôi có vùng chứa Dnsmasq. Số cổng tối thiểu mà Dnsmasq sử dụng để nhận dữ liệu là 1024. Chúng ta có thể sử dụng lệnh sau để cho phép phạm vi cổng lớn hơn được sử dụng để nhận các gói DNS.

Mã nguồn [Chọn]
ufw route allow proto udp from any port 53 to any port 1024:65535
Vì DNS là một dịch vụ rất phổ biến nên đã có sẵn quy tắc tường lửa cho phép phạm vi cổng lớn hơn để nhận các gói DNS.

4. Lý do chọn ufw-user-forward không phải ufw-user-input

4.1. Sử dụng ufw-user-input

Dễ sử dụng và dễ hiểu, hỗ trợ các phiên bản Ubuntu cũ hơn.

Ví dụ: để cho phép công chúng truy cập vào một cổng đã xuất bản có cổng container là 8080, hãy sử dụng lệnh:

Mã nguồn [Chọn]
ufw allow 8080
Nó không chỉ hiển thị các cổng của container mà còn hiển thị các cổng của máy chủ.

Ví dụ: nếu một dịch vụ đang chạy trên máy chủ và cổng là 8080. Lệnh ufw allow 8080cho phép mạng công cộng truy cập dịch vụ và tất cả các cổng được xuất bản có cổng của container là 8080. Nhưng chúng tôi chỉ muốn hiển thị dịch vụ đang chạy trên máy chủ hoặc chỉ dịch vụ chạy bên trong vùng chứa chứ không phải cả hai.

Để tránh vấn đề này, chúng ta có thể cần sử dụng lệnh tương tự như sau cho tất cả các vùng chứa:

Mã nguồn [Chọn]
ufw allow proto tcp from any to 172.16.0.3 port 8080
4.2. Sử dụng ufw-user-forward

Không thể hiển thị các dịch vụ chạy trên máy chủ và vùng chứa cùng lúc bằng cùng một lệnh.

Ví dụ: nếu chúng ta muốn xuất bản cảng 8080container, hãy sử dụng lệnh sau:

Mã nguồn [Chọn]
ufw route allow 8080
Mạng công cộng có thể truy cập tất cả các cổng được xuất bản có cổng container là 8080.

Nhưng cổng 8080của máy chủ vẫn không được mạng công cộng truy cập. Nếu chúng ta muốn làm như vậy, hãy thực hiện lệnh sau để cho phép công chúng truy cập riêng vào cổng trên máy chủ:

Mã nguồn [Chọn]
ufw allow 8080
Không hỗ trợ các phiên bản Ubuntu cũ hơn và lệnh phức tạp hơn một chút. Nhưng bạn có thể sử dụng kịch bản của tôi.

Nếu chúng tôi đang sử dụng phiên bản Ubuntu cũ hơn, chúng tôi có thể sử dụng ufw-user-inputchain. Nhưng hãy cẩn thận để tránh lộ những dịch vụ không nên lộ.

Nếu chúng tôi đang sử dụng phiên bản Ubuntu mới hơn hỗ trợ ufw routelệnh phụ, tốt hơn chúng tôi nên sử dụng ufw-user-forwardchuỗi và sử dụng ufw routelệnh để quản lý quy tắc tường lửa cho vùng chứa.

5. Sử dụng ufw-docker

Tập lệnh này cũng hỗ trợ chế độ Docker Swarm.

5.1. Cài đặt

Tải xuống ufw-docker tập lệnh

Mã nguồn [Chọn]
sudo wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker

sudo chmod +x /usr/local/bin/ufw-docker

Sau đó sử dụng lệnh sau để sửa đổi after.rules tập tin của ufw

Mã nguồn [Chọn]
ufw-docker install
Lệnh này thực hiện những việc sau:

  • Sao lưu tập tin/etc/ufw/after.rules
  • Nối các quy tắc của UFW và Docker vào cuối file

5.2. Cài đặt cho chế độ Docker Swarm

Chúng tôi chỉ có thể sử dụng tập lệnh này trên các nút quản lý để quản lý các quy tắc tường lửa khi sử dụng ở chế độ Swarm.

  • Sửa đổi tất cả after.rulescác tệp trên tất cả các nút, bao gồm cả người quản lý và nhân viên
  • Triển khai tập lệnh này trên các nút quản lý

Chạy ở chế độ Docker Swarm, tập lệnh này sẽ thêm dịch vụ toàn cầu ufw-docker-agent. Image chaifeng/ufw-docker-agent cũng được xây dựng tự động từ dự án này.

5.3. Cách sử dụng

Hiển thị trợ giúp: ufw-docker help

Kiểm tra việc cài đặt các quy tắc tường lửa trong cấu hình UFW: ufw-docker check

Cập nhật cấu hình UFW, thêm các quy tắc tường lửa cần thiết: ufw-docker install

Hiển thị các quy tắc chuyển tiếp được phép tường lửa hiện tại: ufw-docker status

Liệt kê tất cả các quy tắc tường lửa liên quan đến container httpd: ufw-docker list httpd

Lộ cảng 80 container httpd: ufw-docker allow httpd 80

Lộ 443 cổng của container httpd và giao thức là tcp: ufw-docker allow httpd 443/tcp

Lộ 443 cổng của container httpd và giao thức là gì tcp và mạng là foobar-external-networkkhi container httpd được gắn vào nhiều mạng: ufw-docker allow httpd 443/tcp foobar-external-network

Lộ hết các cổng đã công bố của container httpd: ufw-docker allow httpd

Xóa tất cả các quy tắc liên quan đến vùng chứa httpd: ufw-docker delete allow httpd

Loại bỏ quy tắc cổng nào 443và giao thức nào tcp dành cho container httpd: ufw-docker delete allow httpd 443/tcp

Lộ cổng 80 dịch vụ web: docker service create --name web --publish 8080:80 httpd:alpine

Mã nguồn [Chọn]
ufw-docker service allow web 80

ufw-docker service allow web 80/tcp

Xóa quy tắc khỏi tất cả các nút liên quan đến dịch vụ web: ufw-docker service delete allow web