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.
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 (https://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.
Source:https://pentest.blog
Working as a cyber security solutions architect, Alisa focuses on application and network security. Before joining us she held a cyber security researcher positions within a variety of cyber security start-ups. She also experience in different industry domains like finance, healthcare and consumer products.