Website Blocking — Enterprise Implementation
Architecture Options
Method 1: Local Hosts File → Per machine, simple
Method 2: Group Policy / DNS → Domain-wide, recommended
Method 3: Proxy Server (Squid) → Full content filtering
Method 4: DNS Sinkhole (Pi-hole) → Network-wide blocking
Method 5: Firewall / iptables → Deep packet level
Method 1: Hosts File via GPO (Domain-Wide)
powershell
# Script: Deploy-WebBlocking.ps1
# Push blocked sites to all domain machines via hosts file
param(
[string]$BlockListFile = "C:\Scripts\blocked_sites.txt",
[string]$HostsFile = "C:\Windows\System32\drivers\etc\hosts",
[string]$LogFile = "C:\Logs\WebBlock.log"
)
# ── Blocked Sites List ──────────────────────────────────────────────
$blockedSites = @(
# Personal Email
"gmail.com", "www.gmail.com", "mail.google.com",
"outlook.live.com", "hotmail.com", "yahoo.com", "mail.yahoo.com",
"protonmail.com", "tutanota.com",
# Social Media
"facebook.com", "www.facebook.com",
"twitter.com", "x.com",
"instagram.com", "tiktok.com",
"snapchat.com", "reddit.com",
"linkedin.com", # Remove if needed for business
# File Sharing / Cloud Storage
"wetransfer.com", "dropbox.com",
"mega.nz", "mediafire.com",
"pastebin.com", "paste.ee",
# Streaming
"youtube.com", "www.youtube.com",
"netflix.com", "primevideo.com",
"twitch.tv", "spotify.com",
# Gaming
"steam.com", "store.steampowered.com",
"epicgames.com", "roblox.com"
)
function Write-Log($msg) {
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"$ts $msg" | Tee-Object -FilePath $LogFile -Append
}
# ── Backup original hosts file ──────────────────────────────────────
$backup = "$HostsFile.bak_$(Get-Date -Format 'yyyyMMdd')"
if (-not (Test-Path $backup)) {
Copy-Item $HostsFile $backup
Write-Log "Hosts backup: $backup"
}
# ── Read current hosts ──────────────────────────────────────────────
$currentHosts = Get-Content $HostsFile
# ── Remove old block entries ────────────────────────────────────────
$cleaned = $currentHosts | Where-Object { $_ -notmatch "# CORP-BLOCK" }
# ── Add new block entries ────────────────────────────────────────────
$newEntries = @("", "# ── CORPORATE WEB BLOCKS — DO NOT EDIT ── CORP-BLOCK-START ──")
foreach ($site in $blockedSites) {
$newEntries += "0.0.0.0 $site # CORP-BLOCK"
$newEntries += "0.0.0.0 www.$site # CORP-BLOCK"
}
$newEntries += "# ── CORP-BLOCK-END ──"
# ── Write final hosts file ───────────────────────────────────────────
($cleaned + $newEntries) | Set-Content $HostsFile -Encoding UTF8
Write-Log "Blocked $($blockedSites.Count) sites on $env:COMPUTERNAME"
# ── Flush DNS cache ──────────────────────────────────────────────────
ipconfig /flushdns | Out-Null
Write-Log "DNS cache flushed"
Write-Host "[✔] Web blocking applied: $($blockedSites.Count) sites blocked" -ForegroundColor Green
Method 2: Internet Explorer / Edge GPO Restrictions
Computer Configuration →
Administrative Templates →
Windows Components →
Microsoft Edge →
Block access to a list of URLs
Add URLs:
gmail.com
*facebook.com
*twitter.com
*youtube.com
Via Registry (push via GPO):
powershell
# Block sites in Edge/Chrome via registry
$regPath = "HKLM:\SOFTWARE\Policies\Microsoft\Edge\URLBlocklist"
if (-not (Test-Path $regPath)) {
New-Item -Path $regPath -Force | Out-Null
}
$blockedURLs = @(
"gmail.com", "facebook.com", "twitter.com",
"instagram.com", "youtube.com", "tiktok.com",
"dropbox.com", "wetransfer.com"
)
$i = 1
foreach ($url in $blockedURLs) {
Set-ItemProperty -Path $regPath -Name "$i" -Value $url
$i++
}
Write-Host "[✔] Edge URL blocklist applied via registry"
Method 3: Windows Firewall Rules
powershell
# Block by IP ranges (Gmail/Google IPs)
$googleIPs = @(
"142.250.0.0/15", "172.217.0.0/16",
"74.125.0.0/16", "64.233.160.0/19",
"66.249.64.0/19", "216.58.192.0/19"
)
foreach ($ip in $googleIPs) {
$ruleName = "BLOCK-GOOGLE-$($ip -replace '[./]','-')"
New-NetFirewallRule `
-DisplayName $ruleName `
-Direction Outbound `
-Action Block `
-RemoteAddress $ip `
-Profile Any `
-Enabled True | Out-Null
Write-Host "[+] Blocked: $ip"
}
Part 2: Linux — Multiple Methods
Method 1: Hosts File Blocking
bash
#!/bin/bash
# deploy_web_blocking.sh — Linux hosts file blocker
HOSTS_FILE="/etc/hosts"
BACKUP="/etc/hosts.bak.$(date +%Y%m%d)"
LOG="/var/log/web-blocking.log"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG"; }
# Blocked sites list
BLOCKED_SITES=(
# Personal Email
"gmail.com" "mail.google.com"
"outlook.live.com" "hotmail.com"
"yahoo.com" "mail.yahoo.com"
"protonmail.com" "tutanota.com"
# Social Media
"facebook.com" "twitter.com" "x.com"
"instagram.com" "tiktok.com"
"snapchat.com" "reddit.com"
# Cloud Storage / File Sharing
"dropbox.com" "wetransfer.com"
"mega.nz" "mediafire.com"
"pastebin.com"
# Streaming
"youtube.com" "netflix.com"
"twitch.tv" "spotify.com"
"primevideo.com"
# Gaming
"steampowered.com" "epicgames.com"
"roblox.com"
)
# Backup
[ ! -f "$BACKUP" ] && cp "$HOSTS_FILE" "$BACKUP" && log "Backup: $BACKUP"
# Remove old entries
sed -i '/# CORP-BLOCK/d' "$HOSTS_FILE"
sed -i '/CORP-BLOCK-START/,/CORP-BLOCK-END/d' "$HOSTS_FILE"
# Add new block entries
{
echo ""
echo "# ── CORPORATE WEB BLOCKS — DO NOT EDIT — CORP-BLOCK-START ──"
for site in "${BLOCKED_SITES[@]}"; do
echo "0.0.0.0 $site # CORP-BLOCK"
echo "0.0.0.0 www.$site # CORP-BLOCK"
done
echo "# ── CORP-BLOCK-END ──"
} >> "$HOSTS_FILE"
# Flush DNS cache
systemd-resolve --flush-caches 2>/dev/null || \
service nscd restart 2>/dev/null || \
resolvectl flush-caches 2>/dev/null
log "Blocked ${#BLOCKED_SITES[@]} sites on $(hostname)"
echo "[✔] Web blocking applied: ${#BLOCKED_SITES[@]} sites blocked"
Method 2: Squid Proxy — Full Content Filtering (Recommended)
bash
# Install Squid
sudo apt install squid -y # Ubuntu/Debian
sudo dnf install squid -y # RHEL/Fedora
Main config — /etc/squid/squid.conf:
squid
# ── Squid Corporate Web Filter ──────────────────────────────────────
# Network ACLs
acl localnet src 192.168.0.0/16
acl localnet src 10.0.0.0/8
acl SSL_ports port 443
acl Safe_ports port 80 443 8080
acl CONNECT method CONNECT
# Block list files
acl blocked_sites dstdomain "/etc/squid/blocked_domains.txt"
acl blocked_words url_regex -i "/etc/squid/blocked_keywords.txt"
acl social_media dstdomain "/etc/squid/social_media.txt"
acl personal_email dstdomain "/etc/squid/personal_email.txt"
acl file_sharing dstdomain "/etc/squid/file_sharing.txt"
# Time ACLs
acl work_hours time MTWHF 08:00-18:00
acl lunch_time time MTWHF 12:00-13:00
# ── DENY RULES ──────────────────────────────────────────────────────
http_access deny blocked_sites
http_access deny personal_email
http_access deny social_media
http_access deny !work_hours # Block everything outside work hours
http_access deny file_sharing
# Allow local network during work hours
http_access allow localnet work_hours
http_access deny all
# Proxy settings
http_port 3128
https_port 3129 intercept ssl-bump \
cert=/etc/squid/ssl/squid-ca.crt \
key=/etc/squid/ssl/squid-ca.key
# SSL Bump for HTTPS inspection
ssl_bump splice all
# Logging
access_log /var/log/squid/access.log combined
cache_log /var/log/squid/cache.log
# Block page
deny_info http://usb-monitor.company.com/blocked.html all
cache_mem 256 MB
maximum_object_size 10 MB
Blocked domains files:
bash
# /etc/squid/personal_email.txt
cat > /etc/squid/personal_email.txt <<EOF
gmail.com
mail.google.com
googlemail.com
outlook.live.com
hotmail.com
live.com
yahoo.com
mail.yahoo.com
ymail.com
protonmail.com
tutanota.com
zoho.com
guerrillamail.com
temp-mail.org
10minutemail.com
EOF
# /etc/squid/social_media.txt
cat > /etc/squid/social_media.txt <<EOF
facebook.com
fbcdn.net
twitter.com
t.co
x.com
instagram.com
cdninstagram.com
tiktok.com
tiktokcdn.com
snapchat.com
reddit.com
redd.it
pinterest.com
tumblr.com
EOF
# /etc/squid/file_sharing.txt
cat > /etc/squid/file_sharing.txt <<EOF
dropbox.com
dropboxusercontent.com
wetransfer.com
we.tl
mega.nz
mediafire.com
rapidshare.com
zippyshare.com
pastebin.com
hastebin.com
EOF
bash
# Start Squid
sudo squid -k parse # Test config
sudo systemctl enable --now squid
sudo systemctl status squid
Method 3: DNS Sinkhole (Network-Wide)
bash
# Install dnsmasq
sudo apt install dnsmasq -y
# Add block entries
cat >> /etc/dnsmasq.conf <<EOF
# Corporate Web Blocks
address=/gmail.com/0.0.0.0
address=/mail.google.com/0.0.0.0
address=/facebook.com/0.0.0.0
address=/twitter.com/0.0.0.0
address=/x.com/0.0.0.0
address=/instagram.com/0.0.0.0
address=/tiktok.com/0.0.0.0
address=/youtube.com/0.0.0.0
address=/dropbox.com/0.0.0.0
address=/wetransfer.com/0.0.0.0
address=/reddit.com/0.0.0.0
address=/snapchat.com/0.0.0.0
address=/netflix.com/0.0.0.0
address=/spotify.com/0.0.0.0
EOF
sudo systemctl restart dnsmasq
Method 4: iptables Firewall Rules
bash
#!/bin/bash
# Block websites at firewall level
# Flush existing web-block rules
iptables -D OUTPUT -j WEB_BLOCK 2>/dev/null
iptables -F WEB_BLOCK 2>/dev/null
iptables -X WEB_BLOCK 2>/dev/null
# Create new chain
iptables -N WEB_BLOCK
iptables -I OUTPUT -j WEB_BLOCK
# Resolve and block domains
DOMAINS=(
"gmail.com" "facebook.com" "twitter.com"
"instagram.com" "tiktok.com" "youtube.com"
"dropbox.com" "wetransfer.com" "reddit.com"
)
for domain in "${DOMAINS[@]}"; do
IPS=$(dig +short "$domain" | grep -E '^[0-9]+\.')
for ip in $IPS; do
iptables -A WEB_BLOCK -d "$ip" -j DROP
echo "[+] Blocked $domain → $ip"
done
done
# Save rules
iptables-save > /etc/iptables/rules.v4
echo "[✔] Firewall web blocking applied"
Part 3: Centralized Block List Manager
Save as /opt/web-blocker/manage_blocklist.py:
python
#!/usr/bin/env python3
"""
Centralized Web Block List Manager
Manage, deploy, and audit website blocking across all machines
"""
import json, os, subprocess, paramiko
from datetime import datetime
CONFIG_FILE = "/opt/web-blocker/config.json"
REPORT_FILE = "/var/www/html/usb-dashboard/webblocker.html"
# ── Default block categories ─────────────────────────────────────────
DEFAULT_CATEGORIES = {
"personal_email": {
"enabled": True,
"sites": ["gmail.com","mail.google.com","hotmail.com",
"yahoo.com","protonmail.com","tutanota.com"]
},
"social_media": {
"enabled": True,
"sites": ["facebook.com","twitter.com","x.com",
"instagram.com","tiktok.com","snapchat.com","reddit.com"]
},
"file_sharing": {
"enabled": True,
"sites": ["dropbox.com","wetransfer.com","mega.nz",
"mediafire.com","pastebin.com"]
},
"streaming": {
"enabled": True,
"sites": ["youtube.com","netflix.com","twitch.tv",
"spotify.com","primevideo.com"]
},
"gaming": {
"enabled": False,
"sites": ["steampowered.com","epicgames.com","roblox.com"]
}
}
def load_config():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE) as f:
return json.load(f)
return DEFAULT_CATEGORIES
def save_config(config):
os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
with open(CONFIG_FILE, "w") as f:
json.dump(config, f, indent=2)
def get_all_blocked_sites(config):
sites = []
for cat, data in config.items():
if data["enabled"]:
sites.extend(data["sites"])
return sorted(set(sites))
def deploy_to_linux(host, ssh_key, blocked_sites):
"""Deploy block list to Linux machine via SSH"""
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, key_filename=ssh_key, timeout=10)
# Build hosts entries
entries = "\n".join([
f"0.0.0.0 {s} # CORP-BLOCK\n0.0.0.0 www.{s} # CORP-BLOCK"
for s in blocked_sites
])
cmd = f"""
sed -i '/# CORP-BLOCK/d' /etc/hosts
echo '# CORP-BLOCK-START' >> /etc/hosts
echo '{entries}' >> /etc/hosts
echo '# CORP-BLOCK-END' >> /etc/hosts
systemd-resolve --flush-caches 2>/dev/null || true
echo "OK"
"""
_, stdout, _ = client.exec_command(cmd)
result = stdout.read().decode().strip()
client.close()
return result == "OK"
except Exception as e:
print(f"[ERROR] {host}: {e}")
return False
def generate_status_page(config, machines):
cats_html = ""
for cat, data in config.items():
status_color = "#2ecc71" if data["enabled"] else "#e74c3c"
status_text = "ACTIVE" if data["enabled"] else "DISABLED"
sites_html = "".join(f"<span style='background:#2c2f3e;padding:2px 8px;"
f"border-radius:4px;font-size:0.8em;margin:2px'>{s}</span>"
for s in data["sites"])
cats_html += f"""
<div style='background:#1a1d27;border-radius:8px;padding:16px;margin-bottom:12px;
border-left:4px solid {status_color}'>
<div style='display:flex;justify-content:space-between;align-items:center'>
<strong style='text-transform:capitalize'>{cat.replace("_"," ")}</strong>
<span style='background:{status_color};color:white;padding:2px 10px;
border-radius:12px;font-size:0.8em'>{status_text}</span>
</div>
<div style='margin-top:10px;display:flex;flex-wrap:wrap;gap:4px'>{sites_html}</div>
</div>"""
total_blocked = len(get_all_blocked_sites(config))
html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="300">
<title>Web Blocker Status</title>
<style>
body {{ font-family:Segoe UI,Ubuntu,sans-serif;background:#0f1117;
color:#e0e0e0;padding:30px; }}
h1 {{ color:#e74c3c; }}
.summary {{ display:flex;gap:16px;margin:20px 0;flex-wrap:wrap; }}
.card {{ background:#1a1d27;border-radius:10px;padding:18px 24px;
border-left:4px solid #e74c3c;min-width:130px;text-align:center; }}
.card h3 {{ font-size:2em;color:#e74c3c;margin:0; }}
.card p {{ color:#888;font-size:0.85em;margin:4px 0 0; }}
</style>
</head>
<body>
<h1>🚫 Web Blocking Status</h1>
<p style='color:#666'>Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<div class="summary">
<div class="card"><h3>{total_blocked}</h3><p>Sites Blocked</p></div>
<div class="card"><h3>{len(machines)}</h3><p>Machines</p></div>
<div class="card"><h3>{sum(1 for c in config.values() if c['enabled'])}</h3><p>Active Categories</p></div>
</div>
<h2 style='color:#e74c3c;margin:20px 0 12px'>Block Categories</h2>
{cats_html}
</body>
</html>"""
os.makedirs(os.path.dirname(REPORT_FILE), exist_ok=True)
with open(REPORT_FILE, "w") as f:
f.write(html)
print(f"[✔] Status page: {REPORT_FILE}")
if __name__ == "__main__":
config = load_config()
sites = get_all_blocked_sites(config)
print(f"[*] Total blocked sites: {len(sites)}")
machines = ["192.168.1.10", "192.168.1.11"] # Add your machines
generate_status_page(config, machines)
save_config(config)
Deployment Summary
bash
# Linux — deploy immediately
chmod +x /opt/web-blocker/deploy_web_blocking.sh
sudo /opt/web-blocker/deploy_web_blocking.sh
# Push to all Linux machines via Ansible
ansible all -m script -a "/opt/web-blocker/deploy_web_blocking.sh" \
--become -i /etc/ansible/hosts
# Windows — push via GPO startup script
# Place USB-WebBlock.ps1 in SYSVOL and link to GPO
# Schedule daily refresh (block lists update)
echo "0 6 * * * root /opt/web-blocker/deploy_web_blocking.sh" \
>> /etc/crontab
Quick Reference — Methods vs Scale
|
Method |
Scale |
Bypass Risk |
Best For |
|
Hosts file |
Per machine |
Medium |
Small teams |
|
GPO + Registry |
Domain-wide |
Medium |
Windows AD |
|
Squid Proxy |
Network-wide |
Low |
Enterprises |
|
DNS Sinkhole |
Network-wide |
Medium |
All devices |
|
iptables/Firewall |
Network-wide |
Low |
Linux-heavy |
|
Commercial DLP |
Enterprise |
Very Low |
Large orgs
|
custom block page (branded 403 page employees see), bypass request workflow (employees request access), or per-department policies (e.g. Marketing can access social media)?
Save as /var/www/html/blocked/index.html:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Access Blocked — Company IT Policy</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Segoe UI, Ubuntu, sans-serif;
background: #0f1117;
color: #e0e0e0;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: #1a1d27;
border-radius: 16px;
padding: 40px;
max-width: 580px;
width: 100%;
text-align: center;
border-top: 4px solid #e74c3c;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
.icon { font-size: 4em; margin-bottom: 16px; }
.logo {
font-size: 1em;
color: #666;
margin-bottom: 24px;
letter-spacing: 2px;
text-transform: uppercase;
}
h1 { color: #e74c3c; font-size: 1.6em; margin-bottom: 10px; }
p { color: #888; font-size: 0.95em; line-height: 1.7; margin-bottom: 20px; }
.site-box {
background: #0f1117;
border: 1px solid #2c2f3e;
border-radius: 8px;
padding: 12px 20px;
font-family: monospace;
font-size: 1em;
color: #e74c3c;
margin-bottom: 24px;
word-break: break-all;
}
.reason {
background: #2c2f3e;
border-radius: 8px;
padding: 12px 16px;
font-size: 0.85em;
color: #aaa;
margin-bottom: 28px;
text-align: left;
}
.reason strong { color: #e0e0e0; }
.btn {
display: inline-block;
padding: 12px 28px;
border-radius: 8px;
font-size: 0.95em;
font-weight: 600;
cursor: pointer;
text-decoration: none;
border: none;
transition: opacity 0.2s;
margin: 6px;
}
.btn:hover { opacity: 0.85; }
.btn-primary { background: #e74c3c; color: white; }
.btn-secondary {
background: transparent;
color: #7fb3d3;
border: 1px solid #2c2f3e;
}
.divider {
border: none;
border-top: 1px solid #2c2f3e;
margin: 28px 0;
}
.info-row {
display: flex;
justify-content: space-between;
font-size: 0.78em;
color: #555;
flex-wrap: wrap;
gap: 6px;
}
.modal-overlay {
display: none;
position: fixed; inset: 0;
background: rgba(0,0,0,0.7);
z-index: 100;
align-items: center;
justify-content: center;
}
.modal-overlay.active { display: flex; }
.modal {
background: #1a1d27;
border-radius: 14px;
padding: 32px;
max-width: 480px;
width: 90%;
border-top: 3px solid #3498db;
}
.modal h2 { color: #3498db; margin-bottom: 16px; font-size: 1.2em; }
.modal input, .modal select, .modal textarea {
width: 100%;
background: #0f1117;
border: 1px solid #2c2f3e;
border-radius: 8px;
color: #e0e0e0;
padding: 10px 14px;
font-size: 0.9em;
margin-bottom: 14px;
font-family: inherit;
outline: none;
}
.modal textarea { height: 90px; resize: vertical; }
.modal input:focus, .modal select:focus, .modal textarea:focus {
border-color: #3498db;
}
.modal label { font-size: 0.82em; color: #888; display: block; margin-bottom: 5px; }
.success-msg {
display: none;
background: #1a3a2a;
border: 1px solid #2ecc71;
border-radius: 8px;
padding: 16px;
color: #2ecc71;
font-size: 0.9em;
margin-top: 12px;
}
</style>
</head>
<body>
<div class="container">
<div class="icon">🚫</div>
<div class="logo">⚙Company IT Security</div>
<h1>Access Blocked</h1>
<p>This website has been blocked by your organization's IT security policy.</p>
<div class="site-box" id="blocked-url">
<!-- Filled by JS or Squid -->
<span id="url-display">This website</span>
</div>
<div class="reason">
<strong>Reason:</strong>
<span id="block-reason">Personal email / unauthorized web service</span><br><br>
<strong>Policy:</strong>IT Security Policy v2.4 — Acceptable Use of Internet Resources<br>
<strong>Category:</strong><span id="block-category">Personal Communication</span>
</div>
<div>
<button class="btn btn-primary" onclick="openRequestModal()">
🔓Request Access
</button>
<a class="btn btn-secondary" href="javascript:history.back()">
← Go Back
</a>
</div>
<hr class="divider">
<div class="info-row">
<span>🖥Host:<span id="hostname-display">WORKSTATION</span></span>
<span>👤User:<span id="user-display">employee</span></span>
<span>🕐<span id="time-display"></span></span>
<span>📞IT Help: ext. 1234</span>
</div>
</div>
<!-- Access Request Modal -->
<div class="modal-overlay" id="requestModal">
<div class="modal">
<h2>🔓Request Website Access</h2>
<label>Your Name</label>
<input type="text" id="req-name" placeholder="Full Name">
<label>Employee ID</label>
<input type="text" id="req-empid" placeholder="EMP-XXXXX">
<label>Department</label>
<select id="req-dept">
<option value="">Select Department</option>
<option>Engineering</option>
<option>Marketing</option>
<option>Finance</option>
<option>HR</option>
<option>Sales</option>
<option>Operations</option>
<option>Executive</option>
</select>
<label>Website you need access to</label>
<input type="text" id="req-site" placeholder="e.g. gmail.com">
<label>Business Justification</label>
<textarea id="req-reason"
placeholder="Why do you need access? How does it relate to your work?"></textarea>
<label>Manager Email (for approval)</label>
<input type="email" id="req-manager" placeholder="manager@company.com">
<div style="display:flex;gap:10px;margin-top:6px">
<button class="btn btn-primary" style="flex:1" onclick="submitRequest()">
📨Submit Request
</button>
<button class="btn btn-secondary" onclick="closeRequestModal()">
Cancel
</button>
</div>
<div class="success-msg" id="success-msg">
✅Request submitted! Your manager and IT will review within 24 hours.
Reference ID: <strong id="ref-id"></strong>
</div>
</div>
</div>
<script>
// Fill dynamic fields
const params = new URLSearchParams(window.location.search);
document.getElementById('url-display').textContent =
params.get('url') || document.referrer || 'The requested site';
document.getElementById('block-reason').textContent =
params.get('reason') || 'Unauthorized web service';
document.getElementById('block-category').textContent =
params.get('category') || 'Restricted Content';
document.getElementById('time-display').textContent =
new Date().toLocaleString();
// Auto-fill site in modal
document.getElementById('req-site').value =
params.get('url') || '';
function openRequestModal() {
document.getElementById('requestModal').classList.add('active');
}
function closeRequestModal() {
document.getElementById('requestModal').classList.remove('active');
document.getElementById('success-msg').style.display = 'none';
}
async function submitRequest() {
const name = document.getElementById('req-name').value.trim();
const empid = document.getElementById('req-empid').value.trim();
const dept = document.getElementById('req-dept').value;
const site = document.getElementById('req-site').value.trim();
const reason = document.getElementById('req-reason').value.trim();
const manager = document.getElementById('req-manager').value.trim();
if (!name || !site || !reason || !manager || !dept) {
alert('Please fill in all fields.'); return;
}
const refId = 'REQ-' + Date.now().toString(36).toUpperCase();
const payload = { name, empid, dept, site, reason, manager,
refId, timestamp: new Date().toISOString(),
blockedUrl: params.get('url') || '' };
try {
await fetch('/api/access-request', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
} catch(e) { /* offline fallback — still show success */ }
document.getElementById('ref-id').textContent = refId;
document.getElementById('success-msg').style.display = 'block';
setTimeout(closeRequestModal, 4000);
}
</script>
</body>
</html>Part 2: Bypass Request Workflow Backend
Save as
/opt/web-blocker/request_server.py:python
#!/usr/bin/env python3"""Web Access Request Workflow BackendHandles bypass requests, manager approvals, and auto-deploys exceptions"""fromhttp.serverimportHTTPServer,BaseHTTPRequestHandlerimportjson,smtplib,sqlite3,os,subprocessfrommime.multipartimportMIMEMultipartfrommime.textimportMIMETextfromdatetimeimportdatetimefromurllib.parseimporturlparse,parse_qsDB_FILE="/var/lib/web-blocker/requests.db"SMTP_SERVER="smtp.company.com"SMTP_PORT=587SMTP_USER="it-security@company.com"SMTP_PASS="your_smtp_password"APPROVE_BASE="https://usb-monitor.company.com/approve"IT_EMAIL="it-security@company.com"os.makedirs(os.path.dirname(DB_FILE),exist_ok=True)# ── Database ─────────────────────────────────────────────────────────definit_db():conn=sqlite3.connect(DB_FILE)conn.execute("""CREATE TABLE IF NOT EXISTS requests (ref_id TEXT PRIMARY KEY,name TEXT,emp_id TEXT,department TEXT,site TEXT,reason TEXT,manager TEXT,status TEXT DEFAULT 'pending',created_at TEXT,updated_at TEXT,blocked_url TEXT,notes TEXT)""")conn.commit()conn.close()defsave_request(data):conn=sqlite3.connect(DB_FILE)conn.execute("""INSERT OR REPLACE INTO requests(ref_id, name, emp_id, department, site, reason,manager, status, created_at, updated_at, blocked_url)VALUES (?,?,?,?,?,?,?,'pending',?,?,?)""",(data["refId"],data["name"],data.get("empid",""),data["dept"],data["site"],data["reason"],data["manager"],data["timestamp"],data["timestamp"],data.get("blockedUrl","")))conn.commit()conn.close()defupdate_request(ref_id,status,notes=""):conn=sqlite3.connect(DB_FILE)conn.execute("""UPDATE requests SET status=?, updated_at=?, notes=?WHERE ref_id=?""",(status,datetime.now().isoformat(),notes,ref_id))conn.commit()conn.close()defget_all_requests():conn=sqlite3.connect(DB_FILE)conn.row_factory=sqlite3.Rowrows=conn.execute("SELECT * FROM requests ORDER BY created_at DESC").fetchall()conn.close()return[dict(r)forrinrows]defget_request(ref_id):conn=sqlite3.connect(DB_FILE)conn.row_factory=sqlite3.Rowrow=conn.execute("SELECT * FROM requests WHERE ref_id=?",(ref_id,)).fetchone()conn.close()returndict(row)ifrowelseNone# ── Email ────────────────────────────────────────────────────────────defsend_email(to,subject,html):try:msg=MIMEMultipart("alternative")msg["Subject"]=subjectmsg["From"]=SMTP_USERmsg["To"]=tomsg.attach(MIMEText(html,"html"))withsmtplib.SMTP(SMTP_SERVER,SMTP_PORT)ass:s.starttls()s.login(SMTP_USER,SMTP_PASS)s.sendmail(SMTP_USER,[to],msg.as_string())print(f"[EMAIL] Sent to {to}: {subject}")exceptExceptionase:print(f"[EMAIL ERROR] {e}")defnotify_manager(req):approve_url=f"{APPROVE_BASE}?ref={req['ref_id']}&action=approve"deny_url=f"{APPROVE_BASE}?ref={req['ref_id']}&action=deny"html=f"""<div style="font-family:sans-serif;max-width:600px;margin:auto"><div style="background:#2c3e50;color:white;padding:16px 20px;border-radius:8px 8px 0 0"><h2 style="margin:0">🔓Web Access Request — Approval Required</h2></div><div style="background:#f9f9f9;padding:24px;border:1px solid #ddd"><table style="width:100%;border-collapse:collapse"><tr><td style="padding:8px;color:#666;width:140px">Reference</td><td style="padding:8px;font-weight:bold">{req['ref_id']}</td></tr><tr style="background:#fff"><td style="padding:8px;color:#666">Employee</td><td style="padding:8px">{req['name']} ({req['emp_id']})</td></tr><tr><td style="padding:8px;color:#666">Department</td><td style="padding:8px">{req['department']}</td></tr><tr style="background:#fff"><td style="padding:8px;color:#666">Site Requested</td><td style="padding:8px;font-weight:bold;color:#e74c3c">{req['site']}</td></tr><tr><td style="padding:8px;color:#666">Justification</td><td style="padding:8px">{req['reason']}</td></tr><tr style="background:#fff"><td style="padding:8px;color:#666">Submitted</td><td style="padding:8px">{req['created_at'][:19]}</td></tr></table><div style="margin-top:24px;text-align:center"><a href="{approve_url}" style="background:#2ecc71;color:white;padding:12px 28px;border-radius:8px;text-decoration:none;font-weight:bold;margin:6px;display:inline-block">✅Approve Access</a><a href="{deny_url}" style="background:#e74c3c;color:white;padding:12px 28px;border-radius:8px;text-decoration:none;font-weight:bold;margin:6px;display:inline-block">❌Deny Request</a></div><p style="color:#999;font-size:0.8em;margin-top:20px;text-align:center">IT Security | Company Name | This is an automated message</p></div></div>"""send_email(req["manager"],f"[ACTION REQUIRED] Web Access Request — {req['name']} → {req['site']}",html)defnotify_employee(req,approved):status_color="#2ecc71"ifapprovedelse"#e74c3c"status_text="APPROVED ✅"ifapprovedelse"DENIED ❌"msg=("Your request has been approved. Access will be granted within 30 minutes."ifapprovedelse"Your request was not approved. Contact IT if you believe this is an error.")html=f"""<div style="font-family:sans-serif;max-width:600px;margin:auto"><div style="background:{status_color};color:white;padding:16px 20px;border-radius:8px 8px 0 0"><h2 style="margin:0">Web Access Request: {status_text}</h2></div><div style="background:#f9f9f9;padding:24px;border:1px solid #ddd"><p>Hi {req['name']},</p><p style="margin:12px 0">{msg}</p><table style="width:100%;border-collapse:collapse;margin-top:16px"><tr><td style="padding:8px;color:#666;width:140px">Reference</td><td style="padding:8px">{req['ref_id']}</td></tr><tr style="background:#fff"><td style="padding:8px;color:#666">Site</td><td style="padding:8px">{req['site']}</td></tr><tr><td style="padding:8px;color:#666">Status</td><td style="padding:8px;font-weight:bold">{status_text}</td></tr></table><p style="color:#999;font-size:0.8em;margin-top:20px">IT Help Desk: ext. 1234 | it-security@company.com</p></div></div>"""# Send to employee via IT (no direct employee email in request)send_email(IT_EMAIL,f"Web Access {status_text} — {req['name']} — {req['site']}",html)# ── Apply Exception ───────────────────────────────────────────────────defapply_exception(site,action="allow"):"""Add or remove a site from the block list"""HOSTS="/etc/hosts"SQUID_WHITELIST="/etc/squid/whitelist.txt"# Hosts file exceptionifaction=="allow":subprocess.run(["sed","-i",f"/0\\.0\\.0\\.0 {site}/d",HOSTS],check=False)subprocess.run(["sed","-i",f"/0\\.0\\.0\\.0 www\\.{site}/d",HOSTS],check=False)# Add to Squid whitelistwithopen(SQUID_WHITELIST,"a")asf:f.write(f"{site}\n")subprocess.run(["squid","-k","reconfigure"],check=False)print(f"[+] Exception applied: {site} → ALLOWED")else:print(f"[+] Site remains blocked: {site}")subprocess.run(["systemd-resolve","--flush-caches"],check=False,capture_output=True)# ── HTTP Request Handler ──────────────────────────────────────────────classRequestHandler(BaseHTTPRequestHandler):deflog_message(self,format,*args):pass# Suppress default logsdefsend_json(self,code,data):body=json.dumps(data).encode()self.send_response(code)self.send_header("Content-Type","application/json")self.send_header("Access-Control-Allow-Origin","*")self.send_header("Content-Length",len(body))self.end_headers()self.wfile.write(body)defsend_html(self,code,html):body=html.encode()self.send_response(code)self.send_header("Content-Type","text/html")self.end_headers()self.wfile.write(body)defdo_OPTIONS(self):self.send_response(200)self.send_header("Access-Control-Allow-Origin","*")self.send_header("Access-Control-Allow-Methods","POST,GET")self.send_header("Access-Control-Allow-Headers","Content-Type")self.end_headers()defdo_POST(self):ifself.path=="/api/access-request":length=int(self.headers.get("Content-Length",0))body=json.loads(self.rfile.read(length))save_request(body)notify_manager(body)print(f"[REQUEST] {body['refId']} — {body['name']} → {body['site']}")self.send_json(200,{"status":"ok","refId":body["refId"]})defdo_GET(self):parsed=urlparse(self.path)params=parse_qs(parsed.query)# Approval endpointifparsed.path=="/approve":ref_id=params.get("ref",[""])[0]action=params.get("action",[""])[0]req=get_request(ref_id)ifnotreq:self.send_html(404,"<h1>Request not found</h1>")returnapproved=action=="approve"update_request(ref_id,"approved"ifapprovedelse"denied")ifapproved:apply_exception(req["site"],"allow")notify_employee(req,approved)status="APPROVED ✅"ifapprovedelse"DENIED ❌"color="#2ecc71"ifapprovedelse"#e74c3c"self.send_html(200,f"""<html><body style="font-family:sans-serif;text-align:center;padding:60px;background:#0f1117;color:#e0e0e0"><div style="background:#1a1d27;border-radius:16px;padding:40px;max-width:480px;margin:auto;border-top:4px solid {color}"><h1 style="color:{color}">{status}</h1><p>Request <strong>{ref_id}</strong> for<strong>{req['site']}</strong> has been {action}d.</p><p style="color:#666;margin-top:16px">Employee will be notified.</p></div></body></html>""")return# Admin dashboardifparsed.path=="/admin/requests":requests=get_all_requests()rows=""forrinrequests:sc={"approved":"#2ecc71","denied":"#e74c3c"}.get(r["status"],"#f39c12")rows+=f"""<tr><td>{r['ref_id']}</td><td>{r['name']}</td><td>{r['department']}</td><td style='color:#e74c3c;font-weight:bold'>{r['site']}</td><td><span style='background:{sc};color:white;padding:2px 10px;border-radius:10px;font-size:0.8em'>{r['status'].upper()}</span></td><td>{r['created_at'][:16]}</td><td style='font-size:0.8em;color:#888'>{r['reason'][:60]}...</td></tr>"""self.send_html(200,f"""<!DOCTYPE html><html><head><meta charset='UTF-8'><meta http-equiv='refresh' content='60'><title>Access Requests</title><style>body{{font-family:Segoe UI,sans-serif;background:#0f1117;color:#e0e0e0;padding:30px}}h1{{color:#e74c3c}}table{{width:100%;border-collapse:collapse;background:#1a1d27;border-radius:10px;overflow:hidden;margin-top:20px}}th{{background:#2c2f3e;color:#aaa;padding:11px 13px;text-align:left}}td{{padding:10px 13px;border-bottom:1px solid #1e2130;font-size:0.85em}}tr:hover td{{background:#1e2130}}</style></head><body><h1>🔓Web Access Requests</h1><p style='color:#666'>Total: {len(requests)} | Auto-refreshes every 60s</p><table><tr><th>Ref ID</th><th>Name</th><th>Dept</th><th>Site</th><th>Status</th><th>Submitted</th><th>Reason</th></tr>{rows}</table></body></html>""")returnself.send_json(404,{"error":"not found"})# ── Start Server ──────────────────────────────────────────────────────if__name__=="__main__":init_db()print("[*] Access Request Server running on :8080")print(f"[*] Admin dashboard: http://localhost:8080/admin/requests")HTTPServer(("0.0.0.0",8080),RequestHandler).serve_forever()