Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/websecscan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: WebSecScan

on:
schedule:
- cron: "0 3 * * *" # daily at 03:00 UTC
workflow_dispatch:

permissions:
contents: write

concurrency:
group: websecscan
cancel-in-progress: true

jobs:
scan:
runs-on: ubuntu-latest
env:
VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install WebSecScan
run: |
python -m pip install --upgrade pip
pip install .

- name: Run scans
run: |
mkdir -p reports
TS=$(date -u +%Y%m%d_%H%M%S)
if [ -f targets.txt ]; then
while IFS= read -r url; do
[ -z "$url" ] && continue
websecscan "$url" --output html --fast || true
dom=$(echo "$url" | sed 's|https\?://||; s|/||g')
if [ -f "reports/scan_${dom}.html" ]; then
mv "reports/scan_${dom}.html" "reports/${TS}_${dom}.html"
fi
done < targets.txt
else
echo "No targets.txt found; skipping scans."
fi
# Disable Jekyll on GitHub Pages
touch reports/.nojekyll

# Generate a simple index.html listing all report files
echo "<!doctype html><html><head><meta charset=\"utf-8\"><title>WebSecScan Reports</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,\"Helvetica Neue\",sans-serif;max-width:900px;margin:40px auto;padding:0 16px}h1{margin:0 0 8px}small{color:#666}ul{line-height:1.8}code{background:#f4f4f4;padding:2px 6px;border-radius:4px}</style></head><body><h1>WebSecScan Reports</h1><small>Generated by GitHub Actions</small><hr><ul>" > reports/index.html
for f in $(ls -1t reports/*.html 2>/dev/null | sed 's|reports/||' | grep -v '^index.html$'); do
echo "<li><a href=\"$f\">$f</a></li>" >> reports/index.html
done
echo "</ul><p>To run a new scan, go to <a href=\"https://github.com/${GITHUB_REPOSITORY}/actions\">Actions</a> and trigger the <strong>WebSecScan</strong> workflow.</p></body></html>" >> reports/index.html

- name: Upload artifact (reports)
uses: actions/upload-artifact@v4
with:
name: reports-${{ github.run_number }}
path: reports/

- name: Publish to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: reports
keep_files: true
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
recursive-include scanner/templates *
110 changes: 109 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,114 @@ python3 websecscan.py https://example.com --output md --active

---

## 🚀 Run on GitHub (scheduled, auto-published reports)

Follow these steps to host the project on GitHub and keep it running on a schedule via GitHub Actions. Reports will be committed to a `gh-pages` branch and published with GitHub Pages.

### 1) Push this project to your GitHub repo
- Create a new repository on GitHub (public or private)
- From your local machine or CI environment:
```bash
git init
git add .
git commit -m "feat: initial import of WebSecScan"
git branch -M main
git remote add origin git@github.com:<your-username>/<your-repo>.git
git push -u origin main
```

### 2) Add your VirusTotal API key as a secret
- GitHub → Your repository → Settings → Secrets and variables → Actions → New repository secret
- Name: `VIRUSTOTAL_API_KEY`
- Value: your actual API key

### 3) Add a `targets.txt` file (domains you own)
- Create a file named `targets.txt` in the repo root containing one authorized URL per line, for example:
```text
https://example.com
https://sub.example.com
```

### 4) Add the GitHub Actions workflow
- Create the file `.github/workflows/websecscan.yml` with the following content:
```yaml
name: WebSecScan

on:
schedule:
- cron: "0 3 * * *" # daily at 03:00 UTC
workflow_dispatch:

permissions:
contents: write

concurrency:
group: websecscan
cancel-in-progress: true

jobs:
scan:
runs-on: ubuntu-latest
env:
VIRUSTOTAL_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install WebSecScan
run: |
python -m pip install --upgrade pip
pip install .

- name: Run scans
run: |
mkdir -p reports
TS=$(date -u +%Y%m%d_%H%M%S)
while IFS= read -r url; do
[ -z "$url" ] && continue
websecscan "$url" --output html --fast || true
dom=$(echo "$url" | sed 's|https\?://||; s|/||g')
if [ -f "reports/scan_${dom}.html" ]; then
mv "reports/scan_${dom}.html" "reports/${TS}_${dom}.html"
fi
done < targets.txt

- name: Upload artifact (reports)
uses: actions/upload-artifact@v4
with:
name: reports-${{ github.run_number }}
path: reports/

- name: Publish to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: reports
keep_files: true
```

### 5) Enable GitHub Pages
- Repo → Settings → Pages → Build and deployment
- Source: `Deploy from a branch`
- Branch: `gh-pages`, Folder: `/ (root)`
- Save. Your reports will be available at `https://<your-username>.github.io/<your-repo>/`

### 6) Run it now
- Go to Actions → `WebSecScan` → Run workflow
- After it finishes, view the Pages site or download the artifact

### Notes and tips
- Only include domains you own or are authorized to scan in `targets.txt`.
- Remove `--fast` in the workflow to enable all checks (slower, more API use).
- To keep history, the workflow timestamps report filenames; adjust as needed.
- If you need private reports, skip GitHub Pages and rely on build artifacts only.
- For heavy scans or tighter control, consider a VPS + `cron`/`systemd` instead of Actions.

## 🤝 Contributing
Pull requests, feature suggestions, and plugins are welcome!
Feel free to fork the project and submit your ideas, as I will be checking the project from time to time.
Expand All @@ -118,4 +226,4 @@ Built for security researchers, red teamers, and educators.
## ⚠️ Disclaimer
Use responsibly.
This tool is intended for **authorized testing only**.
Do not scan websites without permission.
Do not scan websites without permission.
76 changes: 76 additions & 0 deletions reports/scan_example.com.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 🛡️ WebSecScan Report

**Target:** https://example.com
**Scan Time:** 2025-10-27 18:28:40.472899

---

## 🔐 SSL Certificate

- **issuer:** ((('countryName', 'US'),), (('organizationName', 'DigiCert Inc'),), (('commonName', 'DigiCert Global G3 TLS ECC SHA384 2020 CA1'),))

- **subject:** ((('countryName', 'US'),), (('stateOrProvinceName', 'California'),), (('localityName', 'Los Angeles'),), (('organizationName', 'Internet Corporation for Assigned Names and Numbers'),), (('commonName', '*.example.com'),))

- **valid_from:** Jan 15 00:00:00 2025 GMT

- **valid_until:** Jan 15 23:59:59 2026 GMT


---

## 🧱 WAF Detection
**WAF:** None detected

---

## 📑 Security Headers

- **Content-Security-Policy:** Missing

- **X-Content-Type-Options:** Missing

- **Strict-Transport-Security:** Missing

- **X-Frame-Options:** Missing


---

## 🧬 Tech Stack

- None detected


---

## 📌 CVE Matches

- None


---

## 📁 Sensitive Paths Found

- None


---

## 📊 Risk Score
**Score:** 25/100

- ⚠️ Missing headers: Content-Security-Policy, X-Content-Type-Options, Strict-Transport-Security, X-Frame-Options

- ⚠️ No WAF detected


---

## 🔗 Links Analyzed

### https://iana.org/domains/example

- **IP:** 192.0.43.8
- **WHOIS:**
- **Heuristics:**
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ python-whois>=0.8.0
Jinja2>=3.1.2
aiodns>=3.0.0
scikit-learn>1.3.0
joblib>=1.3.2
joblib>=1.3.2
scikit-learn
Binary file added scanner/__pycache__/__init__.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/active_tester.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/anti_bot.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/async_tools.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/auth_scanner.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/core.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/dir_brute.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/extract.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/headers.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/heuristics.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/ip_tools.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/profiling.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/recon.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/report.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/smart_detect.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/spider.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/ssl_analyzer.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file added scanner/__pycache__/subdomains.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/virustotal.cpython-313.pyc
Binary file not shown.
Binary file added scanner/__pycache__/vuln_check.cpython-313.pyc
Binary file not shown.
2 changes: 1 addition & 1 deletion scanner/async_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def load_plugins(plugin_folder="plugins"):
spec.loader.exec_module(mod)
if hasattr(mod, "run_plugin"):
plugins.append(mod)
return plugins
return plugins

def run_plugins(plugins, url, html, headers):
results = []
Expand Down
10 changes: 6 additions & 4 deletions scanner/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ def scan_website(url, fast_mode=False, use_plugins=False, output_format="json",
soup = BeautifulSoup(resp.text, 'html.parser')
for script in soup.find_all('script'):
if script.string:
js_snippets.appeend(script.string)
js_snippets.append(script.string)
report["suspicious_analysis"] = is_url_suspicious(link, js_snippets)
except:
except Exception:
report["suspicious_analysis"] = {"error": "Failed to analyze scripts"}


Expand All @@ -157,10 +157,12 @@ def scan_website(url, fast_mode=False, use_plugins=False, output_format="json",
report["ip"] = ip
report["host_profile"] = get_host_profile(ip)

except:
except Exception:
report["ip"] = None
report["host_profile"] = {"error": "Could not resolve IP"}
scan_summary["reports"].append(report)

# Always record the report for this link
scan_summary["reports"].append(report)

# Final risk score
scan_summary["risk"] = calculate_risk_score(scan_summary)
Expand Down
2 changes: 1 addition & 1 deletion scanner/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ def extract_links(url):
def detect_ip_grabber(url):
import re
patterns = [r'\d{10,}', r'[a-zA-Z]{20,}', r'[0-9a-f]{32}']
domains = ["grabify.link", "iplogger.org", "ipgrabber.io", "grabify.link"]
domains = ["grabify.link", "iplogger.org", "ipgrabber.io"]
return any(re.search(p, url) for p in patterns) or any(d in url for d in domains)
2 changes: 1 addition & 1 deletion scanner/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ def analyze_headers(headers):
return {h: headers.get(h, "Missing") for h in important}

def detect_waf(headers):
waf_keys = ["Server", "X-CDN", "CF-RAY", "X-Sucuro-ID", "X-Akamai"]
waf_keys = ["Server", "X-CDN", "CF-RAY", "X-Sucuri-ID", "X-Akamai"]
return {k: headers[k] for k in waf_keys if k in headers}
5 changes: 2 additions & 3 deletions scanner/profiling.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def calculate_risk_score(scan_summary):
if missing:
penalties.append(f"Missing headers: {', '.join(missing)}")

if scan_summary.get("waf") in ["None", "", None]:
if not scan_summary.get("waf"):
score += 5
penalties.append("No WAF detected")

Expand All @@ -120,5 +120,4 @@ def calculate_risk_score(scan_summary):


risk_score = min(score, 100)
return {"risk_score": risk_score, "penalties": penalties}

return {"risk_score": risk_score, "penalties": penalties}
Loading