Vaultwarden ปลอดภัยไหม? วิธีตั้งค่าให้ปลอดภัยที่สุด
Vaultwarden เข้ารหัสข้อมูลทุกอย่างที่ฝั่งไคลเอนต์ ทำให้เซิร์ฟเวอร์ไม่มีข้อมูลอ่านได้จริง ความเสี่ยงอยู่ที่ ADMIN_TOKEN ที่คาดเดาง่ายและการสำรองข้อมูลที่ไม่มีการป้องกัน
Vaultwarden ปลอดภัยหรือไม่? คำตอบโดยสรุป
Vaultwarden มีความปลอดภัยในจุดที่สำคัญที่สุด เนื่องจากข้อมูลทุกรายการใน vault จะถูกเข้ารหัสบนอุปกรณ์ของคุณก่อนที่จะส่งไปยังเซิร์ฟเวอร์ เซิร์ฟเวอร์จะจัดเก็บข้อมูลในรูปแบบ blob ที่ไม่สามารถอ่านได้ ผู้ที่คัดลอกฐานข้อมูลไปทั้งหมดก็ยังจำเป็นต้องใช้ master password เพื่อถอดรหัสข้อมูลเหล่านั้นออกมา
คำตอบข้างต้นเป็นเพียงส่วนหนึ่งเท่านั้น ส่วนที่มักจะเกิดปัญหาคือส่วนที่คุณเป็นผู้กำหนดค่าเอง เช่น แผงควบคุมผู้ดูแลระบบที่ใช้ token ซึ่งคาดเดาได้ง่าย, พอร์ตของ container ที่เปิดเผยต่ออินเทอร์เน็ตสาธารณะ, ไฟล์ config.json ที่เป็นข้อความธรรมดา หรือไฟล์สำรองข้อมูล tarball ที่วางไว้ใน home directory บนเครื่องเดียวกัน สิ่งเหล่านี้ไม่ใช่ปัญหาด้านวิทยาการรหัสลับ แต่เป็นสาเหตุที่ทำให้ vault ที่โฮสต์ด้วยตนเองถูกบุกรุก
เนื้อหาทั้งหมดด้านล่างนี้ตั้งอยู่บนสมมติฐานว่าคุณมีการติดตั้งที่ใช้งานได้แล้ว หากคุณยังไม่มี ให้ดำเนินการติดตั้งตาม คู่มือการติดตั้ง Vaultwarden สำหรับ VPS ให้เรียบร้อยก่อน แล้วจึงกลับมาทำตามรายการนี้ตามลำดับ
สิ่งที่เซิร์ฟเวอร์จัดเก็บจริง
Vaultwarden ใช้รูปแบบข้อมูลของ Bitwarden รายการใน vault ทั้งชื่อ, ชื่อผู้ใช้, รหัสผ่าน, บันทึก และ URI จะถูกเข้ารหัสด้วยกุญแจที่สร้างจากรหัสผ่านหลักของคุณ (master password) โดยฝั่งไคลเอนต์ก่อนที่จะส่งคำขอใดๆ ออกไป เนื้อหาของไฟล์แนบก็ถูกเข้ารหัสในลักษณะเดียวกัน เซิร์ฟเวอร์จะได้รับเพียงข้อมูลที่อ่านไม่ออกพร้อมกับ UUID (universally unique identifier) ที่แนบมาด้วย
มีข้อมูลบางอย่างที่ไม่ใช่ ciphertext และคุณควรทราบว่ามีอะไรบ้าง:
- ที่อยู่อีเมลบัญชีของคุณ ซึ่งอยู่ในรูปแบบ plaintext
- การตั้งค่า KDF (key derivation function) และ salt ของคุณ เนื่องจากไคลเอนต์จำเป็นต้องใช้ข้อมูลเหล่านี้เพื่อสร้างกุญแจขึ้นมาใหม่ในการเข้าสู่ระบบครั้งถัดไป
- ค่า hash ฝั่งเซิร์ฟเวอร์ของ hash รหัสผ่านหลักที่ไคลเอนต์ส่งมา ซึ่งใช้สำหรับการยืนยันตัวตนในการเข้าสู่ระบบ
- ข้อมูลเมตา (Metadata): การเป็นสมาชิกองค์กร, ชื่ออุปกรณ์, เวลาที่เข้าสู่ระบบล่าสุด
- ความลับ (Secret) สำหรับวิธีการยืนยันตัวตนสองชั้น (2FA) ที่ใช้ป้องกันการเข้าสู่ระบบ Vaultwarden ข้อมูลนี้จะอยู่ในตาราง
twofactorโดยไม่มีการเข้ารหัส เนื่องจากเซิร์ฟเวอร์ต้องคำนวณรหัสที่คาดหวังเพื่อนำมาเปรียบเทียบกับรหัสของคุณ ข้อมูลนี้ไม่เหมือนกับความลับ TOTP (time-based one-time password) ที่คุณเก็บไว้ ภายใน รายการใน vault ซึ่งจะถูกเข้ารหัสเหมือนกับฟิลด์อื่นๆ
โฟลเดอร์ข้อมูลมีขนาดเล็ก สำหรับการติดตั้งผ่าน Docker ข้อมูลจะอยู่ที่ตำแหน่งที่คุณ mount ไว้ที่ /data
sudo ls -l /vw-data/db.sqlite3 เก็บสถานะเกือบทั้งหมด attachments/ เก็บไฟล์ที่อัปโหลด โดยแยกไฟล์ตาม UUID และเป็นข้อมูลประเภทเดียวที่สำคัญซึ่งไม่ได้อยู่ในตารางฐานข้อมูล sends/ เก็บไฟล์แนบของ Send และมีไว้เพื่อใช้งานชั่วคราวเท่านั้น icon_cache/ เป็นไฟล์ที่ทิ้งได้ rsa_key.pem และไฟล์ที่เกี่ยวข้องใช้สำหรับลงนาม JWT (JSON web tokens) ของผู้ใช้ที่เข้าสู่ระบบ ดังนั้นหากมีสำเนาของ private key นี้ ก็สามารถใช้ปลอมแปลงเซสชันการเข้าสู่ระบบ vault ได้ config.json จะปรากฏขึ้นก็ต่อเมื่อคุณเปิดใช้งานหน้าผู้ดูแลระบบ (admin page) และทางโครงการได้ระบุไว้อย่างชัดเจนว่า ไฟล์นี้เก็บโทเค็นผู้ดูแลระบบและข้อมูลรับรอง SMTP ของคุณในรูปแบบ plaintext
ดังนั้น แบบจำลองภัยคุกคามในทางปฏิบัติคือการเข้าถึงระบบไฟล์ ไม่ใช่การเข้ารหัสผ่านเครือข่าย การเข้าถึงไดเรกทอรีดังกล่าวได้จะทำให้ผู้บุกรุกได้อีเมลของผู้ใช้ทุกคน, ความลับ 2FA สำหรับการเข้าสู่ระบบ, กุญแจสำหรับปลอมแปลงเซสชัน และสำเนาของ vault ทุกรายการแบบออฟไลน์เพื่อนำไปโจมตีได้ตามต้องการ ทุกขั้นตอนด้านล่างนี้มีไว้เพื่อป้องกันไม่ให้ผู้อื่นเข้าถึงไดเรกทอรีนั้นได้
แก้ไขโทเค็นผู้ดูแลระบบก่อน
/admin เป็นแผงควบคุมแบบเต็มรูปแบบ ประกอบด้วยรายการผู้ใช้, การเชิญ, การลบ และการตั้งค่ารันไทม์ทั้งหมด โดยได้รับการป้องกันด้วยความลับที่ใช้ร่วมกันเพียงอย่างเดียว ไม่มีชื่อผู้ใช้ และไม่มีการยืนยันตัวตนสองชั้นสำหรับผู้ใช้แต่ละราย
คู่มือรุ่นเก่าแนะนำให้คุณสร้าง ADMIN_TOKEN ด้วย openssl rand -base64 48 ซึ่งใช้งานได้จริงและจะเขียนค่าความลับแบบข้อความธรรมดาลงใน config.json และในไฟล์ compose ของคุณ แต่ Vaultwarden รองรับสตริง Argon2 PHC (password hashing competition) ด้วย ดังนั้นค่าที่จัดเก็บจึงเป็นแฮชแทน คุณสามารถสร้างแฮชจากคอนเทนเนอร์ที่กำลังทำงานอยู่ได้ดังนี้:
docker exec -it vaultwarden /vaultwarden hashหรือทำโดยไม่ต้องแตะต้องคอนเทนเนอร์ที่กำลังทำงานอยู่เลย:
docker run --rm -it vaultwarden/server /vaultwarden hashระบบจะถามรหัสผ่านสองครั้ง จากนั้นจะแสดงบรรทัดที่ขึ้นต้นด้วย $argon2id$ หากเป็นการติดตั้งบน bare-metal ให้รัน ./vaultwarden hash หากคุณต้องการใช้ CLI ของ argon2 โดยตรง ทางโครงการได้ระบุพารามิเตอร์ขั้นต่ำตามมาตรฐาน OWASP ไว้ดังนี้:
echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1ตอนนี้คือกับดักที่ทำให้เสียเวลาไปเป็นชั่วโมง สตริง PHC เต็มไปด้วยอักขระ $ และ Docker Compose จะมองว่า $ เป็นการแทนที่ตัวแปร หากคุณวางสตริงโดยไม่หลีกอักขระลงในบล็อก environment: ค่าที่ส่งไปยังคอนเทนเนอร์จะผิดเพี้ยน ทำให้ /admin ปฏิเสธโทเค็นที่คุณทราบว่าถูกต้อง มีรูปแบบที่ปลอดภัยสองวิธี ใน docker-compose.yml ให้เบิ้ลอักขระ $ เป็นสองเท่า:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UIในไฟล์ .env ไม่จำเป็นต้องหลีกอักขระ แต่ให้ใช้เครื่องหมายคำพูดเดี่ยว:
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'จากนั้นจำกัดอัตราการเข้าถึงแผงควบคุมและลดระยะเวลาเซสชัน:
ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20หากพยายามเข้าใช้งานผิดพลาด 3 ครั้งภายใน 5 นาที แผงควบคุมจะหยุดตอบสนองต่อไคลเอนต์นั้น และเซสชันของผู้ดูแลระบบจะหมดอายุหลังจากไม่มีการใช้งานเป็นเวลา 20 นาที
วิธีที่ดีกว่านี้คือการปิดหน้าดังกล่าว อินสแตนซ์ส่วนใหญ่ต้องการใช้งานเพียงครั้งเดียวเพื่อตั้งค่า SMTP และเชิญผู้ใช้กลุ่มแรก จากนั้นก็ไม่จำเป็นต้องใช้อีก หากต้องการปิดใช้งาน ให้ตั้งค่าทั้ง ADMIN_TOKEN และ DISABLE_ADMIN_TOKEN เป็นค่าว่าง ลบคีย์ "admin_token" ออกจาก config.json แล้วสร้างคอนเทนเนอร์ใหม่ การลบคีย์ออกจากไฟล์มีความสำคัญเนื่องจากหน้าผู้ดูแลระบบจะเขียนการตั้งค่าลงในนั้น และสิ่งที่อยู่ใน config.json จะมีความสำคัญเหนือกว่าตัวแปรสภาพแวดล้อม การลบเพียงตัวแปรสภาพแวดล้อมจะทำให้หน้าเว็บยังคงเปิดใช้งานอยู่
ปิดการลงทะเบียนก่อนที่จะมีใครพบโดเมนของคุณ
SIGNUPS_ALLOWED=false
SIGNUPS_VERIFY=true
SHOW_PASSWORD_HINT=falseSIGNUPS_ALLOWED มีค่าเริ่มต้นเป็น true หากปล่อยไว้เช่นนั้น ใครก็ตามที่เข้าถึงโดเมนของคุณจะสามารถสร้างบัญชีได้ และข้อมูลของพวกเขาจะถูกเก็บไว้ใน db.sqlite3 เดียวกับของคุณ ให้ตั้งค่าเป็น false และเพิ่มผู้ใช้งานผ่านคำเชิญจากหน้าผู้ดูแลระบบ ซึ่งจำเป็นต้องมีการตั้งค่า SMTP ที่ใช้งานได้ INVITATIONS_ALLOWED ยังถูกตั้งค่าเป็น true โดยค่าเริ่มต้น ซึ่งอนุญาตให้เจ้าขององค์กรเชิญผู้อื่นได้ สิ่งนี้ถือว่ายอมรับได้หากคุณเชื่อใจผู้ใช้งานของคุณ แต่ควรตั้งค่าเป็น false สำหรับ instance ที่มีผู้ใช้งานเพียงคนเดียว หากต้องการจำกัดให้เฉพาะบางโดเมนเท่านั้นที่ลงทะเบียนได้ SIGNUPS_DOMAINS_WHITELIST=example.com จะมีความจำกัดมากกว่าการเปิดลงทะเบียนทั่วไป แต่ก็มีความปลอดภัยน้อยกว่าการใช้ระบบคำเชิญมาก
SHOW_PASSWORD_HINT ถูกตั้งค่าเป็น false โดยค่าเริ่มต้นและควรคงไว้เช่นนั้น หากเปิดใช้งาน การพิมพ์ที่อยู่อีเมลที่ถูกต้องลงในแบบฟอร์มเข้าสู่ระบบจะแสดงคำใบ้รหัสผ่านหลักของบัญชีนั้น ซึ่งเป็นการเปิดเผยทั้งคำใบ้และยืนยันว่าที่อยู่อีเมลนั้นมีอยู่จริง
หาก instance ของคุณเคยเปิดให้ลงทะเบียนในช่วงเวลาใดก็ตาม ให้เปิดหน้าผู้ดูแลระบบและตรวจสอบรายชื่อผู้ใช้งานก่อนที่จะสรุปว่าคุณเป็นบัญชีเดียวที่มีอยู่ในระบบ
พอร์ตที่คุณไม่ได้ตั้งใจจะเปิดเผย
อิมเมจของ Docker จะรอรับการเชื่อมต่อที่พอร์ต 80 ภายในคอนเทนเนอร์ ในขณะที่การติดตั้งแบบ bare-metal จะใช้ค่าเริ่มต้นเป็น ROCKET_PORT=8000 ส่วนคำสั่ง run ที่ระบุไว้ในเอกสารจะทำการเผยแพร่พอร์ตดังนี้:
--publish 127.0.0.1:8000:80คำนำหน้า 127.0.0.1: คือประเด็นสำคัญทั้งหมด หากคุณเขียนเป็น -p 8000:80 แทน Docker จะทำการ bind เข้ากับ 0.0.0.0 ซึ่งทำได้โดยการเขียนกฎ DNAT (destination network address translation) ลงในตาราง nat กฎเหล่านี้จะถูกประเมินก่อน chain filter ที่ ufw จัดการ ส่งผลให้ ufw status รายงานว่าพอร์ตถูกปฏิเสธการเชื่อมต่อ ทั้งที่ในความเป็นจริงพอร์ตนั้นยังคงตอบสนองต่ออินเทอร์เน็ตตามปกติ คุณควรศึกษาข้อมูลกลไกการทำงานทั้งหมดได้จาก คู่มือเกี่ยวกับพอร์ต Docker ที่ข้ามการทำงานของ ufw
ตรวจสอบว่ามีอะไรกำลังรอรับการเชื่อมต่ออยู่จริง:
sudo ss -tlnp | grep 8000ผลลัพธ์ที่ถูกต้องคือต้องมีเพียงบรรทัดเดียวที่ bind อยู่กับ 127.0.0.1:8000 หากพบว่ามีบรรทัดที่ bind กับ 0.0.0.0:8000 แสดงว่า vault ถูกเปิดเผยโดยตรง ให้แก้ไขการ mapping แล้วสร้างคอนเทนเนอร์ขึ้นมาใหม่ เนื่องจากพอร์ตที่ bind จะถูกกำหนดไว้ตั้งแต่ตอนสร้างคอนเทนเนอร์ และ docker compose restart จะไม่สามารถเปลี่ยนแปลงค่านี้ได้:
docker compose up -d --force-recreateยังมีอีกหนึ่งพอร์ตที่มักปรากฏในคู่มือเก่า คือ 3012 ซึ่งเป็นพอร์ต WebSocket แยกต่างหาก การรองรับพอร์ตนี้ถูกยกเลิกไปใน Vaultwarden 1.31.0 เนื่องจากทราฟฟิกการแจ้งเตือนถูกย้ายไปรวมไว้ที่พอร์ต HTTP หลักแล้ว โดย WEBSOCKET_ENABLED และ WEBSOCKET_PORT ถูกเพิกเฉยมาตั้งแต่เวอร์ชัน 1.29.0 ปัจจุบันสวิตช์ที่ใช้คือ ENABLE_WEBSOCKET ซึ่งมีค่าเริ่มต้นเป็น true หากไฟร์วอลล์หรือไฟล์ compose ของคุณยังคงเปิดพอร์ต 3012 อยู่ ให้ทำการปิดพอร์ตดังกล่าวเสีย
ยุติการเชื่อมต่อ TLS ที่ reverse proxy แทนที่จะเป็น Rocket
Vaultwarden สามารถให้บริการ TLS (transport layer security) ได้ด้วยตัวเองผ่าน Rocket ซึ่งเป็นเว็บเฟรมเวิร์กของตัวมันเอง แต่ทางโครงการแนะนำว่าไม่ควรทำเช่นนั้นในสภาพแวดล้อมการใช้งานจริง (production) ระบบ TLS ในตัวของ Rocket ขาดการรองรับ SNI (server name indication) ที่เข้มงวด ซึ่งเป็นเหตุผลว่าทำไมคำแนะนำในการเพิ่มความปลอดภัยจึงระบุให้เข้าถึง instance ผ่านชื่อโฮสต์เท่านั้น และห้ามเข้าถึงผ่าน IP address โดยตรง ช่วง IP สาธารณะจะถูกสแกนอยู่ตลอดเวลา และ vault ที่ตอบสนองผ่าน IP address คือ vault ที่จะถูกค้นพบได้ง่าย
ส่วนประกอบที่สำคัญของ nginx server block:
client_max_body_size 525M;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}nginx กำหนดค่าเริ่มต้น client_max_body_size ไว้ที่ 1 MB ดังนั้นหากไม่มีบรรทัดดังกล่าว การอัปโหลดไฟล์แนบจะล้มเหลวพร้อมข้อความ 413 Request Entity Too Large ใน nginx error log ในขณะที่ Vaultwarden จะไม่บันทึกข้อมูลใดๆ เลย ส่วน header Upgrade และ Connection จะทำหน้าที่ส่งต่อ WebSocket handshake ไปยัง /notifications/hub หากตัดส่วนนี้ออก vault จะยังคงทำงานได้ แต่การเปลี่ยนแปลงข้อมูลจะไม่ปรากฏบนอุปกรณ์อื่นของคุณจนกว่าจะรีโหลดหน้าเว็บด้วยตนเอง
Caddy มีความกระชับกว่าและสามารถขอรับ certificate ได้ด้วยตัวเอง:
vw.example.com {
reverse_proxy 127.0.0.1:8000 {
header_up X-Real-IP {remote_host}
}
}จากนั้นกำหนดค่าให้ Vaultwarden รับทราบ:
DOMAIN=https://vw.example.com
IP_HEADER=X-Real-IPIP_HEADER มีค่าเริ่มต้นเป็น X-Real-IP อยู่แล้ว ดังนั้นหน้าที่ของคุณคือตรวจสอบให้แน่ใจว่า proxy ได้ตั้งค่า header ดังกล่าวจริง หากไม่ตั้งค่า ทุกบรรทัดใน log และทุกการจำกัดอัตราการเข้าสู่ระบบ (login rate limit) จะมองเห็นเป็น 127.0.0.1 ซึ่งก็คือตัว proxy เอง ส่งผลให้ความล้มเหลวของผู้โจมตีรายเดียวถูกนับรวมกับผู้ใช้ทุกคนใน instance นั้น นอกจากนี้ควรตั้งค่า DOMAIN ให้เป็น URL https ที่แท้จริงด้วย เนื่องจาก Vaultwarden จะใช้ URL นี้ในการสร้างลิงก์คำเชิญและลิงก์รีเซ็ตรหัสผ่าน รวมถึง WebAuthn security keys ก็จะผูกอยู่กับ origin นั้นด้วย
รายละเอียดหนึ่งที่ผู้คนมักมองข้ามคือ การเชื่อมต่อ WebSocket จะส่ง session token ผ่าน query string ในชื่อ /notifications/hub?access_token=[JWT] ซึ่งจะปรากฏใน proxy access log ของคุณแบบไม่เข้ารหัส คุณควรทำการปกปิด (redact) พารามิเตอร์ access_token ในรูปแบบ log หรือตรวจสอบให้แน่ใจว่า log เหล่านั้นจะไม่ถูกส่งไปยังที่ใดก็ตามที่คุณไม่สามารถควบคุมได้
การป้องกันการโจมตีแบบ Brute force ที่จุดเข้าใช้งานการล็อกอิน
การจำกัดอัตราการเข้าถึง (Rate limits) ถูกเปิดใช้งานไว้เป็นค่าเริ่มต้น (LOGIN_RATELIMIT_SECONDS=60, LOGIN_RATELIMIT_MAX_BURST=10) ซึ่งช่วยชะลอการโจมตีของผู้ไม่หวังดี แต่ไม่สามารถหยุดยั้งการโจมตีได้โดยสมบูรณ์ fail2ban สามารถทำหน้าที่นี้ได้ แต่ Vaultwarden จำเป็นต้องเขียนไฟล์ log ก่อน ซึ่งโดยปกติแล้วไม่ได้ถูกตั้งค่าไว้ตั้งแต่ต้น:
LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info
EXTENDED_LOGGING=trueเมื่อการล็อกอินล้มเหลว ระบบจะสร้างบรรทัด log ขึ้นมาหนึ่งบรรทัด ซึ่งเป็นข้อความที่ filter ของคุณต้องตรวจจับให้ตรง:
[2026-08-07 09:14:22][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 203.0.113.10. Username: user@example.com.เขียน filter ลงใน /etc/fail2ban/filter.d/vaultwarden.local:
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =และตั้งค่า jail ใน /etc/fail2ban/jail.d/vaultwarden.local:
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /vw-data/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400หากคุณยังคงเปิดใช้งานหน้า admin อยู่ ให้เพิ่ม jail ตัวที่สองที่มี failregex เป็น ^.*Invalid admin token\. IP: <ADDR>.*$ เนื่องจากความล้มเหลวในการเข้าถึงหน้า admin จะถูกบันทึกด้วยข้อความที่ต่างออกไป ซึ่ง filter ของการล็อกอินปกติจะไม่สามารถตรวจพบได้ จากนั้นตรวจสอบการทำงานของคุณ:
sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwardenjail ที่ทำงานถูกต้องจะแสดงรายการไฟล์ log ของคุณภายใต้ File list และรายงาน Currently failed: 0 ให้ลองใส่รหัสผ่านผิด 3 ครั้งจากเครือข่ายอื่น ตัวนับจะเพิ่มขึ้น จากนั้นที่อยู่ IP ดังกล่าวจะปรากฏภายใต้ Banned IP list หากตัวนับไม่เพิ่มขึ้น สาเหตุที่พบบ่อยคือ logpath: ซึ่งต้องเป็น path ของไฟล์บนโฮสต์ ไม่ใช่ path /data/... ภายในคอนเทนเนอร์ สาเหตุที่พบบ่อยเป็นอันดับสองคือการขาด X-Real-IP ซึ่งจะทำให้การแบนทั้งหมดพุ่งเป้าไปที่ proxy ของคุณเอง การตั้งค่าส่วนที่เหลือ รวมถึง SSH jail ที่คุณควรเปิดใช้งานอยู่แล้ว สามารถดูได้ใน คู่มือการใช้งาน fail2ban สำหรับ Ubuntu 24.04
รหัสผ่านหลักยังคงเป็นหัวใจสำคัญของทั้งระบบ
การเข้ารหัสฝั่งไคลเอนต์หมายความว่ารหัสผ่านหลักคือตัวกุญแจสำคัญ หากรหัสผ่านหลักสั้นเกินไปบนอินสแตนซ์ที่ผู้โจมตีคัดลอกฐานข้อมูลไปได้ ข้อมูลนั้นจะไม่มีสิ่งใดในบทความนี้คุ้มครองได้เลย เพราะผู้โจมตีจะทำการโจมตีสำเนาฐานข้อมูลแบบออฟไลน์ด้วยความเร็วเท่าที่ฮาร์ดแวร์ของพวกเขาจะทำได้ การตั้งค่าใดๆ บนเซิร์ฟเวอร์ไม่สามารถเข้าถึงเครื่องของผู้โจมตีได้
PASSWORD_ITERATIONS=600000 คือจำนวนรอบการทำ KDF ที่ส่งให้กับไคลเอนต์เมื่อมีการสร้างบัญชีใหม่ บัญชีที่มีอยู่เดิมจะยังคงใช้ค่าเดิมที่ถูกกำหนดไว้ตอนสร้างบัญชี ดังนั้นการเพิ่มค่านี้จึงไม่มีผลใดๆ ต่อผู้ใช้ที่สมัครใช้งานไปเมื่อปีที่แล้ว ผู้ใช้เหล่านั้นต้องเปลี่ยนค่าด้วยตนเองในการตั้งค่าความปลอดภัยของ web vault ซึ่งจะทำการเข้ารหัสกุญแจของพวกเขาใหม่ คุณต้องแจ้งให้ผู้ใช้ทราบ เนื่องจากไม่มีส่วนใดในอินเทอร์เฟซที่จะแจ้งเตือนเรื่องนี้
จากนั้นให้เปิดใช้งานการยืนยันตัวตนแบบสองชั้น (two-factor authentication) สำหรับแต่ละบัญชี วิธีนี้ไม่ได้ช่วยปกป้องตัว ciphertext เนื่องจากกุญแจของ vault มาจากรหัสผ่านหลักเพียงอย่างเดียว แต่จะช่วยป้องกันไม่ให้รหัสผ่านที่ถูกขโมยไปเพียงอย่างเดียวสามารถเข้าสู่ระบบและซิงค์สำเนาข้อมูลได้ REQUIRE_DEVICE_EMAIL=true จะเพิ่มขั้นตอนการยืนยันผ่านอีเมลในครั้งแรกที่มีการเข้าสู่ระบบจากอุปกรณ์ที่ไม่รู้จัก
การสำรองข้อมูลคือจุดที่ทำให้การทำ self-hosted vault ล้มเหลว
การทำ tar czf ของโฟลเดอร์ข้อมูลแล้วทิ้งไว้ใน home directory บน VPS เครื่องเดียวกันนั้น ทำให้ขั้นตอนทั้งหมดที่กล่าวมาข้างต้นสูญเปล่า ไฟล์ archive นั้นเก็บ db.sqlite3 ซึ่งมี ciphertext ของผู้ใช้ทุกคน, rsa_key.pem ที่ใช้ปลอมแปลงเซสชันการเข้าสู่ระบบ และ config.json ที่มี admin token และรหัสผ่าน SMTP ในรูปแบบข้อความธรรมดา การเข้าถึงไฟล์นั้นได้เพียงไฟล์เดียวก็เท่ากับการเข้าถึง vault ได้ทั้งหมด
มีกฎ 2 ข้อที่ต้องปฏิบัติตาม คือ นำไฟล์ archive ออกจากเซิร์ฟเวอร์ และเข้ารหัสข้อมูลก่อนที่จะส่งออกไป
นอกจากนี้ยังมีปัญหาเรื่องความถูกต้องของข้อมูล การคัดลอก db.sqlite3 ด้วย cp ในขณะที่บริการกำลังทำงานอยู่อาจทำให้ได้ไฟล์ที่อยู่ในสถานะเขียนไม่เสร็จและไม่สามารถเปิดใช้งานได้ ซึ่งคุณจะไม่ทราบเลยจนกว่าจะถึงเวลาที่ต้องกู้คืนข้อมูล ให้ใช้ฟังก์ชัน snapshot ของ SQLite แทน:
sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"ในส่วนของการกู้คืนข้อมูล ซึ่งเป็นขั้นตอนที่ไม่มีใครทดสอบ สามารถดูรายละเอียดได้ที่ คู่มือการสำรองและกู้คืนข้อมูลของ Vaultwarden
สิ่งที่คุณต้องแลกเมื่อเทียบกับการใช้บริการ Bitwarden แบบโฮสต์
การประเมินอย่างตรงไปตรงมา บริการแบบโฮสต์ของ Bitwarden ดำเนินการโดยทีมงานมืออาชีพที่มีหน้าที่ดูแลระบบโดยเฉพาะ มีการตรวจสอบจากบุคคลที่สามที่เปิดเผยต่อสาธารณะ และมีเจ้าหน้าที่พร้อมแก้ไขปัญหาตลอด 24 ชั่วโมง การโฮสต์ด้วยตนเองหมายถึงคุณต้องรับหน้าที่จัดการการอัปเดตแพตช์ด้วยตนเอง
Vaultwarden เผยแพร่การแก้ไขด้านความปลอดภัยในรูปแบบ release ปกติ เวอร์ชัน 1.37.0 ซึ่งปล่อยออกมาเมื่อวันที่ 24 กรกฎาคม 2026 เป็นเวอร์ชันปัจจุบัน ณ เดือนสิงหาคม 2026 โดยบันทึกประจำรุ่นระบุให้ผู้ใช้ควรอัปเดตโดยเร็วที่สุด อินสแตนซ์ที่คุณติดตั้งไว้เมื่อปีที่แล้วและไม่ได้ดูแลต่อจะยังคงรันโค้ดที่มีอายุหนึ่งปี แท็ก latest ไม่ได้ช่วยอะไรด้วยตัวมันเอง เนื่องจากคอนเทนเนอร์ที่รันอยู่จะยังคงใช้ image เดิมที่เริ่มทำงานจนกว่าคุณจะรันคำสั่ง docker compose pull และสร้างคอนเทนเนอร์ขึ้นมาใหม่ คุณควรตั้งค่า การอัปเกรดอัตโนมัติบน Ubuntu สำหรับแพ็กเกจของโฮสต์ และตั้งค่าการแจ้งเตือนในปฏิทินเพื่ออัปเดตคอนเทนเนอร์ที่คุณจะเปิดอ่านจริง ๆ
ข้อสรุปที่ผู้อ่านควรตระหนักคือ ระบบการเข้ารหัสลับที่ใช้นี้เป็นการออกแบบของ Bitwarden ซึ่งมีความน่าเชื่อถือ แต่ความเสี่ยงในการปฏิบัติงานจะตกอยู่กับคุณโดยสมบูรณ์ หากคุณหมั่นอัปเดตแพตช์และสำรองข้อมูลไว้ที่อื่น อินสแตนซ์ Vaultwarden บน VPS ที่คุณควบคุมเองก็เป็นสถานที่ที่เหมาะสมในการจัดเก็บรหัสผ่านของคุณ แต่หากคุณไม่สามารถสร้างนิสัยทั้งสองอย่างนี้ได้ การจ่ายเงินใช้บริการแบบโฮสต์และนำเวลาไปโฟกัสเรื่องอื่นจะเป็นทางเลือกที่ดีกว่า คุณสามารถดูการเปรียบเทียบฟีเจอร์แบบละเอียดได้ที่ การเปรียบเทียบ Vaultwarden กับ Bitwarden แบบโฮสต์เอง
การเสริมความปลอดภัยให้กับโฮสต์ที่รันคอนเทนเนอร์
Vaultwarden เป็นเพียงโพรเซสหนึ่งบนเครื่อง Linux ซึ่งผู้ใช้ระดับ root บนเครื่องนั้นสามารถอ่านไฟล์ /vw-data ได้เสมอ ไม่ว่าแอปพลิเคชันจะถูกตั้งค่าไว้อย่างไรก็ตาม ให้รันคอนเทนเนอร์ด้วยผู้ใช้ที่ไม่มีสิทธิ์ระดับสูงโดยใช้ user: "1000:1000" ในไฟล์ compose ของคุณ พร้อมทั้งกำหนดความเป็นเจ้าของโฟลเดอร์ข้อมูลให้ตรงกัน และ mount สิ่งที่คอนเทนเนอร์ไม่ได้เขียนข้อมูลลงไปให้เป็นแบบอ่านอย่างเดียวด้วย :ro จากนั้นให้ปิดช่องทางเข้าหลัก: การเสริมความปลอดภัย SSH บน VPS ซึ่งครอบคลุมถึงการล็อกอินด้วยคีย์เพียงอย่างเดียวและการปิดการตรวจสอบสิทธิ์ด้วยรหัสผ่าน ซึ่งเป็นสิ่งที่ช่วยหยุดการโจมตีทั่วไปที่สามารถผ่านการป้องกันทั้งหมดที่กล่าวมาข้างต้นได้
FAQ
มีใครอ่านรหัสผ่านของฉันได้หรือไม่หากขโมยฐานข้อมูล Vaultwarden ไป?
ไม่ได้โดยตรง ข้อมูลทุกรายการใน vault จะถูกเข้ารหัสไว้ที่ฝั่งไคลเอนต์ด้วยคีย์ที่สร้างจากรหัสผ่านหลัก ดังนั้น db.sqlite3 จึงมีเพียงข้อมูลที่ถูกเข้ารหัสไว้ สิ่งที่ผู้บุกรุกจะได้ไปทันทีคืออีเมลของแต่ละบัญชี, การตั้งค่า KDF, ข้อมูล metadata ของการล็อกอินและอุปกรณ์ รวมถึงรหัสลับสำหรับ two-factor ในตาราง twofactor ซึ่งถูกเก็บไว้โดยไม่ได้เข้ารหัสเนื่องจากเซิร์ฟเวอร์จำเป็นต้องใช้ในการคำนวณรหัสที่ถูกต้อง นอกจากนี้ผู้บุกรุกยังสามารถโจมตีข้อมูล vault ที่ถูกเข้ารหัสแบบออฟไลน์ได้นานเท่าที่ต้องการ นี่คือเหตุผลว่าทำไมความยาวของรหัสผ่านหลักจึงเป็นปัจจัยชี้ขาดความปลอดภัย
ฉันควรใช้ ADMIN_TOKEN หรือปิดหน้า admin ไปเลย?
หากทำได้ควรปิดการใช้งาน เนื่องจากอินสแตนซ์ส่วนใหญ่ต้องการหน้าดังกล่าวเพียงครั้งเดียวเพื่อตั้งค่า SMTP และเชิญผู้ใช้เท่านั้น หลังจากนั้นก็ไม่จำเป็นต้องใช้อีก หากต้องการปิด ให้เว้นว่างทั้ง ADMIN_TOKEN และ DISABLE_ADMIN_TOKEN, ลบคีย์ "admin_token" ออกจาก config.json แล้วสร้างคอนเทนเนอร์ขึ้นใหม่ การลบเพียง environment variable อย่างเดียวไม่เพียงพอ เพราะการตั้งค่าที่เขียนผ่านหน้า admin จะถูกเก็บไว้ใน config.json ซึ่งมีลำดับความสำคัญสูงกว่า หากคุณยังจำเป็นต้องใช้หน้า admin ให้เก็บ token ในรูปแบบ Argon2 hash ที่สร้างจาก vaultwarden hash แทนการใช้ข้อความธรรมดา และตั้งค่า ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_TOKEN ของฉันถูกต้อง แต่ /admin ปฏิเสธการเข้าถึง เกิดอะไรขึ้น?
สาเหตุส่วนใหญ่มาจากการแทนที่ค่า (interpolation) ใน $ สตริง Argon2 PHC มีอักขระ $ อยู่หลายตัว และ Docker Compose จะตีความอักขระเหล่านี้เป็นตัวแปรภายในบล็อก docker-compose.yml environment: ทำให้คอนเทนเนอร์ได้รับค่าที่ผิดเพี้ยนไปแม้ว่าในไฟล์ของคุณจะดูถูกต้อง ให้เพิ่ม $ เป็นสองเท่าเป็น $$ ในไฟล์ compose หรือย้ายค่าดังกล่าวไปไว้ในไฟล์ .env โดยครอบด้วยเครื่องหมายคำพูดเดี่ยวซึ่งไม่จำเป็นต้องใช้การ escape หลังจากนั้นให้สร้างคอนเทนเนอร์ใหม่ เนื่องจากการรีสตาร์ทเพียงอย่างเดียวจะไม่โหลดการเปลี่ยนแปลงของ environment variable
ฉันยังจำเป็นต้องเปิดพอร์ต 3012 สำหรับการแจ้งเตือน (notifications) หรือไม่?
ไม่จำเป็น การรองรับทราฟฟิก WebSocket บนพอร์ต 3012 ถูกถอดออกใน Vaultwarden 1.31.0 เนื่องจากระบบแจ้งเตือนถูกย้ายไปอยู่บนพอร์ต HTTP หลักแล้ว และ WEBSOCKET_ENABLED กับ WEBSOCKET_PORT ก็ถูกละเว้นมาตั้งแต่เวอร์ชัน 1.29.0 การตั้งค่าปัจจุบันคือ ENABLE_WEBSOCKET ซึ่งมีค่าเริ่มต้นเป็น true ให้ปิดพอร์ต 3012 ในไฟร์วอลล์และลบออกจากไฟล์ compose ของคุณ จากนั้นตรวจสอบให้แน่ใจว่า reverse proxy ของคุณส่งต่อ header Upgrade และ Connection ไปด้วย เพราะนั่นคือสิ่งที่การซิงค์ข้อมูลแบบเรียลไทม์ใช้งานจริงในปัจจุบัน