gophish-ruby 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +99 -2
- data/README.md +491 -1
- data/docs/API_REFERENCE.md +925 -0
- data/docs/EXAMPLES.md +1635 -0
- data/docs/GETTING_STARTED.md +364 -1
- data/lib/gophish/campaign.rb +330 -0
- data/lib/gophish/smtp.rb +99 -0
- data/lib/gophish/template.rb +7 -2
- data/lib/gophish/version.rb +1 -1
- data/lib/gophish-ruby.rb +2 -0
- metadata +3 -1
data/docs/EXAMPLES.md
CHANGED
@@ -8,6 +8,8 @@ This document contains practical examples for common use cases with the Gophish
|
|
8
8
|
- [CSV Operations](#csv-operations)
|
9
9
|
- [Template Operations](#template-operations)
|
10
10
|
- [Page Operations](#page-operations)
|
11
|
+
- [SMTP Operations](#smtp-operations)
|
12
|
+
- [Campaign Operations](#campaign-operations)
|
11
13
|
- [Error Handling](#error-handling)
|
12
14
|
- [Advanced Scenarios](#advanced-scenarios)
|
13
15
|
- [Production Examples](#production-examples)
|
@@ -1236,6 +1238,1639 @@ pages = create_page_variants("Microsoft Security Alert", base_html, variants)
|
|
1236
1238
|
puts "\nCreated #{pages.length} page variants for A/B testing"
|
1237
1239
|
```
|
1238
1240
|
|
1241
|
+
## SMTP Operations
|
1242
|
+
|
1243
|
+
### Basic SMTP Profile Creation
|
1244
|
+
|
1245
|
+
```ruby
|
1246
|
+
# Simple SMTP profile without authentication
|
1247
|
+
basic_smtp = Gophish::Smtp.new(
|
1248
|
+
name: "Company Mail Server",
|
1249
|
+
host: "smtp.company.com",
|
1250
|
+
from_address: "security@company.com"
|
1251
|
+
)
|
1252
|
+
|
1253
|
+
if basic_smtp.save
|
1254
|
+
puts "✓ Basic SMTP profile created: #{basic_smtp.id}"
|
1255
|
+
puts " Host: #{basic_smtp.host}"
|
1256
|
+
puts " From: #{basic_smtp.from_address}"
|
1257
|
+
else
|
1258
|
+
puts "✗ Failed to create SMTP profile: #{basic_smtp.errors.full_messages}"
|
1259
|
+
end
|
1260
|
+
```
|
1261
|
+
|
1262
|
+
### SMTP Profile with Authentication
|
1263
|
+
|
1264
|
+
```ruby
|
1265
|
+
# SMTP with username/password authentication
|
1266
|
+
auth_smtp = Gophish::Smtp.new(
|
1267
|
+
name: "Gmail SMTP with Authentication",
|
1268
|
+
host: "smtp.gmail.com",
|
1269
|
+
from_address: "phishing.test@company.com",
|
1270
|
+
username: "smtp_service@company.com",
|
1271
|
+
password: "app_specific_password",
|
1272
|
+
ignore_cert_errors: false
|
1273
|
+
)
|
1274
|
+
|
1275
|
+
puts "SMTP Configuration:"
|
1276
|
+
puts " Name: #{auth_smtp.name}"
|
1277
|
+
puts " Host: #{auth_smtp.host}"
|
1278
|
+
puts " From: #{auth_smtp.from_address}"
|
1279
|
+
puts " Uses Authentication: #{auth_smtp.has_authentication?}"
|
1280
|
+
puts " Ignores SSL Errors: #{auth_smtp.ignores_cert_errors?}"
|
1281
|
+
|
1282
|
+
if auth_smtp.save
|
1283
|
+
puts "✓ Authenticated SMTP profile created: #{auth_smtp.id}"
|
1284
|
+
end
|
1285
|
+
```
|
1286
|
+
|
1287
|
+
### SMTP Profile with Custom Headers
|
1288
|
+
|
1289
|
+
```ruby
|
1290
|
+
# SMTP profile with multiple custom headers
|
1291
|
+
header_smtp = Gophish::Smtp.new(
|
1292
|
+
name: "Custom Headers Mail Server",
|
1293
|
+
host: "mail.company.com",
|
1294
|
+
from_address: "security-team@company.com"
|
1295
|
+
)
|
1296
|
+
|
1297
|
+
# Add headers for better deliverability and tracking
|
1298
|
+
header_smtp.add_header("X-Mailer", "Gophish Security Training Platform v2.0")
|
1299
|
+
header_smtp.add_header("X-Department", "Information Security")
|
1300
|
+
header_smtp.add_header("X-Campaign-ID", "Q4-2024-PHISH-001")
|
1301
|
+
header_smtp.add_header("Return-Path", "bounces+security@company.com")
|
1302
|
+
header_smtp.add_header("Reply-To", "security-team@company.com")
|
1303
|
+
header_smtp.add_header("X-Priority", "1")
|
1304
|
+
header_smtp.add_header("X-MSMail-Priority", "High")
|
1305
|
+
|
1306
|
+
puts "SMTP Profile with Headers:"
|
1307
|
+
puts " Name: #{header_smtp.name}"
|
1308
|
+
puts " Headers: #{header_smtp.header_count}"
|
1309
|
+
puts " Has Headers: #{header_smtp.has_headers?}"
|
1310
|
+
|
1311
|
+
header_smtp.headers.each_with_index do |header, index|
|
1312
|
+
key = header[:key] || header['key']
|
1313
|
+
value = header[:value] || header['value']
|
1314
|
+
puts " #{index + 1}. #{key}: #{value}"
|
1315
|
+
end
|
1316
|
+
|
1317
|
+
if header_smtp.save
|
1318
|
+
puts "✓ SMTP profile with headers created: #{header_smtp.id}"
|
1319
|
+
end
|
1320
|
+
```
|
1321
|
+
|
1322
|
+
### Production-Ready SMTP Configuration
|
1323
|
+
|
1324
|
+
```ruby
|
1325
|
+
# Comprehensive SMTP setup for production use
|
1326
|
+
def create_production_smtp(name, host, from_address, username = nil, password = nil)
|
1327
|
+
puts "Creating production SMTP profile: #{name}"
|
1328
|
+
|
1329
|
+
smtp = Gophish::Smtp.new(
|
1330
|
+
name: name,
|
1331
|
+
host: host,
|
1332
|
+
from_address: from_address,
|
1333
|
+
username: username,
|
1334
|
+
password: password,
|
1335
|
+
ignore_cert_errors: false, # Always verify SSL in production
|
1336
|
+
interface_type: "SMTP"
|
1337
|
+
)
|
1338
|
+
|
1339
|
+
# Add production-grade headers
|
1340
|
+
smtp.add_header("X-Mailer", "Corporate Security Training System")
|
1341
|
+
smtp.add_header("X-Environment", "Production")
|
1342
|
+
smtp.add_header("X-Security-Classification", "Internal")
|
1343
|
+
smtp.add_header("Return-Path", "bounces+security@#{extract_domain(from_address)}")
|
1344
|
+
smtp.add_header("List-Unsubscribe", "<mailto:security-opt-out@#{extract_domain(from_address)}>")
|
1345
|
+
|
1346
|
+
# Validate configuration
|
1347
|
+
unless smtp.valid?
|
1348
|
+
puts " ✗ Configuration invalid:"
|
1349
|
+
smtp.errors.full_messages.each { |error| puts " - #{error}" }
|
1350
|
+
return nil
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
# Security checks
|
1354
|
+
puts " Security Assessment:"
|
1355
|
+
puts " SSL Verification: #{smtp.ignore_cert_errors? ? '✗ DISABLED' : '✓ ENABLED'}"
|
1356
|
+
puts " Authentication: #{smtp.has_authentication? ? '✓ ENABLED' : '⚠ DISABLED'}"
|
1357
|
+
puts " Custom Headers: #{smtp.header_count}"
|
1358
|
+
puts " From Domain: #{extract_domain(smtp.from_address)}"
|
1359
|
+
|
1360
|
+
if smtp.save
|
1361
|
+
puts " ✓ Production SMTP profile created (ID: #{smtp.id})"
|
1362
|
+
return smtp
|
1363
|
+
else
|
1364
|
+
puts " ✗ Failed to save SMTP profile:"
|
1365
|
+
smtp.errors.full_messages.each { |error| puts " - #{error}" }
|
1366
|
+
return nil
|
1367
|
+
end
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
def extract_domain(email)
|
1371
|
+
email.split('@').last
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
# Usage examples
|
1375
|
+
prod_smtp = create_production_smtp(
|
1376
|
+
"Production Mail Server",
|
1377
|
+
"smtp.company.com",
|
1378
|
+
"security-training@company.com",
|
1379
|
+
"smtp_service_account",
|
1380
|
+
ENV['SMTP_PASSWORD'] # Use environment variables for passwords
|
1381
|
+
)
|
1382
|
+
|
1383
|
+
gmail_smtp = create_production_smtp(
|
1384
|
+
"Gmail SMTP for Testing",
|
1385
|
+
"smtp.gmail.com",
|
1386
|
+
"test-campaigns@company.com",
|
1387
|
+
"test-account@company.com",
|
1388
|
+
ENV['GMAIL_APP_PASSWORD']
|
1389
|
+
)
|
1390
|
+
```
|
1391
|
+
|
1392
|
+
### SMTP Profile Management Operations
|
1393
|
+
|
1394
|
+
```ruby
|
1395
|
+
# List all SMTP profiles with details
|
1396
|
+
def list_smtp_profiles
|
1397
|
+
smtp_profiles = Gophish::Smtp.all
|
1398
|
+
puts "Found #{smtp_profiles.length} SMTP profiles:"
|
1399
|
+
|
1400
|
+
smtp_profiles.each do |smtp|
|
1401
|
+
features = []
|
1402
|
+
features << "🔐 Auth" if smtp.has_authentication?
|
1403
|
+
features << "📧 Headers(#{smtp.header_count})" if smtp.has_headers?
|
1404
|
+
features << "⚠️ No SSL" if smtp.ignores_cert_errors?
|
1405
|
+
|
1406
|
+
feature_text = features.any? ? " [#{features.join(', ')}]" : ""
|
1407
|
+
|
1408
|
+
puts " #{smtp.id}: #{smtp.name}#{feature_text}"
|
1409
|
+
puts " Host: #{smtp.host}"
|
1410
|
+
puts " From: #{smtp.from_address}"
|
1411
|
+
puts " Interface: #{smtp.interface_type}"
|
1412
|
+
puts " Modified: #{smtp.modified_date}" if smtp.modified_date
|
1413
|
+
puts
|
1414
|
+
end
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
# Update existing SMTP profile
|
1418
|
+
def update_smtp_profile(smtp_id, updates = {})
|
1419
|
+
begin
|
1420
|
+
smtp = Gophish::Smtp.find(smtp_id)
|
1421
|
+
rescue StandardError
|
1422
|
+
puts "✗ SMTP profile #{smtp_id} not found"
|
1423
|
+
return false
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
puts "Updating SMTP profile '#{smtp.name}'"
|
1427
|
+
original_values = {}
|
1428
|
+
|
1429
|
+
# Apply updates and track changes
|
1430
|
+
updates.each do |field, value|
|
1431
|
+
if smtp.respond_to?("#{field}=")
|
1432
|
+
original_values[field] = smtp.send(field)
|
1433
|
+
smtp.send("#{field}=", value)
|
1434
|
+
puts " #{field}: '#{original_values[field]}' → '#{value}'"
|
1435
|
+
else
|
1436
|
+
puts " ⚠️ Unknown field: #{field}"
|
1437
|
+
end
|
1438
|
+
end
|
1439
|
+
|
1440
|
+
if smtp.save
|
1441
|
+
puts " ✓ SMTP profile updated successfully"
|
1442
|
+
true
|
1443
|
+
else
|
1444
|
+
puts " ✗ Update failed:"
|
1445
|
+
smtp.errors.full_messages.each { |error| puts " - #{error}" }
|
1446
|
+
false
|
1447
|
+
end
|
1448
|
+
end
|
1449
|
+
|
1450
|
+
# Clone SMTP profile with modifications
|
1451
|
+
def clone_smtp_profile(original_id, new_name, modifications = {})
|
1452
|
+
begin
|
1453
|
+
original = Gophish::Smtp.find(original_id)
|
1454
|
+
rescue StandardError
|
1455
|
+
puts "✗ Original SMTP profile #{original_id} not found"
|
1456
|
+
return nil
|
1457
|
+
end
|
1458
|
+
|
1459
|
+
puts "Cloning SMTP profile '#{original.name}' as '#{new_name}'"
|
1460
|
+
|
1461
|
+
# Create clone with same settings
|
1462
|
+
cloned_smtp = Gophish::Smtp.new(
|
1463
|
+
name: new_name,
|
1464
|
+
host: original.host,
|
1465
|
+
from_address: original.from_address,
|
1466
|
+
username: original.username,
|
1467
|
+
password: original.password,
|
1468
|
+
interface_type: original.interface_type,
|
1469
|
+
ignore_cert_errors: original.ignore_cert_errors
|
1470
|
+
)
|
1471
|
+
|
1472
|
+
# Copy headers
|
1473
|
+
if original.has_headers?
|
1474
|
+
original.headers.each do |header|
|
1475
|
+
key = header[:key] || header['key']
|
1476
|
+
value = header[:value] || header['value']
|
1477
|
+
cloned_smtp.add_header(key, value)
|
1478
|
+
end
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
# Apply modifications
|
1482
|
+
modifications.each do |field, value|
|
1483
|
+
case field
|
1484
|
+
when :headers
|
1485
|
+
# Clear existing headers and add new ones
|
1486
|
+
cloned_smtp.headers.clear
|
1487
|
+
value.each { |key, val| cloned_smtp.add_header(key, val) }
|
1488
|
+
else
|
1489
|
+
cloned_smtp.send("#{field}=", value) if cloned_smtp.respond_to?("#{field}=")
|
1490
|
+
end
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
if cloned_smtp.save
|
1494
|
+
puts " ✓ SMTP profile cloned successfully (ID: #{cloned_smtp.id})"
|
1495
|
+
puts " Features:"
|
1496
|
+
puts " Authentication: #{cloned_smtp.has_authentication?}"
|
1497
|
+
puts " Headers: #{cloned_smtp.header_count}"
|
1498
|
+
puts " SSL Verification: #{cloned_smtp.ignore_cert_errors? ? 'Disabled' : 'Enabled'}"
|
1499
|
+
return cloned_smtp
|
1500
|
+
else
|
1501
|
+
puts " ✗ Clone failed:"
|
1502
|
+
cloned_smtp.errors.full_messages.each { |error| puts " - #{error}" }
|
1503
|
+
return nil
|
1504
|
+
end
|
1505
|
+
end
|
1506
|
+
|
1507
|
+
# Usage examples
|
1508
|
+
list_smtp_profiles
|
1509
|
+
|
1510
|
+
# Update an existing profile
|
1511
|
+
update_smtp_profile(1, {
|
1512
|
+
name: "Updated Company SMTP",
|
1513
|
+
ignore_cert_errors: true
|
1514
|
+
})
|
1515
|
+
|
1516
|
+
# Clone with modifications
|
1517
|
+
cloned = clone_smtp_profile(1, "Test Environment SMTP", {
|
1518
|
+
from_address: "test@company.com",
|
1519
|
+
ignore_cert_errors: true,
|
1520
|
+
headers: {
|
1521
|
+
"X-Environment" => "Testing",
|
1522
|
+
"X-Test-Campaign" => "true"
|
1523
|
+
}
|
1524
|
+
})
|
1525
|
+
```
|
1526
|
+
|
1527
|
+
### Header Management Examples
|
1528
|
+
|
1529
|
+
```ruby
|
1530
|
+
# Advanced header management
|
1531
|
+
def manage_smtp_headers(smtp_id)
|
1532
|
+
begin
|
1533
|
+
smtp = Gophish::Smtp.find(smtp_id)
|
1534
|
+
rescue StandardError
|
1535
|
+
puts "✗ SMTP profile #{smtp_id} not found"
|
1536
|
+
return
|
1537
|
+
end
|
1538
|
+
|
1539
|
+
puts "Managing headers for '#{smtp.name}'"
|
1540
|
+
puts "Current headers: #{smtp.header_count}"
|
1541
|
+
|
1542
|
+
# Display current headers
|
1543
|
+
if smtp.has_headers?
|
1544
|
+
puts " Current headers:"
|
1545
|
+
smtp.headers.each_with_index do |header, index|
|
1546
|
+
key = header[:key] || header['key']
|
1547
|
+
value = header[:value] || header['value']
|
1548
|
+
puts " #{index + 1}. #{key}: #{value}"
|
1549
|
+
end
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
# Add standard deliverability headers
|
1553
|
+
standard_headers = {
|
1554
|
+
"X-Mailer" => "Gophish Security Training v4.0",
|
1555
|
+
"X-Campaign-Type" => "Security Awareness",
|
1556
|
+
"X-Department" => "IT Security",
|
1557
|
+
"Return-Path" => "bounces@#{smtp.from_address.split('@').last}",
|
1558
|
+
"List-Unsubscribe" => "<mailto:unsubscribe@#{smtp.from_address.split('@').last}>",
|
1559
|
+
"X-Priority" => "Normal",
|
1560
|
+
"X-Auto-Response-Suppress" => "All"
|
1561
|
+
}
|
1562
|
+
|
1563
|
+
puts "\nAdding standard headers:"
|
1564
|
+
standard_headers.each do |key, value|
|
1565
|
+
smtp.add_header(key, value)
|
1566
|
+
puts " + #{key}: #{value}"
|
1567
|
+
end
|
1568
|
+
|
1569
|
+
# Remove any problematic headers
|
1570
|
+
problematic_headers = ["X-Spam", "X-Test", "X-Debug"]
|
1571
|
+
removed_count = 0
|
1572
|
+
|
1573
|
+
problematic_headers.each do |header_key|
|
1574
|
+
if smtp.headers.any? { |h| (h[:key] || h['key']) == header_key }
|
1575
|
+
smtp.remove_header(header_key)
|
1576
|
+
removed_count += 1
|
1577
|
+
puts " - Removed: #{header_key}"
|
1578
|
+
end
|
1579
|
+
end
|
1580
|
+
|
1581
|
+
puts "\nHeader management summary:"
|
1582
|
+
puts " Total headers: #{smtp.header_count}"
|
1583
|
+
puts " Added: #{standard_headers.length}"
|
1584
|
+
puts " Removed: #{removed_count}"
|
1585
|
+
|
1586
|
+
if smtp.save
|
1587
|
+
puts " ✓ Headers updated successfully"
|
1588
|
+
else
|
1589
|
+
puts " ✗ Failed to save header changes"
|
1590
|
+
end
|
1591
|
+
end
|
1592
|
+
|
1593
|
+
# Bulk header operations
|
1594
|
+
def standardize_all_smtp_headers
|
1595
|
+
smtp_profiles = Gophish::Smtp.all
|
1596
|
+
puts "Standardizing headers for #{smtp_profiles.length} SMTP profiles"
|
1597
|
+
|
1598
|
+
standard_headers = {
|
1599
|
+
"X-Mailer" => "Corporate Security Training Platform",
|
1600
|
+
"X-Campaign-Source" => "Internal Security Team",
|
1601
|
+
"Return-Path" => nil, # Will be set per profile
|
1602
|
+
"List-Unsubscribe" => nil # Will be set per profile
|
1603
|
+
}
|
1604
|
+
|
1605
|
+
smtp_profiles.each_with_index do |smtp, index|
|
1606
|
+
puts "[#{index + 1}/#{smtp_profiles.length}] Processing '#{smtp.name}'"
|
1607
|
+
|
1608
|
+
# Set dynamic headers based on from_address
|
1609
|
+
domain = smtp.from_address.split('@').last
|
1610
|
+
standard_headers["Return-Path"] = "bounces@#{domain}"
|
1611
|
+
standard_headers["List-Unsubscribe"] = "<mailto:unsubscribe@#{domain}>"
|
1612
|
+
|
1613
|
+
# Add missing standard headers
|
1614
|
+
added_count = 0
|
1615
|
+
standard_headers.each do |key, value|
|
1616
|
+
unless smtp.headers.any? { |h| (h[:key] || h['key']) == key }
|
1617
|
+
smtp.add_header(key, value)
|
1618
|
+
added_count += 1
|
1619
|
+
end
|
1620
|
+
end
|
1621
|
+
|
1622
|
+
if added_count > 0
|
1623
|
+
if smtp.save
|
1624
|
+
puts " ✓ Added #{added_count} standard headers"
|
1625
|
+
else
|
1626
|
+
puts " ✗ Failed to save headers"
|
1627
|
+
end
|
1628
|
+
else
|
1629
|
+
puts " - Already has standard headers"
|
1630
|
+
end
|
1631
|
+
end
|
1632
|
+
end
|
1633
|
+
|
1634
|
+
# Usage
|
1635
|
+
manage_smtp_headers(1)
|
1636
|
+
standardize_all_smtp_headers
|
1637
|
+
```
|
1638
|
+
|
1639
|
+
### SMTP Configuration Templates
|
1640
|
+
|
1641
|
+
```ruby
|
1642
|
+
# Pre-configured SMTP templates for common providers
|
1643
|
+
class SMTPTemplates
|
1644
|
+
TEMPLATES = {
|
1645
|
+
gmail: {
|
1646
|
+
host: "smtp.gmail.com",
|
1647
|
+
port: 587,
|
1648
|
+
interface_type: "SMTP",
|
1649
|
+
ignore_cert_errors: false,
|
1650
|
+
headers: {
|
1651
|
+
"X-Mailer" => "Gmail SMTP Integration",
|
1652
|
+
"X-Provider" => "Gmail"
|
1653
|
+
}
|
1654
|
+
},
|
1655
|
+
|
1656
|
+
office365: {
|
1657
|
+
host: "smtp.office365.com",
|
1658
|
+
port: 587,
|
1659
|
+
interface_type: "SMTP",
|
1660
|
+
ignore_cert_errors: false,
|
1661
|
+
headers: {
|
1662
|
+
"X-Mailer" => "Office 365 SMTP Integration",
|
1663
|
+
"X-Provider" => "Microsoft Office 365"
|
1664
|
+
}
|
1665
|
+
},
|
1666
|
+
|
1667
|
+
sendgrid: {
|
1668
|
+
host: "smtp.sendgrid.net",
|
1669
|
+
port: 587,
|
1670
|
+
interface_type: "SMTP",
|
1671
|
+
ignore_cert_errors: false,
|
1672
|
+
headers: {
|
1673
|
+
"X-Mailer" => "SendGrid SMTP Integration",
|
1674
|
+
"X-Provider" => "SendGrid"
|
1675
|
+
}
|
1676
|
+
},
|
1677
|
+
|
1678
|
+
mailgun: {
|
1679
|
+
host: "smtp.mailgun.org",
|
1680
|
+
port: 587,
|
1681
|
+
interface_type: "SMTP",
|
1682
|
+
ignore_cert_errors: false,
|
1683
|
+
headers: {
|
1684
|
+
"X-Mailer" => "Mailgun SMTP Integration",
|
1685
|
+
"X-Provider" => "Mailgun"
|
1686
|
+
}
|
1687
|
+
},
|
1688
|
+
|
1689
|
+
ses: {
|
1690
|
+
host: "email-smtp.us-east-1.amazonaws.com",
|
1691
|
+
port: 587,
|
1692
|
+
interface_type: "SMTP",
|
1693
|
+
ignore_cert_errors: false,
|
1694
|
+
headers: {
|
1695
|
+
"X-Mailer" => "Amazon SES SMTP Integration",
|
1696
|
+
"X-Provider" => "Amazon SES"
|
1697
|
+
}
|
1698
|
+
}
|
1699
|
+
}.freeze
|
1700
|
+
|
1701
|
+
def self.create_from_template(template_name, name, from_address, username, password)
|
1702
|
+
template = TEMPLATES[template_name.to_sym]
|
1703
|
+
unless template
|
1704
|
+
puts "✗ Unknown template: #{template_name}"
|
1705
|
+
puts "Available templates: #{TEMPLATES.keys.join(', ')}"
|
1706
|
+
return nil
|
1707
|
+
end
|
1708
|
+
|
1709
|
+
puts "Creating SMTP profile from #{template_name} template"
|
1710
|
+
|
1711
|
+
smtp = Gophish::Smtp.new(
|
1712
|
+
name: name,
|
1713
|
+
host: template[:host],
|
1714
|
+
from_address: from_address,
|
1715
|
+
username: username,
|
1716
|
+
password: password,
|
1717
|
+
interface_type: template[:interface_type],
|
1718
|
+
ignore_cert_errors: template[:ignore_cert_errors]
|
1719
|
+
)
|
1720
|
+
|
1721
|
+
# Add template headers
|
1722
|
+
template[:headers].each do |key, value|
|
1723
|
+
smtp.add_header(key, value)
|
1724
|
+
end
|
1725
|
+
|
1726
|
+
# Add common headers
|
1727
|
+
smtp.add_header("Return-Path", "bounces@#{from_address.split('@').last}")
|
1728
|
+
smtp.add_header("List-Unsubscribe", "<mailto:unsubscribe@#{from_address.split('@').last}>")
|
1729
|
+
|
1730
|
+
puts "Template configuration:"
|
1731
|
+
puts " Host: #{smtp.host}"
|
1732
|
+
puts " From: #{smtp.from_address}"
|
1733
|
+
puts " Headers: #{smtp.header_count}"
|
1734
|
+
puts " SSL Verification: #{smtp.ignore_cert_errors? ? 'Disabled' : 'Enabled'}"
|
1735
|
+
|
1736
|
+
if smtp.valid?
|
1737
|
+
if smtp.save
|
1738
|
+
puts " ✓ SMTP profile created successfully (ID: #{smtp.id})"
|
1739
|
+
return smtp
|
1740
|
+
else
|
1741
|
+
puts " ✗ Save failed: #{smtp.errors.full_messages.join(', ')}"
|
1742
|
+
end
|
1743
|
+
else
|
1744
|
+
puts " ✗ Validation failed: #{smtp.errors.full_messages.join(', ')}"
|
1745
|
+
end
|
1746
|
+
|
1747
|
+
nil
|
1748
|
+
end
|
1749
|
+
|
1750
|
+
def self.list_templates
|
1751
|
+
puts "Available SMTP templates:"
|
1752
|
+
TEMPLATES.each do |name, config|
|
1753
|
+
puts " #{name}:"
|
1754
|
+
puts " Host: #{config[:host]}"
|
1755
|
+
puts " Provider: #{config[:headers]['X-Provider']}"
|
1756
|
+
puts " SSL: #{config[:ignore_cert_errors] ? 'Optional' : 'Required'}"
|
1757
|
+
end
|
1758
|
+
end
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
# Usage examples
|
1762
|
+
SMTPTemplates.list_templates
|
1763
|
+
|
1764
|
+
# Create Gmail SMTP
|
1765
|
+
gmail = SMTPTemplates.create_from_template(
|
1766
|
+
:gmail,
|
1767
|
+
"Corporate Gmail SMTP",
|
1768
|
+
"security@company.com",
|
1769
|
+
"security@company.com",
|
1770
|
+
ENV['GMAIL_APP_PASSWORD']
|
1771
|
+
)
|
1772
|
+
|
1773
|
+
# Create Office 365 SMTP
|
1774
|
+
office365 = SMTPTemplates.create_from_template(
|
1775
|
+
:office365,
|
1776
|
+
"Office 365 Mail Server",
|
1777
|
+
"training@company.com",
|
1778
|
+
"smtp_service@company.onmicrosoft.com",
|
1779
|
+
ENV['O365_PASSWORD']
|
1780
|
+
)
|
1781
|
+
|
1782
|
+
# Create SendGrid SMTP
|
1783
|
+
sendgrid = SMTPTemplates.create_from_template(
|
1784
|
+
:sendgrid,
|
1785
|
+
"SendGrid Transactional Mail",
|
1786
|
+
"no-reply@company.com",
|
1787
|
+
"apikey",
|
1788
|
+
ENV['SENDGRID_API_KEY']
|
1789
|
+
)
|
1790
|
+
```
|
1791
|
+
|
1792
|
+
### SMTP Testing and Validation
|
1793
|
+
|
1794
|
+
```ruby
|
1795
|
+
# Comprehensive SMTP testing
|
1796
|
+
class SMTPTester
|
1797
|
+
def self.validate_smtp_profile(smtp_id)
|
1798
|
+
begin
|
1799
|
+
smtp = Gophish::Smtp.find(smtp_id)
|
1800
|
+
rescue StandardError
|
1801
|
+
puts "✗ SMTP profile #{smtp_id} not found"
|
1802
|
+
return false
|
1803
|
+
end
|
1804
|
+
|
1805
|
+
puts "Validating SMTP profile: #{smtp.name}"
|
1806
|
+
puts "=" * 50
|
1807
|
+
|
1808
|
+
# Basic validation
|
1809
|
+
unless smtp.valid?
|
1810
|
+
puts "✗ Basic validation failed:"
|
1811
|
+
smtp.errors.full_messages.each { |error| puts " - #{error}" }
|
1812
|
+
return false
|
1813
|
+
end
|
1814
|
+
puts "✓ Basic validation passed"
|
1815
|
+
|
1816
|
+
# Configuration check
|
1817
|
+
puts "\nConfiguration Details:"
|
1818
|
+
puts " Name: #{smtp.name}"
|
1819
|
+
puts " Host: #{smtp.host}"
|
1820
|
+
puts " From Address: #{smtp.from_address}"
|
1821
|
+
puts " Interface Type: #{smtp.interface_type}"
|
1822
|
+
puts " Username: #{smtp.username || 'Not configured'}"
|
1823
|
+
puts " Password: #{smtp.password ? '[SET]' : '[NOT SET]'}"
|
1824
|
+
puts " SSL Verification: #{smtp.ignore_cert_errors? ? 'Disabled' : 'Enabled'}"
|
1825
|
+
|
1826
|
+
# Authentication check
|
1827
|
+
if smtp.has_authentication?
|
1828
|
+
puts "✓ Authentication configured"
|
1829
|
+
else
|
1830
|
+
puts "⚠ No authentication configured - ensure your SMTP server allows it"
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
# SSL/Security check
|
1834
|
+
if smtp.ignore_cert_errors?
|
1835
|
+
puts "⚠ SSL certificate verification disabled"
|
1836
|
+
puts " This may be acceptable for development but not for production"
|
1837
|
+
else
|
1838
|
+
puts "✓ SSL certificate verification enabled"
|
1839
|
+
end
|
1840
|
+
|
1841
|
+
# Header analysis
|
1842
|
+
puts "\nHeader Analysis:"
|
1843
|
+
if smtp.has_headers?
|
1844
|
+
puts " Custom headers: #{smtp.header_count}"
|
1845
|
+
|
1846
|
+
required_headers = ['Return-Path', 'List-Unsubscribe']
|
1847
|
+
recommended_headers = ['X-Mailer', 'X-Campaign-Type', 'Reply-To']
|
1848
|
+
|
1849
|
+
required_headers.each do |header|
|
1850
|
+
has_header = smtp.headers.any? { |h| (h[:key] || h['key']) == header }
|
1851
|
+
puts " #{has_header ? '✓' : '✗'} #{header}: #{has_header ? 'Present' : 'Missing'}"
|
1852
|
+
end
|
1853
|
+
|
1854
|
+
recommended_headers.each do |header|
|
1855
|
+
has_header = smtp.headers.any? { |h| (h[:key] || h['key']) == header }
|
1856
|
+
puts " #{has_header ? '✓' : '⚠'} #{header}: #{has_header ? 'Present' : 'Recommended'}"
|
1857
|
+
end
|
1858
|
+
|
1859
|
+
# List all headers
|
1860
|
+
puts "\n All headers:"
|
1861
|
+
smtp.headers.each_with_index do |header, index|
|
1862
|
+
key = header[:key] || header['key']
|
1863
|
+
value = header[:value] || header['value']
|
1864
|
+
puts " #{index + 1}. #{key}: #{value}"
|
1865
|
+
end
|
1866
|
+
else
|
1867
|
+
puts " ⚠ No custom headers configured"
|
1868
|
+
puts " Consider adding headers for better deliverability"
|
1869
|
+
end
|
1870
|
+
|
1871
|
+
# Domain analysis
|
1872
|
+
domain = smtp.from_address.split('@').last
|
1873
|
+
puts "\nDomain Analysis:"
|
1874
|
+
puts " From domain: #{domain}"
|
1875
|
+
puts " SMTP host: #{smtp.host}"
|
1876
|
+
|
1877
|
+
# Check if domain matches SMTP host
|
1878
|
+
if smtp.host.include?(domain) || domain.include?(smtp.host.split('.').last(2).join('.'))
|
1879
|
+
puts "✓ Domain and SMTP host appear to match"
|
1880
|
+
else
|
1881
|
+
puts "⚠ Domain and SMTP host don't obviously match"
|
1882
|
+
puts " Ensure your SMTP provider is authorized to send for #{domain}"
|
1883
|
+
end
|
1884
|
+
|
1885
|
+
puts "\n" + "=" * 50
|
1886
|
+
puts "✓ SMTP profile validation completed"
|
1887
|
+
true
|
1888
|
+
end
|
1889
|
+
|
1890
|
+
def self.security_audit_all_smtp
|
1891
|
+
smtp_profiles = Gophish::Smtp.all
|
1892
|
+
puts "Security Audit: #{smtp_profiles.length} SMTP Profiles"
|
1893
|
+
puts "=" * 60
|
1894
|
+
|
1895
|
+
issues = []
|
1896
|
+
|
1897
|
+
smtp_profiles.each_with_index do |smtp, index|
|
1898
|
+
puts "\n[#{index + 1}/#{smtp_profiles.length}] #{smtp.name}"
|
1899
|
+
|
1900
|
+
# Check for insecure settings
|
1901
|
+
if smtp.ignore_cert_errors?
|
1902
|
+
issues << "#{smtp.name}: SSL verification disabled"
|
1903
|
+
puts " ⚠️ SSL certificate verification disabled"
|
1904
|
+
end
|
1905
|
+
|
1906
|
+
unless smtp.has_authentication?
|
1907
|
+
issues << "#{smtp.name}: No authentication configured"
|
1908
|
+
puts " ⚠️ No authentication configured"
|
1909
|
+
end
|
1910
|
+
|
1911
|
+
# Check for test/debug indicators
|
1912
|
+
test_indicators = ['test', 'debug', 'dev', 'staging']
|
1913
|
+
if test_indicators.any? { |indicator| smtp.name.downcase.include?(indicator) }
|
1914
|
+
puts " ℹ️ Appears to be a test/development profile"
|
1915
|
+
end
|
1916
|
+
|
1917
|
+
# Check headers for security info
|
1918
|
+
if smtp.has_headers?
|
1919
|
+
smtp.headers.each do |header|
|
1920
|
+
key = header[:key] || header['key']
|
1921
|
+
value = header[:value] || header['value']
|
1922
|
+
|
1923
|
+
if key.downcase.include?('test') || value.downcase.include?('test')
|
1924
|
+
puts " ℹ️ Contains test-related headers"
|
1925
|
+
end
|
1926
|
+
end
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
puts " ✓ Basic security check completed"
|
1930
|
+
end
|
1931
|
+
|
1932
|
+
puts "\n" + "=" * 60
|
1933
|
+
puts "Security Audit Summary:"
|
1934
|
+
if issues.any?
|
1935
|
+
puts "Issues found:"
|
1936
|
+
issues.each { |issue| puts " - #{issue}" }
|
1937
|
+
else
|
1938
|
+
puts "✓ No security issues detected"
|
1939
|
+
end
|
1940
|
+
end
|
1941
|
+
end
|
1942
|
+
|
1943
|
+
# Usage
|
1944
|
+
SMTPTester.validate_smtp_profile(1)
|
1945
|
+
SMTPTester.security_audit_all_smtp
|
1946
|
+
```
|
1947
|
+
|
1948
|
+
## Campaign Operations
|
1949
|
+
|
1950
|
+
### Basic Campaign Creation
|
1951
|
+
|
1952
|
+
```ruby
|
1953
|
+
# Create a simple campaign using existing components
|
1954
|
+
campaign = Gophish::Campaign.new(
|
1955
|
+
name: "Q1 Security Awareness Campaign",
|
1956
|
+
template: { name: "Security Awareness Training" }, # Reference by name
|
1957
|
+
page: { name: "Microsoft Office 365 Login Clone" }, # Reference by name
|
1958
|
+
groups: [{ name: "Engineering Team" }], # Reference by name
|
1959
|
+
smtp: { name: "Company Mail Server" }, # Reference by name
|
1960
|
+
url: "https://training.company.com"
|
1961
|
+
)
|
1962
|
+
|
1963
|
+
if campaign.save
|
1964
|
+
puts "✓ Campaign created successfully!"
|
1965
|
+
puts " ID: #{campaign.id}"
|
1966
|
+
puts " Name: #{campaign.name}"
|
1967
|
+
puts " Status: #{campaign.status}"
|
1968
|
+
else
|
1969
|
+
puts "✗ Failed to create campaign:"
|
1970
|
+
campaign.errors.full_messages.each { |msg| puts " - #{msg}" }
|
1971
|
+
end
|
1972
|
+
```
|
1973
|
+
|
1974
|
+
### Campaign Creation with Object References
|
1975
|
+
|
1976
|
+
```ruby
|
1977
|
+
# Create campaign using actual object instances
|
1978
|
+
template = Gophish::Template.find(1)
|
1979
|
+
page = Gophish::Page.find(2)
|
1980
|
+
group = Gophish::Group.find(3)
|
1981
|
+
smtp = Gophish::Smtp.find(4)
|
1982
|
+
|
1983
|
+
campaign = Gophish::Campaign.new(
|
1984
|
+
name: "Advanced Security Training",
|
1985
|
+
template: template, # Full template object
|
1986
|
+
page: page, # Full page object
|
1987
|
+
groups: [group], # Array of group objects
|
1988
|
+
smtp: smtp, # Full SMTP object
|
1989
|
+
url: "https://secure-training.company.com"
|
1990
|
+
)
|
1991
|
+
|
1992
|
+
if campaign.save
|
1993
|
+
puts "✓ Campaign created with object references"
|
1994
|
+
puts " Template: #{campaign.template.name}"
|
1995
|
+
puts " Page: #{campaign.page.name}"
|
1996
|
+
puts " Groups: #{campaign.groups.map(&:name).join(', ')}"
|
1997
|
+
end
|
1998
|
+
```
|
1999
|
+
|
2000
|
+
### Scheduled Campaign Creation
|
2001
|
+
|
2002
|
+
```ruby
|
2003
|
+
# Create a campaign with specific launch timing
|
2004
|
+
scheduled_campaign = Gophish::Campaign.new(
|
2005
|
+
name: "Monday Morning Phishing Test",
|
2006
|
+
template: { name: "IT Security Alert" },
|
2007
|
+
page: { name: "Corporate Portal Login" },
|
2008
|
+
groups: [
|
2009
|
+
{ name: "Sales Team" },
|
2010
|
+
{ name: "Marketing Department" }
|
2011
|
+
],
|
2012
|
+
smtp: { name: "Gmail SMTP with Authentication" },
|
2013
|
+
url: "https://training-portal.company.com",
|
2014
|
+
launch_date: (Time.now + 2.days).beginning_of_day.iso8601, # Launch in 2 days at midnight
|
2015
|
+
send_by_date: (Time.now + 2.days).noon.iso8601 # Complete by noon
|
2016
|
+
)
|
2017
|
+
|
2018
|
+
if scheduled_campaign.save
|
2019
|
+
puts "✓ Scheduled campaign created"
|
2020
|
+
puts " Launch: #{scheduled_campaign.launch_date}"
|
2021
|
+
puts " Send by: #{scheduled_campaign.send_by_date}"
|
2022
|
+
puts " Launched? #{scheduled_campaign.launched?}"
|
2023
|
+
puts " Has deadline? #{scheduled_campaign.has_send_by_date?}"
|
2024
|
+
end
|
2025
|
+
```
|
2026
|
+
|
2027
|
+
### Campaign Monitoring and Analysis
|
2028
|
+
|
2029
|
+
```ruby
|
2030
|
+
# Monitor campaign progress
|
2031
|
+
def monitor_campaign(campaign_id)
|
2032
|
+
begin
|
2033
|
+
campaign = Gophish::Campaign.find(campaign_id)
|
2034
|
+
rescue StandardError => e
|
2035
|
+
puts "✗ Campaign not found: #{e.message}"
|
2036
|
+
return
|
2037
|
+
end
|
2038
|
+
|
2039
|
+
puts "Campaign Status Report"
|
2040
|
+
puts "=" * 50
|
2041
|
+
puts "Name: #{campaign.name}"
|
2042
|
+
puts "Status: #{campaign.status}"
|
2043
|
+
puts "Created: #{campaign.created_date}"
|
2044
|
+
puts "Launched: #{campaign.launch_date || 'Not launched'}"
|
2045
|
+
puts "Completed: #{campaign.completed_date || 'Not completed'}"
|
2046
|
+
puts
|
2047
|
+
|
2048
|
+
# Status checks
|
2049
|
+
puts "Status Checks:"
|
2050
|
+
puts " In progress? #{campaign.in_progress?}"
|
2051
|
+
puts " Completed? #{campaign.completed?}"
|
2052
|
+
puts " Has launch date? #{campaign.launched?}"
|
2053
|
+
puts " Has deadline? #{campaign.has_send_by_date?}"
|
2054
|
+
puts
|
2055
|
+
|
2056
|
+
# Results analysis
|
2057
|
+
if campaign.results.any?
|
2058
|
+
puts "Results Summary:"
|
2059
|
+
puts " Total targets: #{campaign.results.length}"
|
2060
|
+
|
2061
|
+
# Count by status
|
2062
|
+
status_counts = Hash.new(0)
|
2063
|
+
campaign.results.each { |result| status_counts[result.status] += 1 }
|
2064
|
+
|
2065
|
+
status_counts.each do |status, count|
|
2066
|
+
percentage = (count.to_f / campaign.results.length * 100).round(1)
|
2067
|
+
puts " #{status}: #{count} (#{percentage}%)"
|
2068
|
+
end
|
2069
|
+
|
2070
|
+
# Behavior analysis
|
2071
|
+
clicked_count = campaign.results.count(&:clicked?)
|
2072
|
+
opened_count = campaign.results.count(&:opened?)
|
2073
|
+
reported_count = campaign.results.count(&:reported?)
|
2074
|
+
submitted_count = campaign.results.count(&:submitted_data?)
|
2075
|
+
|
2076
|
+
puts "\nBehavior Analysis:"
|
2077
|
+
puts " 📧 Opened emails: #{opened_count}"
|
2078
|
+
puts " 🔗 Clicked links: #{clicked_count}"
|
2079
|
+
puts " 📝 Submitted data: #{submitted_count}"
|
2080
|
+
puts " 🚨 Reported phishing: #{reported_count}"
|
2081
|
+
puts " 📊 Click rate: #{(clicked_count.to_f / campaign.results.length * 100).round(1)}%"
|
2082
|
+
puts " 🛡️ Report rate: #{(reported_count.to_f / campaign.results.length * 100).round(1)}%"
|
2083
|
+
|
2084
|
+
# Individual results
|
2085
|
+
if campaign.results.length <= 10
|
2086
|
+
puts "\nIndividual Results:"
|
2087
|
+
campaign.results.each do |result|
|
2088
|
+
icon = result.clicked? ? "🔗" : result.opened? ? "📧" : result.reported? ? "🚨" : "📬"
|
2089
|
+
puts " #{icon} #{result.email} - #{result.status}"
|
2090
|
+
end
|
2091
|
+
end
|
2092
|
+
else
|
2093
|
+
puts "No results available yet"
|
2094
|
+
end
|
2095
|
+
|
2096
|
+
# Timeline events
|
2097
|
+
if campaign.timeline.any?
|
2098
|
+
puts "\nRecent Timeline Events (last 5):"
|
2099
|
+
campaign.timeline.last(5).each do |event|
|
2100
|
+
puts " #{event.time}: #{event.message}"
|
2101
|
+
puts " Email: #{event.email}" if event.email
|
2102
|
+
end
|
2103
|
+
end
|
2104
|
+
end
|
2105
|
+
|
2106
|
+
# Usage
|
2107
|
+
monitor_campaign(1)
|
2108
|
+
```
|
2109
|
+
|
2110
|
+
### Campaign Management Operations
|
2111
|
+
|
2112
|
+
```ruby
|
2113
|
+
# List all campaigns with status
|
2114
|
+
def list_all_campaigns
|
2115
|
+
campaigns = Gophish::Campaign.all
|
2116
|
+
puts "Found #{campaigns.length} campaigns:"
|
2117
|
+
puts
|
2118
|
+
|
2119
|
+
campaigns.each do |campaign|
|
2120
|
+
status_icon = case campaign.status
|
2121
|
+
when "In progress" then "🔄"
|
2122
|
+
when "Completed" then "✅"
|
2123
|
+
else "⏸️"
|
2124
|
+
end
|
2125
|
+
|
2126
|
+
puts "#{status_icon} #{campaign.id}: #{campaign.name}"
|
2127
|
+
puts " Status: #{campaign.status}"
|
2128
|
+
puts " Created: #{campaign.created_date}"
|
2129
|
+
puts " Launched: #{campaign.launch_date || 'Not scheduled'}"
|
2130
|
+
|
2131
|
+
if campaign.results.any?
|
2132
|
+
total = campaign.results.length
|
2133
|
+
clicked = campaign.results.count(&:clicked?)
|
2134
|
+
puts " Results: #{clicked}/#{total} clicked (#{(clicked.to_f/total*100).round(1)}%)"
|
2135
|
+
end
|
2136
|
+
puts
|
2137
|
+
end
|
2138
|
+
end
|
2139
|
+
|
2140
|
+
# Complete a running campaign
|
2141
|
+
def complete_campaign(campaign_id)
|
2142
|
+
begin
|
2143
|
+
campaign = Gophish::Campaign.find(campaign_id)
|
2144
|
+
rescue StandardError => e
|
2145
|
+
puts "✗ Campaign not found: #{e.message}"
|
2146
|
+
return false
|
2147
|
+
end
|
2148
|
+
|
2149
|
+
unless campaign.in_progress?
|
2150
|
+
puts "✗ Campaign '#{campaign.name}' is not in progress (status: #{campaign.status})"
|
2151
|
+
return false
|
2152
|
+
end
|
2153
|
+
|
2154
|
+
puts "Completing campaign '#{campaign.name}'..."
|
2155
|
+
|
2156
|
+
begin
|
2157
|
+
result = campaign.complete!
|
2158
|
+
|
2159
|
+
if result['success']
|
2160
|
+
puts "✓ Campaign completed successfully"
|
2161
|
+
puts " New status: #{campaign.status}"
|
2162
|
+
puts " Completed date: #{campaign.completed_date}"
|
2163
|
+
return true
|
2164
|
+
else
|
2165
|
+
puts "✗ Failed to complete campaign: #{result['message'] || 'Unknown error'}"
|
2166
|
+
return false
|
2167
|
+
end
|
2168
|
+
rescue StandardError => e
|
2169
|
+
puts "✗ Error completing campaign: #{e.message}"
|
2170
|
+
return false
|
2171
|
+
end
|
2172
|
+
end
|
2173
|
+
|
2174
|
+
# Clone campaign with modifications
|
2175
|
+
def clone_campaign(original_id, new_name, modifications = {})
|
2176
|
+
begin
|
2177
|
+
original = Gophish::Campaign.find(original_id)
|
2178
|
+
rescue StandardError => e
|
2179
|
+
puts "✗ Original campaign not found: #{e.message}"
|
2180
|
+
return nil
|
2181
|
+
end
|
2182
|
+
|
2183
|
+
puts "Cloning campaign '#{original.name}' as '#{new_name}'"
|
2184
|
+
|
2185
|
+
# Create clone with same settings
|
2186
|
+
cloned_campaign = Gophish::Campaign.new(
|
2187
|
+
name: new_name,
|
2188
|
+
template: original.template,
|
2189
|
+
page: original.page,
|
2190
|
+
groups: original.groups,
|
2191
|
+
smtp: original.smtp,
|
2192
|
+
url: original.url,
|
2193
|
+
launch_date: original.launch_date,
|
2194
|
+
send_by_date: original.send_by_date
|
2195
|
+
)
|
2196
|
+
|
2197
|
+
# Apply modifications
|
2198
|
+
modifications.each do |field, value|
|
2199
|
+
if cloned_campaign.respond_to?("#{field}=")
|
2200
|
+
cloned_campaign.send("#{field}=", value)
|
2201
|
+
puts " Modified #{field}: #{value}"
|
2202
|
+
else
|
2203
|
+
puts " ⚠️ Unknown field: #{field}"
|
2204
|
+
end
|
2205
|
+
end
|
2206
|
+
|
2207
|
+
if cloned_campaign.save
|
2208
|
+
puts "✓ Campaign cloned successfully (ID: #{cloned_campaign.id})"
|
2209
|
+
return cloned_campaign
|
2210
|
+
else
|
2211
|
+
puts "✗ Clone failed:"
|
2212
|
+
cloned_campaign.errors.full_messages.each { |msg| puts " - #{msg}" }
|
2213
|
+
return nil
|
2214
|
+
end
|
2215
|
+
end
|
2216
|
+
|
2217
|
+
# Usage examples
|
2218
|
+
list_all_campaigns
|
2219
|
+
complete_campaign(1)
|
2220
|
+
|
2221
|
+
# Clone with new launch date
|
2222
|
+
cloned = clone_campaign(1, "Cloned Security Training", {
|
2223
|
+
launch_date: (Time.now + 1.week).iso8601,
|
2224
|
+
url: "https://testing.company.com"
|
2225
|
+
})
|
2226
|
+
```
|
2227
|
+
|
2228
|
+
### Complete Campaign Workflow
|
2229
|
+
|
2230
|
+
```ruby
|
2231
|
+
# End-to-end campaign creation and management
|
2232
|
+
class CampaignManager
|
2233
|
+
def initialize
|
2234
|
+
@logger = Logger.new(STDOUT)
|
2235
|
+
end
|
2236
|
+
|
2237
|
+
def create_complete_campaign(config)
|
2238
|
+
@logger.info "Creating complete campaign: #{config[:name]}"
|
2239
|
+
|
2240
|
+
# Step 1: Create target group
|
2241
|
+
group = create_or_find_group(config[:group_name], config[:csv_file])
|
2242
|
+
return nil unless group
|
2243
|
+
|
2244
|
+
# Step 2: Create template
|
2245
|
+
template = create_template(config[:template])
|
2246
|
+
return nil unless template
|
2247
|
+
|
2248
|
+
# Step 3: Create landing page
|
2249
|
+
page = create_page(config[:page])
|
2250
|
+
return nil unless page
|
2251
|
+
|
2252
|
+
# Step 4: Create SMTP profile
|
2253
|
+
smtp = create_or_find_smtp(config[:smtp])
|
2254
|
+
return nil unless smtp
|
2255
|
+
|
2256
|
+
# Step 5: Create campaign
|
2257
|
+
campaign = create_campaign(config, template, page, group, smtp)
|
2258
|
+
return nil unless campaign
|
2259
|
+
|
2260
|
+
@logger.info "Campaign creation completed successfully"
|
2261
|
+
campaign
|
2262
|
+
end
|
2263
|
+
|
2264
|
+
private
|
2265
|
+
|
2266
|
+
def create_or_find_group(name, csv_file)
|
2267
|
+
@logger.info "Creating group: #{name}"
|
2268
|
+
|
2269
|
+
# Check if group already exists
|
2270
|
+
existing_groups = Gophish::Group.all
|
2271
|
+
existing = existing_groups.find { |g| g.name == name }
|
2272
|
+
|
2273
|
+
if existing
|
2274
|
+
@logger.info "Using existing group: #{name} (#{existing.targets.length} targets)"
|
2275
|
+
return existing
|
2276
|
+
end
|
2277
|
+
|
2278
|
+
# Create new group
|
2279
|
+
group = Gophish::Group.new(name: name)
|
2280
|
+
|
2281
|
+
if csv_file && File.exist?(csv_file)
|
2282
|
+
csv_content = File.read(csv_file)
|
2283
|
+
group.import_csv(csv_content)
|
2284
|
+
@logger.info "Imported #{group.targets.length} targets from #{csv_file}"
|
2285
|
+
else
|
2286
|
+
@logger.warn "No CSV file provided or file not found: #{csv_file}"
|
2287
|
+
return nil
|
2288
|
+
end
|
2289
|
+
|
2290
|
+
if group.save
|
2291
|
+
@logger.info "Group created successfully (ID: #{group.id})"
|
2292
|
+
return group
|
2293
|
+
else
|
2294
|
+
@logger.error "Failed to create group: #{group.errors.full_messages.join(', ')}"
|
2295
|
+
return nil
|
2296
|
+
end
|
2297
|
+
end
|
2298
|
+
|
2299
|
+
def create_template(config)
|
2300
|
+
@logger.info "Creating template: #{config[:name]}"
|
2301
|
+
|
2302
|
+
template = Gophish::Template.new(
|
2303
|
+
name: config[:name],
|
2304
|
+
envelope_sender: config[:envelope_sender],
|
2305
|
+
subject: config[:subject],
|
2306
|
+
html: config[:html],
|
2307
|
+
text: config[:text]
|
2308
|
+
)
|
2309
|
+
|
2310
|
+
# Add attachments if specified
|
2311
|
+
if config[:attachments]
|
2312
|
+
config[:attachments].each do |attachment_config|
|
2313
|
+
file_content = File.read(attachment_config[:file_path])
|
2314
|
+
template.add_attachment(
|
2315
|
+
file_content,
|
2316
|
+
attachment_config[:content_type],
|
2317
|
+
attachment_config[:filename]
|
2318
|
+
)
|
2319
|
+
@logger.info "Added attachment: #{attachment_config[:filename]}"
|
2320
|
+
end
|
2321
|
+
end
|
2322
|
+
|
2323
|
+
if template.save
|
2324
|
+
@logger.info "Template created successfully (ID: #{template.id})"
|
2325
|
+
return template
|
2326
|
+
else
|
2327
|
+
@logger.error "Failed to create template: #{template.errors.full_messages.join(', ')}"
|
2328
|
+
return nil
|
2329
|
+
end
|
2330
|
+
end
|
2331
|
+
|
2332
|
+
def create_page(config)
|
2333
|
+
@logger.info "Creating page: #{config[:name]}"
|
2334
|
+
|
2335
|
+
page = Gophish::Page.new(
|
2336
|
+
name: config[:name],
|
2337
|
+
html: config[:html],
|
2338
|
+
capture_credentials: config[:capture_credentials] || false,
|
2339
|
+
capture_passwords: config[:capture_passwords] || false,
|
2340
|
+
redirect_url: config[:redirect_url]
|
2341
|
+
)
|
2342
|
+
|
2343
|
+
if page.save
|
2344
|
+
@logger.info "Page created successfully (ID: #{page.id})"
|
2345
|
+
return page
|
2346
|
+
else
|
2347
|
+
@logger.error "Failed to create page: #{page.errors.full_messages.join(', ')}"
|
2348
|
+
return nil
|
2349
|
+
end
|
2350
|
+
end
|
2351
|
+
|
2352
|
+
def create_or_find_smtp(config)
|
2353
|
+
@logger.info "Creating SMTP profile: #{config[:name]}"
|
2354
|
+
|
2355
|
+
# Check if SMTP profile already exists
|
2356
|
+
existing_smtps = Gophish::Smtp.all
|
2357
|
+
existing = existing_smtps.find { |s| s.name == config[:name] }
|
2358
|
+
|
2359
|
+
if existing
|
2360
|
+
@logger.info "Using existing SMTP profile: #{config[:name]}"
|
2361
|
+
return existing
|
2362
|
+
end
|
2363
|
+
|
2364
|
+
smtp = Gophish::Smtp.new(
|
2365
|
+
name: config[:name],
|
2366
|
+
host: config[:host],
|
2367
|
+
from_address: config[:from_address],
|
2368
|
+
username: config[:username],
|
2369
|
+
password: config[:password],
|
2370
|
+
ignore_cert_errors: config[:ignore_cert_errors] || false
|
2371
|
+
)
|
2372
|
+
|
2373
|
+
# Add headers if specified
|
2374
|
+
if config[:headers]
|
2375
|
+
config[:headers].each do |key, value|
|
2376
|
+
smtp.add_header(key, value)
|
2377
|
+
end
|
2378
|
+
@logger.info "Added #{config[:headers].length} custom headers"
|
2379
|
+
end
|
2380
|
+
|
2381
|
+
if smtp.save
|
2382
|
+
@logger.info "SMTP profile created successfully (ID: #{smtp.id})"
|
2383
|
+
return smtp
|
2384
|
+
else
|
2385
|
+
@logger.error "Failed to create SMTP profile: #{smtp.errors.full_messages.join(', ')}"
|
2386
|
+
return nil
|
2387
|
+
end
|
2388
|
+
end
|
2389
|
+
|
2390
|
+
def create_campaign(config, template, page, group, smtp)
|
2391
|
+
@logger.info "Creating campaign: #{config[:name]}"
|
2392
|
+
|
2393
|
+
campaign = Gophish::Campaign.new(
|
2394
|
+
name: config[:name],
|
2395
|
+
template: template,
|
2396
|
+
page: page,
|
2397
|
+
groups: [group],
|
2398
|
+
smtp: smtp,
|
2399
|
+
url: config[:url],
|
2400
|
+
launch_date: config[:launch_date],
|
2401
|
+
send_by_date: config[:send_by_date]
|
2402
|
+
)
|
2403
|
+
|
2404
|
+
if campaign.save
|
2405
|
+
@logger.info "Campaign created successfully (ID: #{campaign.id})"
|
2406
|
+
@logger.info "Campaign components:"
|
2407
|
+
@logger.info " Template: #{template.name} (ID: #{template.id})"
|
2408
|
+
@logger.info " Page: #{page.name} (ID: #{page.id})"
|
2409
|
+
@logger.info " Group: #{group.name} (#{group.targets.length} targets)"
|
2410
|
+
@logger.info " SMTP: #{smtp.name} (#{smtp.host})"
|
2411
|
+
return campaign
|
2412
|
+
else
|
2413
|
+
@logger.error "Failed to create campaign: #{campaign.errors.full_messages.join(', ')}"
|
2414
|
+
return nil
|
2415
|
+
end
|
2416
|
+
end
|
2417
|
+
end
|
2418
|
+
|
2419
|
+
# Usage example with complete configuration
|
2420
|
+
manager = CampaignManager.new
|
2421
|
+
|
2422
|
+
campaign_config = {
|
2423
|
+
name: "Q2 2024 Security Awareness Campaign",
|
2424
|
+
group_name: "All Employees Q2",
|
2425
|
+
csv_file: "employees_q2.csv",
|
2426
|
+
template: {
|
2427
|
+
name: "IT Security Alert - Q2 2024",
|
2428
|
+
envelope_sender: "noreply@company.com",
|
2429
|
+
subject: "URGENT: Security Update Required",
|
2430
|
+
html: <<~HTML,
|
2431
|
+
<html>
|
2432
|
+
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
2433
|
+
<div style="background: #f8f9fa; padding: 20px; border-radius: 10px;">
|
2434
|
+
<h2 style="color: #dc3545;">🔒 Security Alert</h2>
|
2435
|
+
<p>Dear {{.FirstName}} {{.LastName}},</p>
|
2436
|
+
<p>We have detected suspicious activity on your account and need you to verify your credentials immediately.</p>
|
2437
|
+
<div style="text-align: center; margin: 30px 0;">
|
2438
|
+
<a href="{{.URL}}" style="background: #007bff; color: white; padding: 15px 30px; text-decoration: none; border-radius: 5px; font-weight: bold;">
|
2439
|
+
Verify Account Now
|
2440
|
+
</a>
|
2441
|
+
</div>
|
2442
|
+
<p><small>This verification link will expire in 24 hours.</small></p>
|
2443
|
+
<p>Best regards,<br>IT Security Team</p>
|
2444
|
+
</div>
|
2445
|
+
</body>
|
2446
|
+
</html>
|
2447
|
+
HTML
|
2448
|
+
text: "Security Alert: Please verify your account at {{.URL}}"
|
2449
|
+
},
|
2450
|
+
page: {
|
2451
|
+
name: "Corporate Security Portal",
|
2452
|
+
html: File.read("templates/security_portal.html"), # Load from file
|
2453
|
+
capture_credentials: true,
|
2454
|
+
capture_passwords: true,
|
2455
|
+
redirect_url: "https://company.com/security-confirmed"
|
2456
|
+
},
|
2457
|
+
smtp: {
|
2458
|
+
name: "Corporate SMTP Server",
|
2459
|
+
host: "smtp.company.com",
|
2460
|
+
from_address: "security@company.com",
|
2461
|
+
username: ENV['SMTP_USERNAME'],
|
2462
|
+
password: ENV['SMTP_PASSWORD'],
|
2463
|
+
headers: {
|
2464
|
+
"X-Mailer" => "Corporate Security Training Platform",
|
2465
|
+
"X-Campaign-Type" => "Security Awareness",
|
2466
|
+
"Return-Path" => "bounces@company.com"
|
2467
|
+
}
|
2468
|
+
},
|
2469
|
+
url: "https://security-portal.company.com",
|
2470
|
+
launch_date: (Time.now + 1.day).iso8601,
|
2471
|
+
send_by_date: (Time.now + 2.days).iso8601
|
2472
|
+
}
|
2473
|
+
|
2474
|
+
campaign = manager.create_complete_campaign(campaign_config)
|
2475
|
+
|
2476
|
+
if campaign
|
2477
|
+
puts "\n🎉 Complete campaign created successfully!"
|
2478
|
+
puts "Campaign ID: #{campaign.id}"
|
2479
|
+
puts "Launch Date: #{campaign.launch_date}"
|
2480
|
+
puts "Monitor progress at: https://your-gophish-server.com/campaigns/#{campaign.id}"
|
2481
|
+
else
|
2482
|
+
puts "\n❌ Campaign creation failed. Check logs for details."
|
2483
|
+
end
|
2484
|
+
```
|
2485
|
+
|
2486
|
+
### Campaign Results Export and Reporting
|
2487
|
+
|
2488
|
+
```ruby
|
2489
|
+
# Advanced campaign reporting and data export
|
2490
|
+
class CampaignReporter
|
2491
|
+
def self.generate_detailed_report(campaign_id, output_file = nil)
|
2492
|
+
begin
|
2493
|
+
campaign = Gophish::Campaign.find(campaign_id)
|
2494
|
+
rescue StandardError => e
|
2495
|
+
puts "✗ Campaign not found: #{e.message}"
|
2496
|
+
return nil
|
2497
|
+
end
|
2498
|
+
|
2499
|
+
report = build_campaign_report(campaign)
|
2500
|
+
|
2501
|
+
if output_file
|
2502
|
+
File.write(output_file, report)
|
2503
|
+
puts "✓ Report saved to #{output_file}"
|
2504
|
+
else
|
2505
|
+
puts report
|
2506
|
+
end
|
2507
|
+
|
2508
|
+
report
|
2509
|
+
end
|
2510
|
+
|
2511
|
+
def self.export_results_csv(campaign_id, output_file)
|
2512
|
+
begin
|
2513
|
+
campaign = Gophish::Campaign.find(campaign_id)
|
2514
|
+
rescue StandardError => e
|
2515
|
+
puts "✗ Campaign not found: #{e.message}"
|
2516
|
+
return false
|
2517
|
+
end
|
2518
|
+
|
2519
|
+
require 'csv'
|
2520
|
+
|
2521
|
+
CSV.open(output_file, 'w') do |csv|
|
2522
|
+
# Headers
|
2523
|
+
csv << [
|
2524
|
+
'First Name', 'Last Name', 'Email', 'Position', 'Status',
|
2525
|
+
'Sent Date', 'IP Address', 'Latitude', 'Longitude',
|
2526
|
+
'Clicked', 'Opened', 'Submitted Data', 'Reported'
|
2527
|
+
]
|
2528
|
+
|
2529
|
+
# Data rows
|
2530
|
+
campaign.results.each do |result|
|
2531
|
+
csv << [
|
2532
|
+
result.first_name,
|
2533
|
+
result.last_name,
|
2534
|
+
result.email,
|
2535
|
+
result.position,
|
2536
|
+
result.status,
|
2537
|
+
result.send_date,
|
2538
|
+
result.ip,
|
2539
|
+
result.latitude,
|
2540
|
+
result.longitude,
|
2541
|
+
result.clicked?,
|
2542
|
+
result.opened?,
|
2543
|
+
result.submitted_data?,
|
2544
|
+
result.reported?
|
2545
|
+
]
|
2546
|
+
end
|
2547
|
+
end
|
2548
|
+
|
2549
|
+
puts "✓ Results exported to #{output_file}"
|
2550
|
+
true
|
2551
|
+
end
|
2552
|
+
|
2553
|
+
private
|
2554
|
+
|
2555
|
+
def self.build_campaign_report(campaign)
|
2556
|
+
report = []
|
2557
|
+
report << "=" * 80
|
2558
|
+
report << "CAMPAIGN REPORT: #{campaign.name}"
|
2559
|
+
report << "=" * 80
|
2560
|
+
report << ""
|
2561
|
+
|
2562
|
+
# Basic information
|
2563
|
+
report << "📋 Basic Information:"
|
2564
|
+
report << " Campaign ID: #{campaign.id}"
|
2565
|
+
report << " Status: #{campaign.status}"
|
2566
|
+
report << " Created: #{campaign.created_date}"
|
2567
|
+
report << " Launched: #{campaign.launch_date || 'Not launched'}"
|
2568
|
+
report << " Completed: #{campaign.completed_date || 'Not completed'}"
|
2569
|
+
report << ""
|
2570
|
+
|
2571
|
+
# Campaign components
|
2572
|
+
report << "🔧 Campaign Components:"
|
2573
|
+
report << " Template: #{campaign.template&.name || 'Unknown'}"
|
2574
|
+
report << " Landing Page: #{campaign.page&.name || 'Unknown'}"
|
2575
|
+
report << " SMTP Profile: #{campaign.smtp&.name || 'Unknown'}"
|
2576
|
+
report << " Target Groups: #{campaign.groups&.map(&:name)&.join(', ') || 'Unknown'}"
|
2577
|
+
report << " Campaign URL: #{campaign.url}"
|
2578
|
+
report << ""
|
2579
|
+
|
2580
|
+
if campaign.results.any?
|
2581
|
+
total_targets = campaign.results.length
|
2582
|
+
|
2583
|
+
# Summary statistics
|
2584
|
+
report << "📊 Results Summary:"
|
2585
|
+
report << " Total Targets: #{total_targets}"
|
2586
|
+
|
2587
|
+
# Count by status
|
2588
|
+
status_counts = Hash.new(0)
|
2589
|
+
campaign.results.each { |result| status_counts[result.status] += 1 }
|
2590
|
+
|
2591
|
+
status_counts.each do |status, count|
|
2592
|
+
percentage = (count.to_f / total_targets * 100).round(1)
|
2593
|
+
report << " #{status}: #{count} (#{percentage}%)"
|
2594
|
+
end
|
2595
|
+
|
2596
|
+
report << ""
|
2597
|
+
|
2598
|
+
# Behavior analysis
|
2599
|
+
sent_count = campaign.results.count(&:sent?)
|
2600
|
+
opened_count = campaign.results.count(&:opened?)
|
2601
|
+
clicked_count = campaign.results.count(&:clicked?)
|
2602
|
+
submitted_count = campaign.results.count(&:submitted_data?)
|
2603
|
+
reported_count = campaign.results.count(&:reported?)
|
2604
|
+
|
2605
|
+
report << "🎯 Behavior Analysis:"
|
2606
|
+
report << " 📧 Emails Sent: #{sent_count} (#{percentage_of(sent_count, total_targets)}%)"
|
2607
|
+
report << " 📖 Emails Opened: #{opened_count} (#{percentage_of(opened_count, total_targets)}%)"
|
2608
|
+
report << " 🔗 Links Clicked: #{clicked_count} (#{percentage_of(clicked_count, total_targets)}%)"
|
2609
|
+
report << " 📝 Data Submitted: #{submitted_count} (#{percentage_of(submitted_count, total_targets)}%)"
|
2610
|
+
report << " 🚨 Phishing Reported: #{reported_count} (#{percentage_of(reported_count, total_targets)}%)"
|
2611
|
+
report << ""
|
2612
|
+
|
2613
|
+
# Risk assessment
|
2614
|
+
report << "⚖️ Security Risk Assessment:"
|
2615
|
+
click_rate = percentage_of(clicked_count, total_targets)
|
2616
|
+
report_rate = percentage_of(reported_count, total_targets)
|
2617
|
+
|
2618
|
+
if click_rate >= 30
|
2619
|
+
risk_level = "HIGH"
|
2620
|
+
risk_icon = "🔴"
|
2621
|
+
elsif click_rate >= 15
|
2622
|
+
risk_level = "MEDIUM"
|
2623
|
+
risk_icon = "🟡"
|
2624
|
+
else
|
2625
|
+
risk_level = "LOW"
|
2626
|
+
risk_icon = "🟢"
|
2627
|
+
end
|
2628
|
+
|
2629
|
+
report << " #{risk_icon} Overall Risk Level: #{risk_level}"
|
2630
|
+
report << " Click Rate: #{click_rate}% (#{rate_assessment(click_rate, 'click')})"
|
2631
|
+
report << " Report Rate: #{report_rate}% (#{rate_assessment(report_rate, 'report')})"
|
2632
|
+
report << ""
|
2633
|
+
|
2634
|
+
# Geographic analysis
|
2635
|
+
if campaign.results.any? { |r| r.latitude && r.longitude }
|
2636
|
+
report << "🗺️ Geographic Distribution:"
|
2637
|
+
locations = campaign.results
|
2638
|
+
.select { |r| r.latitude && r.longitude }
|
2639
|
+
.group_by { |r| "#{r.latitude.round(2)}, #{r.longitude.round(2)}" }
|
2640
|
+
|
2641
|
+
locations.each do |location, results|
|
2642
|
+
report << " #{location}: #{results.length} interactions"
|
2643
|
+
end
|
2644
|
+
report << ""
|
2645
|
+
end
|
2646
|
+
|
2647
|
+
# Timeline analysis
|
2648
|
+
if campaign.timeline.any?
|
2649
|
+
report << "⏰ Timeline Events (last 10):"
|
2650
|
+
campaign.timeline.last(10).each do |event|
|
2651
|
+
report << " #{event.time}: #{event.message}"
|
2652
|
+
end
|
2653
|
+
report << ""
|
2654
|
+
end
|
2655
|
+
|
2656
|
+
# Recommendations
|
2657
|
+
report << "💡 Recommendations:"
|
2658
|
+
recommendations = generate_recommendations(campaign, click_rate, report_rate)
|
2659
|
+
recommendations.each { |rec| report << " • #{rec}" }
|
2660
|
+
|
2661
|
+
else
|
2662
|
+
report << "📊 No results available yet"
|
2663
|
+
end
|
2664
|
+
|
2665
|
+
report << ""
|
2666
|
+
report << "=" * 80
|
2667
|
+
report << "Report generated: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
2668
|
+
report << "=" * 80
|
2669
|
+
|
2670
|
+
report.join("\n")
|
2671
|
+
end
|
2672
|
+
|
2673
|
+
def self.percentage_of(count, total)
|
2674
|
+
return 0 if total.zero?
|
2675
|
+
(count.to_f / total * 100).round(1)
|
2676
|
+
end
|
2677
|
+
|
2678
|
+
def self.rate_assessment(rate, type)
|
2679
|
+
case type
|
2680
|
+
when 'click'
|
2681
|
+
case rate
|
2682
|
+
when 0..5 then "Excellent"
|
2683
|
+
when 6..10 then "Good"
|
2684
|
+
when 11..20 then "Concerning"
|
2685
|
+
when 21..30 then "Poor"
|
2686
|
+
else "Critical"
|
2687
|
+
end
|
2688
|
+
when 'report'
|
2689
|
+
case rate
|
2690
|
+
when 0..5 then "Critical - Low Awareness"
|
2691
|
+
when 6..15 then "Poor - Needs Training"
|
2692
|
+
when 16..25 then "Fair - Some Awareness"
|
2693
|
+
when 26..40 then "Good - Decent Awareness"
|
2694
|
+
else "Excellent - High Awareness"
|
2695
|
+
end
|
2696
|
+
end
|
2697
|
+
end
|
2698
|
+
|
2699
|
+
def self.generate_recommendations(campaign, click_rate, report_rate)
|
2700
|
+
recommendations = []
|
2701
|
+
|
2702
|
+
if click_rate >= 20
|
2703
|
+
recommendations << "High click rate indicates need for immediate security awareness training"
|
2704
|
+
recommendations << "Consider conducting follow-up educational sessions for all targets"
|
2705
|
+
end
|
2706
|
+
|
2707
|
+
if report_rate <= 10
|
2708
|
+
recommendations << "Low report rate suggests users don't know how to report phishing"
|
2709
|
+
recommendations << "Provide clear instructions on how to report suspicious emails"
|
2710
|
+
end
|
2711
|
+
|
2712
|
+
if campaign.results.any? { |r| r.submitted_data? }
|
2713
|
+
recommendations << "Some users submitted credentials - implement additional password security training"
|
2714
|
+
recommendations << "Consider mandatory password changes for users who submitted data"
|
2715
|
+
end
|
2716
|
+
|
2717
|
+
# Positive reinforcement
|
2718
|
+
if report_rate >= 25
|
2719
|
+
recommendations << "Good report rate - consider recognizing users who reported the phishing"
|
2720
|
+
end
|
2721
|
+
|
2722
|
+
if click_rate <= 10
|
2723
|
+
recommendations << "Low click rate indicates good security awareness - maintain current training"
|
2724
|
+
end
|
2725
|
+
|
2726
|
+
recommendations << "Schedule follow-up campaigns in 2-3 months to track improvement"
|
2727
|
+
recommendations
|
2728
|
+
end
|
2729
|
+
end
|
2730
|
+
|
2731
|
+
# Usage
|
2732
|
+
CampaignReporter.generate_detailed_report(1, "campaign_1_report.txt")
|
2733
|
+
CampaignReporter.export_results_csv(1, "campaign_1_results.csv")
|
2734
|
+
```
|
2735
|
+
|
2736
|
+
### Bulk Campaign Operations
|
2737
|
+
|
2738
|
+
```ruby
|
2739
|
+
# Create multiple campaigns for different departments
|
2740
|
+
def create_department_campaigns
|
2741
|
+
departments = [
|
2742
|
+
{
|
2743
|
+
name: "Sales Department",
|
2744
|
+
csv_file: "sales_team.csv",
|
2745
|
+
template_subject: "Q4 Sales Bonus Information",
|
2746
|
+
delay_hours: 0
|
2747
|
+
},
|
2748
|
+
{
|
2749
|
+
name: "HR Department",
|
2750
|
+
csv_file: "hr_team.csv",
|
2751
|
+
template_subject: "Employee Benefits Update",
|
2752
|
+
delay_hours: 24
|
2753
|
+
},
|
2754
|
+
{
|
2755
|
+
name: "IT Department",
|
2756
|
+
csv_file: "it_team.csv",
|
2757
|
+
template_subject: "System Maintenance Notification",
|
2758
|
+
delay_hours: 48
|
2759
|
+
},
|
2760
|
+
{
|
2761
|
+
name: "Finance Department",
|
2762
|
+
csv_file: "finance_team.csv",
|
2763
|
+
template_subject: "Budget Review Meeting",
|
2764
|
+
delay_hours: 72
|
2765
|
+
}
|
2766
|
+
]
|
2767
|
+
|
2768
|
+
created_campaigns = []
|
2769
|
+
|
2770
|
+
departments.each_with_index do |dept, index|
|
2771
|
+
puts "[#{index + 1}/#{departments.length}] Creating campaign for #{dept[:name]}"
|
2772
|
+
|
2773
|
+
# Create group
|
2774
|
+
group = Gophish::Group.new(name: dept[:name])
|
2775
|
+
if File.exist?(dept[:csv_file])
|
2776
|
+
csv_content = File.read(dept[:csv_file])
|
2777
|
+
group.import_csv(csv_content)
|
2778
|
+
else
|
2779
|
+
puts " ⚠️ CSV file not found: #{dept[:csv_file]}"
|
2780
|
+
next
|
2781
|
+
end
|
2782
|
+
|
2783
|
+
unless group.save
|
2784
|
+
puts " ✗ Failed to create group: #{group.errors.full_messages.join(', ')}"
|
2785
|
+
next
|
2786
|
+
end
|
2787
|
+
|
2788
|
+
# Create department-specific template
|
2789
|
+
template = Gophish::Template.new(
|
2790
|
+
name: "#{dept[:name]} - Security Test",
|
2791
|
+
envelope_sender: "noreply@company.com",
|
2792
|
+
subject: dept[:template_subject],
|
2793
|
+
html: generate_department_html(dept[:name], dept[:template_subject])
|
2794
|
+
)
|
2795
|
+
|
2796
|
+
unless template.save
|
2797
|
+
puts " ✗ Failed to create template: #{template.errors.full_messages.join(', ')}"
|
2798
|
+
next
|
2799
|
+
end
|
2800
|
+
|
2801
|
+
# Create campaign with staggered launch
|
2802
|
+
launch_time = Time.now + dept[:delay_hours].hours
|
2803
|
+
|
2804
|
+
campaign = Gophish::Campaign.new(
|
2805
|
+
name: "Security Awareness - #{dept[:name]}",
|
2806
|
+
template: template,
|
2807
|
+
page: { name: "Corporate Login Portal" }, # Assume this exists
|
2808
|
+
groups: [group],
|
2809
|
+
smtp: { name: "Corporate SMTP Server" }, # Assume this exists
|
2810
|
+
url: "https://security-test.company.com",
|
2811
|
+
launch_date: launch_time.iso8601
|
2812
|
+
)
|
2813
|
+
|
2814
|
+
if campaign.save
|
2815
|
+
puts " ✓ Campaign created (ID: #{campaign.id})"
|
2816
|
+
puts " Targets: #{group.targets.length}"
|
2817
|
+
puts " Launch: #{launch_time.strftime('%Y-%m-%d %H:%M')}"
|
2818
|
+
created_campaigns << campaign
|
2819
|
+
else
|
2820
|
+
puts " ✗ Failed to create campaign: #{campaign.errors.full_messages.join(', ')}"
|
2821
|
+
end
|
2822
|
+
|
2823
|
+
puts
|
2824
|
+
end
|
2825
|
+
|
2826
|
+
puts "Bulk campaign creation completed"
|
2827
|
+
puts "Successfully created: #{created_campaigns.length}/#{departments.length} campaigns"
|
2828
|
+
|
2829
|
+
created_campaigns
|
2830
|
+
end
|
2831
|
+
|
2832
|
+
def generate_department_html(department, subject)
|
2833
|
+
<<~HTML
|
2834
|
+
<html>
|
2835
|
+
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
2836
|
+
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
|
2837
|
+
<h1 style="margin: 0; font-size: 28px;">🏢 #{department}</h1>
|
2838
|
+
<p style="margin: 10px 0 0 0; font-size: 18px;">Important Notice</p>
|
2839
|
+
</div>
|
2840
|
+
|
2841
|
+
<div style="background: white; padding: 30px; border: 1px solid #ddd; border-radius: 0 0 10px 10px;">
|
2842
|
+
<h2 style="color: #333; margin-top: 0;">#{subject}</h2>
|
2843
|
+
|
2844
|
+
<p>Dear {{.FirstName}} {{.LastName}},</p>
|
2845
|
+
|
2846
|
+
<p>This message is specifically for members of the #{department}. Please review the information below and take the required action.</p>
|
2847
|
+
|
2848
|
+
<div style="background: #f8f9fa; padding: 20px; border-left: 4px solid #667eea; margin: 20px 0;">
|
2849
|
+
<p style="margin: 0;"><strong>Action Required:</strong> Please verify your department credentials to access the updated information.</p>
|
2850
|
+
</div>
|
2851
|
+
|
2852
|
+
<div style="text-align: center; margin: 30px 0;">
|
2853
|
+
<a href="{{.URL}}" style="background: #667eea; color: white; padding: 15px 30px; text-decoration: none; border-radius: 25px; font-weight: bold; display: inline-block;">
|
2854
|
+
Access #{department} Portal
|
2855
|
+
</a>
|
2856
|
+
</div>
|
2857
|
+
|
2858
|
+
<p style="color: #666; font-size: 12px; margin-top: 30px;">
|
2859
|
+
This is a security awareness exercise. If you believe this email is suspicious, please report it to the IT Security team.
|
2860
|
+
</p>
|
2861
|
+
|
2862
|
+
<p>Best regards,<br>
|
2863
|
+
Corporate Communications</p>
|
2864
|
+
</div>
|
2865
|
+
</body>
|
2866
|
+
</html>
|
2867
|
+
HTML
|
2868
|
+
end
|
2869
|
+
|
2870
|
+
# Usage
|
2871
|
+
campaigns = create_department_campaigns
|
2872
|
+
```
|
2873
|
+
|
1239
2874
|
## Error Handling
|
1240
2875
|
|
1241
2876
|
### Comprehensive Error Handling
|