Automate OpenCanary definitions, localize times
This commit is contained in:
147
installer.sh
147
installer.sh
@ -234,6 +234,7 @@ INSERT OR IGNORE INTO settings(key, value) VALUES('active_profile', '');
|
|||||||
INSERT OR IGNORE INTO settings(key, value) VALUES('admin_port', '');
|
INSERT OR IGNORE INTO settings(key, value) VALUES('admin_port', '');
|
||||||
INSERT OR IGNORE INTO settings(key, value) VALUES('admin_expires_at', '');
|
INSERT OR IGNORE INTO settings(key, value) VALUES('admin_expires_at', '');
|
||||||
INSERT OR IGNORE INTO settings(key, value) VALUES('appliance_name', 'BaldCanary Appliance');
|
INSERT OR IGNORE INTO settings(key, value) VALUES('appliance_name', 'BaldCanary Appliance');
|
||||||
|
INSERT OR IGNORE INTO settings(key, value) VALUES('timezone', '');
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
chown -R "$APP_USER:$APP_GROUP" "$APP_ROOT/db"
|
chown -R "$APP_USER:$APP_GROUP" "$APP_ROOT/db"
|
||||||
@ -241,6 +242,73 @@ chmod 775 "$APP_ROOT/db"
|
|||||||
chmod 660 "$APP_DB"
|
chmod 660 "$APP_DB"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write_opencanary_event_map() {
|
||||||
|
log "Generating OpenCanary event label map..."
|
||||||
|
|
||||||
|
"$APP_ROOT/venv/bin/python" <<'PY'
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from opencanary import logger
|
||||||
|
|
||||||
|
out = {}
|
||||||
|
|
||||||
|
def humanize(name: str) -> str:
|
||||||
|
name = re.sub(r'^LOG_', '', name)
|
||||||
|
parts = name.split('_')
|
||||||
|
|
||||||
|
pretty = []
|
||||||
|
acronyms = {
|
||||||
|
'FTP', 'HTTP', 'HTTPS', 'SSH', 'SMB', 'MYSQL', 'MSSQL',
|
||||||
|
'RDP', 'VNC', 'NTP', 'SNMP', 'TFTP', 'TCP', 'UDP',
|
||||||
|
'DNS', 'LDAP', 'REDIS', 'SIP'
|
||||||
|
}
|
||||||
|
|
||||||
|
for part in parts:
|
||||||
|
if part in acronyms:
|
||||||
|
pretty.append(part)
|
||||||
|
elif part == 'LOGIN':
|
||||||
|
pretty.append('Login')
|
||||||
|
elif part == 'ATTEMPT':
|
||||||
|
pretty.append('Attempt')
|
||||||
|
elif part == 'CONNECTION':
|
||||||
|
pretty.append('Connection')
|
||||||
|
elif part == 'NEW':
|
||||||
|
pretty.append('New')
|
||||||
|
else:
|
||||||
|
pretty.append(part.capitalize())
|
||||||
|
|
||||||
|
label = ' '.join(pretty)
|
||||||
|
|
||||||
|
# A few nicer aliases.
|
||||||
|
label = label.replace('RDP Connection Made', 'RDP Connection')
|
||||||
|
label = label.replace('HTTP GET', 'HTTP GET Request')
|
||||||
|
label = label.replace('HTTP POST', 'HTTP POST Request')
|
||||||
|
|
||||||
|
return label
|
||||||
|
|
||||||
|
for cls_name in ('CanaryLogger', 'PyLogger'):
|
||||||
|
cls = getattr(logger, cls_name, None)
|
||||||
|
if not cls:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for name, value in inspect.getmembers(cls):
|
||||||
|
if not name.startswith('LOG_'):
|
||||||
|
continue
|
||||||
|
if isinstance(value, int):
|
||||||
|
out[str(value)] = humanize(name)
|
||||||
|
|
||||||
|
Path('/opt/baldcanary/config/opencanary_event_labels.json').write_text(
|
||||||
|
json.dumps(out, indent=2, sort_keys=True)
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
|
||||||
|
chown "$APP_USER:$APP_GROUP" "$APP_ROOT/config/opencanary_event_labels.json"
|
||||||
|
chmod 664 "$APP_ROOT/config/opencanary_event_labels.json"
|
||||||
|
}
|
||||||
|
|
||||||
write_common_php() {
|
write_common_php() {
|
||||||
log "Writing shared PHP helpers..."
|
log "Writing shared PHP helpers..."
|
||||||
|
|
||||||
@ -273,6 +341,78 @@ function bc_set_setting(string $key, string $value): void {
|
|||||||
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=CURRENT_TIMESTAMP');
|
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=CURRENT_TIMESTAMP');
|
||||||
$stmt->execute([$key, $value]);
|
$stmt->execute([$key, $value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bc_local_timezone(): string {
|
||||||
|
$tz = bc_setting('timezone', '');
|
||||||
|
if ($tz !== '') {
|
||||||
|
return $tz;
|
||||||
|
}
|
||||||
|
|
||||||
|
$systemTz = trim((string)@shell_exec('timedatectl show -p Timezone --value 2>/dev/null'));
|
||||||
|
if ($systemTz !== '') {
|
||||||
|
return $systemTz;
|
||||||
|
}
|
||||||
|
|
||||||
|
return date_default_timezone_get() ?: 'UTC';
|
||||||
|
}
|
||||||
|
|
||||||
|
function bc_local_time(?string $utcTime): string {
|
||||||
|
$utcTime = trim((string)$utcTime);
|
||||||
|
if ($utcTime === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dt = new DateTime($utcTime, new DateTimeZone('UTC'));
|
||||||
|
$dt->setTimezone(new DateTimeZone(bc_local_timezone()));
|
||||||
|
return $dt->format('F j, Y g:i:s A');
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
return $utcTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bc_event_label(?string $type): string {
|
||||||
|
$type = trim((string)$type);
|
||||||
|
|
||||||
|
$labels = [
|
||||||
|
// BaldCanary web events
|
||||||
|
'page_view' => 'Page View',
|
||||||
|
'form_submit' => 'Form Submission',
|
||||||
|
'xss_probe' => 'XSS Probe',
|
||||||
|
'sql_injection_probe' => 'SQL Injection Probe',
|
||||||
|
'command_injection_probe' => 'Command Injection Probe',
|
||||||
|
'path_traversal_probe' => 'Path Traversal Probe',
|
||||||
|
'sensitive_file_probe' => 'Sensitive File Probe',
|
||||||
|
'session_file_probe' => 'Session File Probe',
|
||||||
|
'exposed_session_directory' => 'Exposed Session Directory',
|
||||||
|
'php_session_file' => 'PHP Session File Access',
|
||||||
|
'backup_directory' => 'Backup Directory Access',
|
||||||
|
'mysql_backup_directory' => 'MySQL Backup Directory Access',
|
||||||
|
'api_docs' => 'API Documentation Access',
|
||||||
|
'swagger_docs' => 'Swagger Documentation Access',
|
||||||
|
'phpinfo_probe' => 'PHP Info Probe',
|
||||||
|
'env_file_probe' => 'Environment File Probe',
|
||||||
|
'config_file_probe' => 'Config File Probe',
|
||||||
|
];
|
||||||
|
|
||||||
|
$mapFile = '/opt/baldcanary/config/opencanary_event_labels.json';
|
||||||
|
if (is_readable($mapFile)) {
|
||||||
|
$oc = json_decode((string)file_get_contents($mapFile), true);
|
||||||
|
if (is_array($oc)) {
|
||||||
|
$labels = $labels + $oc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($labels[$type])) {
|
||||||
|
return $labels[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/^[a-z0-9_\-\.]+$/i', $type)) {
|
||||||
|
return ucwords(str_replace(['_', '-', '.'], ' ', $type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $type !== '' ? $type : 'Unknown Event';
|
||||||
|
}
|
||||||
PHP
|
PHP
|
||||||
|
|
||||||
cat > "$APP_ROOT/app/common/functions.php" <<'PHP'
|
cat > "$APP_ROOT/app/common/functions.php" <<'PHP'
|
||||||
@ -596,7 +736,7 @@ $webhooks = $db->query('SELECT * FROM webhook_targets ORDER BY id DESC')->fetchA
|
|||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($events as $event): ?>
|
<?php foreach ($events as $event): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?=h($event['event_time'])?></td>
|
<td><?=h(bc_local_time($event['event_time']))?></td>
|
||||||
<td><span class="badge text-bg-secondary"><?=h($event['severity'])?></span></td>
|
<td><span class="badge text-bg-secondary"><?=h($event['severity'])?></span></td>
|
||||||
<td><?=h(bc_event_label($event['event_type']))?></td>
|
<td><?=h(bc_event_label($event['event_type']))?></td>
|
||||||
<td><?=h($event['src_ip'])?></td>
|
<td><?=h($event['src_ip'])?></td>
|
||||||
@ -628,7 +768,7 @@ $profile = bc_setting('active_profile', 'Not set');
|
|||||||
body{font-family:Arial,sans-serif;margin:40px;color:#222}.cover{border-bottom:4px solid #222;margin-bottom:24px;padding-bottom:24px}.card{display:inline-block;border:1px solid #ddd;border-radius:10px;padding:16px;margin:8px 8px 8px 0;min-width:160px}.small{color:#666;font-size:12px}table{border-collapse:collapse;width:100%;margin-top:20px}th,td{border-bottom:1px solid #ddd;text-align:left;padding:8px;font-size:12px}th{background:#f1f1f1}@media print{button{display:none}}
|
body{font-family:Arial,sans-serif;margin:40px;color:#222}.cover{border-bottom:4px solid #222;margin-bottom:24px;padding-bottom:24px}.card{display:inline-block;border:1px solid #ddd;border-radius:10px;padding:16px;margin:8px 8px 8px 0;min-width:160px}.small{color:#666;font-size:12px}table{border-collapse:collapse;width:100%;margin-top:20px}th,td{border-bottom:1px solid #ddd;text-align:left;padding:8px;font-size:12px}th{background:#f1f1f1}@media print{button{display:none}}
|
||||||
</style></head><body>
|
</style></head><body>
|
||||||
<button onclick="window.print()">Print / Save as PDF</button>
|
<button onclick="window.print()">Print / Save as PDF</button>
|
||||||
<div class="cover"><h1>BaldCanary Pentest Validation Report</h1><p>Generated: <?=h(date('F j, Y g:i A'))?></p><p>Active profile: <strong><?=h($profile)?></strong></p></div>
|
<div class="cover"><h1>BaldCanary Pentest Validation Report</h1><p>Generated: <?=h((new DateTime('now', new DateTimeZone(bc_local_timezone())))->format('F j, Y g:i A'))?></p><p>Active profile: <strong><?=h($profile)?></strong></p></div>
|
||||||
<div class="card"><div class="small">Total Events</div><h2><?=count($events)?></h2></div>
|
<div class="card"><div class="small">Total Events</div><h2><?=count($events)?></h2></div>
|
||||||
<div class="card"><div class="small">Unique Source IPs</div><h2><?=count(array_unique(array_filter(array_column($events,'src_ip'))))?></h2></div>
|
<div class="card"><div class="small">Unique Source IPs</div><h2><?=count(array_unique(array_filter(array_column($events,'src_ip'))))?></h2></div>
|
||||||
<div class="card"><div class="small">High Interest Events</div><h2><?=count(array_filter($events, fn($e)=>in_array($e['severity'],['high','critical'],true)))?></h2></div>
|
<div class="card"><div class="small">High Interest Events</div><h2><?=count(array_filter($events, fn($e)=>in_array($e['severity'],['high','critical'],true)))?></h2></div>
|
||||||
@ -636,7 +776,7 @@ body{font-family:Arial,sans-serif;margin:40px;color:#222}.cover{border-bottom:4p
|
|||||||
<p>BaldCanary recorded interaction with the selected deception profile and exposed canary services. Events below may indicate penetration test activity, unauthorized curiosity, automated scanning, or attempted exploitation.</p>
|
<p>BaldCanary recorded interaction with the selected deception profile and exposed canary services. Events below may indicate penetration test activity, unauthorized curiosity, automated scanning, or attempted exploitation.</p>
|
||||||
<h2>Event Timeline</h2>
|
<h2>Event Timeline</h2>
|
||||||
<table><thead><tr><th>Time</th><th>Severity</th><th>Type</th><th>Source IP</th><th>Method</th><th>Path</th><th>Bait</th></tr></thead><tbody>
|
<table><thead><tr><th>Time</th><th>Severity</th><th>Type</th><th>Source IP</th><th>Method</th><th>Path</th><th>Bait</th></tr></thead><tbody>
|
||||||
<?php foreach ($events as $e): ?><tr><td><?=h($e['event_time'])?></td><td><?=h($e['severity'])?></td><td><?=h(bc_event_label($e['event_type']))?></td><td><?=h($e['src_ip'])?></td><td><?=h($e['method'])?></td><td><?=h($e['path'])?></td><td><?=h($e['matched_bait'])?></td></tr><?php endforeach; ?>
|
<?php foreach ($events as $e): ?><tr><td><?=h(bc_local_time($e['event_time']))?></td><td><?=h($e['severity'])?></td><td><?=h(bc_event_label($e['event_type']))?></td><td><?=h($e['src_ip'])?></td><td><?=h($e['method'])?></td><td><?=h($e['path'])?></td><td><?=h($e['matched_bait'])?></td></tr><?php endforeach; ?>
|
||||||
</tbody></table>
|
</tbody></table>
|
||||||
</body></html>
|
</body></html>
|
||||||
PHP
|
PHP
|
||||||
@ -1415,6 +1555,7 @@ main() {
|
|||||||
install_packages
|
install_packages
|
||||||
create_user_and_dirs
|
create_user_and_dirs
|
||||||
install_python_env
|
install_python_env
|
||||||
|
write_opencanary_event_map
|
||||||
init_sqlite
|
init_sqlite
|
||||||
write_common_php
|
write_common_php
|
||||||
write_admin_app
|
write_admin_app
|
||||||
|
|||||||
Reference in New Issue
Block a user