authentication and security
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
*.pem
|
||||
*.log
|
||||
*.json
|
||||
*.json
|
||||
*.db
|
||||
42
Dockerfile
Normal file
42
Dockerfile
Normal 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
260
SECURITY_DEPLOYMENT.md
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
327
app/dashboard/blocklist_export.go
Normal file
327
app/dashboard/blocklist_export.go
Normal 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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
111
app/dashboard/security_headers.go
Normal file
111
app/dashboard/security_headers.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
231
app/services/security.go
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
79
docker-compose.yml
Normal 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
|
||||
Reference in New Issue
Block a user