Wall of Text #15: Who compile the compiler

เป็นคำถามที่สงสัยตั้งแต่เด็กๆ ว่าโปรแกรมเราเขียนใน Python แล้วเวลารันต้องใช้ Python interpreter ที่โหลดในเน็ตมา ตัวไฟล์ Python interpreter มันสร้างขึ้นมาได้ยังไง

คำตอบก็ไปดูง่ายๆ คือ CPython ที่โหลดในเว็บมันเขียนด้วยภาษา C ก็เลยใช้ C compiler สร้างไฟล์นั้นขึ้นมา แล้วใครสร้างไฟล์ของ C compiler?

Linux from Scratch

ตอนประมาณ ม. ต้นเคยทำ Linux from Scratch ซึ่งเป็นหนังสือที่สอนวิธีสร้าง Linux ตั้งแต่ต้นด้วยตนเอง ไม่ได้สร้างมาจาก Linux อื่นๆ ที่มีอยู่แล้ว เขาจะมีแผ่นซีดีมาให้ซึ่งมีโปรแกรมพื้นฐานให้ประมาณหนึ่ง (Linux จอดำ) จากนั้นให้เรา compile โปรแกรมต่างๆ

ในเล่มบอกว่าตัว C compiler ที่ใช้คือโปรแกรม GCC ซึ่งเขียนด้วยภาษา C โดยให้เราเอา C compiler ในแผ่นซีดี compile GCC source code ก็แปลว่าต้องมี C compiler ก่อน จึงจะคอมไพล์ C compiler ได้ ก็เลยเป็นปัญหาไก่กับไข่ แต่มันตอบคำถามหนึ่งคือถ้าเราสร้างคอมพิวเตอร์สถาปัตยกรรมใหม่ (new computer architecture) ขึ้นมาเราจะเอา C compiler มาจากไหน โดยวิธีที่เขาเขียน สมมุติว่าผมสร้างเครื่องสถาปัตยกรรม whs32 ขึ้นมา และใช้เครื่อง amd64 เป็นเครื่อง desktop ให้ทำดังนี้

  1. แก้ไขโค้ด gcc เพิ่มให้สามารถ generate machine code ของ whs32 ได้
  2. เอา compiler ในเครื่อง amd64 ที่ generate machine code ของ amd64 มา compile gcc ตัวใหม่ เรียกว่า stage1 ซึ่งยังรันบน amd64 แต่ให้ผลลัพท์เป็นโค้ดที่รันใน whs32 (gcc สามารถเลือกให้ output machine code เป็นสถาปัตยกรรมเครื่องใดๆ ก็ได้ ไม่จำเป็นต้องเป็นเครื่องที่รันอยู่)
  3. เอา stage1 ที่รันในเครื่อง amd64 มา compile ตัวเอง จะได้ gcc อีกตัวเรียกว่า stage2 ซึ่งรันบน whs32 และให้ผลลัพท์เป็นโค้ดที่รันใน whs32
  4. เอา stage2 รันบนเครื่อง whs32 มา compile ตัวเองใหม่ จะได้ stage3 เป็นอันเสร็จ
    • ที่ต้องทำ stage3 เพราะว่า stage2 อาจจะมีการอ้างอิง library, path ต่างๆ บนเครื่อง amd64 ทำให้ไปใช้งานจริงบนเครื่องอื่นไม่ได้ วิธีแก้ไขคือปิดฟีเจอร์ที่ไม่จำเป็นใน stage2 ออกไปก่อนเพื่อให้ compiler จบในตัว แล้วค่อย compile stage3 ที่มีฟีเจอร์ครบถ้วนในระบบจริง
    • เวลา gcc compile ตัวเอง แบบไม่ข้ามสถาปัตยกรรม มันจะ compile 3 ครั้ง
      1. ใช้ stage2 compile ตัวเองจาก source code เรียกว่า stage3.1
      2. ใช้ stage3.1 compile ตัวเองอีก 2 ครั้ง เรียกว่า stage3.2 และ stage3.3
      3. ตรวจสอบว่า stage3.2 = stage3.3 เพื่อทดสอบว่า compiler ทำงานได้ถูกต้อง
      4. ใช้ stage3.3 เป็นผลลัพท์ stage3

สรุปแล้วเราก็พอเข้าใจว่าถ้าสร้างชิพใหม่ขึ้นมาเราจะเอา compiler มาจากไหน แต่ compiler ตัวแรกมาจากไหน…? ก่อนจะเล่าต่อไป ขอไปเล่าเรื่องอื่นก่อนแล้วกัน

Why does this matter

Ken Thompson ผู้สร้างระบบปฏิบัติการ Unix และภาษา Go เคยตีพิมพ์ paper เรื่อง Reflection on trusting trust (1984) ว่าคุณจะรู้ได้อย่างไรว่า compiler ที่คุณใช้ไม่มี backdoor จริง

สมมุติว่ามี compiler ตัวหนึ่งซึ่งมันจะ detect source code pattern อย่างหนึ่งแล้วแทรก backdoor ลงไป และ detect source code ตัวเองเพื่อแทรกโค้ดตัวมันเองเข้าไป ถ้าเรา compile compiler ตัวนี้แล้วก็จะทำให้ backdoor ฝังอยู่ใน compiler ซึ่ง compile compiler ใหม่ก็ไม่หาย แต่ตรวจสอบหา backdoor ใน source code ไม่พบ ถ้าอย่างนั้นแล้วคุณจะแน่ใจได้อย่างไร ว่าระบบที่ใช้งานอยู่ไม่มี backdoor

ปรากฏว่ามีผู้พบว่า Ken Thompson เคยโจมตีด้วยท่านี้จริง โดยเขาเล่าในปี 1995 ว่าหลายสิบปีก่อนเขาสร้าง compiler แบบนี้จริงแล้วส่งให้แผนก UNIX support ติดตั้ง binary เข้าไปในระบบโดยอ้างว่ามีฟีเจอร์ใหม่ที่ไม่สามารถใช้ compiler เก่า compile source code ใหม่ได้ ใน compiler ใหม่นี้มีการฝัง backdoor ของโปรแกรม login ไว้

ต่อมามีผู้พบว่า symbol table (function list) ของ compiler มันแปลกๆ ก็เลยสั่งให้ compiler print assembly ออกมาซึ่งระบบแทรก backdoor ไม่ได้เขียนไว้รองรับคำสั่งนี้ แล้วเอา assembly ไปแปลงเป็น machine code ทำให้ backdoor หายไป

ในปัจจุบันจึงเป็นคำถามว่าถ้าหากเรามี backdoor ที่ซับซ้อนกว่าที่ Ken เคยสร้างไว้จริง เราจะมั่นใจได้อย่างไรว่าระบบคอมพิวเตอร์มีความปลอดภัยเพราะถึงแม้จะอ่านโค้ดทุกบรรทัดก็ไม่ได้แปลว่าจะเป็นโค้ดทั้งหมดของโปรแกรม

Go

Compiler ของภาษา Go เขียนขึ้นด้วยภาษา C ในตอนแรก จากนั้นถูกใช้เครื่องมือแปลงภาษา C เป็น Go ภายหลัง ทำให้ Go compiler ในปัจจุบันเขียนด้วยภาษา Go ดังนั้นทีมงาน Go จึงแนะนำวิธีสร้าง Go compiler ดังนี้

  1. ดาวน์โหลดซอร์สโค้ด Go 1.4 ซึ่งเป็นรุ่นสุดท้ายที่เขียนด้วยภาษา C
  2. Compile Go 1.4
  3. Compile Go 1.17.13 ด้วย Go 1.4
  4. Compile Go 1.20
  5. Compile Go รุ่นต่อๆ ไป โดยทีมงาน Go กำหนดว่า minimum compiler version ที่ใช้ compile รุ่นล่าสุดได้คือ version ที่ออกเมื่อประมาณ 1 ปีที่แล้ว

อีกวิธีหนึ่งที่ทำได้คือมีอีกโปรแกรมที่ compile ภาษา Go ได้นั่นคือ gccgo ซึ่งเขียนด้วยภาษา C และรองรับโค้ด Go ในระดับหนึ่ง สามารถใช้ gccgo compile Go รุ่นใหม่ได้เลย

Reproducible builds

ในปัจจุบัน Linux distribution หลายตัวเข้าร่วมโครงการ Reproducible builds ซึ่งเป็นอีกวิธีหนึ่งที่พอจะแก้ปัญหาความปลอดภัยด้านบนได้บ้าง โดย Linux distro จะทำให้ผลลัพท์ของการสร้าง package นั้นสามารถทำซ้ำได้ไฟล์เดียวกัน 100% ซึ่งใครก็สามารถทำซ้ำ ตรวจสอบได้ตลอดเวลาเพื่อยืนยันว่า package นั้นเกิดจาก source code จริงๆ เพราะ Linux บางตัวไม่มีระบบ CI/CD กลางเนื่องจากเป็นค่าใช้จ่าย แต่ให้ผู้สร้างแพคเกจ compile บนเครื่องตัวเอง เซ็นแล้วอัพโหลดไปแจกจ่าย

ขณะที่เขียนนี้ Package ทั้งหมดของ Linux ต่อไปนี้สามารถทำซ้ำ ตรวจสอบได้ด้วยตนเอง

  • Arch Linux 78.5%
  • Debian amd64 90.8%
  • openSUSE 94%

รวมถึง Go compiler 1.21 ด้วยที่ตัวที่ให้โหลดในหน้าเว็บ สามารถทำซ้ำได้ด้วยตนเอง

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

  • File metadata ไม่ตรงกัน ทำให้เวลาสร้าง/แตก zip แล้วไม่ตรง เช่น ชื่อเจ้าของไฟล์ สิทธิ์ หรือเวลาสร้างไฟล์
  • สถาปัตยกรรมไม่ตรงกัน เช่น compile ครั้งแรกใช้เครื่อง 32 bit ต่อมาใช้เครื่อง 64 bit แต่ compile เป็นโปรแกรม 32 bit ทั้งคู่ อาจจะได้ผลลัพท์ต่างกันเพราะมีการบันทึกค่าไว้
  • มีการฝังวันเวลาที่ compile เข้าไปในโปรแกรม (เช่นคำสั่ง version/about) ทำให้ได้ผลลัพท์ไม่ตรงกัน ซึ่งต้องลบโค้ดออกหรือกำหนดเป็นวันเวลาที่ fix ไว้
  • มีการฝัง path ที่ compile เข้าไปในโปรแกรม (เช่นใน stack trace เวลา crash) เวลาทำซ้ำอาจจะทำที่ path อื่น หรือระบุ path คนละแบบกัน (absolute/relative) ซึ่งจะต้องบันทึก path ไว้หรือให้ compiler normalize path ได้
  • มีการฝังข้อมูลเครื่องที่ compile เข้าไปในโปรแกรม เช่น username, hostname, timezone, language
  • Library ที่ใช้ไม่ตรงกัน เช่น มีการใช้ library ในเครื่องที่ไม่แจ้งไว้ พอคนอื่นไป compile แล้วหาไม่เจอ, ใช้ version ไม่ตรงกัน, compiler คนละตัวกัน
  • ในโค้ดมีการสุ่ม เช่นอ่านข้อมูลจาก filesystem หรือ HashMap มาแล้วไม่ได้ sort แต่ละครั้งจึงได้ลำดับสลับกัน

ดังนั้นผู้สร้างแพคเกจจะต้องตรวจหาปัญหาเหล่านี้และแก้ไขให้เรียบร้อย ซึ่งบางครั้ง compiler เองก็อาจจะเป็นที่มาของปัญหาเหล่านี้ได้เช่นกัน ไม่ใช่เฉพาะแค่ใน source code

เนื่องจาก Linux หลายตัวสามารถสร้างได้จาก source code อยู่แล้ว (ถึงอาจจะไม่ได้เท่ากันทุก bytes) ดังนั้นบริษัทใหญ่ๆ หลายที่จึงสร้าง Linux ของตัวเองซึ่งโปรแกรมส่วนมากก็เอามาจาก Linux อื่นๆ แต่ compile เองจะได้ไม่ต้องไว้ใจคนอื่น และอาจจะ modify ตามที่ตัวเองสนใจได้ด้วย เช่น

Guix Bootstrap

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

Guix เป็น Linux distribution ของโครงการ GNU ซึ่งมองว่าระบบ Linux นั้นสามารถ reproducible ได้จาก source code โดยขั้นตอนการ reproduce ก็เป็น source code เช่นเดียวกันซึ่งเขียนด้วยภาษา Scheme การติดตั้งแพคเกจต่างๆ ก็คือให้มัน reproduce build ของโปรแกรมนั้นๆ ขึ้นมา

ในขณะนี้ Guix ใช้โครงการ bootstrap-seeds เป็นจุดเริ่มต้น โดย bootstrap-seed ทำงานดังนี้

  1. กำหนดไฟล์ hex0 เป็นการแสดงเลขฐาน 2 ในรูปแบบเลขฐาน 16 (เช่นในไฟล์เขียนว่าว่า F0 ให้ output 11110000) และรองรับการใส่ comment
  2. สร้างโปรแกรมสำหรับแปลงไฟล์ hex0 ซึ่งเขียนด้วย machine code ของเครื่อง x86 ในรูปแบบไฟล์ hex0 โดย Guix จะใช้ไฟล์ hex0-seed ขนาด 357 bytes ที่แปลงมาสำเร็จรูปแล้ว แต่เราจะแปลงเองด้วยมือก็ได้
  3. Compile โปรแกรม hex1 ซึ่งเขียนด้วย hex0 โดย hex1 เป็นภาษาที่ปรับปรุงขึ้นมาจาก hex0 สามารถใช้ label สำหรับ jump ได้ (ก็คือต้องสามารถแทรก relative offset ของจุดที่แปะ label ไว้ในไฟล์จากจุดปัจจุบันได้)
  4. Compile โปรแกรม hex2 ซึ่งเขียนด้วย hex1 โดย hex2 จะเพิ่มความสามารถที่ label มีหลายตัวอักษรได้และสามารถหาตำแหน่งแบบ absolute ของ label ได้
  5. Compile โปรแกรม M0 ซึ่งเขียนด้วย hex2 ซึ่งสามารถกำหนด macro ได้ (ชุดคำสั่งที่ให้พิมพ์ซ้ำ)
  6. Compile โปรแกรม cc ซึ่งเขียนด้วย M0 เป็น C compiler แบบง่ายๆ ที่ใช้ชุดคำสั่งได้จำกัด
  7. Compile โปรแกรม M2-Planet ซึ่งเขียนด้วยภาษา C เท่าที่โปรแกรม cc รองรับ เป็น C compiler ที่รองรับชุดคำสั่งได้มากขึ้น
  8. Compile โปรแกรม GNU Mes ซึ่งเป็น C compiler และ interpreter ของภาษา Scheme (ที่ Guix ใช้) โดยสามารถคอมไพล์ได้จาก M2-Planet
  9. Compile โปรแกรม TinyCC (fork) ซึ่งสามารถใช้ compile GCC และไลบรารีอื่นๆ ที่ใช้งานใน Linux ปกติได้

(ในนี้ไม่รวมถึง tool อื่นๆ ที่ใช้ประกอบการรัน tool ข้างบน เช่น cat, shell, debug tool, linker, C standard library เป็นต้น)

หลังจากเรามี GCC และ library อื่นๆ แล้วก็สามารถ compile โปรแกรมอื่นๆ ไปเรื่อยๆ จนถึงโปรแกรมที่เราต้องการได้ เช่น Python interpreter โดยถ้าเราเชื่อว่า source code ทั้งหมดที่ใช้น่าเชื่อถือ ไม่มี backdoor แล้วกระบวนการนี้ก็ทำให้เรามั่นใจได้ว่าสามารถสร้างระบบ Linux ขึ้นมาได้โดยไม่ต้องใช้ compiler ตั้งต้น

ในปัจจุบันข้อจำกัดของระบบนี้คือต้องเชื่อได้ว่า Kernel ที่รันอยู่น่าเชื่อถือ เพราะเรารัน hex0 บน Kernel ปัจจุบัน ในอนาคตทีมงาน bootstrappable.org มีความคิดที่จะพัฒนา CPU architecture “Knight” ซึ่งสามารถออกแบบเป็นวงจร FPGA ได้เพื่อใช้สร้าง hex0 โดยไม่ต้องใช้คอมพิวเตอร์ และ builder-hex0 ที่เป็น OS พื้นฐานสามารถรัน hex0 ได้ในตัว

The original C compiler

แล้ว C compiler ตัวแรกสร้างมาจากไหน?

Dennis Richie (RIP) เขียนบันทึกไว้ว่า UNIX ตัวแรกถูกสร้างขึ้นโดย Ken Thompson (จำชื่อนี้ได้ไหม) ในปี 1968 โดยใช้โปรแกรม assembler (คล้ายๆ M0) ของเครื่อง GE-635 สร้างแถบกระดาษสำหรับเครื่อง PDP-7 จนมีโปรแกรมพื้นฐานพร้อมใช้งาน ได้แก่ Kernel, editor, shell, คำสั่ง rm, cat, cp และ assembler จากนั้นก็เริ่มพัฒนาบนเครื่อง PDP-7 ต่อ

ในปีต่อมา Doug McIlroy สร้างภาษา TMG (TransMoGrifiers) สำหรับ UNIX บน PDP-7 ขึ้นมาเป็นภาษาโปรแกรมระดับสูงตัวแรก แล้ว Ken ก็สร้างภาษา B ขึ้นโดยเขียนในภาษา TMG โดยอ้างอิงแบบมาจากภาษา BCPL ของ Martin Richards หลังจากสร้างขึ้นสำเร็จก็ได้เขียนภาษา B ใหม่อีกครั้งหนึ่งในภาษา B เอง

ในปี 1971 Dennis Richie พบว่าภาษา B เริ่มตามไม่ทันเครื่องคอมพิวเตอร์ที่อัพเกรดเป็นรุ่น PDP-11 จึงปรับปรุงภาษาขึ้นเป็นภาษา NB (New B) พร้อมกับคอมไพเลอร์ตัวใหม่ ซึ่งต่อมาก็ได้เปลี่ยนชื่อเป็น C

สำหรับ GCC ผมยังไม่เห็นประวัติว่าใช้ compiler ใดสร้างขึ้น แต่โปรแกรมส่วนมากของโครงการ GNU ถูกสร้างขึ้นบนเครื่อง UNIX ก็เป็นไปได้ว่าอาจจะใช้ C compiler ของ Dennis Richie

Wall of Text #14: Information security for dev

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

Threat

ตอนที่เรียน information security ชอบรูปนี้มาก รู้สึกว่าอธิบายได้ดี เคยเจอรูปต้นฉบับในเน็ต แต่เข้าใจว่าหนังสือมีลิขสิทธิ์เลยหายาก ก็เลยวาดให้ใหม่ใน Paint

Security มีองค์ประกอบอยู่ 3 อย่าง

  1. Threat (ภัยอันตราย) ในรูปก็คือน้ำจะท่วมแล้ว
  2. Vulnerability (จุดอ่อน) ในรูปก็คือ กำแพงกันน้ำมีรอยรั่ว
  3. Control (การควบคุม) ในรูปก็คือเอามือปิดรอยรั่วไว้

ถ้ากำแพงรั่วแล้วเอามือปิดไว้ ผลคือน้ำไม่ท่วม เท่ากับว่าบรรลุเป้าหมาย แปลว่ามี security แต่ถ้าเกิดน้ำสูงกว่ากำแพงก็สร้างปัญหาได้อยู่ดี

CIA Triad

Security จริงๆ แล้วมีสมบัติ 3 อย่างหลักๆ ซึ่งอาจจะซอยข้อย่อยได้อีก 3 อย่างหลักๆ นี้คือ

  1. Confidentiality (ลับ) คือคนที่จะเห็นข้อมูลได้ต้องเป็นคนที่ตั้งใจจะให้เห็นเท่านั้น
  2. Integrity (สมบูรณ์) คือข้อมูลถูกต้องครบถ้วน เช่น ถ้ามี hacker เข้ามาเปลี่ยนหน้าเว็บเป็น rickroll ถือว่าเสีย Integrity
  3. Availability (ใช้งานได้) เช่น โดนยิง DDoS คนเข้าใช้งานไม่ได้เลย ถึงจะไม่ทำให้ข้อมูลหลุดหรือโดนแก้ไขข้อมูลก็เป็นปัญหา security เหมือนกัน

ซึ่งก็มีมาตรการต่างๆ ที่ช่วยรักษาสมบัติพวกนี้ เช่น

  1. Confidentiality
    • Access control ไม่ให้คนที่ไม่อนุญาตเข้าถึงได้ เช่นระบบ user account, firewall
    • Encryption เข้าถึงได้แต่เปิดอ่านไม่ได้
    • Contract เซ็นสัญญาไม่เปิดเผยความลับ ก็ใช้ควบคุมได้ถ้าไม่ได้อยู่ในระบบคอมพิวเตอร์
    • Monitoring ตรวจจับว่ามีผู้บุกรุก
  2. Integrity
    • Checksum คือสามารถยืนยันได้ว่าข้อมูลถูกต้องครบถ้วน
    • Signing คือจำกัดให้ checksum สร้างได้จำกัดคน ป้องกัน hacker สร้าง checksum ใหม่
    • Backup
    • Access control จำกัดคนที่แก้ไขข้อมูลได้ก็ช่วยได้เช่นกัน
    • Monitoring ก็ช่วยได้อีกเช่นกัน
  3. Availability
    • Spare มีระบบสำรองกรณีระบบหลักมีปัญหา
    • Scale out มีหลายๆ ระบบทำงานพร้อมกันเพื่อลดคอขวดจากจุดเดียว
    • Monitoring ว่าระบบยังทำงานได้ปกติ ไม่มีการใช้งานผิดปกติ

Security assessment

ไม่แน่ใจว่าตำรา security จะเขียนแตกต่างจากนี้ไหม แต่โดยทั่วๆ ไปแล้วเวลาผมประเมินความเสี่ยงจาก security จะทำตามขั้นตอนนี้ ซึ่งจริงๆ ผมว่ามันจะคล้ายๆ กับการทำ Persona ของ UX

  1. Threat modeling พูดง่ายๆ คือ กลัวอะไรอยู่ หลักๆ ก็อาจจะเป็น
    • กลัวคนนอก (hacker)
    • กลัวคนใน (insider attack, data loss)
    • กลัว user อื่น (spammer)
    • กลัวแอพพลิเคชั่นอื่น (lateral movement)
    • กลัวโปรแกรมเมอร์คนอื่น (defensive coding)
    • ซึ่งเราอาจจะไม่ต้องกลัวทุกอย่างก็ได้ เช่นเว็บเล็กๆ มีทีมงานเห็นๆ กันอยู่ 2-3 คน โอกาสที่คนในจะเป็นหนอนอาจจะน้อยก็ไม่จำเป็นต้องทำเรื่อง insider attack
  2. Vulnerability – ลอง list ออกมาว่าเขาจะทำอะไรมิดีมิร้ายได้บ้าง ผมคิดว่า list ทั้งหมดน่าจะยาวมากไม่สามารถเขียนมาได้ ต้องใช้ประสบการณ์ที่จะเห็นแล้วพอนึกออกว่ามีข้อไหนบ้างที่ apply เช่น
    • คนนอกอาจจะ port scan, เอาช่องโหว่มา scan, ทำ SQL injection หรือ XSS, เดารหัสผ่าน หรือ credential stuffing
    • คนในที่มี admin อาจจะใช้อำนาจในทางที่ผิด เช่น โกรธแฟนเลยแอบดูข้อมูลการใช้งานของแฟน (insider attack) โดนหลอกให้ทำ (phishing) หรือขโมยข้อมูลไปให้คู่แข่ง (data loss)
    • user อาจจะส่ง spam หากันเอง
    • คนใน หรือ application ใน network โดนฝัง shell แล้ว scan หา application ภายในเพื่อ hack ต่อ (lateral movement)
    • โปรแกรมเมอร์คนอื่นที่มาเรียกฟังค์ชั่นนี้อาจจะส่ง parameter มาผิดวิธีแล้วทำให้เกิดช่องโหว่ เช่นเรียก sql query แต่ไม่ได้ escape quote
  3. Control เป็นส่วนสำคัญที่สุด ถ้าเรามี control เพียงพอจะ run Ubuntu 8.04 ใน production ก็ได้ ตัวอย่างของ control เช่น
    • ทำ input validation ถ้าเป็นค่าที่ไม่ถูกต้องไม่ให้ประมวลผล
    • อัพเดต Application ให้ล่าสุดเพื่อไม่ให้โดน hack จาก library ที่ไม่อัพเดต เช่น Log4shell, Apache Struts
    • แต่บางที vendor อาจจะยังออก patch ไม่ทัน ก็ต้องกันจากด้านนอกเช่นใช้ WAF SaaS เพื่อตรวจจับการยิง
    • แต่ WAF ก็ bypass ได้ อาจจะเพิ่ม firewall ไปอีกเพื่อจำกัด IP ที่เข้าใช้งานได้กรณีที่เป็นระบบปิด
    • เพิ่ม firewall ขาออกด้านในด้วยเพื่อที่ว่ายิงเข้ามาได้ก็อาจจะส่งข้อมูลออกไป internet ยาก และเข้าไปโจมตี application อื่นๆ ยาก
    • สุดท้ายเพิ่ม access log เพื่อที่ว่าถ้ามีคนทะลุทุกอย่างมาได้จริงยังสามารถจับมือคนดมได้

“Control ที่เพียงพอ” เป็นคำถามที่ตอบยากมากๆ เพราะระบบที่ปลอดภัย 100% ไม่มีจริง ยิ่งทำมากยิ่งเสียเวลา (diminishing return) และทำให้ใช้งานลำบากด้วย อีกส่วนหนึ่งคือมูลค่าของระบบที่จะปกป้องกับระบบรักษาความปลอดภัยควรจะเหมาะสมกัน เช่นถ้าเป็นเว็บ company profile ธรรมดาๆ การซื้อ WAF, L7 firewall มาติดตั้งอาจจะมูลค่าหลักแสนหรือหลักล้าน แต่ความเสียหายคือแค่ restore จาก backup ก็กลับมาใช้งานได้แล้ว แบบนี้ก็ถือว่า backup เป็น control ที่เพียงพอแล้ว

เวลาเขียนโค้ด โค้ดทุกบรรทัดต้องทำ security assessment แบบนี้เสมอ ซึ่งคนส่วนมากน่าจะคิดว่าโอเวอร์เกินไปแล้วไม่ทำเลย แต่ผมคิดว่าถ้ามีประสบการณ์ security มาบ้างจะเริ่มจับทางได้เหมือนเป็น code smell ประเภทหนึ่งว่าในจุดนี้ต้องเริ่มวิเคราะห์ threat modeling อย่างละเอียดแล้ว ทำให้ทำงานได้เร็วขึ้น เช่น

  • ทุกจุดที่ใช้ตัวแปรที่ user ป้อนเข้ามา (โดนแฮคได้หลายแบบ ตามด้านล่างนี้)
  • โค้ดเกี่ยวกับ password (ตรวจดูว่า hash รหัสด้วยอัลกอริธึมและพารามิเตอร์ที่เหมาะสม และเปรียบเทียบรหัสผ่านโดยใช้เวลาคงที่ (constant time comparison))
  • โค้ดที่เกี่ยวกับวิทยาการรหัสลับ (ไม่แนะนำให้ใช้ algorithm โดยตรง ควรจะใช้ use case based library)
  • โค้ดที่ยิง URL แล้ว user กำหนดค่าเองได้ (อาจสุ่มเสี่ยง SSRF หรือ logic ในการ parse กับ validate ไม่ตรงกัน)
  • โค้ดที่รันสคริปต์ที่ user ส่งเข้ามา (ควรจะทำ sandbox ให้เหมาะสม)
  • โค้ดที่ใช้ตรวจสอบข้อมูล (data validation)
  • โค้ดที่จัดการข้อมูลขนาดใหญ่ในครั้งเดียว (ระวังไม่ให้ user ใช้งาน memory หรือ disk ใหญ่เกินไป)

Defense in Depth

ในอุตสาหกรรมการบินจะมีการใช้ Swiss cheese model

By BenAveling – Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=91881875

โมเดลนี้ใช้แนวคิดว่าเราไม่สามารถป้องกันความเสี่ยงได้ 100% ถ้าไม่อยากให้ชีสมีรูทะลุไปอีกฝั่ง การมีชีสเรียงกันหลายๆ ชั้นก็ลดโอกาสที่ชีสทุกแผ่นจะมีรูเดียวกันหมดได้ ในฝั่ง security มักจะเรียกว่า defense in depth

Not security

บางอย่างไม่ทำให้เกิด security

Security theater

เล่นละครไม่ทำให้เกิด security

เช่น นโยบายบอกว่าต้องตรวจสัมภาระก่อนเข้าสู่สถานที่ แต่คนตรวจเปิดอยู่ช่องเดียวแล้วส่องไฟผ่านๆ แบบนี้ไม่เกิดความปลอดภัย

Security is not a product

คนที่ไม่เข้าใจ security มักจะโดนเซลเป่าหูให้ซื้อ security product แต่ไม่ทำให้เกิด security จริง เปรียบเสมือนเช่ายันต์และห้อยพระเครื่องไว้ที่ server ให้อึด ถึก ทน ยิงไม่เข้า (ถ้า product มันเหนียวจริง ซื้อมาแล้วโดนแฮคควรจะฟ้องร้อง vendor ให้ใช้ค่าเสียหายได้)

การซื้อ product อาจจะช่วยเพิ่ม security ได้จริง แต่ก็ต้องกลับไปมองที่ security assessment ว่าสิ่งที่ซื้อมา ซื้อเพราะกลัวอะไร ซื้อแล้วมันได้ผลจริงกี่ % ซึ่งมันไม่ใช่ 100% แน่ๆ ต้องมีแผนให้ชัดเจนว่า control ต้องเพิ่มอะไรอีกบ้างจึงจะเพียงพอ

Security is not a feature

Project manager ที่ไม่เข้าใจ security อาจคิดว่า security เป็น feature หนึ่งแล้วก็แยกเป็น ticket low priority ทำทีหลัง

ผ่านไป 1 ปีก็ยังไม่ได้ทำจนกระทั่งโดน hack แล้วบอกว่าลูกค้าด่าต้องทำให้เสร็จภายใน 1 สัปดาห์

ในความเป็นจริงแล้ว security เป็นส่วนหนึ่งของทุกๆ feature การ “เติม” security เข้าไปภายหลังสร้าง technical debt ได้รวดเร็วมากเพราะมันมักจะส่งผลต่อ design และการแก้ design flaw เป็นเรื่องยาก ใช้เวลานาน

Security by obscurity

Security ที่แท้จริงถ้าเฉลยกลให้ทั้งหมดแต่ไม่บอกรหัสลับก็ไม่สามารถผ่านเข้ามาได้ เช่นวิธีเข้ารหัส AES นั้นเป็นข้อมูลเปิดเผยหาอ่านได้ทั่วไป ถึงอย่างนั้นถ้าไม่บอกกุญแจเข้ารหัสปัจจุบันยังไม่มีใครถอดรหัสข้อความใดๆ ได้

โบราณว่า ทองแท้ไม่แพ้ไฟ

มีบางคนคิดว่า AES เป็นข้อมูลเปิดเผย มี paper มากมายที่ลดความปลอดภัยของ AES ถ้าอย่างนั้นเราออกแบบ algorithm ขึ้นมาเองเฉพาะภายในบริษัทแล้วก็เป็น algorithm ที่มีความปลอดภัยสูงสุด ไม่เคยมีผู้ใดในโลกถอดรหัสได้โดยไม่ต้องรู้กุญแจ

ผ่านไป 6 เดือน hacker reverse engineer โปรแกรมมาดูแล้วพบว่าเป็น XOR กับ hardcode key ธรรมดาๆ แล้วก็เอา key ไปแจกในเน็ต

People is the weakest link

xkcd 538

Cargo cult security

“อาจารย์บอกมา” ไม่ใช่ security ที่ดีเสมอไป เช่น

  • อาจารย์ให้ config มา ตั้งตามแบบนี้แล้วเหนียว ยิงไม่เข้า แต่พอใช้แล้ว CEO โดนบล็อกทำงานไม่ได้
  • อาจารย์สอนให้ต้องมี CSP header แล้วจะป้องกันการแฮคได้ แต่เปิด unsafe-eval ไว้เพราะ marketing จะใช้ Google Tag Manager
  • อาจารย์สอนวิธี encrypt รหัสผ่านด้วย MD5 แล้วทำให้รหัสผ่านปลอดภัย แฮคไม่ได้
  • ซื้อ Tool scan มา scan เจอ 500 ข้อ พอแก้ตามที่ tool แนะนำแล้ว application ใช้งานไม่ได้
  • ซื้อ Tool scan มา scan ก่อน release application ปรากฏว่าจะแก้ให้ผ่านเป็น breaking change ส่งมอบงานไม่ทันเวลา
  • ซื้อ Tool scan ช่องโหว่ 10 อันดับยอดนิยมมา scan แล้วไม่เจอสักรายการที่ต้องแก้ไขแสดงว่าปลอดภัย แต่โดน hack ด้วยช่องโหว่ยอดนิยมอันดับที่ 11

เขาว่ายอด consult จะบอกว่า It depends ซึ่ง security ก็เช่นกัน “อาจารย์บอกมา” อาจจะไม่ได้ผิด แต่ไม่สามารถคำนึงถึง It depends ได้ ต้องมีความเข้าใจที่ถูกต้องว่าทำไมอาจารย์ถึงบอกมา