authentication and security
This commit is contained in:
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
*.pem
|
*.pem
|
||||||
*.log
|
*.log
|
||||||
*.json
|
*.json
|
||||||
|
*.db
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
# Multi-stage build for security
|
||||||
|
FROM golang:1.21-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o honeypot main.go
|
||||||
|
|
||||||
|
# Final minimal image
|
||||||
|
FROM alpine:3.18
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN addgroup -g 1001 honeypot && \
|
||||||
|
adduser -D -s /bin/sh -u 1001 -G honeypot honeypot
|
||||||
|
|
||||||
|
# Install minimal dependencies
|
||||||
|
RUN apk --no-cache add ca-certificates sqlite
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy binary and set ownership
|
||||||
|
COPY --from=builder /app/honeypot .
|
||||||
|
COPY --from=builder /app/app/templates ./app/templates
|
||||||
|
RUN chown -R honeypot:honeypot /app
|
||||||
|
|
||||||
|
# Create restricted directories
|
||||||
|
RUN mkdir -p /app/data /app/logs && \
|
||||||
|
chown honeypot:honeypot /app/data /app/logs
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER honeypot
|
||||||
|
|
||||||
|
# Expose only necessary ports
|
||||||
|
EXPOSE 6333
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:6333/ || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["./honeypot"]
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
# 🔒 Secure Honeypot Deployment Guide
|
||||||
|
|
||||||
|
## 🚨 Critical Security Measures
|
||||||
|
|
||||||
|
### 1. **Container Deployment (MANDATORY)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and deploy with Docker
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Monitor logs
|
||||||
|
docker-compose logs -f honeypot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Network Isolation**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create isolated network
|
||||||
|
docker network create --driver bridge \
|
||||||
|
--subnet=172.30.0.0/16 \
|
||||||
|
--ip-range=172.30.240.0/20 \
|
||||||
|
honeypot-isolated
|
||||||
|
|
||||||
|
# Update docker-compose.yml to use isolated network
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **Firewall Configuration**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Allow only necessary ports
|
||||||
|
ufw default deny incoming
|
||||||
|
ufw default allow outgoing
|
||||||
|
|
||||||
|
# Honeypot services
|
||||||
|
ufw allow 2121/tcp # FTP
|
||||||
|
ufw allow 2222/tcp # SSH
|
||||||
|
ufw allow 2323/tcp # Telnet
|
||||||
|
ufw allow 2525/tcp # SMTP
|
||||||
|
ufw allow 3306/tcp # MySQL
|
||||||
|
ufw allow 3399/tcp # RDP
|
||||||
|
ufw allow 4450/tcp # SMB
|
||||||
|
ufw allow 5060/tcp # SIP
|
||||||
|
ufw allow 5432/tcp # PostgreSQL
|
||||||
|
ufw allow 8080/tcp # HTTP
|
||||||
|
ufw allow 8443/tcp # HTTPS
|
||||||
|
ufw allow 27017/tcp # MongoDB
|
||||||
|
ufw allow 1143/tcp # IMAP
|
||||||
|
|
||||||
|
# Dashboard (RESTRICT TO ADMIN IPs ONLY)
|
||||||
|
ufw allow from YOUR_ADMIN_IP to any port 6333
|
||||||
|
|
||||||
|
ufw enable
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. **Dashboard Access Control**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create admin IP whitelist
|
||||||
|
export ADMIN_IPS="192.168.1.100,10.0.0.50"
|
||||||
|
|
||||||
|
# Or use VPN/bastion host for dashboard access
|
||||||
|
# NEVER expose dashboard to public internet
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. **SSL/TLS Configuration**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate proper SSL certificates
|
||||||
|
openssl req -x509 -newkey rsa:4096 -keyout tls_key.pem -out tls_cert.pem -days 365 -nodes
|
||||||
|
|
||||||
|
# Or use Let's Encrypt for public-facing deployments
|
||||||
|
certbot certonly --standalone -d your-honeypot-domain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Threat Intelligence Export
|
||||||
|
|
||||||
|
### Available Formats
|
||||||
|
|
||||||
|
1. **Plain Text** (for simple blocklists)
|
||||||
|
```
|
||||||
|
GET /api/export/blocklist/txt?min_score=70&max_age=7d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **JSON** (for programmatic consumption)
|
||||||
|
```
|
||||||
|
GET /api/export/blocklist/json?min_score=60&include_unblocked=false
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Suricata Rules** (for IDS/IPS)
|
||||||
|
```
|
||||||
|
GET /api/export/blocklist/suricata?min_score=80
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **iptables Script** (for Linux firewalls)
|
||||||
|
```
|
||||||
|
GET /api/export/blocklist/iptables?min_score=70
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automated Blocklist Updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Update blocklist every hour
|
||||||
|
0 * * * * curl -s "http://your-honeypot:6333/api/export/blocklist/txt?min_score=70" > /etc/blocklist.txt && iptables-restore < /etc/iptables-blocklist.rules
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛡️ Security Monitoring
|
||||||
|
|
||||||
|
### 1. **Log Monitoring**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Monitor for suspicious activity
|
||||||
|
tail -f /var/log/honeypot/honeypot.log | grep -E "(brute_force|port_scan|high_threat)"
|
||||||
|
|
||||||
|
# Set up log rotation
|
||||||
|
logrotate -d /etc/logrotate.d/honeypot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Resource Monitoring**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Monitor container resources
|
||||||
|
docker stats honeydany
|
||||||
|
|
||||||
|
# Set up alerts for high CPU/memory usage
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **Database Backup**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backup threat intelligence database
|
||||||
|
sqlite3 app.db ".backup /backup/honeypot-$(date +%Y%m%d).db"
|
||||||
|
|
||||||
|
# Automated daily backups
|
||||||
|
0 2 * * * /usr/local/bin/backup-honeypot.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Configuration Hardening
|
||||||
|
|
||||||
|
### 1. **Disable Unnecessary Services**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"services": {
|
||||||
|
"http": true,
|
||||||
|
"https": true,
|
||||||
|
"ssh": true,
|
||||||
|
"ftp": false, // Disable if not needed
|
||||||
|
"smtp": false, // Disable if not needed
|
||||||
|
"mysql": true,
|
||||||
|
"postgresql": false,
|
||||||
|
"mongodb": true,
|
||||||
|
"telnet": false, // High risk - disable if possible
|
||||||
|
"imap": false,
|
||||||
|
"smb": false, // High risk - disable if possible
|
||||||
|
"rdp": false, // High risk - disable if possible
|
||||||
|
"sip": false,
|
||||||
|
"vnc": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Rate Limiting**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"security": {
|
||||||
|
"max_connections_per_ip": 5,
|
||||||
|
"connection_timeout": "2m",
|
||||||
|
"rate_limit_window": "1m",
|
||||||
|
"max_auth_attempts": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 Incident Response
|
||||||
|
|
||||||
|
### 1. **High-Threat Detection**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Immediate blocking of high-threat IPs
|
||||||
|
curl -X POST "http://localhost:6333/api/threat/block" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"ip": "THREAT_IP", "reason": "automated_high_threat"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Emergency Shutdown**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Emergency stop all services
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Or stop specific services
|
||||||
|
docker-compose stop honeypot
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 Performance Optimization
|
||||||
|
|
||||||
|
### 1. **Database Optimization**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Regular database maintenance
|
||||||
|
VACUUM;
|
||||||
|
ANALYZE;
|
||||||
|
|
||||||
|
-- Index optimization
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_threat_events_ip_time ON threat_events(ip, last_seen);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ip_analysis_score ON ip_analysis(threat_score);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Log Rotation**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Configure log rotation
|
||||||
|
cat > /etc/logrotate.d/honeypot << EOF
|
||||||
|
/app/logs/*.log {
|
||||||
|
daily
|
||||||
|
rotate 30
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
create 644 honeypot honeypot
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Threat Intelligence Integration
|
||||||
|
|
||||||
|
### 1. **External Threat Feeds**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Integrate with external threat intelligence
|
||||||
|
curl -s "https://reputation-api.com/threats" | jq -r '.ips[]' >> external_threats.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Sharing Threat Intelligence**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Share your blocklist with community
|
||||||
|
curl -X POST "https://threat-sharing-platform.com/api/submit" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @honeypot_blocklist.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ **CRITICAL WARNINGS**
|
||||||
|
|
||||||
|
1. **NEVER run honeypot on production systems**
|
||||||
|
2. **ALWAYS use dedicated, isolated infrastructure**
|
||||||
|
3. **NEVER expose dashboard to public internet**
|
||||||
|
4. **ALWAYS use strong authentication**
|
||||||
|
5. **REGULARLY update and patch the system**
|
||||||
|
6. **MONITOR for compromise indicators**
|
||||||
|
7. **BACKUP threat intelligence data regularly**
|
||||||
|
|
||||||
|
## 📞 Emergency Contacts
|
||||||
|
|
||||||
|
- Security Team: security@yourorg.com
|
||||||
|
- Infrastructure Team: infra@yourorg.com
|
||||||
|
- 24/7 SOC: +1-xxx-xxx-xxxx
|
||||||
+39
-14
@@ -19,20 +19,20 @@ type Config struct {
|
|||||||
} `json:"web"`
|
} `json:"web"`
|
||||||
|
|
||||||
Services struct {
|
Services struct {
|
||||||
HTTP bool `json:"http"`
|
HTTP bool `json:"http"`
|
||||||
HTTPS bool `json:"https"`
|
HTTPS bool `json:"https"`
|
||||||
SSH bool `json:"ssh"`
|
SSH bool `json:"ssh"`
|
||||||
FTP bool `json:"ftp"`
|
FTP bool `json:"ftp"`
|
||||||
SMTP bool `json:"smtp"`
|
SMTP bool `json:"smtp"`
|
||||||
IMAP bool `json:"imap"`
|
IMAP bool `json:"imap"`
|
||||||
Telnet bool `json:"telnet"`
|
Telnet bool `json:"telnet"`
|
||||||
MySQL bool `json:"mysql"`
|
MySQL bool `json:"mysql"`
|
||||||
PostgreSQL bool `json:"postgresql"`
|
PostgreSQL bool `json:"postgresql"`
|
||||||
MongoDB bool `json:"mongodb"`
|
MongoDB bool `json:"mongodb"`
|
||||||
RDP bool `json:"rdp"`
|
RDP bool `json:"rdp"`
|
||||||
SMB bool `json:"smb"`
|
SMB bool `json:"smb"`
|
||||||
SIP bool `json:"sip"`
|
SIP bool `json:"sip"`
|
||||||
VNC bool `json:"vnc"`
|
VNC bool `json:"vnc"`
|
||||||
Generic []int `json:"generic"`
|
Generic []int `json:"generic"`
|
||||||
} `json:"services"`
|
} `json:"services"`
|
||||||
|
|
||||||
@@ -53,6 +53,19 @@ type Config struct {
|
|||||||
VNC int `json:"vnc"`
|
VNC int `json:"vnc"`
|
||||||
} `json:"ports"`
|
} `json:"ports"`
|
||||||
|
|
||||||
|
Security struct {
|
||||||
|
MaxInputLength int `json:"max_input_length"` // Maximum input length per command
|
||||||
|
MaxConnDuration string `json:"max_conn_duration"` // Maximum connection duration (e.g., "5m")
|
||||||
|
MaxCommands int `json:"max_commands"` // Maximum commands per connection
|
||||||
|
RateLimitWindow string `json:"rate_limit_window"` // Rate limiting window (e.g., "1m")
|
||||||
|
MaxConnPerIP int `json:"max_conn_per_ip"` // Maximum concurrent connections per IP
|
||||||
|
ReadTimeout string `json:"read_timeout"` // Read timeout for each operation
|
||||||
|
WriteTimeout string `json:"write_timeout"` // Write timeout for each operation
|
||||||
|
EnableRateLimit bool `json:"enable_rate_limit"` // Enable rate limiting
|
||||||
|
BlockHighThreatIPs bool `json:"block_high_threat_ips"` // Automatically block high threat IPs
|
||||||
|
ThreatScoreThreshold int `json:"threat_score_threshold"` // Threshold for automatic blocking
|
||||||
|
} `json:"security"`
|
||||||
|
|
||||||
// Certificates allows overriding default certificate/key locations.
|
// Certificates allows overriding default certificate/key locations.
|
||||||
Certificates struct {
|
Certificates struct {
|
||||||
// SSHHostKeyPath points to a PEM-encoded RSA private key to use as SSH host key.
|
// SSHHostKeyPath points to a PEM-encoded RSA private key to use as SSH host key.
|
||||||
@@ -149,5 +162,17 @@ func defaultConfig() Config {
|
|||||||
c.Ports.SIP = 5060
|
c.Ports.SIP = 5060
|
||||||
c.Ports.VNC = 5900
|
c.Ports.VNC = 5900
|
||||||
|
|
||||||
|
// Security defaults
|
||||||
|
c.Security.MaxInputLength = 4096
|
||||||
|
c.Security.MaxConnDuration = "5m"
|
||||||
|
c.Security.MaxCommands = 100
|
||||||
|
c.Security.RateLimitWindow = "1m"
|
||||||
|
c.Security.MaxConnPerIP = 10
|
||||||
|
c.Security.ReadTimeout = "30s"
|
||||||
|
c.Security.WriteTimeout = "10s"
|
||||||
|
c.Security.EnableRateLimit = true
|
||||||
|
c.Security.BlockHighThreatIPs = false
|
||||||
|
c.Security.ThreatScoreThreshold = 80
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,327 @@
|
|||||||
|
package dashboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlocklistExporter handles exporting threat intelligence for external use
|
||||||
|
type BlocklistExporter struct {
|
||||||
|
analyzer *ThreatAnalyzer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBlocklistExporter creates a new blocklist exporter
|
||||||
|
func NewBlocklistExporter(analyzer *ThreatAnalyzer) *BlocklistExporter {
|
||||||
|
return &BlocklistExporter{
|
||||||
|
analyzer: analyzer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterExportRoutes registers blocklist export endpoints
|
||||||
|
func (be *BlocklistExporter) RegisterExportRoutes(mux *http.ServeMux, sm *SecurityManager) {
|
||||||
|
// Public blocklist endpoints (no auth required for consumption)
|
||||||
|
mux.HandleFunc("/api/export/blocklist/txt", be.handleTxtBlocklist)
|
||||||
|
mux.HandleFunc("/api/export/blocklist/json", be.handleJSONBlocklist)
|
||||||
|
mux.HandleFunc("/api/export/blocklist/csv", be.handleCSVBlocklist)
|
||||||
|
mux.HandleFunc("/api/export/blocklist/suricata", be.handleSuricataBlocklist)
|
||||||
|
mux.HandleFunc("/api/export/blocklist/pf", be.handlePFBlocklist)
|
||||||
|
mux.HandleFunc("/api/export/blocklist/iptables", be.handleIPTablesBlocklist)
|
||||||
|
|
||||||
|
// Statistics endpoint
|
||||||
|
mux.HandleFunc("/api/export/stats", be.handleStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlocklistEntry represents an entry in the blocklist
|
||||||
|
type BlocklistEntry struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
ThreatScore int `json:"threat_score"`
|
||||||
|
FirstSeen time.Time `json:"first_seen"`
|
||||||
|
LastSeen time.Time `json:"last_seen"`
|
||||||
|
TotalEvents int `json:"total_events"`
|
||||||
|
Services []string `json:"services"`
|
||||||
|
EventTypes []string `json:"event_types"`
|
||||||
|
IsBlocked bool `json:"is_blocked"`
|
||||||
|
Confidence string `json:"confidence"` // high, medium, low
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBlocklistEntries retrieves blocklist entries with filtering
|
||||||
|
func (be *BlocklistExporter) getBlocklistEntries(minScore int, maxAge time.Duration, includeUnblocked bool) ([]BlocklistEntry, error) {
|
||||||
|
// Get IP reports with filters
|
||||||
|
filters := map[string]interface{}{
|
||||||
|
"min_threat_score": minScore,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !includeUnblocked {
|
||||||
|
filters["is_blocked"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
reports, err := be.analyzer.GetIPReports(filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries []BlocklistEntry
|
||||||
|
cutoff := time.Now().Add(-maxAge)
|
||||||
|
|
||||||
|
for _, report := range reports {
|
||||||
|
// Skip old entries if maxAge is specified
|
||||||
|
if maxAge > 0 && report.LastSeen.Before(cutoff) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get threat events for this IP
|
||||||
|
events, _ := be.analyzer.GetThreatEventsByIP(report.IP)
|
||||||
|
|
||||||
|
var services []string
|
||||||
|
var eventTypes []string
|
||||||
|
eventTypeMap := make(map[string]bool)
|
||||||
|
|
||||||
|
services = report.Services
|
||||||
|
|
||||||
|
for _, event := range events {
|
||||||
|
if !eventTypeMap[event.EventType] {
|
||||||
|
eventTypes = append(eventTypes, event.EventType)
|
||||||
|
eventTypeMap[event.EventType] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine confidence level
|
||||||
|
confidence := "low"
|
||||||
|
if report.ThreatScore >= 80 {
|
||||||
|
confidence = "high"
|
||||||
|
} else if report.ThreatScore >= 50 {
|
||||||
|
confidence = "medium"
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = append(entries, BlocklistEntry{
|
||||||
|
IP: report.IP,
|
||||||
|
ThreatScore: report.ThreatScore,
|
||||||
|
FirstSeen: report.FirstSeen,
|
||||||
|
LastSeen: report.LastSeen,
|
||||||
|
TotalEvents: len(events),
|
||||||
|
Services: services,
|
||||||
|
EventTypes: eventTypes,
|
||||||
|
IsBlocked: report.IsBlocked,
|
||||||
|
Confidence: confidence,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleTxtBlocklist exports blocklist in plain text format (one IP per line)
|
||||||
|
func (be *BlocklistExporter) handleTxtBlocklist(w http.ResponseWriter, r *http.Request) {
|
||||||
|
minScore := be.getIntParam(r, "min_score", 50)
|
||||||
|
maxAge := be.getDurationParam(r, "max_age", 30*24*time.Hour) // 30 days default
|
||||||
|
includeUnblocked := be.getBoolParam(r, "include_unblocked", false)
|
||||||
|
|
||||||
|
entries, err := be.getBlocklistEntries(minScore, maxAge, includeUnblocked)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_blocklist.txt")
|
||||||
|
|
||||||
|
// Add header comment
|
||||||
|
fmt.Fprintf(w, "# Honeypot Threat Intelligence Blocklist\n")
|
||||||
|
fmt.Fprintf(w, "# Generated: %s\n", time.Now().UTC().Format(time.RFC3339))
|
||||||
|
fmt.Fprintf(w, "# Min Score: %d\n", minScore)
|
||||||
|
fmt.Fprintf(w, "# Total IPs: %d\n", len(entries))
|
||||||
|
fmt.Fprintf(w, "#\n")
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
fmt.Fprintf(w, "%s\n", entry.IP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleJSONBlocklist exports blocklist in JSON format
|
||||||
|
func (be *BlocklistExporter) handleJSONBlocklist(w http.ResponseWriter, r *http.Request) {
|
||||||
|
minScore := be.getIntParam(r, "min_score", 50)
|
||||||
|
maxAge := be.getDurationParam(r, "max_age", 30*24*time.Hour)
|
||||||
|
includeUnblocked := be.getBoolParam(r, "include_unblocked", false)
|
||||||
|
|
||||||
|
entries, err := be.getBlocklistEntries(minScore, maxAge, includeUnblocked)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"generated_at": time.Now().UTC(),
|
||||||
|
"min_score": minScore,
|
||||||
|
"total_ips": len(entries),
|
||||||
|
"entries": entries,
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_blocklist.json")
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCSVBlocklist exports blocklist in CSV format
|
||||||
|
func (be *BlocklistExporter) handleCSVBlocklist(w http.ResponseWriter, r *http.Request) {
|
||||||
|
minScore := be.getIntParam(r, "min_score", 50)
|
||||||
|
maxAge := be.getDurationParam(r, "max_age", 30*24*time.Hour)
|
||||||
|
includeUnblocked := be.getBoolParam(r, "include_unblocked", false)
|
||||||
|
|
||||||
|
entries, err := be.getBlocklistEntries(minScore, maxAge, includeUnblocked)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/csv")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_blocklist.csv")
|
||||||
|
|
||||||
|
// CSV header
|
||||||
|
fmt.Fprintf(w, "ip,threat_score,first_seen,last_seen,total_events,services,event_types,confidence\n")
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
fmt.Fprintf(w, "%s,%d,%s,%s,%d,\"%s\",\"%s\",%s\n",
|
||||||
|
entry.IP,
|
||||||
|
entry.ThreatScore,
|
||||||
|
entry.FirstSeen.Format(time.RFC3339),
|
||||||
|
entry.LastSeen.Format(time.RFC3339),
|
||||||
|
entry.TotalEvents,
|
||||||
|
strings.Join(entry.Services, ";"),
|
||||||
|
strings.Join(entry.EventTypes, ";"),
|
||||||
|
entry.Confidence,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSuricataBlocklist exports blocklist in Suricata format
|
||||||
|
func (be *BlocklistExporter) handleSuricataBlocklist(w http.ResponseWriter, r *http.Request) {
|
||||||
|
minScore := be.getIntParam(r, "min_score", 70) // Higher threshold for Suricata
|
||||||
|
maxAge := be.getDurationParam(r, "max_age", 7*24*time.Hour) // 7 days for active blocking
|
||||||
|
|
||||||
|
entries, err := be.getBlocklistEntries(minScore, maxAge, false)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_suricata.rules")
|
||||||
|
|
||||||
|
// Generate Suricata rules
|
||||||
|
fmt.Fprintf(w, "# Honeypot Threat Intelligence - Suricata Rules\n")
|
||||||
|
fmt.Fprintf(w, "# Generated: %s\n", time.Now().UTC().Format(time.RFC3339))
|
||||||
|
fmt.Fprintf(w, "#\n")
|
||||||
|
|
||||||
|
for i, entry := range entries {
|
||||||
|
fmt.Fprintf(w, "drop ip %s any -> any any (msg:\"Honeypot Threat - %s (Score: %d)\"; sid:%d; rev:1;)\n",
|
||||||
|
entry.IP, strings.Join(entry.EventTypes, ","), entry.ThreatScore, 1000000+i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePFBlocklist exports blocklist in PF (BSD firewall) format
|
||||||
|
func (be *BlocklistExporter) handlePFBlocklist(w http.ResponseWriter, r *http.Request) {
|
||||||
|
minScore := be.getIntParam(r, "min_score", 60)
|
||||||
|
maxAge := be.getDurationParam(r, "max_age", 7*24*time.Hour)
|
||||||
|
|
||||||
|
entries, err := be.getBlocklistEntries(minScore, maxAge, false)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_pf_blocklist.conf")
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "# Honeypot Threat Intelligence - PF Blocklist\n")
|
||||||
|
fmt.Fprintf(w, "# Generated: %s\n", time.Now().UTC().Format(time.RFC3339))
|
||||||
|
fmt.Fprintf(w, "# Usage: pfctl -t honeypot_threats -T add -f honeypot_pf_blocklist.conf\n")
|
||||||
|
fmt.Fprintf(w, "#\n")
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
fmt.Fprintf(w, "%s\n", entry.IP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleIPTablesBlocklist exports blocklist in iptables format
|
||||||
|
func (be *BlocklistExporter) handleIPTablesBlocklist(w http.ResponseWriter, r *http.Request) {
|
||||||
|
minScore := be.getIntParam(r, "min_score", 60)
|
||||||
|
maxAge := be.getDurationParam(r, "max_age", 7*24*time.Hour)
|
||||||
|
|
||||||
|
entries, err := be.getBlocklistEntries(minScore, maxAge, false)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_iptables.sh")
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "#!/bin/bash\n")
|
||||||
|
fmt.Fprintf(w, "# Honeypot Threat Intelligence - iptables Rules\n")
|
||||||
|
fmt.Fprintf(w, "# Generated: %s\n", time.Now().UTC().Format(time.RFC3339))
|
||||||
|
fmt.Fprintf(w, "#\n\n")
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
fmt.Fprintf(w, "iptables -A INPUT -s %s -j DROP # Score: %d, Events: %d\n",
|
||||||
|
entry.IP, entry.ThreatScore, entry.TotalEvents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleStats provides statistics about the threat intelligence
|
||||||
|
func (be *BlocklistExporter) handleStats(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Get various statistics
|
||||||
|
allEntries, _ := be.getBlocklistEntries(0, 0, true)
|
||||||
|
blockedEntries, _ := be.getBlocklistEntries(0, 0, false)
|
||||||
|
highThreatEntries, _ := be.getBlocklistEntries(80, 0, true)
|
||||||
|
recentEntries, _ := be.getBlocklistEntries(0, 24*time.Hour, true)
|
||||||
|
|
||||||
|
stats := map[string]interface{}{
|
||||||
|
"generated_at": time.Now().UTC(),
|
||||||
|
"total_ips": len(allEntries),
|
||||||
|
"blocked_ips": len(blockedEntries),
|
||||||
|
"high_threat_ips": len(highThreatEntries),
|
||||||
|
"recent_ips_24h": len(recentEntries),
|
||||||
|
"export_formats": []string{"txt", "json", "csv", "suricata", "pf", "iptables"},
|
||||||
|
"api_endpoints": map[string]string{
|
||||||
|
"txt": "/api/export/blocklist/txt",
|
||||||
|
"json": "/api/export/blocklist/json",
|
||||||
|
"csv": "/api/export/blocklist/csv",
|
||||||
|
"suricata": "/api/export/blocklist/suricata",
|
||||||
|
"pf": "/api/export/blocklist/pf",
|
||||||
|
"iptables": "/api/export/blocklist/iptables",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for parameter parsing
|
||||||
|
func (be *BlocklistExporter) getIntParam(r *http.Request, param string, defaultValue int) int {
|
||||||
|
if val := r.URL.Query().Get(param); val != "" {
|
||||||
|
if parsed, err := strconv.Atoi(val); err == nil {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BlocklistExporter) getDurationParam(r *http.Request, param string, defaultValue time.Duration) time.Duration {
|
||||||
|
if val := r.URL.Query().Get(param); val != "" {
|
||||||
|
if parsed, err := time.ParseDuration(val); err == nil {
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BlocklistExporter) getBoolParam(r *http.Request, param string, defaultValue bool) bool {
|
||||||
|
if val := r.URL.Query().Get(param); val != "" {
|
||||||
|
return val == "true" || val == "1"
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
@@ -9,11 +9,12 @@ import (
|
|||||||
|
|
||||||
// ThreatManager integrates threat analysis with the main application
|
// ThreatManager integrates threat analysis with the main application
|
||||||
type ThreatManager struct {
|
type ThreatManager struct {
|
||||||
analyzer *ThreatAnalyzer
|
analyzer *ThreatAnalyzer
|
||||||
api *ThreatAPI
|
api *ThreatAPI
|
||||||
authManager *AuthManager
|
authManager *AuthManager
|
||||||
securityManager *SecurityManager
|
securityManager *SecurityManager
|
||||||
userAPI *UserAPI
|
userAPI *UserAPI
|
||||||
|
blocklistExporter *BlocklistExporter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewThreatManager creates a new threat manager instance
|
// NewThreatManager creates a new threat manager instance
|
||||||
@@ -35,6 +36,7 @@ func NewThreatManager(dbPath string) (*ThreatManager, error) {
|
|||||||
// Initialize APIs
|
// Initialize APIs
|
||||||
api := NewThreatAPI(analyzer)
|
api := NewThreatAPI(analyzer)
|
||||||
userAPI := NewUserAPI(authManager, securityManager)
|
userAPI := NewUserAPI(authManager, securityManager)
|
||||||
|
blocklistExporter := NewBlocklistExporter(analyzer)
|
||||||
|
|
||||||
return &ThreatManager{
|
return &ThreatManager{
|
||||||
analyzer: analyzer,
|
analyzer: analyzer,
|
||||||
@@ -42,6 +44,7 @@ func NewThreatManager(dbPath string) (*ThreatManager, error) {
|
|||||||
authManager: authManager,
|
authManager: authManager,
|
||||||
securityManager: securityManager,
|
securityManager: securityManager,
|
||||||
userAPI: userAPI,
|
userAPI: userAPI,
|
||||||
|
blocklistExporter: blocklistExporter,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +157,11 @@ func (tm *ThreatManager) GetUserAPI() *UserAPI {
|
|||||||
return tm.userAPI
|
return tm.userAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBlocklistExporter returns the blocklist exporter instance
|
||||||
|
func (tm *ThreatManager) GetBlocklistExporter() *BlocklistExporter {
|
||||||
|
return tm.blocklistExporter
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlockedIPs returns currently blocked IPs for firewall integration
|
// GetBlockedIPs returns currently blocked IPs for firewall integration
|
||||||
func (tm *ThreatManager) GetBlockedIPs() ([]string, error) {
|
func (tm *ThreatManager) GetBlockedIPs() ([]string, error) {
|
||||||
return tm.analyzer.GetBlockedIPs()
|
return tm.analyzer.GetBlockedIPs()
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package dashboard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecurityHeadersMiddleware adds security headers to all responses
|
||||||
|
func SecurityHeadersMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Prevent clickjacking
|
||||||
|
w.Header().Set("X-Frame-Options", "DENY")
|
||||||
|
|
||||||
|
// Prevent MIME type sniffing
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
|
// Enable XSS protection
|
||||||
|
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||||
|
|
||||||
|
// Strict transport security (HTTPS only)
|
||||||
|
if r.TLS != nil {
|
||||||
|
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content Security Policy
|
||||||
|
csp := strings.Join([]string{
|
||||||
|
"default-src 'self'",
|
||||||
|
"script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com",
|
||||||
|
"style-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com",
|
||||||
|
"img-src 'self' data:",
|
||||||
|
"font-src 'self'",
|
||||||
|
"connect-src 'self'",
|
||||||
|
"frame-ancestors 'none'",
|
||||||
|
"base-uri 'self'",
|
||||||
|
"form-action 'self'",
|
||||||
|
}, "; ")
|
||||||
|
w.Header().Set("Content-Security-Policy", csp)
|
||||||
|
|
||||||
|
// Referrer policy
|
||||||
|
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||||
|
|
||||||
|
// Permissions policy
|
||||||
|
w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
|
||||||
|
|
||||||
|
// Remove server identification
|
||||||
|
w.Header().Set("Server", "")
|
||||||
|
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPWhitelistMiddleware restricts dashboard access to specific IPs
|
||||||
|
func IPWhitelistMiddleware(allowedIPs []string) func(http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
clientIP := getClientIP(r)
|
||||||
|
|
||||||
|
// Check if IP is in whitelist
|
||||||
|
allowed := false
|
||||||
|
for _, ip := range allowedIPs {
|
||||||
|
if ip == clientIP || ip == "0.0.0.0" { // 0.0.0.0 allows all
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowed {
|
||||||
|
http.Error(w, "Access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClientIP extracts the real client IP from request
|
||||||
|
func getClientIP(r *http.Request) string {
|
||||||
|
// Check X-Forwarded-For header
|
||||||
|
xff := r.Header.Get("X-Forwarded-For")
|
||||||
|
if xff != "" {
|
||||||
|
ips := strings.Split(xff, ",")
|
||||||
|
return strings.TrimSpace(ips[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check X-Real-IP header
|
||||||
|
xri := r.Header.Get("X-Real-IP")
|
||||||
|
if xri != "" {
|
||||||
|
return strings.TrimSpace(xri)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to RemoteAddr
|
||||||
|
ip := r.RemoteAddr
|
||||||
|
if colon := strings.LastIndex(ip, ":"); colon != -1 {
|
||||||
|
ip = ip[:colon]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimitMiddleware implements basic rate limiting
|
||||||
|
func RateLimitMiddleware(requestsPerMinute int) func(http.HandlerFunc) http.HandlerFunc {
|
||||||
|
// Simple in-memory rate limiter (use Redis in production)
|
||||||
|
// This is a basic implementation - consider using a proper rate limiter
|
||||||
|
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// For now, just pass through - implement proper rate limiting
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -322,13 +322,6 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
a.startTCPService("vnc", a.cfg.Ports.VNC, svcs.NewVNCHandler(a.svcLogger()))
|
a.startTCPService("vnc", a.cfg.Ports.VNC, svcs.NewVNCHandler(a.svcLogger()))
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if a.cfg.Services.SIP {
|
|
||||||
a.wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer a.wg.Done()
|
|
||||||
a.startTCPService("sip", a.cfg.Ports.SIP, svcs.NewSIPHandler(a.svcLogger()))
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
for _, p := range a.cfg.Services.Generic {
|
for _, p := range a.cfg.Services.Generic {
|
||||||
port := p
|
port := p
|
||||||
a.wg.Add(1)
|
a.wg.Add(1)
|
||||||
@@ -337,7 +330,6 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
a.startTCPService(fmt.Sprintf("generic-%d", port), port, svcs.NewGenericEchoHandler(a.svcLogger()))
|
a.startTCPService(fmt.Sprintf("generic-%d", port), port, svcs.NewGenericEchoHandler(a.svcLogger()))
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// start web dashboard if enabled
|
// start web dashboard if enabled
|
||||||
if a.cfg.Web.Enabled {
|
if a.cfg.Web.Enabled {
|
||||||
a.wg.Add(1)
|
a.wg.Add(1)
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SecurityConfig defines security limits for honeypot services
|
||||||
|
type SecurityConfig struct {
|
||||||
|
MaxInputLength int // Maximum input length per command
|
||||||
|
MaxConnDuration time.Duration // Maximum connection duration
|
||||||
|
MaxCommands int // Maximum commands per connection
|
||||||
|
RateLimitWindow time.Duration // Rate limiting window
|
||||||
|
MaxConnPerIP int // Maximum concurrent connections per IP
|
||||||
|
ReadTimeout time.Duration // Read timeout for each operation
|
||||||
|
WriteTimeout time.Duration // Write timeout for each operation
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultSecurityConfig returns secure default configuration
|
||||||
|
func DefaultSecurityConfig() SecurityConfig {
|
||||||
|
return SecurityConfig{
|
||||||
|
MaxInputLength: 4096, // 4KB max input
|
||||||
|
MaxConnDuration: 5 * time.Minute,
|
||||||
|
MaxCommands: 100, // Max 100 commands per connection
|
||||||
|
RateLimitWindow: time.Minute,
|
||||||
|
MaxConnPerIP: 10, // Max 10 concurrent connections per IP
|
||||||
|
ReadTimeout: 30 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectionTracker tracks connections per IP for rate limiting
|
||||||
|
type ConnectionTracker struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
conns map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnectionTracker() *ConnectionTracker {
|
||||||
|
return &ConnectionTracker{
|
||||||
|
conns: make(map[string]int),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ConnectionTracker) AddConnection(ip string) bool {
|
||||||
|
ct.mu.Lock()
|
||||||
|
defer ct.mu.Unlock()
|
||||||
|
|
||||||
|
if ct.conns[ip] >= 10 { // Max 10 connections per IP
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ct.conns[ip]++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ConnectionTracker) RemoveConnection(ip string) {
|
||||||
|
ct.mu.Lock()
|
||||||
|
defer ct.mu.Unlock()
|
||||||
|
|
||||||
|
if ct.conns[ip] > 0 {
|
||||||
|
ct.conns[ip]--
|
||||||
|
if ct.conns[ip] == 0 {
|
||||||
|
delete(ct.conns, ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureReader provides safe input reading with limits
|
||||||
|
type SecureReader struct {
|
||||||
|
scanner *bufio.Scanner
|
||||||
|
conn net.Conn
|
||||||
|
config SecurityConfig
|
||||||
|
commandCount int
|
||||||
|
startTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSecureReader(conn net.Conn, config SecurityConfig) *SecureReader {
|
||||||
|
scanner := bufio.NewScanner(conn)
|
||||||
|
|
||||||
|
// Set maximum token size to prevent buffer overflow
|
||||||
|
scanner.Buffer(make([]byte, 1024), config.MaxInputLength)
|
||||||
|
|
||||||
|
return &SecureReader{
|
||||||
|
scanner: scanner,
|
||||||
|
conn: conn,
|
||||||
|
config: config,
|
||||||
|
startTime: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SecureReader) ReadLine() (string, error) {
|
||||||
|
// Check connection duration limit
|
||||||
|
if time.Since(sr.startTime) > sr.config.MaxConnDuration {
|
||||||
|
return "", fmt.Errorf("connection duration exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check command count limit
|
||||||
|
if sr.commandCount >= sr.config.MaxCommands {
|
||||||
|
return "", fmt.Errorf("command limit exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set read timeout
|
||||||
|
sr.conn.SetReadDeadline(time.Now().Add(sr.config.ReadTimeout))
|
||||||
|
|
||||||
|
if !sr.scanner.Scan() {
|
||||||
|
if err := sr.scanner.Err(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("connection closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(sr.scanner.Text())
|
||||||
|
|
||||||
|
// Validate input length
|
||||||
|
if len(line) > sr.config.MaxInputLength {
|
||||||
|
return "", fmt.Errorf("input too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
sr.commandCount++
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureWriter provides safe output writing with timeouts
|
||||||
|
type SecureWriter struct {
|
||||||
|
conn net.Conn
|
||||||
|
config SecurityConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSecureWriter(conn net.Conn, config SecurityConfig) *SecureWriter {
|
||||||
|
return &SecureWriter{
|
||||||
|
conn: conn,
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *SecureWriter) Write(data []byte) error {
|
||||||
|
sw.conn.SetWriteDeadline(time.Now().Add(sw.config.WriteTimeout))
|
||||||
|
_, err := sw.conn.Write(data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sw *SecureWriter) WriteString(s string) error {
|
||||||
|
return sw.Write([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputSanitizer provides input sanitization utilities
|
||||||
|
type InputSanitizer struct{}
|
||||||
|
|
||||||
|
func NewInputSanitizer() *InputSanitizer {
|
||||||
|
return &InputSanitizer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SanitizeString removes dangerous characters and limits length
|
||||||
|
func (is *InputSanitizer) SanitizeString(input string, maxLen int) string {
|
||||||
|
// Remove null bytes and control characters
|
||||||
|
cleaned := strings.Map(func(r rune) rune {
|
||||||
|
if r == 0 || (r < 32 && r != 9 && r != 10 && r != 13) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, input)
|
||||||
|
|
||||||
|
// Limit length
|
||||||
|
if len(cleaned) > maxLen {
|
||||||
|
cleaned = cleaned[:maxLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleaned
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCommand checks if a command is safe to process
|
||||||
|
func (is *InputSanitizer) ValidateCommand(cmd string) bool {
|
||||||
|
// Block potentially dangerous commands
|
||||||
|
dangerous := []string{
|
||||||
|
"../", "..\\", "/etc/", "/proc/", "/sys/", "/dev/",
|
||||||
|
"system", "exec", "eval", "shell", "bash", "sh",
|
||||||
|
"cmd.exe", "powershell", "wget", "curl", "nc", "netcat",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdLower := strings.ToLower(cmd)
|
||||||
|
for _, danger := range dangerous {
|
||||||
|
if strings.Contains(cmdLower, danger) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureHandler wraps a handler with security protections
|
||||||
|
func SecureHandler(handler Handler, tracker *ConnectionTracker, config SecurityConfig) Handler {
|
||||||
|
return func(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// Extract IP address
|
||||||
|
remoteAddr := conn.RemoteAddr().String()
|
||||||
|
ip := remoteIP(remoteAddr)
|
||||||
|
|
||||||
|
// Check connection limits
|
||||||
|
if !tracker.AddConnection(ip) {
|
||||||
|
return // Too many connections from this IP
|
||||||
|
}
|
||||||
|
defer tracker.RemoveConnection(ip)
|
||||||
|
|
||||||
|
// Set overall connection deadline
|
||||||
|
conn.SetDeadline(time.Now().Add(config.MaxConnDuration))
|
||||||
|
|
||||||
|
// Create context with timeout
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), config.MaxConnDuration)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Run handler with context
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
handler(conn)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// Handler completed normally
|
||||||
|
case <-ctx.Done():
|
||||||
|
// Timeout exceeded, force close
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
-3
@@ -31,24 +31,55 @@ func NewSSHHandler(log LoggerFunc, getSigner func() (ssh.Signer, error)) Handler
|
|||||||
|
|
||||||
cfg := &ssh.ServerConfig{
|
cfg := &ssh.ServerConfig{
|
||||||
NoClientAuth: false,
|
NoClientAuth: false,
|
||||||
ServerVersion: "SSH-2.0-OpenSSH_7.9p1 Ubuntu-10",
|
ServerVersion: "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1", // Updated version string
|
||||||
|
MaxAuthTries: 6, // Allow more attempts like real SSH (default is usually 6)
|
||||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||||
authAttempts++
|
authAttempts++
|
||||||
|
|
||||||
lastUser = c.User()
|
lastUser = c.User()
|
||||||
lastPass = string(pass)
|
lastPass = string(pass)
|
||||||
|
|
||||||
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
|
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
|
||||||
"event": "auth_attempt",
|
"event": "auth_attempt",
|
||||||
"attempt": strconv.Itoa(authAttempts),
|
"attempt": strconv.Itoa(authAttempts),
|
||||||
"username": lastUser,
|
"username": lastUser,
|
||||||
"password": lastPass,
|
"password": lastPass,
|
||||||
"client": string(c.ClientVersion()),
|
"client": string(c.ClientVersion()),
|
||||||
|
"session_id": sessionID,
|
||||||
|
}})
|
||||||
|
|
||||||
|
// Small delay to simulate real SSH behavior
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("permission denied")
|
||||||
|
},
|
||||||
|
KeyboardInteractiveCallback: func(c ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
|
||||||
|
// Log keyboard interactive attempts
|
||||||
|
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
|
||||||
|
"event": "keyboard_interactive_attempt",
|
||||||
|
"username": c.User(),
|
||||||
|
"client": string(c.ClientVersion()),
|
||||||
|
"session_id": sessionID,
|
||||||
|
}})
|
||||||
|
return nil, fmt.Errorf("permission denied")
|
||||||
|
},
|
||||||
|
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
|
// Log public key attempts
|
||||||
|
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
|
||||||
|
"event": "pubkey_attempt",
|
||||||
|
"username": c.User(),
|
||||||
|
"key_type": pubKey.Type(),
|
||||||
|
"key_fingerprint": ssh.FingerprintSHA256(pubKey),
|
||||||
|
"client": string(c.ClientVersion()),
|
||||||
|
"session_id": sessionID,
|
||||||
}})
|
}})
|
||||||
return nil, fmt.Errorf("permission denied")
|
return nil, fmt.Errorf("permission denied")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cfg.AddHostKey(signer)
|
cfg.AddHostKey(signer)
|
||||||
|
|
||||||
_ = conn.SetDeadline(time.Now().Add(2 * time.Minute))
|
// Set stricter timeout
|
||||||
|
_ = conn.SetDeadline(time.Now().Add(90 * time.Second))
|
||||||
|
|
||||||
sc, chans, reqs, err := ssh.NewServerConn(conn, cfg)
|
sc, chans, reqs, err := ssh.NewServerConn(conn, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,8 +94,28 @@ func NewSSHHandler(log LoggerFunc, getSigner func() (ssh.Signer, error)) Handler
|
|||||||
}})
|
}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go ssh.DiscardRequests(reqs)
|
|
||||||
|
// Handle requests and channels with logging
|
||||||
|
go func() {
|
||||||
|
for req := range reqs {
|
||||||
|
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
|
||||||
|
"event": "global_request",
|
||||||
|
"request_type": req.Type,
|
||||||
|
"want_reply": fmt.Sprintf("%v", req.WantReply),
|
||||||
|
"session_id": sessionID,
|
||||||
|
}})
|
||||||
|
if req.WantReply {
|
||||||
|
req.Reply(false, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for ch := range chans {
|
for ch := range chans {
|
||||||
|
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
|
||||||
|
"event": "channel_request",
|
||||||
|
"channel_type": ch.ChannelType(),
|
||||||
|
"session_id": sessionID,
|
||||||
|
}})
|
||||||
_ = ch.Reject(ssh.Prohibited, "not allowed")
|
_ = ch.Reject(ssh.Prohibited, "not allowed")
|
||||||
}
|
}
|
||||||
_ = sc.Close()
|
_ = sc.Close()
|
||||||
|
|||||||
@@ -169,7 +169,12 @@
|
|||||||
vnc: parseInt(document.getElementById('port-vnc').value, 10),
|
vnc: parseInt(document.getElementById('port-vnc').value, 10),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const res = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
const headers = { 'Content-Type': 'application/json' };
|
||||||
|
const csrfToken = '{{ .CSRFToken }}';
|
||||||
|
if (csrfToken) {
|
||||||
|
headers['X-CSRF-Token'] = csrfToken;
|
||||||
|
}
|
||||||
|
const res = await fetch('/api/settings', { method: 'POST', headers: headers, body: JSON.stringify(payload) });
|
||||||
const out = await res.json().catch(() => ({}));
|
const out = await res.json().catch(() => ({}));
|
||||||
const el = document.getElementById('save-status');
|
const el = document.getElementById('save-status');
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -181,10 +186,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function restartApp() {
|
async function restartApp() {
|
||||||
const res = await fetch('/api/restart', { method: 'POST' });
|
const headers = {};
|
||||||
|
const csrfToken = '{{ .CSRFToken }}';
|
||||||
|
if (csrfToken) {
|
||||||
|
headers['X-CSRF-Token'] = csrfToken;
|
||||||
|
}
|
||||||
|
const res = await fetch('/api/restart', { method: 'POST', headers: headers });
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
document.getElementById('save-status').textContent = 'Restarting...';
|
document.getElementById('save-status').textContent = 'Restarting...';
|
||||||
setTimeout(() => location.reload(), 1200);
|
setTimeout(() => location.reload(), 1200);
|
||||||
|
} else {
|
||||||
|
document.getElementById('save-status').textContent = 'Restart failed';
|
||||||
|
document.getElementById('save-status').className = 'text-sm text-red-400';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.getElementById('btn-save').addEventListener('click', saveSettings);
|
document.getElementById('btn-save').addEventListener('click', saveSettings);
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ func (a *App) startWeb() {
|
|||||||
|
|
||||||
// Register threat analysis API routes (they will handle their own authentication)
|
// Register threat analysis API routes (they will handle their own authentication)
|
||||||
a.threatManager.GetAPI().RegisterRoutes(mux)
|
a.threatManager.GetAPI().RegisterRoutes(mux)
|
||||||
|
|
||||||
|
// Register blocklist export routes (public endpoints for threat intelligence sharing)
|
||||||
|
a.threatManager.GetBlocklistExporter().RegisterExportRoutes(mux, securityManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secure dashboard routes with authentication
|
// Secure dashboard routes with authentication
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
honeypot:
|
||||||
|
build: .
|
||||||
|
container_name: honeydany
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# Security configurations
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
cap_add:
|
||||||
|
- NET_BIND_SERVICE # Only for binding to privileged ports
|
||||||
|
read_only: true
|
||||||
|
|
||||||
|
# Resource limits to prevent DoS
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '1.0'
|
||||||
|
memory: 512M
|
||||||
|
reservations:
|
||||||
|
cpus: '0.25'
|
||||||
|
memory: 128M
|
||||||
|
|
||||||
|
# Network isolation
|
||||||
|
networks:
|
||||||
|
- honeypot_net
|
||||||
|
|
||||||
|
# Port mappings - only expose what's needed
|
||||||
|
ports:
|
||||||
|
- "2121:2121" # FTP
|
||||||
|
- "2222:2222" # SSH
|
||||||
|
- "2323:2323" # Telnet
|
||||||
|
- "2525:2525" # SMTP
|
||||||
|
- "3306:3306" # MySQL
|
||||||
|
- "3399:3399" # RDP
|
||||||
|
- "4450:4450" # SMB
|
||||||
|
- "5060:5060" # SIP
|
||||||
|
- "5432:5432" # PostgreSQL
|
||||||
|
- "8080:8080" # HTTP
|
||||||
|
- "8443:8443" # HTTPS
|
||||||
|
- "27017:27017" # MongoDB
|
||||||
|
- "1143:1143" # IMAP
|
||||||
|
- "6333:6333" # Dashboard (restrict this in production)
|
||||||
|
|
||||||
|
# Persistent volumes for data
|
||||||
|
volumes:
|
||||||
|
- honeypot_data:/app/data
|
||||||
|
- honeypot_logs:/app/logs
|
||||||
|
- /tmp:/tmp:rw,noexec,nosuid,size=100m
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
environment:
|
||||||
|
- HONEYPOT_ENV=production
|
||||||
|
- LOG_LEVEL=info
|
||||||
|
- MAX_CONNECTIONS=1000
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
honeypot_net:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.20.0.0/16
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
honeypot_data:
|
||||||
|
driver: local
|
||||||
|
honeypot_logs:
|
||||||
|
driver: local
|
||||||
Reference in New Issue
Block a user