ตั้งค่า HTTP Security Headers ให้เว็บไซต์

ตั้งค่า HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy และ Content-Security-Policy บน Nginx, Apache, Plesk และ IIS อย่างปลอดภัย

โดย ทีมงาน THAI DATA CLOUD อัปเดต อ่าน 12 นาที เข้าชม 6 ครั้ง

แชร์FacebookXLINELinkedIn
HTTP Security Headers — ตั้งค่า HTTP Security Headers ให้เว็บไซต์
ตั้งค่า HTTP Security Headers ให้เว็บไซต์

คำตอบสั้น ๆ: ตั้งค่า HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy และ Content-Security-Policy บน Nginx, Apache, Plesk และ IIS อย่างปลอดภัย

คู่มือนี้อธิบาย HTTP Security Headers ทีละขั้นตอน พร้อมคำสั่งที่ใช้ได้จริงและปัญหาที่พบบ่อย

HTTP Security Headers คือคำสั่งที่เซิร์ฟเวอร์ส่งไปพร้อมหน้าเว็บ เพื่อบอกเบราว์เซอร์ว่าควรป้องกันผู้ใช้อย่างไร เช่น ต้องใช้ HTTPS เสมอ ห้ามนำหน้าเว็บไปฝังใน iframe ของเว็บอื่น หรือห้ามเดาชนิดไฟล์เอง Header เหล่านี้ไม่ได้แทนการอัปเดตระบบหรือเขียนโค้ดให้ปลอดภัย แต่ช่วยปิดช่องทางโจมตีฝั่งเบราว์เซอร์หลายแบบ เช่น Clickjacking และ XSS บางรูปแบบ

บทความนี้อธิบาย Header ที่ควรมี ความหมาย ค่าที่แนะนำ และวิธีตั้งบน Nginx, Apache, Plesk และ IIS พร้อมข้อควรระวังที่ทำให้เว็บพังได้ถ้าตั้งเร็วเกินไป

สิ่งที่ต้องเตรียม (HTTP Security Headers)

  • เว็บไซต์ที่ใช้ HTTPS ได้สมบูรณ์แล้ว (ทุกหน้าและทุก Subdomain ที่เกี่ยวข้อง)
  • สิทธิ์แก้ไฟล์ตั้งค่า Web Server หรือไฟล์ .htaccess หรือสิทธิ์เข้า Plesk
  • สำเนาไฟล์ตั้งค่าเดิม และช่วงเวลาที่ทดสอบเว็บได้หลังเปลี่ยน

Header ที่แนะนำและความหมาย (HTTP Security Headers)

Headerค่าที่แนะนำเป็นจุดเริ่มต้นป้องกันอะไร
Strict-Transport-Securitymax-age=300 แล้วค่อยเพิ่มเป็น max-age=31536000บังคับให้เบราว์เซอร์ใช้ HTTPS เสมอ ป้องกันการดักเปลี่ยนเป็น HTTP
X-Content-Type-Optionsnosniffห้ามเบราว์เซอร์เดาชนิดไฟล์ เช่นตีความไฟล์ข้อความเป็นสคริปต์
X-Frame-OptionsSAMEORIGINClickjacking โดยห้ามเว็บอื่นนำหน้าเราไปฝังใน iframe
Referrer-Policystrict-origin-when-cross-originไม่ส่ง URL เต็ม (ซึ่งอาจมีข้อมูลส่วนตัว) ไปให้เว็บอื่น
Permissions-Policycamera=(), microphone=(), geolocation=()ปิดการใช้ฟีเจอร์ของเบราว์เซอร์ที่เว็บไม่ได้ใช้
Content-Security-Policyเริ่มแบบ Content-Security-Policy-Report-Onlyกำหนดว่าโหลดสคริปต์ รูป ฟอนต์ จากที่ไหนได้บ้าง ลดผลกระทบของ XSS

Header X-XSS-Protection เลิกใช้แล้ว เบราว์เซอร์รุ่นปัจจุบันไม่รองรับ ไม่จำเป็นต้องตั้ง ถ้าเครื่องมือตรวจบางตัวแนะนำ ให้ตั้งเป็น 0 หรือไม่ตั้งเลย ส่วน X-Frame-Options ใช้ร่วมกับ frame-ancestors 'self' ใน CSP ได้ เบราว์เซอร์รุ่นใหม่จะยึด CSP เป็นหลัก

ขั้นตอนที่ 1: ตรวจ Header ที่มีอยู่ตอนนี้ (HTTP Security Headers)

curl -sI https://example.com/

ดูว่ามี Header ใดในตารางอยู่แล้วบ้าง เพราะแผงควบคุม, ปลั๊กอินความปลอดภัย หรือ CDN อาจส่งมาให้แล้ว ถ้าตั้งซ้ำ เบราว์เซอร์จะได้รับสองค่า ซึ่งบาง Header (เช่น CSP) จะถูกนำมาใช้ร่วมกันแบบเข้มขึ้นจนเว็บพังได้

ขั้นตอนที่ 2: ตั้งค่าบน Nginx (HTTP Security Headers)

ใส่ในบล็อก server { } ที่ฟัง 443 ของเว็บ

add_header Strict-Transport-Security "max-age=300" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

คำว่า always ทำให้ส่ง Header แม้หน้าตอบกลับเป็น Error เช่น 404 หรือ 500

ข้อควรระวังที่พบบ่อยที่สุด: ถ้าบล็อก location ใดมี add_header ของตัวเอง (เช่นตั้ง Cache-Control ให้ไฟล์รูป) Header ทั้งหมดจากระดับ server จะไม่ถูกส่งใน location นั้นเลย วิธีแก้คือเก็บ Header ไว้ในไฟล์แยก แล้ว include ทั้งในระดับ server และใน location ที่มี add_header ของตัวเอง

sudo nano /etc/nginx/snippets/security-headers.conf

ย้ายบรรทัด add_header ทั้งห้าบรรทัดข้างต้นไปไว้ในไฟล์นี้ ในบล็อก server เหลือเพียง include snippets/security-headers.conf; และใน location ที่มี Header ของตัวเองให้เขียนแบบนี้

location ~* \.(jpg|jpeg|png|webp|svg|css|js)$ {
    include snippets/security-headers.conf;
    add_header Cache-Control "public, max-age=2592000";
}

ตรวจและโหลดค่าใหม่

sudo nginx -t
sudo systemctl reload nginx

บน AlmaLinux/Rocky ไม่มีโฟลเดอร์ snippets มาให้ สร้างเองด้วย sudo mkdir -p /etc/nginx/snippets แล้ว include ด้วย Path เต็ม

ขั้นตอนที่ 3: ตั้งค่าบน Apache หรือไฟล์ .htaccess

ต้องเปิดโมดูล mod_headers ก่อน บน Ubuntu ใช้

sudo a2enmod headers
sudo systemctl reload apache2

บน AlmaLinux/Rocky โมดูลนี้เปิดอยู่แล้ว ตรวจด้วย httpd -M | grep headers จากนั้นใส่ใน Virtual Host หรือด้านบนของไฟล์ .htaccess

<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=300"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

ใช้ set ไม่ใช่ add เพื่อแทนที่ค่าเดิมแทนการส่งซ้ำ ถ้าแก้ Virtual Host ให้รัน apachectl configtest ก่อน reload

ขั้นตอนที่ 4: บน Plesk และ IIS

บน Plesk ที่เปิด Proxy mode ใช้วิธีของ Apache ใน .htaccess ได้ หรือไปที่ Websites & Domains เลือกโดเมน เปิด Apache & nginx Settings แล้วใส่คำสั่ง add_header ในช่อง Additional nginx directives ส่วน HSTS อาจตั้งจากส่วนขยาย SSL It! ได้ในบางเวอร์ชัน ถ้าตั้งที่นั่นแล้ว อย่าตั้งซ้ำในที่อื่น

บน IIS ใส่ในไฟล์ web.config ของเว็บ

<system.webServer>
  <httpProtocol>
    <customHeaders>
      <add name="X-Content-Type-Options" value="nosniff" />
      <add name="X-Frame-Options" value="SAMEORIGIN" />
      <add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
    </customHeaders>
  </httpProtocol>
</system.webServer>

หรือตั้งผ่าน IIS Manager ที่ไอคอน HTTP Response Headers ของเว็บ สำหรับ HSTS บน IIS 10 รุ่น Windows Server 2019 ขึ้นไป ตั้งได้ในหน้าตั้งค่า HSTS ของ Site

ขั้นตอนที่ 5: เพิ่ม HSTS อย่างระมัดระวัง

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

  1. เริ่มที่ max-age=300 (5 นาที) ทดสอบทุกหน้า
  2. เพิ่มเป็น max-age=86400 (1 วัน) แล้ว max-age=2592000 (30 วัน)
  3. เมื่อมั่นใจแล้วจึงใช้ max-age=31536000 (1 ปี)
  4. ใส่ includeSubDomains เฉพาะเมื่อ ทุก Subdomain ใช้ HTTPS ได้ รวมถึง Subdomain ภายในอย่าง mail.example.com
  5. preload ทำให้ชื่อโดเมนถูกฝังในเบราว์เซอร์ ถอนออกได้ยากและใช้เวลานาน ใส่เฉพาะเมื่อตั้งใจจริงเท่านั้น

ดูวิธีเฝ้าวันหมดอายุของใบรับรองได้ที่ ตรวจสอบ SSL Certificate และวันหมดอายุ

ขั้นตอนที่ 6: เริ่ม Content-Security-Policy แบบ Report-Only

CSP ทรงพลังที่สุดแต่ก็ทำให้เว็บพังง่ายที่สุด เพราะต้องระบุทุกแหล่งที่เว็บโหลดสคริปต์ เช่น Google Analytics, Tag Manager, แชต หรือแผนที่ เริ่มจากโหมดรายงานก่อน ซึ่งเบราว์เซอร์จะแจ้งเตือนใน Console แต่ไม่บล็อกจริง

add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'" always;

เปิดหน้าเว็บหลายๆ หน้า แล้วดูข้อความ Content-Security-Policy ในแท็บ Console ของ DevTools เพิ่มแหล่งที่ถูกต้องลงใน Policy จนไม่มีข้อความเตือน จากนั้นจึงเปลี่ยนชื่อ Header เป็น Content-Security-Policy ให้มีผลจริง เว็บ WordPress ที่ใช้ปลั๊กอินจำนวนมากมักต้องใช้เวลาปรับนาน ถ้าเวลาจำกัด ตั้งเฉพาะ frame-ancestors 'self' ไว้ก่อนก็ได้ประโยชน์แล้ว

ตรวจสอบผลลัพธ์

curl -sI https://example.com/ | grep -iE 'strict-transport|x-content-type|x-frame|referrer-policy|permissions-policy|content-security'

ควรเห็นแต่ละ Header เพียงหนึ่งบรรทัด ทดสอบกับไฟล์รูปหรือ CSS และกับหน้าที่ไม่มีอยู่จริงด้วย เพื่อยืนยันว่า Header ถูกส่งในทุกกรณี

curl -sI https://example.com/wp-content/themes/x/style.css | grep -i x-content-type
curl -sI https://example.com/not-found-test | grep -i x-frame

จากนั้นเปิดเว็บในเบราว์เซอร์ ทดลองฟังก์ชันสำคัญ เช่น ฟอร์มติดต่อ, ตะกร้าสินค้า, หน้าชำระเงิน และหลังบ้าน

ปัญหาที่พบบ่อยเกี่ยวกับ HTTP Security Headers

Header หายไปในบางไฟล์ (Nginx)

เป็นเรื่องการสืบทอด add_header ที่อธิบายในขั้นตอนที่ 2 ให้ include ไฟล์ Header ในทุก location ที่มี add_header ของตัวเอง

ฟีเจอร์ที่ฝังจากเว็บอื่นใช้ไม่ได้

ถ้าเว็บของคุณต้องถูกฝังใน iframe ของอีกโดเมนหนึ่ง (เช่นระบบจองที่ฝังในเว็บพาร์ทเนอร์) SAMEORIGIN จะบล็อก ให้ลบ X-Frame-Options แล้วใช้ CSP frame-ancestors 'self' https://partner.example.net แทน

ตั้ง HSTS แล้ว Subdomain เข้าไม่ได้

เกิดจากใส่ includeSubDomains ขณะที่บาง Subdomain ยังไม่มี SSL แก้โดยติดตั้ง SSL ให้ Subdomain นั้น การลดค่า max-age จะมีผลกับผู้เข้าชมเมื่อเบราว์เซอร์ของเขากลับมาเยี่ยมชมผ่าน HTTPS อีกครั้งเท่านั้น

Header เป็นเพียงส่วนหนึ่งของความปลอดภัย ดูภาพรวมเพิ่มเติมได้ที่ เช็กลิสต์ความปลอดภัยเว็บไซต์ WordPress หากต้องการความช่วยเหลือ ติดต่อทีมงาน THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

สรุปขั้นตอน HTTP Security Headers

ทบทวน HTTP Security Headers ทั้งหมดตามลำดับ ก่อนนำไปใช้กับระบบจริง

  1. ตรวจ Header ที่มีอยู่ตอนนี้ (HTTP Security Headers)
  2. ตั้งค่าบน Nginx (HTTP Security Headers)
  3. ตั้งค่าบน Apache หรือไฟล์ .htaccess
  4. บน Plesk และ IIS
  5. เพิ่ม HSTS อย่างระมัดระวัง
  6. เริ่ม Content-Security-Policy แบบ Report-Only

เมื่อทำครบทุกข้อแล้ว HTTP Security Headers จะพร้อมใช้งาน หากผลลัพธ์ไม่เป็นไปตามที่คาด ให้ย้อนกลับไปดูหัวข้อปัญหาที่พบบ่อยด้านบน ซึ่งรวบรวมกรณีที่พบจริงจากการใช้งาน HTTP Security Headers ไว้แล้ว

อ่านเพิ่มเติมเกี่ยวกับ HTTP Security Headers

แชร์FacebookXLINELinkedIn

มีคำถามเรื่องบริการ? ติดต่อทีมงาน