Pendahuluan

Semenjak beberapa hari terakhir ini, Developer aplikasi berbasis Java dan Tim IT Security sedang berkutat dengan Vulnerability dari Log4j2.

Dalam istilah bahasa Indonesianya adalah Kerentanan log4j.

Vulnerability/Kerentanan ini dikenal dengan nama Log4Shell.

Vulnerability ini dimasukkan dalam daftar CVE (Common Vulnerabilities and Exposure), yaitu daftar ancaman Cybersecurity. Nomornya yaitu CVE-2021-44228.

Vulnerability ini sebelumnya dianggap sebagai Zero Day Vulnerability, yaitu Vulnerability yang sudah diumumkan, akan tetapi belum ada patchingnya. (pada waktu itu)

Isu ini disampaikan oleh Chen Zhaojun, karyawan dari Alibaba Group Holding, dari Tim Cloud Security, pada 24 November 2021, dan semenjak itu menjadi meluas dan membuat panik kancah per-IT an terutama yang terkait aplikasi berbasis Java.

Vulnerability/Kerentanan ini mempunyai level 10/10 di CVSS (Common Vulnerability Scoring System) v3 rating. Artinya sangat critical.

Pada saat tulisan ini dibuat, sudah ada patching dari Apache Log4j2 dengan release 2.15.0 pada tanggal 9 Desember 2021.

Kemudian diikuti dengan release 2.16.0 pada tanggal 14 Desember 2021.

Hari-hari yang mungkin bisa digunakan untuk belanja online nasional, cuti, jalan-jalan sebelum Natal dan Tahun Baru (Nataru), dan euphoria liburan karena PPKM Level 3 dibatalkan, mungkin akan terganggu dengan waktu untuk patching Vulnerability ini.

Patching di akhir tahun untuk menyelesaikan Vulnerability ini mungkin menjadi agenda penting bagi beberapa perusahaan-perusahaan yang mempunyai aplikasi berbasis Java.

Apalagi Java terkenal dengan modul-modulnya yang banyak terkait dengan fungsi-fungsi penting untuk enterprise.


Kenapa Vulnerability ini menjadi terkenal ?

Alasannya karena Log4j2 adalah salah satu dari Logging Framework terkenal untuk aplikasi berbasis Java. Contoh lainnya yang semirip misalnya Log4j versi 1, Logback.

Log4j2 adalah kelanjutan dari Log4j versi 1.

Log4j2 ini adalah Java Logging Framework yang banyak digunakan oleh aplikasi-aplikasi/framework besar lainnya, seperti Spring Boot, Apache Druid, Apache Solr, Twitter, Minecraft, Cloudflare, ElasticSearch, Steam, dll.

Jutaan developer Java kemungkinan besar menggunakan Log4j2 sebagai logging frameworknya di aplikasi-aplikasi mereka. Hal ini tentunya menambah tingkat kegentingan Vulnerability nya ini.

Vulnerability ini juga mudah ditest dan disimulasikan tanpa membutuhkan kemampuan security/hacking yang tinggi.


Sebenarnya apa sih vulnerability nya ini ?

Ok, coba kita kulik perlahan-lahan.

Vulnerability / Kerentanan nya ini terkait dengan fasilitas Property Support dari Log4j

Property Support artinya log4j2 bisa mereplace parameter loggingnya dengan value yang diambil dari property file konfigurasi, java environment, system properties, environment variables, konfigurasi file Spring .yml, dll.

Atau Log4j bisa mendelegasikan fungsi untuk mendapatkan value ke komponen tertentu dengan bantuan komponen lookup plugin.

Misalnya kita buat contoh sederhana untuk Property Support untuk mengambil Informasi Hardware yang digunakan oleh JVM java di komputer kita..

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PocLog4j2Controller  {

	Logger logger = LogManager.getLogger(PocLog4j2Controller.class);

	@RequestMapping("/testLookup")
    public String index() {
		String javaHardware = "${java:hw}";

		logger.info("Informasi Hardware => {}", javaHardware);

		return "Hallo bro !";
    }
}

${java:hw} ini adalah Syntaks Property Support Log4j2 terkait enviroment variable Java.


Ketika kita invoke http://localhost:8080/testLookup, maka hasilnya di console atau di log file:

Informasi Hardware => processors: 8, architecture: amd64-64, instruction sets: amd64

Otomatis nilai ${java:hw} diganti menjadi processors: 8, architecture: amd64-64, instruction sets: amd64


Apa saja jenis Property Support Log4j2 ?

Banyak jenis Property Substitution ini.

Cara untuk mendapatkan value nya itu disebut Lookup. Bisa diintip di dokumen offisialnya Log4j2 disini

Jadi property Substitution ini memakai syntaks lookup : ${name} , atau ${prefix:variableName}

Misalnya :

    • ${java:xxxx} ==> ketika ingin mengambil value terkait Java environment variable, termasuk OS, hardware, locale, dll.
    • ${spring:xxxxx} ==> ketika ingin mengambil value yang kita set di file .yml atau .properties nya yang bisa dibaca oleh Spring Framework.
    • ${ctx:xxxxx} ==> ketika ingin mengambil value dari Log4j ThreadContext Map.
    • ${date:xxxxx} ==> ketika ingin mengambil tanggal sekarang dan menampilkannya dalam format tertentu.
    • ${docker:xxx} ==> ketika ingin mendapatkan informasi terkait Docker container dimana aplikasi berjalan.
    • ${jndi:xxx} ==> ketika ingin lookup data ke server tertentu menggunakan JNDI.
    • dll

Fungsi lookup dari nomor 1 s/d 5 sebenarnya tidak terlalu bermasalah, karena kita hanya melakukan lookup/mengganti value dengan data di server kita sendiri, atau dari file konfigurasi, environment variable, dll.


Lalu apa masalahnya ?

Masalahnya di Lookup nomor 6 yaitu ${jndi:xxx} yaitu ketika ingin lookup data ke server tertentu menggunakan JNDI.

Ketika Log4j2 menemukan syntaks lookup ${jndi:xxx} ini di parameter loggingnya, maka akan otomatis melakukan Lookup, berupa menginisiasi koneksi ke alamat URL JNDI nya tersebut.

Misalkan kita buat contoh sederhana, REST Controller yang menggunakan log4j2 sebagai logging framework :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PocLog4j2Controller  {

	Logger logger = LogManager.getLogger(PocLog4j2Controller.class);

	@RequestMapping("/testLookup")
    public String index() {
		String jndiLdap = "${jndi:ldap://localhost/a}";

		logger.info("A JNDI Lookup : {}", jndiLdap);

		return "Testing log4j2 vulnerability !";
    }
}

Karena tidak ada server LDAP yang hidup di komputer lokal saya, maka hasilnya di lognya akan ada warning bahwa Log4j2 mencoba menginisiasi koneksi ke LDAP, tapi gagal, seperti berikut ini.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
http-nio-8080-exec-1 WARN Error looking up JNDI resource [ldap://localhost/a].
javax.naming.CommunicationException: localhost:389 [Root exception is java.net.ConnectException: Connection refused: connect]
	at java.naming/com.sun.jndi.ldap.Connection.<init>(Connection.java:244)
	at java.naming/com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137)
	at java.naming/com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1616)
	at java.naming/com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2847)
	at java.naming/com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:348)
	at java.naming/com.sun.jndi.url.ldap.ldapURLContextFactory.getUsingURLIgnoreRootDN(ldapURLContextFactory.java:60)
	at java.naming/com.sun.jndi.url.ldap.ldapURLContext.getRootURLContext(ldapURLContext.java:61)
	at java.naming/com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:204)
	at java.naming/com.sun.jndi.url.ldap.ldapURLContext.lookup(ldapURLContext.java:94)
	at java.naming/javax.naming.InitialContext.lookup(InitialContext.java:409)
	at org.apache.logging.log4j.core.net.JndiManager.lookup(JndiManager.java:172)
	at org.apache.logging.log4j.core.lookup.JndiLookup.lookup(JndiLookup.java:56)

Tapi dari gambaran diatas, kita bisa lihat bahwa Log4j2 secara otomatis akan menginisiasi koneksi JNDI ke url yang dipassing dari parameter string ${jndi:ldap://localhost/a}.

Coba bayangkan kalau kita melakukan logging dengan log4j2 ini untuk parameter HTTP Request Header, URL param, URL path variable, atau HTTP body payload, seketika kita menerima request di RestController diatas.

Kemudian user yang nakal yang mencoba-coba akan mengirimkan parameter ${jndi:xxx} tersebut, maka secara tidak langsung user nakal tersebut, atau orang awam bisa passing data String saja ke API sebuah aplikasi, dan secara otomatis log4j2 ini akan menginisiasi koneksi JNDI ke alamat URL yang dipassing tersebut.


Lho, kalau cuma koneksi keluar saja nggak masalah kan ?

Tentunya bermasalah, membiarkan koneksi dari server kita ke server luar tanpa bisa kita kontrol kemananya akan sangat membahayakan, misalnya :

  1. ketika koneksi JNDI berhasil ke server luar, maka data environment variable, java version, dll dengan menggunakan fasilitas Property Support diatas akan bisa diterima juga oleh server JNDI luar tersebut. Itu berarti data internal server kita bisa diakses oleh orang lain.

  2. JNDI mempunyai sebuah mekanisme yang memungkinkan program yang memanggilnya untuk meload dan menginstansiasi Object yang berasal dari respons server RMI/CORBA/LDAP/DNS dan menjalankannya.

Istilahnya adalah JNDI Remote Class Loading.

Hal ini memang menjadi salah fitur dari JNDI, dengan menggunakan ObjectFactory. dan Serialized Remote Codebase, maka program Java yang didefinisikan oleh peretas bisa dieksekusi secara otomatis.

Dan ini tentunya berbahaya sekali. Ketika dengan hanya passing string ${jndi:ldap://xxxx}, maka akan otomatis inisiasi koneksi JNDI ke server luar, kemudian bisa ngeload object, dan menjalankan objectnya tersebut berupa program Java yang berbahaya. Waah, mirip-mirip dengan SQL Injection lah yaa.

Hal inilah yang disebut serangan Remote Code Execution (RCE).

Hmmm, ngeri-ngeri sedap ternyata.