临时修补Apache Log4j2漏洞 for VMware vCenter Server 6.7.x

1、关于 Apache Log4j 漏洞

Apache Log4j2漏洞事件进一步发酵,诸多重要产品、组件相继受其影响。VMware厂商于12月11日在其官网发布安全公告,其下属主流产品VMware Horizon、VMware vCenter Server、VMware HCX等均受Log4j2漏洞影响,危害评级均为“危急(Critical)”。作为虚拟化基础设施,相关威胁影响极大。

Apache Log4j 中的一个由 CVE-2021-44228 识别的严重漏洞和一个由 CVE-2021-45046 识别的低严重性漏洞已公开披露,影响 VMware 产品。

多个产品通过 Apache Log4j 受到远程代码执行漏洞和部分拒绝服务漏洞(CVE-2021-44228、CVE-2021-45046)的影响。

对受影响的 VMware 产品具有网络访问权限的恶意行为者可能会利用此问题来获得对目标系统的完全控制和/或执行拒绝服务攻击。

VMware官网通告:
https://www.vmware.com/security/advisories/VMSA-2021-0028.html

2、获取修补VMSA-2021-0028 的自动化脚本

http://soft.yjsec.com/log4j.py

import sys
import os
import subprocess
from datetime import datetime
import shutil
import json
import re
import traceback

sys.path.append(os.environ['VMWARE_PYTHON_PATH'])
isLinux = os.name == 'posix'
if not isLinux:
    SERVICE_CTL = '"' + os.getenv('VMWARE_CIS_HOME') + '\\bin\\service-control.bat' + '"'
    if sys.version_info[0] >= 3:
        from six.moves import winreg
    else:
        import _winreg as winreg
    WReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
    key = winreg.OpenKey(WReg, r"SOFTWARE\VMware, Inc.\vCenter Server")
    vcbuild = winreg.QueryValueEx(key, 'BuildNumber')[0]
    vcversion = winreg.QueryValueEx(key, 'ProductVersion')[0]
    """
    Windows file paths needs to be updated
    vmon_config_file = "/usr/lib/vmware-vmon/java-wrapper-vmon"
    vum_config_file = "/usr/lib/vmware-updatemgr/bin/jetty/start.ini"
    analytics_jar_file = "/usr/lib/vmware/common-jars/log4j-core-2.8.2.jar"
    dbcc_jar_file = "/usr/lib/vmware-dbcc/lib/log4j-core-2.8.2.jar"
    """
    now = datetime.now()
    date_time = now.strftime("%d-%b-%y-%H-%M-%S")
    print("This Script is not configured to run on Windows vCenter Server, please follow the KB https://kb.vmware.com/s/article/87081 to apply the workaround steps")
    from cis.tools import get_install_parameter
    exit(0)
else:
    SERVICE_CTL = 'service-control'
    now = datetime.now()
    date_time = now.strftime("%d-%b-%y-%H-%M-%S")
    from cis.tools import get_install_parameter
    vmon_config_file = "/usr/lib/vmware-vmon/java-wrapper-vmon"
    vum_config_file = "/usr/lib/vmware-updatemgr/bin/jetty/start.ini"
    analytics_jar_file = "/usr/lib/vmware/common-jars/log4j-core-2.8.2.jar"
    dbcc_jar_file = "/usr/lib/vmware-dbcc/lib/log4j-core-2.8.2.jar"
    cm_jar_file = "/usr/lib/vmware-cm/lib/log4j-core.jar"
    stsd_config_file = "/etc/rc.d/init.d/vmware-stsd"
    idmd_config_file = "/etc/rc.d/init.d/vmware-sts-idmd"
    pscclient_config_file = "/etc/rc.d/init.d/vmware-psc-client"
    with open("/etc/applmgmt/appliance/update.conf") as f:
        data = json.load(f)
        vcbuild = data['build']
    f = open("/etc/issue")
    for line in f:
        if not line.strip():
            continue
        else:
            vcversion = line
            break
    vcversion = vcversion.rsplit(' ', 1)[1]
    vcversion = vcversion.strip()

if sys.version_info[0] < 3:
    inputfunction = raw_input
else:
    inputfunction = input

def execute_cmd(cmd, shellvalue=True, stdin=None, quiet=False):
    p = None
    p = subprocess.Popen(cmd, shell=shellvalue, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdout, stderr) = p.communicate()
    return p.returncode, stdout, stderr

def color_green(input_string):
    OKGREEN = '\033[92m'
    ENDC = '\033[0m'
    new_string = OKGREEN + input_string + ENDC
    return new_string

def color_red(input_string):
    OKRED = '\033[91m'
    ENDC = '\033[0m'
    new_string = OKRED + input_string + ENDC
    return new_string

def backup_config_file(filename,dest_path=None):
    print("...Taking Backup of file %s" % filename)
    try:
        if dest_path:
            parent_path, base_filename = os.path.split(filename)
            backupfilename = dest_path + base_filename + "_backup_" + date_time
        else:
            backupfilename = filename + "_backup_" + date_time
        shutil.copyfile(filename, backupfilename)
        print("...Successfully completed the backup - %s" % backupfilename)
    except Exception as e:
        print(color_red("Script terminated - Failed taking backup of Config file " + filename + "Operation failed "
                                                                                                 "with error" + str(e)))
        exit(1)

def check_if_string_in_file(file_name, string_to_search):
    with open(file_name, 'r') as file_contents:
        for line in file_contents:
            if string_to_search in line:
                return True
    return False

def add_string_to_file(filename, old_string, new_string):
    file_handle = open(filename, 'r')
    file_contents = file_handle.read()
    file_handle.close()
    file_contents = (re.sub(old_string, new_string, file_contents))
    file_handle = open(filename, 'w')
    file_handle.write(file_contents)
    file_handle.close()

def add_string_to_end_of_file(filename, new_string):
    with open(filename, "a") as file_object:
        file_object.write(new_string)
    return True

def vmon_remediation(config_filename):
    print("\nRemediating vMon Config files")
    if not check_if_string_in_file(config_filename, "-Dlog4j2.formatMsgNoLookups=true"):
        backup_config_file(config_filename)
        print("...Updating Config file")
        if check_if_string_in_file(config_filename,'exec $java_start_bin $jvm_dynargs "$@"'):
            new_config_entries='log4j_arg="-Dlog4j2.formatMsgNoLookups=true"' + "\n" + 'exec $java_start_bin $jvm_dynargs $log4j_arg "$@"'
            add_string_to_file(config_filename, 'exec \$java_start_bin \$jvm_dynargs "\$@"', new_config_entries)
        elif check_if_string_in_file(config_filename,'exec $java_start_bin $jvm_dynargs $security_dynargs $original_args'):
            new_config_entries='log4j_arg="-Dlog4j2.formatMsgNoLookups=true"' + "\n" + 'exec $java_start_bin $jvm_dynargs $log4j_arg  $security_dynargs $original_args'
            add_string_to_file(config_filename, 'exec \$java_start_bin \$jvm_dynargs \$security_dynargs \$original_args', new_config_entries)
        print("...Completed Config file update")
    else:
        print("...Config files already have the entries to workaround the VMSA")
        print("...Proceeding further to check other services")
    print("...Stopping all Services")
    restart_all_services("stop")
    print("...Starting all Services")
    restart_all_services("start")
    print("...Successfully Started All Services")
    print(color_green("...Completed remediating vMon services"))

def vum_remediation(config_filename):
    print("\nRemediating VMware Update Manager Config files")
    if check_if_string_in_file(config_filename, "-Dlog4j2.formatMsgNoLookups=true"):
        print("...Config files already have the entries to workaround the VMSA")
    else:
        backup_config_file(config_filename)
        print("...Updating Config file")
        add_string_to_end_of_file(config_filename,'-Dlog4j2.formatMsgNoLookups=true')
        print("...Completed Config file update")
    print("...Restarting Update Manager Service")
    restart_service("vmware-updatemgr")
    print("...Successfully restarted Update Manager Service")
    print(color_green("...Completed remediating Update Manager service"))

def analytics_remediation(config_filename):
    print("\nRemediating Analytics Service Config files")
    backup_config_file(config_filename)
    cmd = 'zip -q -d ' + config_filename + ' org/apache/logging/log4j/core/lookup/JndiLookup.class'
    print("...Updating Config file")
    (code, result, err) = execute_cmd(cmd, True, None)
    if code == 0:
        print("...Successfully updated the Jar file")
    elif("Nothing to do" in result.decode('utf-8').strip()):
        print("...Required Changes Already exists in the Jar file")
    print("...Restarting Analytics Service")
    restart_service("vmware-analytics")
    print("...Successfully restarted Analytics Service")
    print(color_green("...Completed remediating Analytics service"))

def dbcc_remediation(config_filename):
    global skip_dbcc
    skip_dbcc = False
    print("\nRemediating DBCC Utility Config files")
    if os.path.isfile(config_filename):
        backup_config_file(config_filename)
        cmd = 'zip -q -d ' + config_filename + ' org/apache/logging/log4j/core/lookup/JndiLookup.class'
        print("...Updating Config file")
        (code, result, err) = execute_cmd(cmd, True, None)
        if code == 0:
            print("...Successfully updated the Jar file")
        elif("Nothing to do" in result.decode('utf-8').strip()):
            print("...Required Changes Already exists in the Jar file")
            print(color_green("...Completed remediating DBCC configuration"))
    else:
        skip_dbcc = True
        print(color_green("Skipping DBCC Remediation as Log4j Jar file %s does not exist on this VC build") % config_filename)

def cm_remediation(config_filename):
    print("\nRemediating CM Service Config files")
    backup_config_file(config_filename)
    cmd = 'zip -q -d ' + config_filename + ' org/apache/logging/log4j/core/lookup/JndiLookup.class'
    print("...Updating Config file")
    (code, result, err) = execute_cmd(cmd, True, None)
    if code == 0:
        print("...Successfully updated the Jar file")
    elif("Nothing to do" in result.decode('utf-8').strip()):
        print("...Required Changes Already exists in the Jar file")
    print("...Restarting CM Service")
    restart_service("vmware-cm")
    print("...Successfully restarted CM Service")
    print(color_green("...Completed remediating CM service"))

def stsd_remediation(config_filename):
    print("\nRemediating STSD Config files")
    if not check_if_string_in_file(config_filename, "-Dlog4j2.formatMsgNoLookups=true"):
        backup_config_file(config_filename,"/root/")
        print("...Updating Config file")
        new_config_entries='           -Dlog4j2.formatMsgNoLookups=true \\' + "\n" + '            $DAEMON_CLASS start'
        add_string_to_file(config_filename, '           \$DAEMON_CLASS start', new_config_entries)
        print("...Completed Config file update")
    else:
        print("...Config files already have the entries to workaround the VMSA")
        print("...Proceeding further to check other services")
    print("...Restarting vmware-stsd Service")
    restart_service("vmware-stsd")
    print("...Successfully restarted vmware-stsd Service")
    print(color_green("...Completed remediating vmware-stsd service"))

def idmd_remediation(config_filename):
    print("\nRemediating IDMD Config files")
    if not check_if_string_in_file(config_filename, "-Dlog4j2.formatMsgNoLookups=true"):
        backup_config_file(config_filename,"/root/")
        print("...Updating Config file")
        new_config_entries='                  -Dlog4j2.formatMsgNoLookups=true \\' + "\n" + '                  $DEBUG_OPTS'
        add_string_to_file(config_filename, "                  \$DEBUG_OPTS", new_config_entries)
        print("...Completed Config file update")
    else:
        print("...Config files already have the entries to workaround the VMSA")
        print("...Proceeding further to check other services")
    print("...Restarting vmware-stsd-idmd Service")
    restart_service("vmware-sts-idmd")
    print("...Successfully restarted vmware-sts-idmd Service")
    print(color_green("...Completed remediating vmware-sts-idmd service"))

def pscclient_remediation(config_filename):
    print("\nRemediating PSC Client Config files")
    if not check_if_string_in_file(config_filename, "-Dlog4j2.formatMsgNoLookups=true"):
        backup_config_file(config_filename,"/root/")
        print("...Updating Config file")
        new_config_entries='           -Dlog4j2.formatMsgNoLookups=true \\' + "\n" + '             $DAEMON_CLASS start'
        add_string_to_file(config_filename, '           \$DAEMON_CLASS start', new_config_entries)
        print("...Completed Config file update")
    else:
        print("...Config files already have the entries to workaround the VMSA")
        print("...Proceeding further to check other services")
    print("...Restarting vmware-psc-client Service")
    restart_service("vmware-psc-client")
    print("...Successfully restarted vmware-psc-client Service")
    print(color_green("...Completed remediating vmware-psc-client service"))

def verify_vmon_mitigation():
    status = False
    cmd = ' ps auxww | grep formatMsgNoLookups'
    (code, result, err) = execute_cmd(cmd)
    ps_result = result.decode('utf-8').split('\n')
    for line in ps_result:
        if '.launcher' in line:
            if '-Dlog4j2.formatMsgNoLookups=true' in line:
                status = True
            else:
                status = False
    return status

def verify_MsgNoLookups_with_ps_command(process_string):
    status = False
    cmd = ' ps auxww | grep formatMsgNoLookups'
    (code, result, err) = execute_cmd(cmd)
    ps_result = result.decode('utf-8').split('\n')
    for line in ps_result:
        if process_string in line:
            if '-Dlog4j2.formatMsgNoLookups=true' in line:
                status = True
            else:
                status = False
    return status

def verify_vum_mitigation():
    log4j2_string = "log4j2.formatMsgNoLookups = true (/usr/lib/vmware-updatemgr/bin/jetty/start.ini)"
    cmd = ' cd /usr/lib/vmware-updatemgr/bin/jetty/ && java -jar start.jar --list-config'
    (code, result, err) = execute_cmd(cmd)
    if log4j2_string in result.decode('utf-8').replace('\n','\n'):
        return True
    else:
        return False

def verify_jndilookup(jar_path):
    cmd = ' grep -i jndilookup ' +jar_path + ' | wc -l'
    (code, result, err) = execute_cmd(cmd)
    a = result.decode('utf-8').strip()
    if a == '0':
        return True
    else:
        return False

"""
Check availability of file
"""
def is_file_exists(file_name_to_verify):
    if os.path.isfile(file_name_to_verify):
        return True
    else:
        return False

"""
Check for VCHA Configuaration
"""
def is_vcha_node():
    if os.path.isfile("/etc/vmware-vcha/vcha.cfg"):
        return True
    else:
        return False
"""
This function helps to restart all services
"""
def restart_all_services(action):
    if action in ['stop','Stop','STOP']:
        service_action = " --stop"
    elif action in ['start','Start','START']:
        service_action = " --start"
    cmd = SERVICE_CTL + service_action + ' --all '
    try:
        (code, result, err) = execute_cmd(cmd, True, None)
        if code != 0:
            return (False,result,err)
        else:
            return (True,result,err)
    except Exception as e:
        msg = 'Error while performing all services stop/start operation : {0}'.format(e)
        print(msg)
        return (False,result,err)

"""
This function helps to restart an individual service
It accepts service name as argument
"""
def restart_service(service_name):
    try:
        cmd = SERVICE_CTL + ' --stop ' + service_name
        (code, result, err) = execute_cmd(cmd, True, None)
        cmd = SERVICE_CTL + ' --start ' + service_name
        (code, result, err) = execute_cmd(cmd, True, None)
        if code != 0:
            return False
        else:
            return True
    except Exception as e:
        msg = 'Error while restarting service : {0}'.format(e)
        print(msg)
        return False

def main():
    vmonstatus = vumstatus = analyticsstatus = dbccstatus = cmstatus = stsdstatus = stsidmdstatus = pscclientstatus = True
    verifystsd = verifyidmd = verifypscclient = False
    do_analytics = False
    print("\nThis script will help to automate the steps described in VMware KB https://kb.vmware.com/s/article/87081\n")
    if is_vcha_node():
        print(color_red("You need to remove VCHA to apply the workarounds as mentioned in Impacts section of KB https://kb.vmware.com/s/article/87081\n"))
        exit(1)
    userconfirmation = inputfunction("All Services will be restarted by the script to mitigate the VMSA, Please enter YES to proceed further or NO to Exit [[Yes/No/Y/N]] ? ")
    if userconfirmation.lower() not in ['y','Y','Yes','YES','yes','yES','yeS']:
        print(color_green("Terminating the script based on user input, you may follow the steps described in https://kb.vmware.com/s/article/87081 "))
        exit(1)
    try:
        if vcversion.startswith("7.0") or vcversion.startswith("6.7") or vcversion.startswith("6.5"):
            vmon_remediation(vmon_config_file)
        if vcversion.startswith("6.5") or vcversion.startswith("6.7"):
            if is_file_exists(stsd_config_file):
                verifystsd = True
                stsd_remediation(stsd_config_file)
            else:
                print(color_green("\nSkipping SSO STSD check, this check is not required on Management vCenter pointing to External PSC"))
            if is_file_exists(idmd_config_file):
                verifyidmd = True
                idmd_remediation(idmd_config_file)
            else:
                print(color_green("\nSkipping SSO IDMD check, this check is not required on Management vCenter pointing to External PSC"))
        if vcversion.startswith("6.5"):
            if is_file_exists(pscclient_config_file):
                verifypscclient = True
                pscclient_remediation(pscclient_config_file)
            else:
                print(color_green("\nSkipping PSC Client check, this check is not required on Management vCenter pointing to External PSC"))
        if vcversion.startswith("7.0"):
            vum_remediation(vum_config_file)
        if vcversion.startswith("7.0") or (vcversion.startswith("6.7") and int(vcversion.split('.')[3]) <= 50000):
            do_analytics = True
            analytics_remediation(analytics_jar_file)
        if vcversion.startswith("7.0"):
            dbcc_remediation(dbcc_jar_file)
        if vcversion.startswith("6.7") or vcversion.startswith("6.5"):
            cm_remediation(cm_jar_file)
    except Exception as e:
        traceback.print_exc()
        print(color_red("Script failed with error - Please follow the KB https://kb.vmware.com/s/article/87081 manually" + str(e)))
        exit()

    print("\nVerifying the vulnerability status after applying the workaround :\n")
    print("..Verifying the status of vMon Services")
    if verify_vmon_mitigation():
        print(color_green("....SUCCESS"))
    else:
        print(color_red("....FAILED"))
        vmonstatus = False

    if verifystsd:
        print("..Verifying the status of vmware-stsd Service")
        if verify_MsgNoLookups_with_ps_command("procname vmware-stsd"):
            print(color_green("....SUCCESS"))
        else:
            print(color_red("....FAILED"))
            stsdstatus = False

    if verifyidmd:
        print("..Verifying the status of vmware-sts-idmd Service")
        if verify_MsgNoLookups_with_ps_command("procname vmware-sts-idmd"):
            print(color_green("....SUCCESS"))
        else:
            print(color_red("....FAILED"))
            stsidmdstatus = False

    if verifypscclient:
        print("..Verifying the status of vmware-psc-client Service")
        if verify_MsgNoLookups_with_ps_command("procname vmware-psc-client"):
            print(color_green("....SUCCESS"))
        else:
            print(color_red("....FAILED"))
            pscclientstatus = False

    if vcversion.startswith("7.0"):
        print("..Verifying the status of VMware Update Manager")
        if verify_vum_mitigation():
            print(color_green("....SUCCESS"))
        else:
            print(color_red("....FAILED"))
            vumstatus = False

    if (do_analytics):
        print("..Verifying the status of VMware Analytics Service")
        if verify_jndilookup("/usr/lib/vmware/common-jars/log4j-core-2.8.2.jar"):
            print(color_green("....SUCCESS"))
        else:
            print(color_red("....FAILED"))
            analyticsstatus = False

    if vcversion.startswith("7.0"):
        print("..Verifying the status of DBCC Utility")
        if skip_dbcc:
            print(color_green("....SKIPPED (Not Applicable)"))
        elif verify_jndilookup("/usr/lib/vmware-dbcc/lib/log4j-core-2.8.2.jar"):
            print(color_green("....SUCCESS"))
        else:
            print(color_red("....FAILED"))
            dbccstatus = False

    if vcversion.startswith("6.7") or vcversion.startswith("6.5"):
        print("..Verifying the status of CM Service")
        if verify_jndilookup("/usr/lib/vmware-cm/lib/log4j-core.jar"):
            print(color_green("....SUCCESS"))
        else:
            print(color_red("....FAILED"))
            cmstatus = False

    if vmonstatus and vumstatus and analyticsstatus and dbccstatus and cmstatus:
        print(color_green("Successfully applied the workaround steps in KB 87081 to mitigate the VMSA-2021-0028"))
    else:
        print(color_red("Script failed to mitigate some services, please follow the manual steps in KB 87081 to mitigate the VMSA-2021-0028"))


if __name__ == "__main__":
    main()

3、登录到vCenter Server的Sheel下,执行自动化修补脚本

Connection established.
To escape to local shell, press 'Ctrl+Alt+]'.


VMware vCenter Server Appliance 6.7.0.50000

Type: vCenter Server with an embedded Platform Services Controller

WARNING! The remote SSH server rejected X11 forwarding request.
Connected to service

    * List APIs: "help api list"
    * List Plugins: "help pi list"
    * Launch BASH: "shell"

Command> shell
Shell access is granted to root

root@photon-machine [ ~ ]# vi log4j.py

root@photon-machine [ ~ ]# python log4j.py

This script will help to automate the steps described in VMware KB https://kb.vmware.com/s/article/87081

All Services will be restarted by the script to mitigate the VMSA, Please enter YES to proceed further or NO to Exit [[Yes/No/Y/N]] ? y

Remediating vMon Config files
...Taking Backup of file /usr/lib/vmware-vmon/java-wrapper-vmon
...Successfully completed the backup - /usr/lib/vmware-vmon/java-wrapper-vmon_backup_16-Dec-21-09-25-22
...Updating Config file
...Completed Config file update
...Stopping all Services
...Starting all Services
...Successfully Started All Services
...Completed remediating vMon services

Remediating STSD Config files
...Taking Backup of file /etc/rc.d/init.d/vmware-stsd
...Successfully completed the backup - /root/vmware-stsd_backup_16-Dec-21-09-25-22
...Updating Config file
...Completed Config file update
...Restarting vmware-stsd Service
...Successfully restarted vmware-stsd Service
...Completed remediating vmware-stsd service

Remediating IDMD Config files
...Taking Backup of file /etc/rc.d/init.d/vmware-sts-idmd
...Successfully completed the backup - /root/vmware-sts-idmd_backup_16-Dec-21-09-25-22
...Updating Config file
...Completed Config file update
...Restarting vmware-stsd-idmd Service
...Successfully restarted vmware-sts-idmd Service
...Completed remediating vmware-sts-idmd service

Remediating Analytics Service Config files
...Taking Backup of file /usr/lib/vmware/common-jars/log4j-core-2.8.2.jar
...Successfully completed the backup - /usr/lib/vmware/common-jars/log4j-core-2.8.2.jar_backup_16-Dec-21-09-25-22
...Updating Config file
...Successfully updated the Jar file
...Restarting Analytics Service
...Successfully restarted Analytics Service
...Completed remediating Analytics service

Remediating CM Service Config files
...Taking Backup of file /usr/lib/vmware-cm/lib/log4j-core.jar
...Successfully completed the backup - /usr/lib/vmware-cm/lib/log4j-core.jar_backup_16-Dec-21-09-25-22
...Updating Config file
...Successfully updated the Jar file
...Restarting CM Service
...Successfully restarted CM Service
...Completed remediating CM service

Verifying the vulnerability status after applying the workaround :

..Verifying the status of vMon Services
....SUCCESS
..Verifying the status of vmware-stsd Service
....SUCCESS
..Verifying the status of vmware-sts-idmd Service
....SUCCESS
..Verifying the status of VMware Analytics Service
....SUCCESS
..Verifying the status of CM Service
....SUCCESS
Successfully applied the workaround steps in KB 87081 to mitigate the VMSA-2021-0028
root@photon-machine [ ~ ]#

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注