mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-28 14:37:01 -07:00
detectJoomla matched any body containing the lowercase word "joomla", so pages that merely mention it (joomla hosting marketing like hostinger.com) were misreported as Joomla. the canonical Joomla! generator tag is capitalised, so the lowercase match also missed sites whose only tell is that tag. match the Joomla! generator meta tag and the joomla asset paths (/media/vendor/joomla, /media/system/js/core.js) instead. keying on the meta attribute rather than the plain tagline also rejects prose that quotes it.
152 lines
4.5 KiB
Go
152 lines
4.5 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package scan
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dropalldatabases/sif/internal/httpx"
|
|
"github.com/dropalldatabases/sif/internal/logger"
|
|
"github.com/dropalldatabases/sif/internal/output"
|
|
)
|
|
|
|
type CMSResult struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
func CMS(url string, timeout time.Duration, logdir string) (*CMSResult, error) {
|
|
log := output.Module("CMS")
|
|
log.Start()
|
|
|
|
spin := output.NewSpinner("Detecting content management system")
|
|
spin.Start()
|
|
|
|
sanitizedURL := stripScheme(url)
|
|
|
|
if logdir != "" {
|
|
if err := logger.WriteHeader(sanitizedURL, logdir, "CMS detection"); err != nil {
|
|
spin.Stop()
|
|
log.Error("Error creating log file: %v", err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
client := httpx.Client(timeout)
|
|
|
|
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, http.NoBody)
|
|
if err != nil {
|
|
spin.Stop()
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
spin.Stop()
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 5*1024*1024))
|
|
if err != nil {
|
|
spin.Stop()
|
|
return nil, err
|
|
}
|
|
bodyString := string(body)
|
|
|
|
// WordPress
|
|
if detectWordPress(url, client, bodyString) {
|
|
spin.Stop()
|
|
result := &CMSResult{Name: "WordPress", Version: "Unknown"}
|
|
log.Success("Detected CMS: %s", output.Highlight.Render(result.Name))
|
|
log.Complete(1, "detected")
|
|
return result, nil
|
|
}
|
|
|
|
// Drupal
|
|
if strings.Contains(resp.Header.Get("X-Drupal-Cache"), "HIT") || strings.Contains(bodyString, "Drupal.settings") {
|
|
spin.Stop()
|
|
result := &CMSResult{Name: "Drupal", Version: "Unknown"}
|
|
log.Success("Detected CMS: %s", output.Highlight.Render(result.Name))
|
|
log.Complete(1, "detected")
|
|
return result, nil
|
|
}
|
|
|
|
// Joomla
|
|
if detectJoomla(bodyString) {
|
|
spin.Stop()
|
|
result := &CMSResult{Name: "Joomla", Version: "Unknown"}
|
|
log.Success("Detected CMS: %s", output.Highlight.Render(result.Name))
|
|
log.Complete(1, "detected")
|
|
return result, nil
|
|
}
|
|
|
|
spin.Stop()
|
|
log.Info("No CMS detected")
|
|
log.Complete(0, "detected")
|
|
return nil, nil //nolint:nilnil // no CMS found is not an error
|
|
}
|
|
|
|
func detectWordPress(url string, client *http.Client, bodyString string) bool {
|
|
// Check for common WordPress indicators in the HTML
|
|
wpIndicators := []string{
|
|
"wp-content",
|
|
"wp-includes",
|
|
"wp-json",
|
|
"wordpress",
|
|
}
|
|
|
|
for _, indicator := range wpIndicators {
|
|
if strings.Contains(bodyString, indicator) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Check for WordPress-specific files
|
|
wpFiles := []string{
|
|
"/wp-login.php",
|
|
"/wp-admin/",
|
|
"/wp-config.php",
|
|
}
|
|
|
|
for _, file := range wpFiles {
|
|
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url+file, http.NoBody)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
resp, err := client.Do(req) //nolint:bodyclose // drained and closed via httpx.DrainClose
|
|
if err == nil {
|
|
found := resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusFound
|
|
// status only; drain so the conn returns to the pool.
|
|
httpx.DrainClose(resp)
|
|
if found {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// detectJoomla keys on the capital Joomla! generator and joomla asset paths. a
|
|
// bare "joomla" mention (the old check) matched marketing pages, so it is gone.
|
|
func detectJoomla(body string) bool {
|
|
return strings.Contains(body, `generator" content="Joomla!`) ||
|
|
strings.Contains(body, "/media/vendor/joomla") ||
|
|
strings.Contains(body, "/media/system/js/core.js")
|
|
}
|