Merhaba,
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.
Kısaca sistemin çalışma mantığı şu şekildedir, whm cpanel webmail gibi sayfalara girişte öncelikle ngnix karşılar ve domain şifre sorar, örnek: meohost.com:meohost şeklinde giriş denemesi yapıldıgında sorunsuz girecektir. sunucuda aktif domaini kontrol eder, sizin belirlediğiniz varsayılan şifre ile whm panel login sayfası açılır. botlar ise bunu ayırt edemez ve attıkları istek size ulaşmaz.
ö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ç&nbsp;<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
Bitince https://sunucun:2087/ kapı sayfasını gösterir.
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.