บันทึก

ถ้า Server โดนเจาะ Secret ของเราจะเป็นอย่างไร

💻 Development

บางทีคำถามที่ดีกว่า “เก็บ Key ไว้อย่างไรให้ปลอดภัย” อาจเป็น “เราจำเป็นต้องมี Key ชุดนี้จริงหรือไม่ และระบบควรได้รับสิทธิ์อะไรบ้าง”

ช่วงนี้เห็นข่าวระบบถูกเจาะอยู่เรื่อย ๆ เลยลองกลับมาคิดเรื่องระบบของตัวเอง

ถ้าวันหนึ่ง server ของเราถูกเจาะขึ้นมาจริง ๆ ผู้บุกรุกจะเข้าถึงอะไรได้บ้าง

เรื่องแรก ๆ ที่ผมนึกถึงคือ secret ต่าง ๆ ที่ระบบใช้ ไม่ว่าจะเป็น database password, API key, JWT secret หรือ credential สำหรับเข้าถึงบริการอื่น

ทุกวันนี้ผมมีทั้งระบบที่รันบน VPS ซึ่งเก็บค่าไว้ใน .env และระบบที่ใช้ Cloudflare Workers ซึ่งเก็บค่า sensitive ไว้ใน Secrets

พอมาคิดดูจริง ๆ แล้ว ทั้งสองแบบมีความเสี่ยงต่างกันอย่างไร และสิ่งที่ทำอยู่ถือว่าเพียงพอแล้วหรือยัง

ปัญหาของ .env ไม่ได้อยู่ที่นามสกุลไฟล์

สำหรับระบบที่รันบน VPS เรามักเก็บค่าต่าง ๆ ไว้ในไฟล์ .env แล้วให้แอปอ่านค่าตอนเริ่มทำงาน

วิธีนี้ไม่ได้ผิด เป็นวิธีที่ใช้ง่ายและเหมาะกับระบบเล็ก ๆ ได้ แต่ secret พวกนี้ยังอยู่บนเครื่องที่เราต้องดูแลเอง

ถ้า server ถูกเจาะจนผู้บุกรุกอ่านไฟล์ได้ เขาก็อาจเห็นค่าต่าง ๆ ที่เราเก็บไว้ในนั้น

ที่สำคัญ ต่อให้ปิดสิทธิ์การอ่านไฟล์ .env อย่างดีแล้ว ถ้าผู้บุกรุกสามารถรันโค้ดใน Node.js process ของเราได้ เขาก็อาจอ่านค่าผ่าน process.env ซึ่งแอปกำลังใช้งานอยู่ได้เช่นกัน

ปัญหาเลยไม่ได้อยู่แค่การกันไม่ให้ใครเปิดไฟล์ .env แต่รวมถึงการกันไม่ให้ผู้บุกรุกเข้าถึงตัวแอป หรือสิทธิ์ของผู้ใช้ที่รันแอปด้วย

แล้วถ้าผู้บุกรุกได้สิทธิ์ระดับ root ล่ะ?

ในกรณีนั้น เราควรตั้งสมมติฐานไว้ก่อนว่า secret ที่อยู่บนเครื่องอาจถูกเข้าถึงได้ทั้งหมด รวมถึง credential อื่น ๆ ที่เครื่องนั้นมีสิทธิ์ใช้งานอยู่

เรื่องที่น่ากังวลกว่าไฟล์ .env คือ ถ้าเราใช้ database credential ชุดเดียวกันกับหลายระบบ หรือใช้ API key ที่มีสิทธิ์กว้างมาก การเจาะระบบเพียงจุดเดียวอาจกลายเป็นทางผ่านไปยังระบบอื่นได้ด้วย

แล้ว Cloudflare Workers Secrets ต่างกันอย่างไร

ส่วนหนึ่งของระบบที่ผมทำอยู่ใช้ Cloudflare Workers ซึ่งมีระบบ Secrets ให้เก็บค่าที่เป็นความลับ โดยไม่ต้องเอาค่าจริงไปใส่ไว้ใน source code หรือจัดการไฟล์ .env บน VPS ของตัวเอง

ค่าที่กำหนดเป็น Secret จะถูกเข้ารหัส และหลังตั้งค่าแล้วค่าจริงจะไม่แสดงให้เห็นใน Dashboard หรือ Wrangler แต่ Worker ยังคงอ่านและใช้งานค่าเหล่านั้นได้ขณะทำงาน

ตรงนี้ช่วยลดความเสี่ยงบางอย่างได้จริง เพราะเราไม่ต้องรับผิดชอบการดูแลเครื่องเซิร์ฟเวอร์ด้วยตัวเอง และไม่ต้องจัดการไฟล์ secret บนเครื่องแบบเดิม

แต่ก็ต้องเข้าใจข้อจำกัดด้วย

คำว่า secret ในที่นี้ ไม่ได้หมายความว่าโค้ดของเราไม่มีทางเห็นค่าดังกล่าว เพราะ Worker จำเป็นต้องอ่าน secret เพื่อทำงาน ถ้าโค้ดมีช่องโหว่จนผู้โจมตีสั่งให้มันส่งค่า secret ออกไปได้ ความเสี่ยงก็ยังมีอยู่

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

การไม่มี VPS ที่ต้องดูแลเอง ไม่ได้แปลว่าไม่มีอะไรให้โจมตีอีกแล้ว มันแปลว่าเราลดภาระการดูแล infrastructure บางส่วน และไปใช้กลไกความปลอดภัยที่แพลตฟอร์มมีให้

สิ่งที่น่าสนใจกว่าการซ่อน Key คือการไม่ต้องมี Key ตั้งแต่แรก

พอไล่ดูเรื่องนี้ต่อ ผมพบแนวคิดหนึ่งที่น่าสนใจมาก คือการลดจำนวน secret ที่ระบบต้องถือ

ยกตัวอย่าง ถ้า Worker ต้องเข้าถึงไฟล์ใน Cloudflare R2 เราสามารถใช้ R2 binding เพื่อให้ Worker เข้าถึง bucket ที่กำหนดได้ โดยไม่ต้องสร้าง Access Key และ Secret Key แยกต่างหากเพื่อให้ Worker ใช้งาน

แนวคิดเดียวกันนี้ใช้กับ resource อื่น ๆ เช่น D1 ได้ด้วย

แทนที่เราจะสร้าง credential ชุดใหม่ แล้วต้องหาวิธีเก็บ ป้องกัน และหมุนเวียนมัน เราให้สิทธิ์กับตัวระบบผ่านกลไกที่แพลตฟอร์มจัดเตรียมไว้แทน

แต่ binding ก็ยังให้สิทธิ์กับ Worker อยู่ ถ้า Worker มีสิทธิ์เขียนข้อมูลลง bucket ผู้โจมตีที่ควบคุมการทำงานของ Worker ได้ก็อาจใช้สิทธิ์นั้นเขียนหรือลบข้อมูลได้เช่นกัน

เรื่องนี้ทำให้ผมคิดว่า บางทีคำถามที่ดีกว่า “เก็บ Key ไว้อย่างไรให้ปลอดภัย” อาจเป็น “เราจำเป็นต้องมี Key ชุดนี้จริงหรือไม่ และระบบควรได้รับสิทธิ์อะไรบ้าง”

แล้วระบบที่ยังใช้ VPS หรือ Docker เขาทำกันอย่างไร

ไม่ใช่ทุกระบบจะย้ายไปใช้ Serverless ได้ และการใช้ VPS ก็ไม่ได้แปลว่าระบบจะไม่ปลอดภัย

ระบบที่ต้องใช้ VPS ยังจัดการ secret ให้รัดกุมขึ้นได้หลายวิธี

วิธีพื้นฐานคือเก็บ .env แยกจาก source code ไม่ commit ลง Git จำกัดสิทธิ์อ่านไฟล์ และแยก credential ของ Development, Staging และ Production ออกจากกัน

ถ้าใช้ Docker ก็มี Docker Compose Secrets ที่ช่วยส่ง secret ให้เฉพาะ container ที่จำเป็นต้องใช้ โดย mount เป็นไฟล์ใน /run/secrets/ แทนการส่งผ่าน environment variable

แต่ต้องไม่เข้าใจผิดว่าเพียงประกาศ secrets: ใน Docker Compose แล้วทุกอย่างจะปลอดภัยโดยอัตโนมัติ ไฟล์ต้นทางบน host ก็ยังต้องได้รับการป้องกัน และ container ที่ได้รับ secret ก็ยังต้องถูกจำกัดสิทธิ์อย่างเหมาะสม

สำหรับระบบที่อยากจัดการเป็นเรื่องเป็นราวมากขึ้น ก็มีบริการอย่าง AWS Secrets Manager, Google Cloud Secret Manager และ HashiCorp Vault ซึ่งช่วยเรื่องการควบคุมสิทธิ์ การตรวจสอบการเข้าถึง และการหมุนเวียน secret

ระบบบน Cloud บางแห่งยังใช้ Workload Identity หรือ IAM Role เพื่อให้แอปยืนยันตัวตนกับบริการที่เกี่ยวข้องได้ โดยไม่ต้องสร้าง access key แบบถาวรให้แอปถือไว้เอง

สิ่งสำคัญคือแต่ละวิธีมีขอบเขตการป้องกันต่างกัน Secret Manager ช่วยลดความจำเป็นในการเก็บ secret ไว้กับเครื่องแอป แต่ถ้าแอปมีสิทธิ์อ่าน secret นั้น ผู้โจมตีที่ควบคุมแอปได้ก็อาจใช้สิทธิ์เดียวกันได้

การเปลี่ยนที่เก็บ secret จึงเป็นเพียงส่วนหนึ่งของการออกแบบความปลอดภัย

สิ่งที่ผมคิดว่าควรทำเป็นพื้นฐาน

หลังจากทบทวนเรื่องนี้ ผมคิดว่ามีหลายอย่างที่ควรทำ ไม่ว่าจะใช้ VPS, Docker หรือ Serverless

1. ให้แต่ละระบบมีสิทธิ์เท่าที่จำเป็น

ไม่ใช้ database account ที่มีสิทธิ์สูงเกินความจำเป็น และไม่ใช้ API key ชุดเดียวกันกับทุก service ถ้าแยกสิทธิ์ได้

เพราะถ้าระบบหนึ่งถูกเจาะ เราอยากให้ความเสียหายจำกัดอยู่ในขอบเขตของระบบนั้นให้มากที่สุด

2. แยก credential ระหว่างแต่ละ environment

Development ไม่ควรใช้ database password ชุดเดียวกับ Production รวมถึง API key และ credential ของบริการอื่น ๆ ด้วย

การแยก environment ช่วยลดโอกาสที่การทดลองหรือช่องโหว่ในระบบทดสอบจะกระทบข้อมูลจริง

3. อย่าให้ secret หลุดไปกับ source code, build หรือ log

การใส่ .env ลงใน .gitignore เป็นจุดเริ่มต้นที่ดี แต่ยังต้องตรวจ Docker image, build logs, CI/CD, error reports และระบบ logging ด้วย

โดยเฉพาะ CI/CD ที่มีสิทธิ์ deploy โค้ดหรือเข้าถึง secret ได้ เพราะถ้า pipeline ถูกยึด ผู้โจมตีอาจใช้มันเป็นช่องทางเข้าถึงระบบ Production

4. ทำให้ secret เปลี่ยนหรือเพิกถอนได้

ถ้าพบว่า API key รั่ว เราต้องรู้ว่าจะเพิกถอน key เดิมและเปลี่ยนเป็น key ใหม่อย่างไร โดยไม่ทำให้ทุกระบบหยุดทำงานเกินความจำเป็น

การเปลี่ยน key ไม่ใช่แค่สร้างค่าใหม่ แต่ต้องตามไปเปลี่ยนทุกจุดที่ใช้งาน และตรวจว่า key เก่าใช้ไม่ได้แล้ว

ไม่จำเป็นต้องหมุนเวียนทุก secret ด้วยความถี่เดียวกัน แต่ secret ทุกตัวควรมีวิธีจัดการเมื่อครบอายุหรือเมื่อเกิดเหตุรั่วไหล

5. ป้องกันบัญชีและสิทธิ์ที่ใช้ deploy

ถ้าคนที่ deploy โค้ดสามารถเปลี่ยนโค้ดให้ส่ง secret ออกไปได้ สิทธิ์ deploy ก็มีความสำคัญไม่แพ้สิทธิ์อ่าน secret

เลยควรป้องกันบัญชี Cloud, API token และ CI/CD ด้วยการให้สิทธิ์เท่าที่จำเป็น จำกัดขอบเขตของ token และตรวจการเปลี่ยนแปลงที่ผิดปกติ

6. เตรียมรับมือกรณีที่ป้องกันไม่สำเร็จ

เราไม่ควรตั้งสมมติฐานว่าระบบจะไม่มีวันถูกเจาะ แต่ควรคิดล่วงหน้าว่า ถ้า secret รั่วจะต้องทำอะไรบ้าง

เช่น เพิกถอน credential ตรวจการใช้งานย้อนหลัง ตรวจ log หาเส้นทางที่ข้อมูลรั่ว และประเมินว่าผู้โจมตีอาจเข้าถึงระบบใดต่อได้บ้าง

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

สรุป

ก่อนหน้านี้ผมอาจมองเรื่อง secret ในฐานะค่าที่ต้องเก็บให้พ้นจากสายตาของคนอื่น ไม่ว่าจะใส่ไว้ใน .env หรือใช้ Secrets ของ Cloudflare

แต่เมื่อคิดถึงกรณีที่ระบบถูกเจาะจริง ๆ ก็พบว่าการซ่อนค่าไว้ที่ใดที่หนึ่งไม่ใช่คำตอบทั้งหมด

เราอาจต้องถามตัวเองให้ครบกว่านั้นว่า

  • ใครบ้างที่อ่าน secret นี้ได้
  • ระบบที่ถือ secret มีสิทธิ์ทำอะไรได้บ้าง
  • ถ้าระบบถูกเจาะ ผู้โจมตีจะใช้ secret นี้ไปทำอะไรต่อได้
  • ถ้า secret รั่ว เราจะเพิกถอนและเปลี่ยนมันได้เร็วแค่ไหน

สำหรับผม การใช้ Cloudflare Workers ช่วยลดภาระดูแล infrastructure และมีรูปแบบการจัดการ secret กับ resource binding ที่น่าสนใจ แต่ก็ไม่ได้ทำให้เราละเลยความปลอดภัยของโค้ดหรือสิทธิ์ในการ deploy ได้

ส่วนระบบที่ยังต้องใช้ VPS ก็มีวิธีจัดการ secret ที่ดีได้เช่นกัน ถ้าเข้าใจขอบเขตความเสี่ยงและออกแบบสิทธิ์ให้เหมาะสม

สุดท้ายแล้ว ผมคิดว่าความปลอดภัยไม่ได้อยู่ที่ว่าเราเลือกใช้เทคโนโลยีอะไรเพียงอย่างเดียว แต่อยู่ที่เราออกแบบให้ระบบมีความลับน้อยลง ถือสิทธิ์เท่าที่จำเป็น และรับมือกับความผิดพลาดได้ดีแค่ไหน

แหล่งอ้างอิง