Welcome! Meet our Python Code Assistant, your new coding buddy. Why wait? Start exploring now!
In this tutorial, we’ll be building a honeypot. A honeypot is a cybersecurity mechanism that acts as a decoy to lure attackers away from critical systems. It's a trap designed to detect, deflect, or study hacking attempts. We’ll be building a port-scanning variant.
Information gathering, particularly through port scanning, is often the first step in a cyber attack. Attackers scan networks looking for:
A honeypot defense system helps you:
This tutorial implements a sophisticated three-tier port security model:
For this demonstration, I'll make use of two machines:
Install Scapy:
$ sudo pip3 install scapy
Kali usually comes with nmap pre-installed, but verify:
$ nmap --version
If not installed:
$ sudo apt update
$ sudo apt install nmap
1. Find your Ubuntu IP address:
$ ip addr show
Or
$ Ifconfig
Take note of your network interface (e.g., enp0s8, eth0) and IP address (e.g., 192.168.56.101). In my case, the interface I’ll be using is enp0s8 and my IP address is 192.168.56.101 as seen in the screenshot above.
You can get your Kali IP with the same commands.
2. Test connectivity:
From Kali, ping Ubuntu
$ ping 192.168.56.101
Open up a Python file, name it lock_port_scanner.py. The honeypot defense system is built with Python and Scapy. Let’s go over the code together.
We start by importing the necessary modules, specifying our interface (very important) and adding key variables like allowed IPs, decoy ports, max attempts and log file.
#!/usr/bin/env python3
"""Detects port scanners using decoy ports and blocks malicious IPs"""
from scapy.all import *
from datetime import datetime
import sys
# ==================== NETWORK INTERFACE ====================
conf.iface = "enp0s8" # Specify your network interface
# ==================== CONFIGURATION ====================
DEFENDER_IP = "192.168.56.101" # Change this to your Ubuntu IP
# Three-tier port system
PUBLIC_PORTS = [80] # Open to everyone (realistic services)
HONEYPOT_PORTS = [8080, 8443, 3389, 3306] # Decoy ports to trap attackers
PROTECTED_PORTS = [443, 53, 22, 5432] # Hidden unless IP is allowed
ALLOWED_IPS = [
"192.168.1.100", # Add your Kali IP here
"192.168.1.1", # Add other trusted IPs
]
MAX_ATTEMPTS = 3 # Block after this many honeypot accesses (changeable)
LOG_FILE = "honeypot_logs.txt"
# ==================== GLOBALS ====================
blocked_ips = []
attempt_tracker = {} # {IP: attempt_count}
total_scans = 0
total_blocks = 0
The defender_ip variable specifies which IP address to protect. Replace this with your actual Ubuntu IP.
conf.iface = "enp0s8" # Network interface
The line above tells Scapy which network interface to listen on. Replace with your actual interface name from ip addr show.
PUBLIC_PORTS = [80]
HONEYPOT_PORTS = [8080, 8443, 3389, 22, 3306]
PROTECTED_PORTS = [443, 53, 5432]
These define the three-tier model:
For the other variables too - like allowed_ips and max_attempts and log_file, feel free to adjust them to fit your scenario.
The honeypot_logs.txt will be saved in your current directory.
Next, we create helper functions - functions for different use cases.
def log_message(message, color_code=None):
"""Print and save log messages with timestamps"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"[{timestamp}] {message}"
# Color output for terminal
if color_code:
print(f"\033[{color_code}m{log_entry}\033[0m")
else:
print(log_entry)
# Save to file
with open(LOG_FILE, "a") as f:
f.write(log_entry + "\n")
The log_messages function writes messages to both the console and log file with timestamps and color coding.
Parameters:
def is_allowed_ip(ip):
"""Check if IP is in the allowlist"""
return ip in ALLOWED_IPS
Checks if an IP address is in the trusted whitelist.
Parameters:
Returns: True if the IP is allowed, False otherwise
def track_attempt(ip):
"""Track honeypot access attempts and return current count"""
if ip not in attempt_tracker:
attempt_tracker[ip] = 0
attempt_tracker[ip] += 1
return attempt_tracker[ip]
Parameters:
Returns: Current attempt count for that IP
def block_ip(ip):
"""Add IP to blocklist"""
global total_blocks
if ip not in blocked_ips:
blocked_ips.append(ip)
total_blocks += 1
log_message(f"[!] IP BLOCKED: {ip}", "91") # Red
Parameters:
It enforces security by preventing further access from malicious IPs.
def create_response(packet, flags):
"""Create a TCP response packet"""
if packet.haslayer(IP):
response = (
Ether(src=packet[Ether].dst, dst=packet[Ether].src) /
IP(src=packet[IP].dst, dst=packet[IP].src) /
TCP(
sport=packet[TCP].dport,
dport=packet[TCP].sport,
flags=flags,
seq=0,
ack=packet[TCP].seq + 1
)
)
else: # IPv6
response = (
Ether(src=packet[Ether].dst, dst=packet[Ether].src) /
IPv6(src=packet[IPv6].dst, dst=packet[IPv6].src) /
TCP(
sport=packet[TCP].dport,
dport=packet[TCP].sport,
flags=flags,
seq=0,
ack=packet[TCP].seq + 1
)
)
return response
Parameters:
Returns: A crafted response packet
It allows the script to control what the attacker sees - making ports appear open or closed.
def handle_packet(packet):
"""Process incoming TCP packets with three-tier security"""
global total_scans
# Only process SYN packets (connection attempts)
if packet[TCP].flags != "S":
return
# Extract source IP and destination port
if packet.haslayer(IP):
source_ip = packet[IP].src
else:
source_ip = packet[IPv6].src
dest_port = packet[TCP].dport
total_scans += 1
# ===== CHECK IF IP IS BLOCKED FIRST =====
if source_ip in blocked_ips:
# Drop packet silently - no response to show as "filtered" in nmap
log_message(f"[-] Blocked IP {source_ip} denied access to port {dest_port}", "90")
return # Don't send any response - this makes it appear "filtered"
# ===== PUBLIC PORTS (open to everyone) =====
if dest_port in PUBLIC_PORTS:
# Let the real service handle it - no response needed from script
log_message(f"[+] Public port {dest_port} accessed by {source_ip}", "94") # Blue
return
# ===== HONEYPOT PORTS (trap for attackers) =====
if dest_port in HONEYPOT_PORTS:
# Always respond with SYN-ACK to appear "open"
response = create_response(packet, "SA")
sendp(response, verbose=False)
# Check if IP is allowed
if is_allowed_ip(source_ip):
log_message(
f"[+] HONEYPOT ACCESS from {source_ip}:{dest_port}\n"
f"[!] Status: TRUSTED IP (allowed)",
"92" # Green
)
else:
# Track attempts for unknown IPs
attempts = track_attempt(source_ip)
log_message(
f"[!] HONEYPOT ACCESS from {source_ip}:{dest_port}\n"
f"[-] Status: UNKNOWN IP - POTENTIAL ATTACKER\n"
f"[!] Strike {attempts}/{MAX_ATTEMPTS}",
"93" # Yellow
)
# Block after max attempts
if attempts >= MAX_ATTEMPTS:
block_ip(source_ip)
return
# ===== PROTECTED PORTS (only allowed IPs) =====
if dest_port in PROTECTED_PORTS:
if is_allowed_ip(source_ip):
# Respond with SYN-ACK for allowed IPs
response = create_response(packet, "SA")
sendp(response, verbose=False)
log_message(f"[!] Protected port {dest_port} accessed by TRUSTED IP {source_ip}", "92")
else:
# Drop packet silently for unknown IPs (appears filtered)
log_message(f"[!] Protected port {dest_port} hidden from {source_ip}", "93")
return
# ===== OTHER PORTS (default behavior - drop silently) =====
# Unknown ports are silently dropped (appear filtered)
Flow of execution:
Step 1: Filter packets
if packet[TCP].flags != "S":
return
Only processes SYN packets (new connection attempts) ignores ongoing connections.
Step 2: Extract information
source_ip = packet[IP].src
dest_port = packet[TCP].dport
Identifies who is connecting and which port they're targeting.
Step 3: Check if IP is blocked
if source_ip in blocked_ips:
log_message(f"Blocked IP {source_ip} denied access")
return # Drop packet silently
If the IP is already blocked, drop the packet without responding. This makes all ports appear "filtered" to the attacker.
Step 4: Handle public ports
if dest_port in PUBLIC_PORTS:
log_message(f"Public port {dest_port} accessed")
return
Public ports are left alone - the real service handles them. Just logs the access.
Step 5: Handle honeypot ports
if dest_port in HONEYPOT_PORTS:
response = create_response(packet, "SA")
sendp(response, verbose=False)
Always responds with SYN-ACK to make the port appear "open" regardless of whether a service is actually running.
Then checks if the IP is trusted:
Step 6: Handle protected ports
if dest_port in PROTECTED_PORTS:
if is_allowed_ip(source_ip):
response = create_response(packet, "SA")
sendp(response, verbose=False)
else:
# Drop silently
This function is the brain of the system - making real-time decisions about how to respond to each connection attempt.
def print_banner():
"""Display startup information"""
print("\n" + "="*60)
print("[+] HONEYPOT DEFENSE SYSTEM ACTIVE")
print("="*60)
print(f"Defending IP: {DEFENDER_IP}")
print(f"Public Ports (open to all): {PUBLIC_PORTS}")
print(f"Honeypot Ports (trap): {HONEYPOT_PORTS}")
print(f"Protected Ports (allowed IPs only): {PROTECTED_PORTS}")
print(f"Allowed IPs: {ALLOWED_IPS}")
print(f"Block Threshold: {MAX_ATTEMPTS} attempts")
print(f"Log File: {LOG_FILE}")
print("="*60)
print("Monitoring traffic... Press Ctrl+C to stop\n")
def print_summary():
"""Display statistics on exit"""
print("\n" + "="*60)
print("[+] SESSION SUMMARY")
print("="*60)
print(f"Total scans detected: {total_scans}")
print(f"IPs blocked: {total_blocks}")
print(f"Current blocklist: {blocked_ips if blocked_ips else 'None'}")
print("="*60 + "\n")
Information displayed:
Provides insight into how much attack activity occurred during the session.
def main():
"""Main execution"""
print_banner()
# Create BPF filter
packet_filter = f"dst host {DEFENDER_IP} and tcp"
try:
# Start sniffing
sniff(filter=packet_filter, prn=handle_packet, store=False)
except KeyboardInterrupt:
print("\n\n[!] Stopping honeypot defense...")
print_summary()
sys.exit(0)
if __name__ == "__main__":
# Check for root privileges
if os.geteuid() != 0:
print("[!] This script requires root privileges. Run with: sudo python3 honeypot_defender.py")
sys.exit(1)
main()
Catches Ctrl+C gracefully and displays the summary.
Before running the script, you must disable the kernel's automatic RST (reset) packets:
$ sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP
Why this is necessary: By default, when someone tries to connect to a closed port, the Linux kernel automatically sends a RST packet saying "port closed." This would interfere with our honeypot's ability to control responses. By dropping RST packets, our script has full control over what the attacker sees.
To remove this rule later:
$ sudo iptables -D OUTPUT -p tcp --tcp-flags RST RST -j DROP
Please make sure to run the script as root.
On Ubuntu:
$ sudo python3 block_port_scan.py
You should see:
I will try to scan for open ports as an allowed user. Remember to add the IP address to the code.
On kali:
$ sudo nmap -sS -sV 192.168.56.101 -T4
Result:
On Ubuntu:
As Expected:
On Kali:
$ sudo nmap -sS -sV 192.168.56.101 -T4
Result:
On Ubuntu:
We can see that in the first scan, we got the three non-existent (honeypots) opened. And from the ubuntu terminal, we can see that after strike three, you get blocked.
Now, trying another scan (after getting blocked)
$ sudo nmap -sS -sV 192.168.56.101 -p 8080,8443,3389 -Pn
Result:
As Expected:
First scan:
Second scan:
Third scan:
Fourth scan (after blocking):
Want to be more lenient or stricter?
MAX_ATTEMPTS = 5 # Block after 5 attempts instead of 3
Common ports attackers scan:
HONEYPOT_PORTS = [
22, # SSH
23, # Telnet
3306, # MySQL
3389, # RDP
5432, # PostgreSQL
6379, # Redis
8080, # HTTP Proxy
8443, # HTTPS Alt
]
Ports you may want to hide from attackers:
PROTECTED_PORTS = [
443, # HTTPS
53, # DNS
22, # SSH (if you want to hide it)
3306, # Database
]
ALLOWED_IPS = [
"192.168.56.1", # Admin workstation
"10.0.0.50", # Monitoring server
"172.16.0.100", # Backup system
]
ALLOWED_IPS currentThis honeypot can be extended to:
I hope you enjoyed this one. See you in the next one! For now, check out similar tutorials
Liked what you read? You'll love what you can learn from our AI-powered Code Explainer. Check it out!
View Full Code Transform My Code
Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!