commit
4631524455
@ -0,0 +1,8 @@ |
||||
# Example AppArmor profile (/etc/apparmor.d/usr.local.bin.SYN-Scan-Firewall) |
||||
/usr/local/bin/SYN-Scan-Firewall { |
||||
include <abstractions/base> |
||||
capability net_admin, |
||||
capability net_raw, |
||||
/etc/syn-firewall/config.yaml r, |
||||
/var/lib/syn-firewall/** rw, |
||||
} |
||||
@ -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. |
||||
@ -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 |
||||
``` |
||||
Binary file not shown.
@ -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 |
||||
} |
||||
} |
||||
} |
||||
@ -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 |
||||
@ -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 |
||||
Binary file not shown.
@ -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) |
||||
} |
||||
} |
||||
@ -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 |
||||
@ -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 |
||||
) |
||||
@ -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= |
||||
@ -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 |
||||
@ -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 |
||||
@ -0,0 +1,3 @@ |
||||
[Service] |
||||
SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @swap |
||||
SystemCallErrorNumber=EPERM |
||||
Loading…
Reference in new issue