Son dönemde cPanel / WHM tarafında ortaya çıkan kritik login/auth bypass riskleri, özellikle WHM erişimi internete açık olan sunucular için ciddi tehdit oluşturmaktadır.
Bu tip zafiyetlerde saldırganlar, bazı durumlarda normal kullanıcı adı/şifre doğrulamasını geçmeden WHM API, session, cookie veya login akışındaki zayıflıkları hedefleyerek yetkisiz erişim elde etmeye çalışabilir.
Aşağıdaki liste; şüpheli UID 0 kullanıcıları, SSH backdoor’ları, cron persistence, reverse shell, web shell, WHM access log izleri ve sistem sertleştirme adımlarını kontrol etmek için hazırlanmıştır.
Detaylı incelemelerimiz sonucu bu tarz saldırıya mağruz kalan sunucularımızdan elde ettiğimiz bilgileri sizlerlede paylaşmak istiyoruz. Sunucu temizlendikten sonra güvenlik önlemi olarak alabileceğiniz scripti de ek olarak paylaşıyoruz.
- Açık: cpsrvd üzerinde CRLF injection — saldırgan kimlik doğrulamadan geçmeden root yetkisinde komut çalıştırabiliyor.
- Patched cPanel sürümleri: 11.110.0.97 / 11.118.0.63 / 11.126.0.54 / 11.132.0.29 / 11.134.0.20 / 11.136.0.5
- Risk altındakiler: Yukarıdaki sürümlerin altında olan tüm cPanel/WHM kurulumları.
Not: komut çalıştırmadan önce lütfen yedek alın saldırı izleri her sunucuda farklılık gösterebilir. Bu tek başına yeterli bir koruma önlemi değildir. hack girişiminde bulunulmuş sunucuyu korumaz. ancak eski versionda iseniz yada mevcut sunucunuzda gelecekte bu tarz bir durum ile karşılaşmak istemezseniz. ek güvenlik katmanı eklemek isterseniz bot giriş denemelerini kesecek bir yapıdır.
örnek giriş adresi: https://95-134-4-44.cprapid.com:2083/- https://95-134-4-44.cprapid.com:2087
domain: meohost.com
şifre: meohost
girişe yazılması gereken: meohost.com:meohost
İnstall komutu
#!/bin/bash
# cPanel/WHM Yetkilendirme Kapısı — otomatik kurucu
# Hazırlayan: meohost
#
# Kullanım:
# sudo bash install.sh
set -euo pipefail
PKG_DIR="$(cd "$(dirname "$0")" && pwd)"
NIC="${NIC:-}"
red() { printf "\033[1;31m%s\033[0m\n" "$*"; }
green() { printf "\033[1;32m%s\033[0m\n" "$*"; }
blue() { printf "\033[1;34m%s\033[0m\n" "$*"; }
yellow() { printf "\033[1;33m%s\033[0m\n" "$*"; }
[ "$EUID" -eq 0 ] || { red "Bu script root olarak calistirilmalidir."; exit 1; }
[ -d /usr/local/cpanel ] || { red "cPanel/WHM tespit edilemedi (/usr/local/cpanel yok)."; exit 1; }
blue "=== 1/8 Genel ag arayuzu tespit ediliyor ==="
if [ -z "$NIC" ]; then
NIC=$(ip -o -4 route show to default | awk '{print $5}' | head -1)
fi
[ -n "$NIC" ] || { red "Genel ag arayuzu bulunamadi. NIC=ens192 gibi elle ayarlayin."; exit 1; }
green " NIC = $NIC"
blue "=== 2/8 Mevcut iptables yedegi aliniyor ==="
mkdir -p /root/cpanel-gate-backup
BACKUP="/root/cpanel-gate-backup/iptables-$(date +%Y%m%d-%H%M%S).rules"
iptables-save > "$BACKUP"
green " Yedek: $BACKUP"
blue "=== 3/8 Paketler kuruluyor (nginx, iptables-services) ==="
dnf install -y nginx iptables-services >/dev/null
green " nginx + iptables-services yuklendi"
blue "=== 4/8 Dizinler olusturuluyor, gate.py ve secret yerlestiriliyor ==="
mkdir -p /opt/cpanel-gate /etc/cpanel-gate/ssl /var/log/cpanel-gate
install -m 0755 "$PKG_DIR/gate.py" /opt/cpanel-gate/gate.py
if [ ! -s /etc/cpanel-gate/secret ]; then
python3 -c "import secrets; print(secrets.token_hex(32))" > /etc/cpanel-gate/secret
chmod 600 /etc/cpanel-gate/secret
green " Yeni HMAC secret uretildi"
else
yellow " Mevcut HMAC secret korundu (/etc/cpanel-gate/secret)"
fi
blue "=== 5/8 cpsrvd SSL sertifikasi cert/key olarak ayriliyor ==="
python3 - <<'PYEOF'
import re
src = open('/var/cpanel/ssl/cpanel/cpanel.pem').read()
key = re.search(r'-----BEGIN (RSA )?PRIVATE KEY-----.*?-----END (RSA )?PRIVATE KEY-----', src, re.DOTALL)
certs = re.findall(r'-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----', src, re.DOTALL)
assert key and certs, 'cpanel.pem parse edilemedi'
open('/etc/cpanel-gate/ssl/key.pem','w').write(key.group(0)+'\n')
open('/etc/cpanel-gate/ssl/cert.pem','w').write('\n'.join(certs)+'\n')
PYEOF
chmod 640 /etc/cpanel-gate/ssl/{key,cert}.pem
chown root:nginx /etc/cpanel-gate/ssl/{key,cert}.pem
green " /etc/cpanel-gate/ssl/{cert,key}.pem hazir"
blue "=== 6/8 systemd unit + nginx config + sysctl yerlestiriliyor ==="
install -m 0644 "$PKG_DIR/cpanel-gate.service" /etc/systemd/system/cpanel-gate.service
install -m 0644 "$PKG_DIR/cpanel-gate-nginx.conf" /etc/nginx/conf.d/cpanel-gate.conf
install -m 0644 "$PKG_DIR/99-cpanel-gate.conf" /etc/sysctl.d/99-cpanel-gate.conf
# nginx default 80 sunucusunu pasif yap (LiteSpeed/Apache zaten 80'i tutar)
if grep -q 'listen 80 default_server' /etc/nginx/nginx.conf; then
sed -i.bak '/^ server {/,/^ }/{/listen.*80 default_server/,/^ }/d}' /etc/nginx/nginx.conf || true
yellow " /etc/nginx/nginx.conf icindeki default :80 server blogu pasif yapildi"
fi
# Eger sysctl dosyasinda NIC ens192 ise ve sistemin NIC'i farkli ise duzelt
if [ "$NIC" != "ens192" ]; then
sed -i "s/ens192/$NIC/g" /etc/sysctl.d/99-cpanel-gate.conf
yellow " sysctl NIC '$NIC' olarak guncellendi"
fi
systemctl daemon-reload
sysctl --system >/dev/null 2>&1 || true
green " systemd + nginx + sysctl kuruldu"
blue "=== 7/8 iptables DNAT/DROP kurallari uygulaniyor ==="
# Tekrarli calismayi tolere et: ayni kural varsa atla
add_nat() {
iptables -t nat -C "$@" 2>/dev/null || iptables -t nat -A "$@"
}
add_in() {
iptables -C "$@" 2>/dev/null || iptables -I "$@"
}
add_nat PREROUTING -i "$NIC" -p tcp --dport 2087 -j DNAT --to-destination 127.0.0.1:5087
add_nat PREROUTING -i "$NIC" -p tcp --dport 2083 -j DNAT --to-destination 127.0.0.1:5083
add_nat PREROUTING -i "$NIC" -p tcp --dport 2096 -j DNAT --to-destination 127.0.0.1:5096
add_in INPUT -i "$NIC" -p tcp --dport 2086 -j DROP
add_in INPUT -i "$NIC" -p tcp --dport 2082 -j DROP
add_in INPUT -i "$NIC" -p tcp --dport 2095 -j DROP
iptables-save > /etc/sysconfig/iptables
green " Kurallar kaydedildi (/etc/sysconfig/iptables)"
blue "=== 8/8 Servisler etkinlestiriliyor ==="
nginx -t
systemctl enable --now cpanel-gate.service
systemctl enable --now nginx
systemctl enable iptables
systemctl reload nginx 2>/dev/null || systemctl restart nginx
sleep 1
green " Aktif servisler:"
for s in cpanel-gate nginx iptables; do
printf " %-20s %s\n" "$s" "$(systemctl is-active $s)"
done
echo ""
blue "=== Sonuc ==="
green "Kurulum tamamlandi."
echo ""
echo "Test: https://$(hostname):2087/ (gate sayfasi gelmeli)"
echo "Parola degistirmek icin: /opt/cpanel-gate/gate.py icindeki PASSWORD satiri,"
echo "ardindan 'systemctl restart cpanel-gate'."
echo ""
echo "Loglar:"
echo " /var/log/cpanel-gate/gate.log (basari/hata kayitlari)"
echo " /var/log/cpanel-gate/nginx-access.log (HTTP istekleri)"
echo ""
echo "Geri almak icin: bash $PKG_DIR/uninstall.sh" Gate.py - Servis kodu
#!/usr/bin/env python3
"""
cPanel/WHM Yetkilendirme Kapısı — meohost
nginx önünde 127.0.0.1:8123'te dinler. auth_request + giriş formu sağlar.
GET / -> giriş sayfası (HTML form)
POST /__gate/verify -> domain:anahtar doğrula, HMAC cookie ver
GET /__gate/auth -> nginx auth_request: cookie geçerliyse 200, değilse 401
Hazırlayan: meohost
"""
# ============================================================================
# YAPILANDIRMA — buradan değiştir
# ============================================================================
# Geçiş anahtarı. Kullanıcı 'domain:ANAHTAR' formatında girer.
# Örnek: meohost.com:meohost -> PASSWORD = 'meohost'
PASSWORD = 'meohost'
# Cookie geçerlilik süresi (saniye). 600 = 10 dakika.
TTL = 600
# Servisin dinleyeceği yerel adres ve port. nginx buraya proxy eder.
LISTEN_HOST = '127.0.0.1'
LISTEN_PORT = 8123
# Cookie adı — genelde değiştirmeye gerek yok.
COOKIE_NAME = 'cp_gate_token'
# HMAC secret dosyası. İlk kurulumda otomatik üretildi (32 byte hex).
# Yeniden üretmek için:
# python3 -c "import secrets; print(secrets.token_hex(32))" > /etc/cpanel-gate/secret
SECRET_FILE = '/etc/cpanel-gate/secret'
# ============================================================================
# Aşağısı çekirdek — değiştirmeye gerek yok
# ============================================================================
import http.server
import socketserver
import hmac
import hashlib
import time
import urllib.parse
import subprocess
import sys
import re
from http.cookies import SimpleCookie
SECRET = open(SECRET_FILE).read().strip().encode()
DOMAIN_RE = re.compile(r'^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?)+$')
def domain_owned(domain):
if not DOMAIN_RE.match(domain) or len(domain) > 253:
return False
try:
r = subprocess.run(
['/scripts/whoowns', domain],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
timeout=3
)
owner = r.stdout.decode('utf-8', errors='replace').strip()
return bool(owner) and owner != 'nobody' and owner != 'root'
except Exception:
return False
def make_token(expires):
msg = str(expires).encode()
sig = hmac.new(SECRET, msg, hashlib.sha256).hexdigest()
return f"{expires}.{sig}"
def verify_token(token):
try:
expires_str, sig = token.split('.', 1)
expires = int(expires_str)
if expires < int(time.time()):
return False
expected = hmac.new(SECRET, expires_str.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
except Exception:
return False
GATE_HTML = """<!DOCTYPE html>
<html lang="tr"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="robots" content="noindex,nofollow,nosnippet,noarchive">
<meta name="referrer" content="no-referrer">
<title>Yetkilendirme — meohost</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='10' fill='%23173a5e'/%3E%3Ctext x='12' y='17' font-family='Georgia,serif' font-size='14' font-style='italic' fill='%23faf8f3' text-anchor='middle'%3Em%3C/text%3E%3C/svg%3E">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#faf8f3;
--bg-soft:#f0ece2;
--border:#d4cdb9;
--border-soft:#e8e3d3;
--text:#0c1428;
--text-muted:#5a6275;
--text-dim:#9a9990;
--accent:#173a5e;
--accent-soft:rgba(23,58,94,.07);
--danger:#a4422f;
}
html{height:100%}
body{
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
background:var(--bg);
color:var(--text);
min-height:100vh;
display:grid;
grid-template-rows:auto 1fr auto;
letter-spacing:-.005em;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
}
/* hafif kâğıt dokusu — statik */
body::before{
content:'';position:fixed;inset:0;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.18 0 0 0 0 0.18 0 0 0 0 0.22 0 0 0 0.35 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
opacity:.4;pointer-events:none;mix-blend-mode:multiply;
z-index:0;
}
header,main,footer{position:relative;z-index:1}
header{
padding:26px 38px 0 38px;
display:flex;align-items:center;justify-content:flex-end;
}
.brand-link{
font-family:ui-serif,Charter,Cambria,'Times New Roman',Georgia,serif;
font-size:14px;font-style:italic;letter-spacing:-.005em;
color:var(--accent);text-decoration:none;
border-bottom:1px solid transparent;
transition:border-color .15s ease;
}
.brand-link:hover{border-color:var(--accent)}
main{
display:flex;align-items:center;justify-content:center;
padding:40px 24px;
}
.panel{width:100%;max-width:420px}
.eyebrow{
font-family:ui-monospace,'SF Mono',Menlo,monospace;
font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;
color:var(--text-dim);margin-bottom:22px;
display:flex;align-items:center;gap:12px;
}
.eyebrow::before{
content:'';display:inline-block;width:20px;height:1px;background:var(--accent);
}
h1{
font-family:ui-serif,Charter,'Iowan Old Style',Cambria,'Times New Roman',Georgia,serif;
font-weight:400;font-size:36px;line-height:1.12;letter-spacing:-.024em;
margin-bottom:16px;color:var(--text);
}
h1 em{font-style:italic;color:var(--accent)}
.lede{
font-size:14.5px;line-height:1.6;color:var(--text-muted);
margin-bottom:38px;max-width:380px;
}
form{margin:0}
.field{
display:flex;align-items:stretch;
border-bottom:1px solid var(--border);
transition:border-color .2s ease;
}
.field:focus-within{border-color:var(--accent)}
input[name=credentials]{
flex:1;min-width:0;
background:transparent;border:0;outline:0;
padding:14px 0;
font-family:ui-monospace,'SF Mono',Menlo,Consolas,monospace;
font-size:15px;color:var(--text);letter-spacing:-.005em;
caret-color:var(--accent);
}
input[name=credentials]::placeholder{color:var(--text-dim);font-weight:400}
button{
background:transparent;border:0;cursor:pointer;
color:var(--accent);
font:inherit;font-size:13px;font-weight:500;letter-spacing:-.005em;
padding:0 0 0 16px;flex-shrink:0;
display:inline-flex;align-items:center;gap:6px;
transition:opacity .15s ease;
}
button:hover{opacity:.72}
button .arrow{display:inline-block;transition:transform .2s ease}
button:hover .arrow{transform:translateX(3px)}
.err{
margin-top:18px;font-size:13px;color:var(--danger);
font-family:ui-monospace,Menlo,monospace;letter-spacing:-.005em;
min-height:16px;
display:flex;align-items:center;gap:10px;
opacity:0;transform:translateY(-2px);
transition:opacity .25s ease,transform .25s ease;
}
.err.show{opacity:1;transform:translateY(0)}
.err::before{
content:'';width:2px;height:13px;background:var(--danger);display:inline-block;flex-shrink:0;
}
.hint{
margin-top:46px;
padding-top:22px;
border-top:1px solid var(--border-soft);
font-size:12.5px;line-height:1.7;color:var(--text-muted);
max-width:380px;
}
.hint code{
font-family:ui-monospace,'SF Mono',Menlo,monospace;
font-size:11.5px;
color:var(--text);
background:var(--bg-soft);
padding:1px 6px;border-radius:3px;
border:1px solid var(--border-soft);
}
footer{
padding:22px 38px 26px 38px;
font-family:ui-monospace,'SF Mono',Menlo,monospace;
font-size:10.5px;color:var(--text-dim);letter-spacing:.06em;
text-align:center;
}
footer .credit{
font-size:11px;letter-spacing:.14em;text-transform:uppercase;
color:var(--text-muted);
}
footer .credit b{color:var(--accent);font-weight:600;font-style:italic;font-family:ui-serif,Charter,Georgia,serif;text-transform:none;letter-spacing:-.005em;font-size:13px}
footer .ts{
margin-top:8px;font-size:10px;color:var(--text-dim);letter-spacing:.08em;
}
@media (max-width:520px){
header,footer{padding-left:22px;padding-right:22px}
main{padding:24px 22px}
h1{font-size:28px}
.lede{font-size:14px;margin-bottom:32px}
.hint{margin-top:36px}
}
</style></head>
<body>
<header>
<a href="https://meohost.com" class="brand-link" target="_blank" rel="noopener noreferrer">meohost</a>
</header>
<main>
<div class="panel">
<div class="eyebrow">Yetkilendirme</div>
<h1>Yalnızca <em>Müşterilerimize</em>.</h1>
<p class="lede">Bu sunucuda hesabı olan birinin domain'i ve gizli anahtarı, geçmek için yeterli.</p>
<form method="post" action="/__gate/verify" autocomplete="off" novalidate>
<div class="field">
<input
name="credentials"
placeholder="ornek.com:anahtar"
autofocus required
spellcheck="false" autocapitalize="off" autocorrect="off"
inputmode="text">
<button type="submit">Aç <span class="arrow">→</span></button>
</div>
<div class="err" id="err"></div>
</form>
<div class="hint">
Format: <code>domain:anahtar</code>. Domain bu sunucuda barınmıyorsa veya anahtar hatalıysa giriş düşer. Oturum 10 dakika sonra biter ve aynı kapı yeniden açılır.
</div>
</div>
</main>
<footer>
<div class="credit"><b>meohost</b> tarafından hazırlanmıştır</div>
<div class="ts" id="ts">—</div>
</footer>
<script>
(function(){
try{
var u=new URL(location);
var e=u.searchParams.get('err');
if(e){
var n=document.getElementById('err');
n.textContent=e;
requestAnimationFrame(function(){n.classList.add('show')});
history.replaceState(null,'',u.pathname);
}
var ts=document.getElementById('ts');
function pad(x){return String(x).padStart(2,'0')}
function tick(){
var d=new Date();
ts.textContent=d.getUTCFullYear()+'-'+pad(d.getUTCMonth()+1)+'-'+pad(d.getUTCDate())+' '+pad(d.getUTCHours())+':'+pad(d.getUTCMinutes())+' UTC';
}
tick(); setInterval(tick,30000);
}catch(_){}
})();
</script>
</body></html>"""
class Handler(http.server.BaseHTTPRequestHandler):
server_version = 'gate/1.0'
sys_version = ''
def log_message(self, fmt, *args):
sys.stderr.write("[gate] %s - %s\n" % (self.address_string(), fmt % args))
def _send_simple(self, code, body=b'', ctype='text/plain'):
self.send_response(code)
self.send_header('Content-Type', ctype)
self.send_header('Content-Length', str(len(body)))
self.send_header('X-Content-Type-Options', 'nosniff')
self.send_header('X-Frame-Options', 'DENY')
self.end_headers()
if body:
self.wfile.write(body)
def do_GET(self):
path = self.path.split('?', 1)[0]
if path == '/auth':
cookies = SimpleCookie(self.headers.get('Cookie', ''))
tok = cookies.get(COOKIE_NAME)
if tok and verify_token(tok.value):
self._send_simple(200)
else:
self._send_simple(401)
return
if path == '/health':
self._send_simple(200, b'ok')
return
self._send_simple(200, GATE_HTML.encode('utf-8'), 'text/html; charset=utf-8')
def do_POST(self):
if self.path != '/verify':
self._send_simple(404)
return
try:
length = int(self.headers.get('Content-Length', '0'))
except ValueError:
self._send_simple(400)
return
if length <= 0 or length > 1024:
self._send_simple(400)
return
body = self.rfile.read(length).decode('utf-8', errors='replace')
params = urllib.parse.parse_qs(body)
cred = (params.get('credentials') or [''])[0].strip()
if ':' not in cred:
return self._fail('Format: domain:sifre')
domain, password = cred.split(':', 1)
domain = domain.strip().lower().lstrip('www.')
if not password or not domain:
return self._fail('Eksik bilgi')
pw_ok = hmac.compare_digest(password.encode(), PASSWORD.encode())
dom_ok = domain_owned(domain) if pw_ok else False
time.sleep(0.4)
if not (pw_ok and dom_ok):
sys.stderr.write("[gate] FAIL ip=%s domain=%r\n" % (self.address_string(), domain))
return self._fail('Hatali bilgi')
sys.stderr.write("[gate] OK ip=%s domain=%s\n" % (self.address_string(), domain))
expires = int(time.time()) + TTL
token = make_token(expires)
self.send_response(302)
self.send_header('Location', '/')
self.send_header(
'Set-Cookie',
f'{COOKIE_NAME}={token}; Path=/; Secure; HttpOnly; SameSite=Strict; Max-Age={TTL}'
)
self.send_header('Content-Length', '0')
self.end_headers()
def _fail(self, msg):
self.send_response(302)
self.send_header('Location', f'/?err={urllib.parse.quote(msg)}')
self.send_header('Content-Length', '0')
self.end_headers()
class ThreadedServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
daemon_threads = True
allow_reuse_address = True
if __name__ == '__main__':
srv = ThreadedServer((LISTEN_HOST, LISTEN_PORT), Handler)
sys.stderr.write(f"[gate] listening on {LISTEN_HOST}:{LISTEN_PORT}\n")
srv.serve_forever() Cpanel gate service komutu
[Unit] Description=cPanel/WHM access gate After=network-online.target Wants=network-online.target [Service] Type=simple User=root Environment=PYTHONUNBUFFERED=1 ExecStart=/usr/bin/python3 /opt/cpanel-gate/gate.py Restart=on-failure RestartSec=2s StandardOutput=append:/var/log/cpanel-gate/gate.log StandardError=append:/var/log/cpanel-gate/gate.log NoNewPrivileges=yes ProtectSystem=strict ReadWritePaths=/var/log/cpanel-gate PrivateTmp=yes [Install] WantedBy=multi-user.target
cpanel gate nginx conf yapısı
# cPanel/WHM erişim kapısı
# Harici 2087/2083 trafiği iptables tarafından bu dahili dinleyicilere DNAT ile yönlendirilir.
# nginx, gate servisi aracılığıyla geçerli bir HMAC çerezi olup olmadığını kontrol eder; aksi halde
# gate sayfasını sunar. Geçerli bir çerez varsa, trafik yerel cpsrvd’ye proxy’lenir.
limit_req_zone $binary_remote_addr zone=cpgate_login:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=cpgate_general:10m rate=120r/m;
upstream cpsrvd_whm { server 127.0.0.1:2087; }
upstream cpsrvd_cpanel { server 127.0.0.1:2083; }
upstream cpsrvd_webmail { server 127.0.0.1:2096; }
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5:!3DES;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:5m;
ssl_session_timeout 1h;
map $server_port $upstream_target {
5087 https://cpsrvd_whm;
5083 https://cpsrvd_cpanel;
5096 https://cpsrvd_webmail;
default https://cpsrvd_cpanel;
}
map $server_port $external_port {
5087 2087;
5083 2083;
5096 2096;
default 2087;
}
server {
listen 127.0.0.1:5087 ssl http2;
listen 127.0.0.1:5083 ssl http2;
listen 127.0.0.1:5096 ssl http2;
server_name _;
ssl_certificate /etc/cpanel-gate/ssl/cert.pem;
ssl_certificate_key /etc/cpanel-gate/ssl/key.pem;
error_page 497 =301 https://$host:$external_port$request_uri;
access_log /var/log/cpanel-gate/nginx-access.log;
error_log /var/log/cpanel-gate/nginx-error.log warn;
client_max_body_size 4096m;
client_body_timeout 3600s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 30s;
send_timeout 3600s;
location = /__gate/verify {
limit_req zone=cpgate_login burst=3 nodelay;
proxy_pass http://127.0.0.1:8123/verify;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
location = /__gate/ {
proxy_pass http://127.0.0.1:8123/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location = /__gate/auth {
internal;
proxy_pass http://127.0.0.1:8123/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Cookie $http_cookie;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
limit_req zone=cpgate_general burst=60 nodelay;
auth_request /__gate/auth;
error_page 401 = @show_gate;
proxy_pass $upstream_target;
proxy_ssl_verify off;
proxy_ssl_server_name on;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Disable buffering for streaming responses
proxy_buffering off;
proxy_request_buffering off;
}
location @show_gate {
proxy_pass http://127.0.0.1:8123;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
} Kurulum Dökümanı
cPanel Gate
cPanel/WHM giriş portlarının önüne nginx tabanlı bir kapı koyar. Bot taramaları ve CVE-2026-41940 gibi auth-bypass açıkları cpsrvd'ye hiç ulaşmadan kapıda durur.
Mantık
cpsrvd 2082/2083/2086/2087/2095/2096 portlarında tüm dünyaya açık dinler — sıfır gün açıkları doğrudan oradan giriyor. Bu paket trafiği iptables ile yerel nginx'e DNAT eder. nginx önce HMAC imzalı bir çerez ister, çerez yoksa cpsrvd'ye tek byte geçmez.
arola ile bir kerelik doğrulama yapılır. Domain'i cPanel'in kendi /scripts/whoowns'u tanıyor mu, ona bakar — WHM'de yeni hesap açıldığında o domain otomatik geçer.
Portlar
2083 / 2087 / 2096 → kapının arkasında2082 / 2086 / 2095 → DROP (HTTPS varken HTTP'ler işsiz)
Mail portları (25, 465, 587, 110, 143, 993, 995) ellenmez. HTTP olmadıkları için bu kapıyla korunamaz, onları cPHulk + güçlü parola tutar.
Gereksinimler
AlmaLinux/RHEL 8 veya 9, cPanel 11.110+, root erişim. nginx yoksa script kurar.
Kurulum
cd /root/cpanel-gate-package bash install.sh
Script şunları yapar:
- iptables yedeğini /root/cpanel-gate-backup/ altına alır
- nginx ve iptables-services'i kurar
- /opt/cpanel-gate/gate.py'ı yerleştirir, HMAC secret üretir
- cpsrvd sertifikasını key/cert olarak ayrıştırıp nginx'e verir
- systemd unit, nginx config ve sysctl dosyalarını koyar
- DNAT/DROP kurallarını uygular ve kalıcı yazar
- Servisleri başlatır
Manuel kurulum
Paketler:
dnf install -y nginx iptables-services
Dosyaları yerleştir:
mkdir -p /opt/cpanel-gate /etc/cpanel-gate/ssl /var/log/cpanel-gate cp gate.py /opt/cpanel-gate/gate.py cp cpanel-gate.service /etc/systemd/system/cpanel-gate.service cp cpanel-gate-nginx.conf /etc/nginx/conf.d/cpanel-gate.conf cp 99-cpanel-gate.conf /etc/sysctl.d/99-cpanel-gate.conf chmod 0755 /opt/cpanel-gate/gate.py
HMAC secret üret:
python3 -c "import secrets; print(secrets.token_hex(32))" > /etc/cpanel-gate/secret chmod 600 /etc/cpanel-gate/secret
cpsrvd sertifikasını key + cert olarak ayır:
python3 - <<'EOF' import re src = open('/var/cpanel/ssl/cpanel/cpanel.pem').read() key = re.search(r'-----BEGIN (RSA )?PRIVATE KEY-----.*?-----END (RSA )?PRIVATE KEY-----', src, re.DOTALL) certs = re.findall(r'-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----', src, re.DOTALL) open('/etc/cpanel-gate/ssl/key.pem','w').write(key.group(0)+'n') open('/etc/cpanel-gate/ssl/cert.pem','w').write('n'.join(certs)+'n') EOF chmod 640 /etc/cpanel-gate/ssl/{key,cert}.pem chown root:nginx /etc/cpanel-gate/ssl/{key,cert}.pem
/etc/nginx/nginx.conf içinde listen 80 default_server; olan server bloğunu yorum satırı yap.
iptables + sysctl (kendi NIC'inle):
NIC=$(ip -o -4 route show to default | awk '{print $5}') sed -i "s/ens192/$NIC/g" /etc/sysctl.d/99-cpanel-gate.conf sysctl --system
iptables -t nat -A PREROUTING -i $NIC -p tcp --dport 2087 -j DNAT --to-destination 127.0.0.1:5087 iptables -t nat -A PREROUTING -i $NIC -p tcp --dport 2083 -j DNAT --to-destination 127.0.0.1:5083 iptables -t nat -A PREROUTING -i $NIC -p tcp --dport 2096 -j DNAT --to-destination 127.0.0.1:5096 iptables -I INPUT -i $NIC -p tcp --dport 2086 -j DROP iptables -I INPUT -i $NIC -p tcp --dport 2082 -j DROP iptables -I INPUT -i $NIC -p tcp --dport 2095 -j DROP
iptables-save > /etc/sysconfig/iptables systemctl enable iptables
Servisleri ayağa kaldır:
nginx -t systemctl daemon-reload systemctl enable --now cpanel-gate nginx
Ayarlar
/opt/cpanel-gate/gate.py'ın başında duruyor:
PASSWORD = 'meohost' # giriş parolası TTL = 600 # çerez süresi (sn) LISTEN_HOST = '127.0.0.1' LISTEN_PORT = 8123 COOKIE_NAME = 'cp_gate_token' SECRET_FILE = '/etc/cpanel-gate/secret'
Default parolayı bırakma. 12+ karakter, harf-rakam-sembol karışık bir şeyle değiştir sonra systemctl restart cpanel-gate.
Komutlar
systemctl status cpanel-gate nginx systemctl restart cpanel-gate journalctl -u cpanel-gate -f tail -f /var/log/cpanel-gate/gate.log iptables -t nat -L PREROUTING -n -v
HMAC secret'i yenilemek istersen (eski çerezlerin hepsi iptal olur):
python3 -c "import secrets; print(secrets.token_hex(32))" > /etc/cpanel-gate/secret systemctl restart cpanel-gate
Geri çekme
DNAT'ı söker, cpsrvd yine doğrudan açılır:
iptables -t nat -F PREROUTING iptables-save > /etc/sysconfig/iptables
Yedeği geri al:
iptables-restore < /root/cpanel-gate-backup/iptables-YYYYMMDD-HHMMSS.rules iptables-save > /etc/sysconfig/iptables
Tamamen kaldır: bash uninstall.sh
Sorun giderme
Bad Gateway — nginx çalışmıyordur. systemctl status nginx, nginx -t.
Parola doğru ama kabul etmiyor — domain bu sunucuda barınmıyordur. /scripts/whoowns ornek.com boş dönerse cPanel o domaini tanımıyordur.
Reboot sonrası kapı açılmıyor — systemctl is-enabled iptables enabled olmalı, sysctl net.ipv4.conf.all.route_localnet 1 olmalı.
Çerez sürekli reddediliyor — HMAC secret değişmiştir, tarayıcıdan çerezleri temizle.
Terminal/File Manager boş açılıyor — nginx config'inde WebSocket Upgrade header'ı geçmiyor, cpanel-gate.conf'a bak.
Nasıl çalışıyor
Çerez HMAC-SHA256 ile imzalı, sunucu state tutmuyor (stateless). DNAT route_localnet=1 sayesinde 127.0.0.1'e yönleniyor, cpsrvd tek satır ayar değişmeden yerinde kalıyor.
nginx'in auth_request direktifi her istek için gate servisine soruyor — çerez geçerliyse 200, değilse 401 ve giriş sayfası. /__gate/verify dakikada 10 deneme ile rate-limit'li.
WebSocket (cPanel Terminal, File Manager) için Upgrade header'ları geçer, timeout 1 saat.
Sadece cpsrvd dinlediği portları (2083,2096 vs.) kapatarak güvenlik önlenebilir mi? iptables ile drop, reject yaptım ama emin olamadım.