Web Site Pentester (Web Site Güvenlik Analiz Aracı)
# scanner.py - SitePenTest v1.1
import requests
import os
import time
import random
import html as html_lib # Çakışmayı önlemek için isimlendirdik
from urllib.parse import urljoin, urlparse, unquote
from datetime import datetime
import urllib3
import ssl
import socket
from bs4 import BeautifulSoup
urllib3.disable_warnings()
class WebScanner:
def __init__(self, target_url):
self.target = self.clean_url(target_url)
self.domain = urlparse(self.target).netloc
self.session = requests.Session()
# WAF Bypass: Rastgele User-Agent Havuzu
self.user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
]
self.findings = []
self.goods = []
self.checked_features = []
def clean_url(self, url):
if not url.startswith(("http://", "https://")):
url = "https://" + url
return url.rstrip("/")
def get_random_header(self):
return {"User-Agent": random.choice(self.user_agents)}
def request(self, url, method="GET", timeout=8, data=None, allow_redirects=True):
"""WAF dostu, gecikmeli ve random header'lı istek atar"""
try:
# WAF'ı tetiklememek için rastgele kısa bekleme (0.3 - 0.8 sn)
time.sleep(random.uniform(0.3, 0.8))
headers = self.get_random_header()
return self.session.request(
method,
url,
headers=headers,
timeout=timeout,
allow_redirects=allow_redirects,
verify=False,
data=data
)
except Exception:
return None
def add_finding(self, title, risk, desc, exploit, fix, owasp=""):
self.findings.append({
"title": title, "risk": risk, "desc": desc,
"exploit": exploit, "fix": fix, "owasp": owasp
})
def add_good(self, msg):
self.goods.append(msg)
def add_checked_feature(self, feature):
self.checked_features.append(feature)
def scan(self, progress_callback=None):
steps = 10
current = 0
# Adımları gruplayarak progress barı daha akıcı yaptık
scan_groups = [
(self.scan_critical_paths, "Kritik dosyalar ve Dizinler"),
(self.scan_security_headers, "Güvenlik Header Analizi"),
(self.scan_cookies, "Cookie Yapılandırması"),
(self.scan_server_leak, "Sunucu Bilgi Sızıntıları"),
(self.scan_robots, "Robots.txt ve Security.txt"),
(self.scan_ssl, "SSL/TLS Şifreleme"),
(self.scan_cors, "CORS Yapılandırması"),
(self.scan_outdated, "Versiyon Kontrolleri"),
(self.scan_basic_vuln, "XSS ve SQL Enjeksiyon Testleri (WAF Bypass Modu)"),
(self.scan_csrf_and_session, "CSRF ve Oturum Güvenliği")
]
for func, msg in scan_groups:
current += 1
if progress_callback:
progress_callback(current / steps * 100, f"{msg} taranıyor...")
func()
progress_callback(100, "Rapor derleniyor...")
# --- GELİŞMİŞ TARAMA FONKSİYONLARI ---
def scan_critical_paths(self):
self.add_checked_feature("Kritik Dosya Taraması")
paths = ['/.env', '/wp-config.php', '/.git/HEAD', '/backup.sql', '/phpinfo.php']
for p in paths:
resp = self.request(urljoin(self.target, p))
if resp and resp.status_code == 200:
# False Positive Önleme: İçerik kontrolü
content = resp.text.lower()
is_real = False
if ".env" in p and ("db_password" in content or "app_key" in content):
is_real = True
elif "wp-config" in p and "db_name" in content:
is_real = True
elif ".git" in p and "ref: refs/" in content:
is_real = True
elif "phpinfo" in p and "php version" in content:
is_real = True
if is_real:
self.add_finding("Kritik Dosya İfşası", "KRİTİK", f"{p} dosyası okunabiliyor.",
"Veritabanı şifreleri çalınabilir.",
"Dosyayı sunucudan silin veya erişimi kapatın.", "A05")
def scan_security_headers(self):
self.add_checked_feature("Header Analizi")
resp = self.request(self.target)
if not resp: return
h = {k.lower(): v for k, v in resp.headers.items()}
required = {
'strict-transport-security': ("HSTS Eksik", "YÜKSEK"),
'content-security-policy': ("CSP Eksik", "ORTA"),
'x-frame-options': ("Clickjacking Koruması Yok", "ORTA")
}
for head, (title, risk) in required.items():
if head not in h:
self.add_finding(title, risk, f"{head} header'ı sunucudan dönmedi.",
"Tarayıcı tabanlı korumalar devre dışı.", f"{head} header'ını ekleyin.", "A05")
else:
self.add_good(f"{head} aktif")
def scan_cookies(self):
self.add_checked_feature("Cookie Güvenliği")
resp = self.request(self.target)
if not resp or 'set-cookie' not in resp.headers: return
cookies = resp.headers['set-cookie'].lower()
if 'httponly' not in cookies:
self.add_finding("HttpOnly Flag Eksik", "ORTA", "Cookie'lere JS ile erişilebilir.",
"XSS saldırılarında session çalınabilir.", "HttpOnly=True yapın.", "A03")
if 'secure' not in cookies and self.target.startswith("https"):
self.add_finding("Secure Flag Eksik", "DÜŞÜK", "Cookie HTTP üzerinden gidebilir.",
"MITM saldırısı riski.", "Secure=True yapın.", "A05")
def scan_server_leak(self):
self.add_checked_feature("Bilgi Sızıntısı")
resp = self.request(self.target)
if not resp: return
h = {k.lower(): v for k, v in resp.headers.items()}
if 'server' in h:
val = h['server']
# Sadece versiyon numarası varsa uyar (örn: Apache/2.4.49)
if any(char.isdigit() for char in val):
self.add_finding("Sunucu Versiyon İfşası", "DÜŞÜK", f"Server: {val}",
"Saldırgan spesifik exploit arayabilir.",
"ServerTokens Prod (Apache) veya hide_server_tokens (Nginx)", "A05")
def scan_robots(self):
self.add_checked_feature("Robots.txt")
resp = self.request(urljoin(self.target, "/robots.txt"))
if resp and resp.status_code == 200:
if "admin" in resp.text:
self.add_finding("robots.txt Hassas Bilgi", "DÜŞÜK", "Admin paneli yolu robots.txt'de ifşa edilmiş.",
"Saldırganlar gizli panelleri bulabilir.", "Hassas yolları robots.txt'ye yazmayın.",
"A01")
self.add_good("robots.txt mevcut")
else:
self.add_finding("robots.txt Bulunamadı", "DÜŞÜK", "Dosya 404 dönüyor.", "Bot kontrolü yapılamıyor.",
"Dosyayı oluşturun.", "A05")
def scan_ssl(self):
self.add_checked_feature("SSL/TLS")
try:
ctx = ssl.create_default_context()
with socket.create_connection((self.domain, 443), timeout=5) as sock:
with ctx.wrap_socket(sock, server_hostname=self.domain) as ssock:
ver = ssock.version()
if ver in ["TLSv1", "TLSv1.1"]:
self.add_finding("Zayıf TLS Protokolü", "YÜKSEK", f"{ver} kullanılıyor.",
"POODLE vb. saldırılara açık.", "Sadece TLS 1.2 ve 1.3'e izin verin.", "A02")
else:
self.add_good(f"Güvenli Protokol: {ver}")
except:
pass # Bağlantı hatası raporlamıyoruz, zaten request çalışmazsa belli olur.
def scan_cors(self):
self.add_checked_feature("CORS")
resp = self.request(self.target, method="OPTIONS")
if resp and 'access-control-allow-origin' in resp.headers:
if resp.headers['access-control-allow-origin'] == '*':
self.add_finding("Gevşek CORS Politikası", "ORTA", "Access-Control-Allow-Origin: *",
"Herkes API'ye erişebilir.", "Origin'i spesifik domainlerle sınırlayın.", "A07")
def scan_outdated(self):
self.add_checked_feature("Yazılım Versiyonları")
# Basit header kontrolü, derinlemesine analiz için Wappalyzer gerekir ama bu temel bir kontrol.
pass
def scan_basic_vuln(self):
self.add_checked_feature("Enjeksiyon Testleri")
# 1. Reflected XSS (False Positive Korumalı)
# Benzersiz bir string kullanıyoruz ki sayfada zaten var olan "alert" kelimesiyle karışmasın.
unique_str = "XSS_TEST_RND_99"
payload = f"<script>console.log('{unique_str}')</script>"
test_url = urljoin(self.target, f"?search={payload}")
resp = self.request(test_url)
if resp:
# Payload sayfada var mı VE escape edilmemiş mi? (örn: <script> olarak dönmemeli)
if payload in resp.text:
self.add_finding("Reflected XSS", "YÜKSEK", "JS kodu sayfada çalıştırılabilir halde yansıyor.",
"Kullanıcı oturumu çalınabilir.", "Input'ları HTML Encode yapın.", "A03")
# 2. SQL Injection (Hata Bazlı - False Positive Korumalı)
# Normal request
resp_norm = self.request(self.target)
len_norm = len(resp_norm.text) if resp_norm else 0
# SQL Payload
sql_payload = "' OR '1'='1"
test_url_sql = urljoin(self.target, f"?id={sql_payload}")
resp_sql = self.request(test_url_sql)
if resp_sql:
errors = ["sql syntax", "mysql_fetch", "ora-01756", "syntax error"]
found_error = any(e in resp_sql.text.lower() for e in errors)
# Eğer normal sayfada bu hata yoksa VE sql payload ile hata çıkıyorsa
if found_error and (resp_norm and not any(e in resp_norm.text.lower() for e in errors)):
self.add_finding("SQL Injection (Error Based)", "KRİTİK", "Veritabanı hata mesajı tetiklendi.",
"Tüm veritabanı okunabilir.", "Prepared Statements kullanın.", "A03")
def scan_csrf_and_session(self):
self.add_checked_feature("CSRF & Session")
resp = self.request(self.target)
if resp:
soup = BeautifulSoup(resp.text, 'html.parser')
forms = soup.find_all('form')
for form in forms:
# Login veya update formu mu?
action = form.get('action', '').lower()
if 'login' in action or 'register' in action or 'update' in action:
# Hidden inputlarda csrf, token vb. ara
inputs = form.find_all('input', type='hidden')
has_token = any(
x.get('name') and ('csrf' in x.get('name').lower() or 'token' in x.get('name').lower()) for x in
inputs)
if not has_token:
self.add_finding("CSRF Token Eksik", "ORTA", "Kritik formda CSRF token bulunamadı.",
"Kullanıcı adına habersiz işlem yapılabilir.",
"Formlara anti-CSRF token ekleyin.", "A01")
break # Bir tane bulsak yeter
def generate_report(self):
os.makedirs("reports", exist_ok=True)
filename = f"reports/Rapor_{self.domain.replace('.', '_')}.html"
risk_score = 100 - (len(self.findings) * 10)
if risk_score < 0: risk_score = 0
# --- HTML & CSS (Print-Friendly & PDF Ready) ---
html_content = f"""
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>Güvenlik Raporu - {self.domain}</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
:root {{ --primary: #2563eb; --danger: #dc2626; --warning: #d97706; --success: #16a34a; --bg: #f8fafc; }}
body {{ font-family: 'Inter', sans-serif; background: var(--bg); color: #1e293b; margin: 0; padding: 40px; line-height: 1.5; }}
/* EKRAN GÖRÜNÜMÜ */
.container {{ max-width: 900px; margin: 0 auto; background: white; padding: 40px; border-radius: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); }}
h1 {{ font-size: 28px; margin-bottom: 5px; color: #0f172a; }}
.meta {{ color: #64748b; font-size: 14px; margin-bottom: 30px; border-bottom: 2px solid #e2e8f0; padding-bottom: 20px; }}
.score-card {{ display: flex; justify-content: space-between; align-items: center; background: #f1f5f9; padding: 20px; border-radius: 12px; margin-bottom: 30px; }}
.score {{ font-size: 32px; font-weight: bold; color: {"#16a34a" if risk_score > 70 else "#dc2626"}; }}
.finding {{ border: 1px solid #e2e8f0; border-radius: 8px; padding: 15px; margin-bottom: 15px; page-break-inside: avoid; }}
.finding.KRİTİK {{ border-left: 5px solid var(--danger); }}
.finding.YÜKSEK {{ border-left: 5px solid #f97316; }}
.finding.ORTA {{ border-left: 5px solid var(--warning); }}
.finding.DÜŞÜK {{ border-left: 5px solid #94a3b8; }}
.badge {{ display: inline-block; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: bold; color: white; margin-bottom: 8px; }}
.btn-print {{ position: fixed; top: 20px; right: 20px; background: var(--primary); color: white; padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; box-shadow: 0 4px 10px rgba(37,99,235,0.3); }}
.btn-print:hover {{ background: #1d4ed8; }}
/* PDF / YAZICI AYARLARI (TEK SAYFAYA SIĞDIRMA ÇABASI) */
@media print {{
@page {{ margin: 10mm; size: A4; }}
body {{ background: white; padding: 0; font-size: 11px; -webkit-print-color-adjust: exact; }}
.container {{ box-shadow: none; max-width: 100%; padding: 0; }}
.btn-print {{ display: none; }}
h1 {{ font-size: 20px; margin-top: 0; }}
.meta {{ margin-bottom: 10px; padding-bottom: 10px; }}
.score-card {{ padding: 10px; margin-bottom: 15px; }}
.score {{ font-size: 24px; }}
/* Grid yapısı ile bulguları yan yana dizerek yerden tasarruf et */
.findings-grid {{ display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }}
.finding {{ margin-bottom: 0; padding: 10px; border: 1px solid #ccc; }}
/* Gereksiz detayları kısalt */
p {{ margin: 4px 0; }}
/* Kritik olmayan bölümleri gizleyerek tek sayfaya sığdırabiliriz (Opsiyonel) */
.footer {{ display: none; }}
}}
</style>
</head>
<body>
<button class="btn-print" onclick="window.print()">🖨️ PDF Olarak Kaydet / Yazdır</button>
<div class="container">
<div style="display:flex; justify-content:space-between; align-items:center;">
<div>
<h1>🛡️ Güvenlik Tarama Raporu</h1>
<div class="meta">
Hedef: <b>{self.target}</b><br>
Tarih: {datetime.now().strftime('%d.%m.%Y %H:%M')}<br>
Tarayıcı: SitePenTest v1.1
</div>
</div>
<div style="text-align:right;">
<span style="font-size:40px;">{risk_score}/100</span><br>
<span style="color:#64748b; font-size:12px;">Güvenlik Skoru</span>
</div>
</div>
<div class="score-card">
<div>
<strong>Tespitler:</strong> {len(self.findings)} Riskli Bulgu
</div>
<div>
<span style="color:#22c55e;">✓ {len(self.goods)} Güvenli Nokta</span>
</div>
</div>
<h3 style="border-bottom: 2px solid #e2e8f0; padding-bottom: 5px;">⚠️ Tespit Edilen Riskler</h3>
<div class="findings-grid">
"""
for f in self.findings:
bg_color = "#dc2626" if f['risk'] == "KRİTİK" else "#f97316" if f['risk'] == "YÜKSEK" else "#eab308" if f[
'risk'] == "ORTA" else "#94a3b8"
html_content += f"""
<div class="finding {f['risk']}">
<span class="badge" style="background:{bg_color}">{f['risk']}</span>
<strong style="display:block; font-size:13px; margin-bottom:5px;">{f['title']}</strong>
<p style="font-size:11px; color:#475569;">{f['desc']}</p>
<div style="background:#f1f5f9; padding:5px; border-radius:4px; margin-top:5px; font-size:10px; font-family:monospace;">
<strong>Çözüm:</strong> {f['fix']}
</div>
</div>
"""
if not self.findings:
html_content += """<div style="grid-column: span 2; text-align:center; padding: 40px; background:#f0fdf4; border-radius:10px; color:#16a34a;">
<h3>Harika!</h3><p>Otomatik taramada kritik bir zafiyete rastlanmadı.</p></div>"""
html_content += f"""
</div> <h3 style="margin-top:30px; border-bottom: 2px solid #e2e8f0; padding-bottom: 5px;">✅ Kontrol Edilen Alanlar</h3>
<ul style="font-size:11px; color:#64748b; columns: 2;">
{''.join([f'<li>{item}</li>' for item in self.checked_features])}
</ul>
<div class="footer" style="text-align:center; margin-top:40px; font-size:10px; color:#cbd5e1;">
Bu rapor otomatik oluşturulmuştur. Kesin güvenlik garantisi vermez.<br>
Generated by SitePenTest v1.1
</div>
</div>
</body>
</html>
"""
with open(filename, "w", encoding="utf-8") as f:
f.write(html_content)
return filename
Bu uygulamayı yalnızca kendi web siteniz / hostinginiz ya da yazılı izin aldığınız yerlerde kullanınız. Kanunen şahsınıza ait olmayan ya da yetkilendirilmediğiniz web site/ hosting vb. ortamlarda bu araçları kullanmak kanunen suçtur !!
Hiç bir sorumluluk kabul etmemekteyim. Uygulamanın kullanılmasından kaynaklı her türlü hukuki vb problem kullanana aittir.
-----Program adresini girdiğiniz web sitesinin en çok bilinen güvenlik açıkları (xss, csrf, csp, hi jack, fingerprint vb..) 16 fonksiyon ile taramasını yapar ve size bir sistem raporu ile puanı bildirir.
Bir açık ya da tehdit var ise bunun nasıl zarar verebileceğini ve düzeltmek için neler yapacağınız konusun da tavsiyelerde bulunur.
Web sitenizi zırhlandırmasa da yoldan geçenlerin size tehdit oluşmasını engellemeyi sağlar.
MIT lisansı ile dağıtılmaktadır.
---Pardus 25 / Debian 13 linux tabanlı dağıtımlar için üretilmiştir.
---Kurmak için
İndir
linkinden .deb paketi indiripkurabilirya da
kaynak kod indirip
sudo sh ./build_deb.sh komutu ile paketleyip sudo dpkg -i sitepentest_v1.0_amd64.deb ile kurabilirsiniz. sitepentest yazarak yada başlat menüsünden çalıştırabilirsiniz Meta Veri (Özet)
Web masterlar ya da özellikle ai ile web sitesi yapanlar için siteniz / hosting inizin güvenlik taramasını yapın. Sorunları / açıkları, tehditleri raporlayın