SSD Nodes Learn
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-07-24

วิธีตั้งค่า UFW และ IPv6 บน VPS ให้ปลอดภัย

ป้องกันช่องโหว่จากการที่ UFW หรือ Cloud Firewall กรองเฉพาะ IPv4 แต่ปล่อยให้ IPv6 เปิดรับการเชื่อมต่อจากอินเทอร์เน็ตโดยไม่ตั้งใจบน Ubuntu 24.04 VPS

กับดักของ IPv6 firewall ในประโยคเดียว

Firewall ของคุณป้องกัน IPv4 แต่ VPS ของคุณเกือบทุกเครื่องมี public IPv6 address และมีหลายบริการที่เปิดรับการเชื่อมต่อผ่าน IPv6 เป็นค่าเริ่มต้น หาก firewall ของคุณครอบคลุมเพียง IPv4 หรือหากคุณใช้ cloud firewall ที่กรองเฉพาะ IPv4 บริการเหล่านั้นจะสามารถเข้าถึงได้จากอินเทอร์เน็ตทั้งหมดผ่าน IPv6 แม้ว่าฝั่ง IPv4 จะดูเหมือนถูกปิดกั้นไว้อย่างปลอดภัยก็ตาม หากคุณทดสอบ port ด้วย curl แล้วพบว่าการเชื่อมต่อถูกปฏิเสธ คุณอาจรู้สึกปลอดภัย แต่ผู้โจมตีสามารถเชื่อมต่อเข้าสู่ port เดียวกันผ่าน IPv6 เพื่อบุกรุกเข้ามาได้

คู่มือนี้จะแสดงให้เห็นว่าช่องโหว่ดังกล่าวเกิดขึ้นได้อย่างไรบน Ubuntu 24.04 VPS ทั่วไป วิธีการตรวจสอบว่าคุณกำลังเปิดเผยข้อมูลส่วนใดบ้าง และวิธีปิดช่องโหว่นั้น UFW ไม่ใช่ต้นเหตุของปัญหา เนื่องจาก Ubuntu เวอร์ชันปัจจุบัน UFW สามารถจัดการ IPv6 ได้อยู่แล้ว ช่องโหว่ที่เกิดขึ้นมาจากเลเยอร์อื่นที่อยู่รอบตัวมัน และมาจากบริการที่คุณไม่ทราบว่ากำลังเปิดรับการเชื่อมต่ออยู่

สาเหตุที่ VPS ของคุณใช้งาน IPv6

VPS เกือบทุกเครื่องในปัจจุบันมาพร้อมกับ public IPv6 address ซึ่งมักจะเป็นทั้ง /64 นอกเหนือจาก IPv4 address ของคุณ สามารถตรวจสอบได้ดังนี้:

ip -6 addr show scope global
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
    inet6 2001:db8:2a::1/64 scope global

2001:db8:2a::1 นั้นสามารถ routable ได้จากทุกที่บนอินเทอร์เน็ต เช่นเดียวกับ IPv4 address ของคุณ ต่อไปนี้คือรายการ service ที่กำลัง listening อยู่:

sudo ss -tlnp
State   Recv-Q  Local Address:Port   Process
LISTEN  0       0.0.0.0:22           sshd
LISTEN  0       [::]:22              sshd
LISTEN  0       127.0.0.1:5432       postgres
LISTEN  0       [::]:8080            docker-proxy

โปรดตรวจสอบคอลัมน์ Local Address อย่างละเอียด 0.0.0.0:22 หมายถึง "listen on every IPv4 address" [::]:22 หมายถึง "listen on every IPv6 address" ส่วน 127.0.0.1:5432 ถูก bound ไว้กับ loopback และไม่ได้เป็น public ดังนั้นบรรทัดของ Postgres จึงมีความปลอดภัย ส่วนบรรทัด [::] ทั้งสองบรรทัดจะตอบรับการเชื่อมต่อจากอินเทอร์เน็ตทั้งหมดผ่าน IPv6 และบรรทัด docker-proxy คือประเภทที่คุณอาจลืมว่าได้เริ่มใช้งานไปแล้ว

Daemon ส่วนใหญ่จะ bind กับ :: มาตั้งแต่เริ่มต้น เนื่องจากบน Linux socket แบบ :: มักจะรองรับ IPv4 ด้วย ดังนั้นค่าเริ่มต้นของ server ใหม่คือ "ตอบรับทั้งสอง stack จากทุกที่" Firewall จึงเป็นสิ่งเดียวที่คอยป้องกันไว้ ซึ่งเป็นสาเหตุที่ firewall ที่ตรวจพบเพียง stack เดียวอาจก่อให้เกิดปัญหาได้

สาเหตุที่แท้จริงของช่องโหว่ IPv6

สาเหตุทั่วไปมี 4 ประการ ในแต่ละเครื่องอาจเกิดจากสาเหตุเดียวหรือหลายสาเหตุพร้อมกัน

1. Cloud firewall ที่กรองเฉพาะ IPv4. Firewall ของผู้ให้บริการและผลิตภัณฑ์ security-group จำนวนมากถูกพัฒนาขึ้นมาเพื่อ IPv4 จึงอาจเพิกเฉยต่อ IPv6 หรือต้องมีการเพิ่มกฎ IPv6 แยกต่างหากด้วยตนเอง หาก firewall เพียงอย่างเดียวของคุณคือตัวที่อยู่ใน dashboard ของผู้ให้บริการ และตัวนั้นไม่ครอบคลุม IPv6 บริการ [::] ของคุณจะถูกเปิดออกโดยไม่คำนึงถึงการตั้งค่า port 22 บน IPv4 โปรดอ่านเอกสาร firewall ของผู้ให้บริการและค้นหาคำว่า IPv6 โดยเฉพาะ

2. การใช้ iptables ที่ไม่ได้ใช้ ip6tables. คำสั่ง iptables จัดการเฉพาะ IPv4 tables เท่านั้น IPv6 มีคำสั่งแยกต่างหากคือ ip6tables ซึ่งมีกฎแยกจากกัน หากคุณเขียน script firewall ที่เต็มไปด้วยบรรทัด iptables -A INPUT ... แต่ไม่ได้เขียนกฎ ip6tables ที่สอดคล้องกัน firewall ของ IPv6 จะไม่มีกฎใดๆ และ INPUT chain ที่ว่างเปล่าพร้อมนโยบาย default ACCEPT จะอนุญาตทุกการเชื่อมต่อ:

sudo ip6tables -L INPUT -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

ผลลัพธ์ดังกล่าวคือกับดักที่ปรากฏบนหน้าจอเดียว IPv4 ถูกกรองไว้ แต่ IPv6 กลับอนุญาตทุกอย่าง

3. Docker ทำการ publish ports ข้ามผ่าน firewall. เมื่อคุณรัน docker run -p 8080:80 Docker จะแทรกกฎของตนเองไว้ก่อนกฎของ UFW ดังนั้น port ที่ถูก publish จะสามารถเข้าถึงได้แม้ว่า ufw status จะระบุว่า port นั้นถูกปฏิเสธ และใน Docker เวอร์ชันปัจจุบัน หลักการนี้ใช้กับ IPv6 เช่นกัน ทำไม Docker ถึง bypass UFW และวิธีกรอง container ports อย่างถูกต้อง จะอธิบายถึงกลไกและวิธีการแก้ไข ส่วน พื้นฐานของ Docker Compose บน VPS จะอธิบายถึงวิธีการประกาศ publish ports เหล่านี้

4. UFW ที่ปิดการใช้งาน IPv6 ไว้. UFW สามารถจัดการ IPv6 ได้ แต่ต้องได้รับการสั่งการก่อน ตรวจสอบการตั้งค่าได้ที่:

grep IPV6 /etc/default/ufw

Ubuntu เวอร์ชันปัจจุบันมาพร้อมกับ IPV6=yes ดังนั้น UFW จะใช้กฎกับทั้งสอง stack หากคุณพบ IPV6=no จาก image เก่าหรือคู่มือเก่า กฎ UFW ทุกข้อที่คุณเขียนจะใช้กับ IPv4 เท่านั้น และ IPv6 จะไม่ถูกควบคุมโดย firewall

ตรวจสอบสิ่งที่เปิดเผยสู่ภายนอกอย่างแม่นยำ

ห้ามคาดเดา ให้ทำการวัดผลจากภายนอก เริ่มต้นด้วยการแสดงรายการ listeners และจดบันทึกรายการที่ผูกกับ :::

sudo ss -tlnp | grep '::'

จากนั้น ให้เชื่อมต่อจากเครื่องอื่นไปยัง public IPv6 address ของ server และทดสอบ port ที่คุณคาดว่าปิดอยู่:

curl -6 -v http://[2001:db8:2a::1]:8080/

หากได้รับหน้าเว็บหรือ banner แสดงว่า port นั้นเปิดใช้งานบน IPv6 หาก port ปิดอยู่ คุณจะได้รับ Connection refused หรือเกิด timeout เพื่อให้เห็นภาพรวมทั้งหมด ให้ใช้ nmap สแกน IPv6 address จากเครื่องอื่นที่ไม่ใช่ server:

nmap -6 2001:db8:2a::1

ทุก port ที่ nmap รายงานว่า open บน IPv6 คือ port ที่อินเทอร์เน็ตทั้งหมดสามารถเข้าถึงได้ ไม่ว่าผลการสแกน IPv4 จะแสดงอย่างไรก็ตาม การเปรียบเทียบผลการสแกน IPv4 และ IPv6 ควบคู่กันเป็นวิธีที่เร็วที่สุดในการหาช่องว่าง หากมีสิ่งใดเปิดอยู่บน -6 แต่ปิดอยู่บน IPv4 แสดงว่า firewall ของคุณยังครอบคลุมไม่ถึง service นั้น

Close the gap

ตั้งค่า UFW ให้ครอบคลุมทั้งสอง stack และตั้งค่าเริ่มต้นเป็น deny ตรวจสอบการเปลี่ยนค่า จากนั้นกำหนดนโยบาย default-deny สำหรับ inbound และอนุญาตเฉพาะสิ่งที่จำเป็นเท่านั้น:

sudo sed -i 's/^IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verbose

หาก UFW กำลังทำงานอยู่ขณะที่คุณเปลี่ยนค่า IPV6=yes การเปลี่ยนแปลงจะมีผลก็ต่อเมื่อคุณรันคำสั่ง sudo ufw reload เท่านั้น

ufw status แสดงรายการกฎแต่ละข้อสองครั้ง คือแบบปกติและแบบที่มี suffix (v6) เมื่อคุณเห็นบรรทัดที่มี (v6) แสดงว่า UFW กำลังกรอง IPv6 อยู่:

22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)

หากคุณจัดการ iptables ด้วยตนเอง ให้สร้างกฎใน ip6tables ให้เหมือนกันทุกประการ หรือเปลี่ยนไปใช้ nftables ซึ่งมี tables inet ที่ครอบคลุมทั้ง IPv4 และ IPv6 ในที่เดียว เพื่อป้องกันความผิดพลาดประเภทนี้ การใช้ nftables inet filter table เพียงตารางเดียวเป็นวิธีแก้ไขที่สะอาดที่สุดเมื่อคุณต้องเขียนกฎด้วยตนเอง

ผูกบริการที่ไม่ต้องการให้เข้าถึงจากสาธารณะเข้ากับ loopback ฐานข้อมูล, แผงควบคุมผู้ดูแลระบบ หรือ endpoint สำหรับเก็บข้อมูล metrics มักไม่จำเป็นต้องมี public address ให้ผูกบริการเข้ากับ 127.0.0.1 และ ::1 เพื่อไม่ให้บริการนั้นรับฟัง (listen) บน routable address ตั้งแต่แรก สำหรับ Postgres ให้ตั้งค่า listen_addresses = 'localhost' สำหรับ app server ให้ผูกเข้ากับ 127.0.0.1 และใช้ reverse proxy วางไว้ด้านหน้า การปิด listener มีประสิทธิภาพมากกว่าการใช้ firewall เพราะจะไม่มีช่องทางให้เข้าถึงได้เลย

อย่าเชื่อใจ UFW ในการป้องกัน port ที่ถูก publish โดย Docker ให้ publish container ports ไปยัง address ที่เฉพาะเจาะจงแทนที่จะเป็นทุก interface เช่น -p 127.0.0.1:8080:80 เพื่อให้ port นั้นเข้าถึงได้เฉพาะจาก host และบริการที่คุณตั้งใจทำ proxy ไปหาเท่านั้น เมื่อ container จำเป็นต้องเป็นสาธารณะ ให้วางไว้หลัง a Traefik reverse proxy และ publish เฉพาะตัว proxy เท่านั้น ไม่ใช่ทุกแอปพลิเคชัน

เพิ่มกฎ IPv6 ใน firewall ของผู้ให้บริการของคุณ หรือยอมรับว่านั่นไม่ใช่ firewall สำหรับ IPv6 ของคุณ และปล่อยให้ UFW หรือ nftables บน host ทำหน้าที่นั้นแทน

ตรวจสอบว่าการปิดการเชื่อมต่อสำเร็จจริงหรือไม่

รันการทดสอบจากภายนอกอีกครั้งหลังจากทำการเปลี่ยนแปลง:

curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1

Port ที่เคยตอบสนองก่อนหน้านี้ควรจะปฏิเสธการเชื่อมต่อ (refuse) หรือหมดเวลา (time out) และ nmap ควรรายงานสถานะเป็น filtered หรือ closed หาก port ยังคงเปิดอยู่ ให้ตรวจสอบสาเหตุจากสี่แหล่งข้อมูลข้างต้น: มี service ยังคงผูกติดกับ :: โดยไม่มี rule กั้นอยู่, มี Docker rule ทำงานอยู่ก่อนหน้า UFW, หรือมี provider firewall ที่ไม่ได้รองรับ IPv6

การแยก service ที่สำคัญออกจาก public internet โดยสิ้นเชิงเป็นวิธีที่มีประสิทธิภาพสูงสุด การใช้ WireGuard VPN เพื่อปกป้อง SSH และ admin panels และตั้งค่า firewall ให้ตอบสนองเฉพาะผ่าน tunnel เท่านั้น เพื่อตัดปัญหาการรั่วไหลผ่าน IPv6 สำหรับ service เหล่านั้น หากต้องการชะลอการสแกนแบบ brute-force ที่พุ่งเป้าไปยังส่วนที่ยังเปิดสาธารณะ ให้ติดตั้ง Fail2ban สำหรับ SSH ควบคู่ไปกับการตั้งค่า firewall แบบ default-deny

หากคุณยังไม่คุ้นเคยกับเรื่อง port ให้เริ่มอ่านจาก พื้นฐานเรื่อง port และการทำงานของ service

FAQ

UFW บล็อก IPv6 โดยค่าเริ่มต้นหรือไม่?

สำหรับการติดตั้ง Ubuntu 24.04 รุ่นปัจจุบัน คำตอบคือใช่ UFW อ่านค่า IPV6=yes จาก /etc/default/ufw และใช้กฎกับทั้ง IPv4 และ IPv6 โดย ufw status จะแสดงกฎ IPv6 พร้อมกับ suffix (v6) ปัญหาจะเกิดขึ้นเมื่อใช้ IPV6=no (จาก image เก่าหรือบทความเก่า) เมื่อคุณใช้ firewall ของผู้ให้บริการที่กรองเฉพาะ IPv4 หรือเมื่อ Docker ทำการ publish port ข้ามผ่าน UFW ตรวจสอบการตั้งค่าได้ด้วย grep IPV6 /etc/default/ufw

ฉันจะตรวจสอบได้อย่างไรว่า VPS ของฉันเปิดเผยข้อมูลใดผ่าน IPv6 บ้าง?

รันคำสั่ง sudo ss -tlnp และสังเกต listener ทุกตัวที่มี local address เริ่มต้นด้วย [::] ซึ่งหมายความว่ามันตอบสนองบนทุก interface ของ IPv6 จากนั้นให้ใช้เครื่องอื่นทดสอบ public IPv6 address ของเซิร์ฟเวอร์โดยตรงด้วย curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/ หรือสแกนด้วย nmap -6 YOUR:IPV6::ADDR หากพอร์ตใดเปิดอยู่ในการสแกน IPv6 แต่ปิดอยู่ใน IPv4 แสดงว่านั่นคือช่องโหว่ของคุณ

ทำไมฉันถึงเข้าถึง port ของ Docker container ได้ ทั้งที่ UFW ระบุว่าถูกบล็อก?

Docker จะแทรกกฎ firewall ของตัวเองไว้ก่อนหน้า UFW เมื่อคุณ publish port ด้วย -p ดังนั้น port ที่ถูก publish จึงสามารถเข้าถึงได้แม้ว่า ufw status จะระบุว่าถูกปฏิเสธ (denied) สิ่งนี้เกิดขึ้นบน IPv4 และเกิดขึ้นบน IPv6 ด้วยหากเปิดใช้งาน IPv6 support ของ Docker วิธีแก้ไขคือให้ publish ไปยัง address ที่เฉพาะเจาะจง เช่น -p 127.0.0.1:8080:80 หรือวาง container ไว้หลัง reverse proxy และ publish เฉพาะ port ของ proxy เท่านั้น

ฉันยังจำเป็นต้องมี IPv6 firewall หรือไม่ หาก IPv4 firewall ของฉันปลอดภัยดีแล้ว?

จำเป็น เพราะ IPv4 และ IPv6 เป็น network stack ที่แยกจากกันและใช้กฎ firewall แยกกัน กฎ IPv4 ที่สมบูรณ์ไม่สามารถป้องกัน traffic ของ IPv6 ได้ หาก VPS ของคุณมี public IPv6 address (ซึ่งเกือบทุกเครื่องมี) บริการใดก็ตามที่ listen บน :: จะยังคงเข้าถึงได้ผ่าน IPv6 จนกว่าจะมีกฎ IPv6 firewall หรือการทำ loopback binding มาปิดกั้นไว้

ฉันจะทำให้ service listen เฉพาะ IPv4 หรือเฉพาะ localhost ได้อย่างไร?

ให้ตั้งค่า bind address ของ service นั้นๆ ในไฟล์ config ของมันเอง ใช้ 127.0.0.1 สำหรับ IPv4 loopback เท่านั้น หรือใช้ 0.0.0.0 สำหรับทุก IPv4 address โดยไม่มี IPv6 listener ตัวอย่างเช่น Postgres ใช้ listen_addresses, SSH ใช้ ListenAddress และ app server ส่วนใหญ่จะมี flag host หรือ bind ให้ใช้งาน ตรวจสอบผลลัพธ์ด้วย sudo ss -tlnp และตรวจสอบว่า Local Address ไม่แสดง [::] อีกต่อไป