[UNIX] ประเด็นความปลอดภัยของการใช้งาน Environment Variables

Thanwa Jindarattana
5 min readSep 20, 2020

--

Outline

  • นิยามและบทบาทของ environment variable
  • การรับมรดก environment variable จาก parent process ไปยัง child process
  • อิทธิพลของ environment variable ที่จะส่งผลต่อพฤติกรรมของโปรแกรม
  • ความไม่ปลอดภัยในการใช้งาน environment variable
  • เปรียบเทียบระหว่างแนวทาง Set-UID และ Service

เนื่องจากบทความนี้มีเนื้อหาบางส่วนพูดถึง Set-UID Program ซึ่งสามารถเรียนรู้เพิ่มเติมได้จากบทความ => ว่าด้วยเรื่อง Set-UID Privileged Program

Environment variable คืออะไร?

Environment variable คือ ค่าของตัวแปรต่าง ๆ ที่ใช้เป็นบริบทสำคัญในการประมวลผลของ process ซึ่งจะมีผลกระทบต่อพฤติกรรมของ process โดยมีการริเริมแนวคิดนี้มาจากระบบปฏิบัติการ UNIX และต่อมา Microsoft Windows ได้นำแนวคิดนี้มาใช้

ตัวอย่างเช่นค่า “PATH” ซึ่ง shell process ใช้ค่านี้ในการหาโปรแกรมที่จะรันในกรณีที่ไม่ได้ระบุ full path

การเข้าถึง Environment variable

Process รับค่า environment variable มาได้อย่างไร

  • เมื่อมีการ fork() child process จะได้รับ environment variable ของ parent process มาทั้งหมด
  • Process ที่มาจากการรันผ่าน execve() จะมี address space ใหม่ และสามารถกำหนด environment variable เซ็ทใหม่ได้
  • เราสามารถส่งผ่าน environment variable เมื่อใช้งานฟังก์ชัน execve() ได้

execve() กับ environment variable

โปรแกรมนี้จะเรียกคำสั่ง /usr/bin/env (executable สำหรับแสดงค่า environment variable) ของ process ปัจจุบัน โดยตัวแปร newenv จะถูกใส่เป็น argument ที่ 3 ของฟังก์ชัน execve()

หลายคนสงสัยว่าทำไม switch case ไม่มีคำสั่ง break; คำตอบก็คือฟังก์ชัน execve() จะไม่มีการ return ผลลัพธ์ และมันได้แทนที่ address space ของ process ณ ขณะ execute ฟังก์ชันนี้ไปแล้ว

การจัดเก็บ environment variable

  • envp และ environ ชี้ไปที่ตำแหน่งเดียวกันในตอนเริ่มต้น
  • envp เข้าถึงผ่าน main เท่านั้น ขณะที่ environ เป็น global variable
  • ถ้ามีการเปลี่ยนแปลง environment variable ตำแหน่งที่เก็บอาจเปลี่ยนไปที่ heap และค่า environ จะเปลี่ยนไปขณะที่ envp ไม่เปลี่ยน

Shell variable กับ environment variable

  • คนทั่วไปจะเข้าใจว่า shell variable กับ environment variable เป็นตัวเดียวกัน
  • shell variable เป็นตัวแปรภายในที่ใช้งานขณะรันโปรแกรม shell
  • shell มีคำสั่ง built-in สำหรับ create, assign หรือ delete shell variable

ตัวอย่างการสร้างตัวแปร FOO สำหรับ shell variable

คำสั่ง unset เป็นคำสั่งสำหรับเคลียร์ค่าใน shell variable

เมื่อโปรแกรม shell เริ่มต้นทำงาน มันจะ copy ค่าของ environment variable ทั้งหมดมายัง shell variable ที่มี ชื่อเหมือนกัน แต่การเปลี่ยนแปลงค่าของ shell variable จะ ไม่ส่งผล กระทบต่อ environment variable

คำสั่ง strings จะคล้ายกับคำสั่ง cat ซึ่งใช้สำหรับแสดงผล
Shell จะแทนที่ $$ ด้วย process id ของมัน

ภาพแสดง environment variable ของ child process ที่ได้มากจาก shell variable ของ parent process

/proc file system

  • /proc เป็น virtual file system บนระบบปฏิบัติการ Linux ที่เก็บ directory ของแต่ละ process โดยใช้ process ID เป็นชื่อ directory
  • แต่ละ process directory จะมี virtual file ชื่อ environ ที่ระบุ environment variable ของ process นั้น

เช่น virtual file /proc/932/environ เก็บค่า environment variable ของ process 932

คำสั่ง “strings /proc/$$/environ” จะแสดงค่าของ environment variable ของ process ปัจจุบัน

  • เมื่อโปรแกรม env ถูกเรียกใช้งานจาก bash shell มันจะถูกรันเป็น child process และแสดงค่า environment variable ของ child process (ไม่ใช่ของ shell นะ)

เมื่อเราพิมพ์คำสั่ง env ตัว shell จะสร้าง child process ผ่านฟังก์ชัน execve()

Attack surface สำหรับ environment variables

การใช้งาน environment variable ที่ซ่อนอยู่เบื้องหลังเป็นอันตรายต่อความปลอดภัยของระบบ เนื่อจาก user สามารถที่จะ set ค่าของ environment variable ที่จะตกทอดไปยัง Set-UID program ได้

การโจมตีผ่าน Dynamic Linker

กระบวนการ link จะหา library ที่โปรแกรมอ้างถึงซึ่งสามารถแบ่งได้เป็น 2 ประเภทคือ

  1. Link ขณะ compile program จะเรียกว่า static linking
  2. Link ขณะรันโปรแกรมจริง จะเรียกว่า dynamic linking

ซึ่ง dynamic linking สำหรับโปรแกรมที่ใช้ environment variable จะเป็นจุดหนึ่งใน attack surface

เปรียบเทียบระหว่าง static link กับ dynamic link

จะเห็นว่าขนาดของไฟล์สำหรับโปรแกรมที่ compile แบบ static linking จะมีขนาดใหญ่กว่ามาก เนื่องจากมันจะรวบรวมส่วนของ library ต่าง ๆ ไว้ใน executable file อันเดียว แต่ก็มีข้อดีคือโปรแกรมจะสมบูรณ์ด้วยตัวมันเอง

เนื่องจากการใช้งาน dynamic linking ตัวโปรแกรม dynamic linker จะถูก link เข้ามากับตัวโปรแกรมหลักก่อนโดยแสดงดังรูป

คำสั่ง ldd จะบอก shared library ที่โปรแกรมต้องใช้

แม้ว่า dynamic linking จะประหยัด memory กว่า แต่โปรแกมจะไม่สมบูรณ์ในตัวมันเอง โค้ดบางส่วนจะต้องมาจากสิ่งที่ load ขณะทำการรันโปรแกรม ซึ่งถ้า user มีอิทธิพลต่อการ load โค้ดในส่วนนี้ จะทำให้ integrity (ความบริสุทธิ์ผุดผ่อง) ของโปรแกรมอาจมีการสูญเสียได้

การโจมตีผ่าน dynamic linker: ตัวอย่างที่ 1

เรียกใช้งาน sleep function ในลักษณะของ shared library

สร้างฟังก์ชัน sleep() เวอร์ชันของเราเอง

หลังจากนั้นให้ compile โปรแกรม sleep แล้วสร้าง shared library ใหม่ จากนั้นเปลี่ยนค่าของ LD_PRELOAD

การโจมตีผ่าน dynamic linker: ตัวอย่างที่ 2

ใช้เทคนิคเดียวกันกับตัวอย่างที่ 1 แต่เป็นโปรแกรม Set-UID

จะเห็นว่าฟังก์ชัน sleep() ที่เราสร้างขึ้นจะใช้งานไม่ได้ ทั้งนี้เพราะมี countermeasure ในตัว dynamic linker ที่มันจะไม่สนใจค่าของ LD_PRELOAD และ LD_LIBRARY_PATH เมื่อค่า EUID และ RUID ต่างกัน

ลองมาทดสอบโดยการสร้าง myenv จาก env ให้เป็นโปรแกม Set-UID

หลังจากนั้นให้ export LD_LIBRARY_PATH และ LD_PRELOAD แล้วรันโปรแกรมทั้ง 2 โปรแกรม

การโจมตีผ่าน dynamic linker: ตัวอย่างที่ 3

Apple OS X 10.10 (OS X Yosemite) มี environment variable ตัวใหม่คือ DYLD_PRINT_TO_FILE ซึ่ง user สามารถกำหนด filename สำหรับ dyld ได้ ซึ่งถ้าโปรแกรมที่เรียกใช้เป็น Set-UID โปรแกรม จะทำให้ user สามารถเข้าถึงไฟล์ที่ write-protected สามารถดูข้อมูลเพิ่มเติมได้จาก https://www.sektioneins.de/en/blog/15-07-07-dyld_print_to_file_lpe.html

สรุปสั้น ๆ ก็คือ เกิด capability leak เนื่องจากไม่ได้ close ตัว file descriptor

ตัวอย่างการโจมตี

  • ให้ค่า DYLD_PRINT_TO_FILE เป็น /etc/sudoers
  • เปลี่ยน user เป็น Bob แล้วไก้ไขไฟล์ /etc/sudoers

ซึ่ง user bob เป็น user ที่ผู้โจมตีสามารถควบคุมได้

Countermeasures

ระวังการใช้งานและการปฏิสัมพันธ์ระหว่าง environment variable กับโปรแกรม Set-UID ที่มีสิทธิ์ root ใช้ฟังก์ชัน genenv() ในเวอร์ชันที่ปลอดภัยเช่น secure_getenv() โดย

  • getenv() จะทำการค้นหา environment variable และจะให้ pointer ของ string ที่ตรงกับค่าที่ค้นหากลับมา
  • secure_getenv() จะให้ค่า NULL กลับมา หากตัวแปรนั้นไม่ใช่ variable ที่ define โดย system ซึ่งอาจนิยามได้จากการตรวจสอบ EUID กับ RUID ที่แตกต่างกัน

จะเห็นว่า Set-UID มี attack surface ที่มากกว่าตัว environment variable เพราะ environment variables ไม่มีคุณสมบัติ trustworthy ที่จะ trust ในแบบของ Set-UID เพราะค่ามันมาจาก user แต่ environment variables แบบ service สามารถเชื่อถือได้เพราะถูกให้ค่ามาจาก admin ของระบบ

ดังนั้นแนวทาง service จึงมีความปลอดภัยมากกว่าแบบ Set-UID ซึ่งระบบปฏิบัติการ Android ได้ตัดกลไก Set-UID และ Set-GID ออกไปด้วยทางด้านความปลอดภัย

อ้างอิง

https://www.handsonsecurity.net/resources.html by Kevin Du

https://www.handsonsecurity.net/files/slides/02_Environment_Variables.pptx

[01204554] Data Encryption & Security by Paruj Ratanaworabhan

--

--