authentication and security

This commit is contained in:
2025-09-28 16:01:27 +01:00
parent f81b0f3c28
commit 22185904be
13 changed files with 1176 additions and 33 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
*.pem
*.log
*.json
*.json
*.db

42
Dockerfile Normal file
View File

@@ -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"]

260
SECURITY_DEPLOYMENT.md Normal file
View File

@@ -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

View File

@@ -19,20 +19,20 @@ type Config struct {
} `json:"web"`
Services struct {
HTTP bool `json:"http"`
HTTPS bool `json:"https"`
SSH bool `json:"ssh"`
FTP bool `json:"ftp"`
SMTP bool `json:"smtp"`
IMAP bool `json:"imap"`
Telnet bool `json:"telnet"`
MySQL bool `json:"mysql"`
PostgreSQL bool `json:"postgresql"`
MongoDB bool `json:"mongodb"`
RDP bool `json:"rdp"`
SMB bool `json:"smb"`
SIP bool `json:"sip"`
VNC bool `json:"vnc"`
HTTP bool `json:"http"`
HTTPS bool `json:"https"`
SSH bool `json:"ssh"`
FTP bool `json:"ftp"`
SMTP bool `json:"smtp"`
IMAP bool `json:"imap"`
Telnet bool `json:"telnet"`
MySQL bool `json:"mysql"`
PostgreSQL bool `json:"postgresql"`
MongoDB bool `json:"mongodb"`
RDP bool `json:"rdp"`
SMB bool `json:"smb"`
SIP bool `json:"sip"`
VNC bool `json:"vnc"`
Generic []int `json:"generic"`
} `json:"services"`
@@ -53,6 +53,19 @@ type Config struct {
VNC int `json:"vnc"`
} `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 struct {
// 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.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
}

View File

@@ -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
}

View File

@@ -9,11 +9,12 @@ import (
// ThreatManager integrates threat analysis with the main application
type ThreatManager struct {
analyzer *ThreatAnalyzer
api *ThreatAPI
authManager *AuthManager
securityManager *SecurityManager
userAPI *UserAPI
analyzer *ThreatAnalyzer
api *ThreatAPI
authManager *AuthManager
securityManager *SecurityManager
userAPI *UserAPI
blocklistExporter *BlocklistExporter
}
// NewThreatManager creates a new threat manager instance
@@ -35,6 +36,7 @@ func NewThreatManager(dbPath string) (*ThreatManager, error) {
// Initialize APIs
api := NewThreatAPI(analyzer)
userAPI := NewUserAPI(authManager, securityManager)
blocklistExporter := NewBlocklistExporter(analyzer)
return &ThreatManager{
analyzer: analyzer,
@@ -42,6 +44,7 @@ func NewThreatManager(dbPath string) (*ThreatManager, error) {
authManager: authManager,
securityManager: securityManager,
userAPI: userAPI,
blocklistExporter: blocklistExporter,
}, nil
}
@@ -154,6 +157,11 @@ func (tm *ThreatManager) GetUserAPI() *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
func (tm *ThreatManager) GetBlockedIPs() ([]string, error) {
return tm.analyzer.GetBlockedIPs()

View File

@@ -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)
}
}
}

View File

@@ -322,13 +322,6 @@ func (a *App) Run(ctx context.Context) error {
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 {
port := p
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()))
}()
}
// start web dashboard if enabled
if a.cfg.Web.Enabled {
a.wg.Add(1)

231
app/services/security.go Normal file
View File

@@ -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()
}
}
}

View File

@@ -31,24 +31,55 @@ func NewSSHHandler(log LoggerFunc, getSigner func() (ssh.Signer, error)) Handler
cfg := &ssh.ServerConfig{
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) {
authAttempts++
lastUser = c.User()
lastPass = string(pass)
log(Record{Timestamp: Now(), RemoteAddr: remoteIP(remote), RemotePort: remotePort(remote), Service: "ssh", Details: map[string]string{
"event": "auth_attempt",
"attempt": strconv.Itoa(authAttempts),
"username": lastUser,
"password": lastPass,
"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")
},
}
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)
if err != nil {
@@ -63,8 +94,28 @@ func NewSSHHandler(log LoggerFunc, getSigner func() (ssh.Signer, error)) Handler
}})
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 {
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")
}
_ = sc.Close()

View File

@@ -169,7 +169,12 @@
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 el = document.getElementById('save-status');
if (res.ok) {
@@ -181,10 +186,18 @@
}
}
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) {
document.getElementById('save-status').textContent = 'Restarting...';
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);

View File

@@ -68,6 +68,9 @@ func (a *App) startWeb() {
// Register threat analysis API routes (they will handle their own authentication)
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

79
docker-compose.yml Normal file
View File

@@ -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