commit 4631524455a8602ecfe9d6f3daf391c4203f36dd Author: Robert Date: Fri May 23 20:47:37 2025 -0400 init diff --git a/AppArmor.policy b/AppArmor.policy new file mode 100644 index 0000000..f30bb0b --- /dev/null +++ b/AppArmor.policy @@ -0,0 +1,8 @@ +# Example AppArmor profile (/etc/apparmor.d/usr.local.bin.SYN-Scan-Firewall) +/usr/local/bin/SYN-Scan-Firewall { + include + capability net_admin, + capability net_raw, + /etc/syn-firewall/config.yaml r, + /var/lib/syn-firewall/** rw, +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..094a1be --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2025 Robert Strutts + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b726ba0 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# SYN-Scan-Firewall +## To block the IP from port scans... +``` +./install.sh +``` +## /etc/SYN-Scan-Firewall/config.yaml +``` +blockDuration: 10m +maxScanAttempts: 5 +device: "enp2s0" # Ethernet Device name + +logging: + filePath: "/var/log/SYN-Scan-Firewall.log" + maxSizeMB: 10 # Max log size in megabytes + backups: 5 # Number of backup logs to keep + compressBackups: true # Whether to gzip old logs + timestampFormat: "2006-01-02T15:04:05" # Go time format + +ignoredPorts: + - 80 # HTTP + - 443 # HTTPS + - 9980 # php -S + - 631 # CUPS (printing) + - 9100 # print server ports + - 53 # DNS + - 123 # NTP + - 68 # DHCP client +# - 22 # SSH + +whitelistedIPs: + - "192.168.10.2" # own IP + - "192.168.1.100" # Example local admin + - "10.0.0.50" # Example monitoring server + - "127.0.0.1" # Localhost +``` diff --git a/SYN-Scan-Firewall b/SYN-Scan-Firewall new file mode 100755 index 0000000..d60a8fe Binary files /dev/null and b/SYN-Scan-Firewall differ diff --git a/SYN-Scan-Firewall.go b/SYN-Scan-Firewall.go new file mode 100644 index 0000000..9d0a439 --- /dev/null +++ b/SYN-Scan-Firewall.go @@ -0,0 +1,486 @@ +package main + +import ( + "compress/gzip" + "fmt" + "io" + "log" + "os" + "os/exec" + "os/signal" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "gopkg.in/yaml.v3" +) + +const ( + defaultConfigFile = "/etc/SYN-Scan-Firewall/config.yaml" +) + +// Config structures +type LoggingConfig struct { + FilePath string `yaml:"filePath"` + MaxSizeMB int `yaml:"maxSizeMB"` + Backups int `yaml:"backups"` + CompressBackups bool `yaml:"compressBackups"` + TimestampFormat string `yaml:"timestampFormat"` +} + +type Config struct { + BlockDuration string `yaml:"blockDuration"` + MaxScanAttempts int `yaml:"maxScanAttempts"` + Device string `yaml:"device"` + Logging LoggingConfig `yaml:"logging"` + IgnoredPorts []int `yaml:"ignoredPorts"` + WhitelistedIPs []string `yaml:"whitelistedIPs"` +} + +type AppConfig struct { + BlockDuration time.Duration + MaxScanAttempts int + Device string + Logging LoggingConfig + IgnoredPorts map[int]bool + WhitelistedIPs map[string]bool +} + +type UnblockTask struct { + IP string + UnblockAt time.Time +} + +type Sniffer struct { + unblockTasks []UnblockTask + tracker *ScanTracker + handle *pcap.Handle + config *AppConfig + logger *log.Logger +} + +// ScanTracker implementation +type ScanTracker struct { + sync.Mutex + entries map[string]*ScanEntry +} + +type ScanEntry struct { + Count int + Timestamp time.Time +} + +// NewScanTracker creates and initializes a new ScanTracker +func NewScanTracker() *ScanTracker { + return &ScanTracker{ + entries: make(map[string]*ScanEntry), + } +} + +// RotatingLogger implementation +type RotatingLogger struct { + config LoggingConfig + currentFile *os.File + mu sync.Mutex +} + +func NewRotatingLogger(config LoggingConfig) (*RotatingLogger, error) { + rl := &RotatingLogger{config: config} + if err := rl.openFile(); err != nil { + return nil, err + } + return rl, nil +} + +func (rl *RotatingLogger) openFile() error { + if err := os.MkdirAll(filepath.Dir(rl.config.FilePath), 0755); err != nil { + return fmt.Errorf("failed to create log directory: %v", err) + } + + file, err := os.OpenFile(rl.config.FilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0640) + if err != nil { + return fmt.Errorf("failed to open log file: %v", err) + } + + rl.currentFile = file + return nil +} + +func (rl *RotatingLogger) rotate() error { + rl.mu.Lock() + defer rl.mu.Unlock() + + if err := rl.currentFile.Close(); err != nil { + return err + } + + for i := rl.config.Backups - 1; i >= 0; i-- { + src := rl.getBackupName(i) + if _, err := os.Stat(src); err == nil { + dst := rl.getBackupName(i + 1) + if i+1 >= rl.config.Backups { + os.Remove(dst) + } else { + if rl.config.CompressBackups && !strings.HasSuffix(src, ".gz") { + if err := rl.compressFile(src); err != nil { + return err + } + src += ".gz" + dst += ".gz" + } + os.Rename(src, dst) + } + } + } + + if err := os.Rename(rl.config.FilePath, rl.getBackupName(0)); err != nil { + return err + } + + return rl.openFile() +} + +func (rl *RotatingLogger) compressFile(src string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(src + ".gz") + if err != nil { + return err + } + defer out.Close() + + gz := gzip.NewWriter(out) + defer gz.Close() + + if _, err = io.Copy(gz, in); err != nil { + return err + } + + return os.Remove(src) +} + +func (rl *RotatingLogger) getBackupName(index int) string { + if index == 0 { + return rl.config.FilePath + ".1" + } + return fmt.Sprintf("%s.%d", rl.config.FilePath, index+1) +} + +func (rl *RotatingLogger) needsRotation() (bool, error) { + info, err := rl.currentFile.Stat() + if err != nil { + return false, err + } + return info.Size() >= int64(rl.config.MaxSizeMB*1024*1024), nil +} + +func (rl *RotatingLogger) Write(p []byte) (n int, err error) { + if rotate, err := rl.needsRotation(); rotate && err == nil { + if err := rl.rotate(); err != nil { + return 0, err + } + } + + rl.mu.Lock() + defer rl.mu.Unlock() + return rl.currentFile.Write(p) +} + +func (rl *RotatingLogger) Close() error { + rl.mu.Lock() + defer rl.mu.Unlock() + return rl.currentFile.Close() +} + +// Helper functions +func loadConfig(path string) (*AppConfig, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %v", err) + } + + var cfg Config + if err := yaml.Unmarshal(data, &cfg); err != nil { + return nil, fmt.Errorf("failed to parse config: %v", err) + } + + // Set defaults + if cfg.Logging.FilePath == "" { + cfg.Logging.FilePath = "/var/log/SYN-Scan-Firewall.log" + } + if cfg.Logging.MaxSizeMB == 0 { + cfg.Logging.MaxSizeMB = 10 + } + if cfg.Logging.Backups == 0 { + cfg.Logging.Backups = 5 + } + if cfg.Logging.TimestampFormat == "" { + cfg.Logging.TimestampFormat = "2006-01-02 15:04:05" + } + + blockDuration, err := time.ParseDuration(cfg.BlockDuration) + if err != nil { + return nil, fmt.Errorf("invalid blockDuration format: %v", err) + } + + ignoredPorts := make(map[int]bool) + for _, port := range cfg.IgnoredPorts { + ignoredPorts[port] = true + } + + whitelistedIPs := make(map[string]bool) + for _, ip := range cfg.WhitelistedIPs { + whitelistedIPs[ip] = true + } + + return &AppConfig{ + BlockDuration: blockDuration, + MaxScanAttempts: cfg.MaxScanAttempts, + Device: cfg.Device, + Logging: cfg.Logging, + IgnoredPorts: ignoredPorts, + WhitelistedIPs: whitelistedIPs, + }, nil +} + +func (st *ScanTracker) Add(ip string, config *AppConfig) { + st.Lock() + defer st.Unlock() + + entry, exists := st.entries[ip] + if !exists { + entry = &ScanEntry{} + st.entries[ip] = entry + } + + now := time.Now() + if entry.Timestamp.Add(config.BlockDuration).Before(now) { + entry.Count = 0 + } + + entry.Count++ + entry.Timestamp = now +} + +func (st *ScanTracker) GetCount(ip string) int { + st.Lock() + defer st.Unlock() + + if entry, exists := st.entries[ip]; exists { + return entry.Count + } + return 0 +} + +func isIPBlocked(ip string) bool { + cmd := exec.Command("sudo", "iptables", "-L", "-n") + output, err := cmd.Output() + if err != nil { + return false + } + + return strings.Contains(string(output), ip) +} + +func blockIP(ip string, logger *log.Logger) { + if isIPBlocked(ip) { + logger.Printf("IP %s is already blocked", ip) + return + } + + logger.Printf("Redirecting IP: %s to port 9999 BANNER", ip) + cmd := exec.Command("sudo", "iptables", "-t", "nat", "-A", "PREROUTING", "-s", ip, "-p", "tcp", "--dport", "1:65535", "-j", "REDIRECT", "--to-port", "9999") + if err := cmd.Run(); err != nil { + logger.Printf("Error redirecting IP %s to banner service: %v", ip, err) + } + + logger.Printf("Blocking IP: %s", ip) + cmd = exec.Command("sudo", "iptables", "-A", "INPUT", "-s", ip, "-j", "DROP") + if err := cmd.Run(); err != nil { + logger.Printf("Error blocking IP %s: %v", ip, err) + } +} + +func unblockIP(ip string, logger *log.Logger) { + logger.Printf("Unblocking IP: %s", ip) + cmd := exec.Command("sudo", "iptables", "-D", "INPUT", "-s", ip, "-j", "DROP") + if err := cmd.Run(); err != nil { + logger.Printf("Error unblocking IP %s: %v", ip, err) + } +} + +// Sniffer methods +func (s *Sniffer) isWhitelisted(ip string) bool { + return s.config.WhitelistedIPs[ip] +} + +func (s *Sniffer) handlePacket(packet gopacket.Packet) { + tcpLayer := packet.Layer(layers.LayerTypeTCP) + if tcpLayer == nil { + return + } + + tcp, _ := tcpLayer.(*layers.TCP) + + if tcp.SYN && !tcp.ACK { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + return + } + ip, _ := ipLayer.(*layers.IPv4) + + srcIP := ip.SrcIP.String() + dstPort := int(tcp.DstPort) + + if s.isWhitelisted(srcIP) { + return + } + + if s.config.IgnoredPorts[dstPort] { + return + } + + s.logger.Printf("Scan detected on port %d from %s", dstPort, srcIP) + + s.tracker.Add(srcIP, s.config) + count := s.tracker.GetCount(srcIP) + + if count > s.config.MaxScanAttempts { + s.logger.Printf("IP %s exceeded scan limit (%d attempts), blocking for %.0f minutes", + srcIP, s.config.MaxScanAttempts, s.config.BlockDuration.Minutes()) + blockIP(srcIP, s.logger) + unblockTime := time.Now().Add(s.config.BlockDuration) + s.logger.Printf("IP %s will be unblocked at %s", srcIP, unblockTime.Format(s.config.Logging.TimestampFormat)) + s.unblockTasks = append(s.unblockTasks, UnblockTask{ + IP: srcIP, + UnblockAt: unblockTime, + }) + } + } +} + +func (s *Sniffer) unblockExpiredIPs() { + now := time.Now() + var remainingTasks []UnblockTask + + for _, task := range s.unblockTasks { + if now.After(task.UnblockAt) || now.Equal(task.UnblockAt) { + unblockIP(task.IP, s.logger) + } else { + remainingTasks = append(remainingTasks, task) + } + } + + s.unblockTasks = remainingTasks +} + +func (s *Sniffer) StartSniffing() error { + s.logger.Printf("Starting port scan detection") + s.logger.Printf("Configuration:") + s.logger.Printf(" Device: %s", s.config.Device) + s.logger.Printf(" Ignored ports: %v", s.getSortedPorts()) + s.logger.Printf(" Whitelisted IPs: %v", s.getSortedIPs()) + s.logger.Printf(" Block duration: %.0f minutes", s.config.BlockDuration.Minutes()) + s.logger.Printf(" Max scan attempts before blocking: %d", s.config.MaxScanAttempts) + + handle, err := pcap.OpenLive(s.config.Device, 1600, false, pcap.BlockForever) + if err != nil { + return fmt.Errorf("error opening device %s: %v", s.config.Device, err) + } + s.handle = handle + + err = handle.SetBPFFilter("tcp") + if err != nil { + return fmt.Errorf("error setting filter: %v", err) + } + + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + s.handlePacket(packet) + } + + return nil +} + +func (s *Sniffer) getSortedPorts() []int { + ports := make([]int, 0, len(s.config.IgnoredPorts)) + for port := range s.config.IgnoredPorts { + ports = append(ports, port) + } + sort.Ints(ports) + return ports +} + +func (s *Sniffer) getSortedIPs() []string { + ips := make([]string, 0, len(s.config.WhitelistedIPs)) + for ip := range s.config.WhitelistedIPs { + ips = append(ips, ip) + } + sort.Strings(ips) + return ips +} + +// Main program +func main() { + if os.Geteuid() != 0 { + log.Fatal("This program must be run as root (sudo)") + } + + config, err := loadConfig(defaultConfigFile) + if err != nil { + log.Fatalf("Configuration error: %v", err) + } + + rl, err := NewRotatingLogger(config.Logging) + if err != nil { + log.Fatalf("Failed to initialize logger: %v", err) + } + defer rl.Close() + + logger := log.New(rl, "", 0) + logger.SetPrefix(fmt.Sprintf("[%s] ", time.Now().Format(config.Logging.TimestampFormat))) + + tracker := NewScanTracker() + sniffer := &Sniffer{ + tracker: tracker, + config: config, + logger: logger, + } + sniffer.tracker.entries = make(map[string]*ScanEntry) + + go func() { + if err := sniffer.StartSniffing(); err != nil { + logger.Fatalf("Sniffer error: %v", err) + } + }() + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt) + + logger.Println("Running... Press Ctrl+C to stop") + + for { + select { + case <-ticker.C: + sniffer.unblockExpiredIPs() + case <-sigChan: + logger.Println("Stopping...") + if sniffer.handle != nil { + sniffer.handle.Close() + } + return + } + } +} diff --git a/SYN-Scan-Firewall.service b/SYN-Scan-Firewall.service new file mode 100644 index 0000000..4fc10aa --- /dev/null +++ b/SYN-Scan-Firewall.service @@ -0,0 +1,66 @@ +[Unit] +Description=SYN Scan Detection Firewall +Documentation=https:// +After=network.target network-online.target +Requires=network-online.target +ConditionPathExists=/usr/local/bin/SYN-Scan-Firewall +AssertPathExists=/etc/SYN-Scan-firewall/config.yaml +AssertFileIsExecutable=/usr/local/bin/SYN-Scan-Firewall + +[Service] +Type=notify +User=synfirewall +Group=synfirewall +WorkingDirectory=/var/lib/syn-firewall + +# Hardened execution +ExecStart=/usr/local/bin/SYN-Scan-Firewall +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartSec=5s +TimeoutStopSec=30s +KillSignal=SIGTERM +KillMode=process + +# Capabilities (minimal) +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW + +# Security confinement +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=read-only +PrivateTmp=true +PrivateDevices=true +PrivateUsers=true +ProtectHostname=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +LockPersonality=true +RemoveIPC=true + +# Memory protection +MemoryDenyWriteExecute=true +SystemCallFilter=@system-service @network-io @signal +SystemCallArchitectures=native +UMask=0077 + +# Network restrictions +IPAddressDeny=any +IPAddressAllow=localhost +IPAddressAllow=127.0.0.1 +IPAddressAllow=::1 + +# Resource limits +LimitNOFILE=4096 +LimitNPROC=64 +LimitMEMLOCK=64K +LimitSTACK=8M + +[Install] +WantedBy=multi-user.target diff --git a/banner.service b/banner.service new file mode 100644 index 0000000..92f1c21 --- /dev/null +++ b/banner.service @@ -0,0 +1,41 @@ +[Unit] +Description=Portscan Firewall Banner Service +After=network.target +ConditionPathExists=/usr/local/bin/banner_service +ConditionCapability=CAP_NET_BIND_SERVICE + +[Service] +Type=simple +User=bannersvc +Group=bannersvc +WorkingDirectory=/var/lib/banner-service +ExecStart=/usr/local/bin/banner_service +AmbientCapabilities=CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictAddressFamilies=AF_INET AF_INET6 +RestrictNamespaces=true +RestrictRealtime=true +RestrictSUIDSGID=true +MemoryDenyWriteExecute=true +LockPersonality=true +SystemCallFilter=@system-service +SystemCallArchitectures=native +IPAddressDeny=any +IPAddressAllow=localhost +IPAddressAllow=127.0.0.1 +IPAddressAllow=::1 + +# Connection rate limiting +LimitNOFILE=1024 +LimitNPROC=8 + +[Install] +WantedBy=multi-user.target diff --git a/banner_service b/banner_service new file mode 100755 index 0000000..72bb930 Binary files /dev/null and b/banner_service differ diff --git a/banner_service.go b/banner_service.go new file mode 100644 index 0000000..5cdd070 --- /dev/null +++ b/banner_service.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "net" + "os" +) + +const ( + banner = "\n*** UNAUTHORIZED ACCESS PROHIBITED ***\n*** YOUR CONNECTION ATTEMPT HAS BEEN LOGGED ***\n\n" + port = "9999" +) + +func handleConnection(conn net.Conn) { + conn.Write([]byte(banner)) + conn.Close() +} + +func main() { + ln, err := net.Listen("tcp", ":"+port) + if err != nil { + fmt.Println("Error starting banner service:", err) + os.Exit(1) + } + defer ln.Close() + + for { + conn, err := ln.Accept() + if err != nil { + continue + } + go handleConnection(conn) + } +} diff --git a/config-example.yaml b/config-example.yaml new file mode 100644 index 0000000..3e989dd --- /dev/null +++ b/config-example.yaml @@ -0,0 +1,28 @@ +# config.yaml +blockDuration: 10m +maxScanAttempts: 5 +device: "enp2s0" + +logging: + filePath: "/var/log/SYN-Scan-Firewall.log" + maxSizeMB: 10 # Max log size in megabytes + backups: 5 # Number of backup logs to keep + compressBackups: true # Whether to gzip old logs + timestampFormat: "2006-01-02T15:04:05" # Go time format + +ignoredPorts: + - 80 # HTTP + - 443 # HTTPS + - 9980 # php -S + - 631 # CUPS (printing) + - 9100 # print server ports + - 53 # DNS + - 123 # NTP + - 68 # DHCP client +# - 22 # SSH + +whitelistedIPs: + - "192.168.10.2" # own IP + - "192.168.1.100" # Example local admin +# - "10.0.0.50" # Example monitoring server + - "127.0.0.1" # Localhost diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f421fea --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module SYN-Scan-Firewall + +go 1.24.3 + +require ( + github.com/google/gopacket v1.1.19 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2d23b70 --- /dev/null +++ b/go.sum @@ -0,0 +1,17 @@ +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..f01f130 --- /dev/null +++ b/install.sh @@ -0,0 +1,72 @@ +#!/bin/bash +/usr/bin/echo "Installing libpcap-dev" +/usr/bin/sudo /usr/bin/touch /var/log/SYN-Scan-Firewall.log +/usr/bin/sudo /usr/bin/chmod 640 /var/log/SYN-Scan-Firewall.log +/usr/bin/sudo /usr/bin/apt update +/usr/bin/sudo /usr/bin/apt install -y libpcap-dev + +/usr/bin/echo "Create the service account for Banner" +/usr/bin/sudo /usr/sbin/groupadd bannersvc +/usr/bin/sudo /usr/sbin/useradd -r -g bannersvc -s /usr/sbin/nologin -d /var/lib/banner-service bannersvc +/usr/bin/sudo /usr/bin/mkdir -p /var/lib/banner-service +/usr/bin/sudo /usr/bin/chown bannersvc:bannersvc /var/lib/banner-service +/usr/bin/sudo /usr/bin/chmod 750 /var/lib/banner-service + +# Force rebuild of packages, Remove file system paths from executable, Reduces binary size and removes debug info, Enables ASLR (Address Space Layout Randomization), and Use Go's native DNS resolver. +/usr/bin/echo "Building Banner Service..." +/usr/local/bin/go build \ + -a \ + -trimpath \ + -ldflags="-s -w -extldflags=-z,now,-z,relro" \ + -buildmode=pie \ + -tags=netgo \ + -o banner_service \ + banner_service.go + +/usr/bin/sudo /usr/bin/cp banner_service /usr/local/bin/ +/usr/bin/sudo /usr/bin/chown root:bannersvc /usr/local/bin/banner_service +/usr/bin/sudo /usr/bin/chmod 750 /usr/local/bin/banner_service + +/usr/bin/echo "Set capabilities (for binding to port 9999 without root)" +/usr/bin/sudo /usr/sbin/setcap 'cap_net_bind_service=+ep' /usr/local/bin/banner_service + +/usr/bin/echo "Copy over Service Files" +/usr/bin/sudo /usr/bin/cp banner.service /etc/systemd/system/banner.service +/usr/bin/sudo /usr/bin/chmod 644 /etc/systemd/system/banner.service +/usr/bin/sudo /usr/bin/mkdir -p /etc/systemd/system/banner.service.d +/usr/bin/sudo /usr/bin/cp seccomp.conf /etc/systemd/system/banner.service.d/seccomp.conf +/usr/bin/sudo /usr/bin/chmod 644 /etc/systemd/system/banner.service + +/usr/bin/echo "Enable the service for Banner" +/usr/bin/sudo /usr/bin/systemctl daemon-reload +/usr/bin/sudo /usr/bin/systemctl enable --now banner.service + +/usr/bin/echo "Create the service account for synfirewall" +sudo groupadd synfirewall +sudo useradd -r -g synfirewall -s /usr/sbin/nologin \ + -d /var/lib/syn-firewall -c "SYN Scan Firewall" synfirewall + +/usr/bin/echo "Making config.yaml" +/usr/bin/sudo /usr/bin/mkdir -p /etc/SYN-Scan-Firewall +/usr/bin/sudo /usr/bin/chown synfirewall:synfirewall /etc/SYN-Scan-Firewall +/usr/bin/sudo /usr/bin/chmod 750 /etc/SYN-Scan-Firewall +/usr/bin/sudo /usr/bin/cp config-example.yaml /etc/SYN-Scan-Firewall/config.yaml +/usr/bin/chmod 640 /etc/SYN-Scan-Firewall/config.yaml +/usr/bin/sudo /usr/bin/nano /etc/SYN-Scan-Firewall/config.yaml + +/usr/bin/echo "Making lib dir..." +sudo mkdir -p /var/lib/syn-firewall +sudo chown synfirewall:synfirewall /var/lib/syn-firewall +sudo chmod 750 /var/lib/syn-firewall +./reBuild.sh + +/usr/bin/echo "Copy over Service Files for SYN-Scan-Firewall" +/usr/bin/sudo /usr/bin/cp SYN-Scan-Firewall.service /etc/systemd/system/ +/usr/bin/sudo /usr/bin/chmod 644 /etc/systemd/system/SYN-Scan-Firewall.service + +/usr/bin/echo "Adding AppArmor policy file..." +/usr/bin/sudo /usr/bin/cp AppArmor.policy /etc/apparmor.d/usr.local.bin.SYN-Scan-Firewall + +#/usr/bin/echo "Enable the service for SYN-Scan-Firewall" +#sudo systemctl daemon-reload +#sudo systemctl enable --now SYN-Scan-Firewall.service diff --git a/reBuild.sh b/reBuild.sh new file mode 100755 index 0000000..d1493a6 --- /dev/null +++ b/reBuild.sh @@ -0,0 +1,9 @@ +#!/bin/bash +/usr/bin/echo "Building SYN-Scan-Firewall..." +go build -buildmode=pie -ldflags="-s -w -extldflags=-z,now,-z,relro" -tags=netgo -o SYN-Scan-Firewall SYN-Scan-Firewall.go + +/usr/bin/echo "Setting up local bin..." +/usr/bin/sudo /usr/bin/cp SYN-Scan-Firewall /usr/local/bin/ +/usr/bin/sudo /usr/bin/chown root:synfirewall /usr/local/bin/SYN-Scan-Firewall +/usr/bin/sudo /usr/bin/chmod 750 /usr/local/bin/SYN-Scan-Firewall +/usr/bin/sudo /usr/sbin/setcap 'cap_net_admin,cap_net_raw+ep' /usr/local/bin/SYN-Scan-Firewall diff --git a/seccomp.conf b/seccomp.conf new file mode 100644 index 0000000..c0489d0 --- /dev/null +++ b/seccomp.conf @@ -0,0 +1,3 @@ +[Service] +SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @swap +SystemCallErrorNumber=EPERM