Skip to content

Commit

Permalink
Added probe to identify copyright year
Browse files Browse the repository at this point in the history
- Added copyright probe
- Useful for finding old software
- Will display Copyright year if it can find it
- Otherwise will display any years found 1990-2024
  • Loading branch information
nyxgeek committed Oct 17, 2024
1 parent d58ad9d commit 980b4e1
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
| Body Hash | true | Header Hash | true |
| Redirect chain | false | URL Scheme | true |
| JARM Hash | false | ASN | false |
| Copyright Year | false |

# Installation Instructions

Expand Down Expand Up @@ -106,6 +107,7 @@ PROBES:
-lc, -line-count display response body line count
-wc, -word-count display response body word count
-title display page title
-copyright display copyright years or years if present
-bp, -body-preview display first N characters of response body (default 100)
-server, -web-server display server name
-td, -tech-detect display technology in use based on wappalyzer dataset
Expand Down
87 changes: 87 additions & 0 deletions common/httpx/copyright.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package httpx

import (
"regexp"
"sort"
"strings"
)

var crreYear = regexp.MustCompile(`(?:copyright|Copyright|COPYRIGHT|\(C\)|\(c\)|©|&copy;|&#169;)?\s*(?:[a-zA-Z0-9 ,-]+\s*)?[\s,]*(199[0-9]|20[0-1][0-9]|202[0-4])[\s,<-]+(?:copyright|Copyright|COPYRIGHT|\(C\)|\(c\)|©|&copy;|&#169;|199[0-9]|20[0-1][0-9]|202[0-4])?`)


func cleanText(text string) string {
text = strings.ReplaceAll(text, "<span>", "")
text = strings.ReplaceAll(text, "</span>", "")
text = strings.ReplaceAll(text, "\u00a0", " ")
text = strings.ReplaceAll(text, "&#xA9;", "&#169;")
text = strings.ReplaceAll(text, "–", "-")
text = strings.ReplaceAll(text, "-->", "")
text = strings.ReplaceAll(text, "<!--", "")
return text
}

// ExtractCopyright extracts all copyright dates or years from the raw response body and returns them as a space-delimited string
func ExtractCopyright(resp *Response) string {
var years []string // To store all matched years
var copyrightyears []string // To store any bonafide copyrights
var copyrightresults string // Declare variables outside the blocks
var yearresults string

// Convert response data to string and clean it
textContent := string(resp.Data)
textContent = cleanText(textContent)


// Apply regex to extract the years and check for indicators
matches := crreYear.FindAllStringSubmatch(textContent, -1)
for _, match := range matches {
year := strings.TrimSpace(match[1])

// Check if the year has a copyright indicator around it
if strings.Contains(match[0], "copyright") || strings.Contains(match[0], "Copyright") || strings.Contains(match[0], "COPYRIGHT") || strings.Contains(match[0], "(C)") || strings.Contains(match[0], "(c)") || strings.Contains(match[0], "©") || strings.Contains(match[0], "&#169;") || strings.Contains(match[0], "&copy;") {
copyrightyears = append(copyrightyears, year)
}

years = append(years, year)
}

// If we have any copyrights found, craft our string
if len(copyrightyears) > 0 {
// Sort, unique, and flatten our array
sort.Strings(copyrightyears)

// Make the years list unique
uniqueCopyrightYears := make([]string, 0, len(copyrightyears))
seen := make(map[string]bool)
for _, copyrightyear := range copyrightyears {
if !seen[copyrightyear] {
uniqueCopyrightYears = append(uniqueCopyrightYears, copyrightyear)
seen[copyrightyear] = true
}
}

green := "\033[32m"
reset := "\033[0m"
copyrightresults = "Copyright: " + green + strings.Join(uniqueCopyrightYears, " ") + reset
return copyrightresults
}

if len(years) > 0 {
sort.Strings(years)

// Make the years list unique
uniqueYears := make([]string, 0, len(years))
seen := make(map[string]bool)
for _, year := range years {
if !seen[year] {
uniqueYears = append(uniqueYears, year)
seen[year] = true
}
}
yearresults = "Possible Years: " + strings.Join(uniqueYears, " ")
return yearresults
}

return ""
}

4 changes: 4 additions & 0 deletions runner/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type ScanOptions struct {
RequestBody string
VHost bool
OutputTitle bool
OutputCopyright bool
OutputStatusCode bool
OutputLocation bool
OutputContentLength bool
Expand Down Expand Up @@ -111,6 +112,7 @@ func (s *ScanOptions) Clone() *ScanOptions {
RequestBody: s.RequestBody,
VHost: s.VHost,
OutputTitle: s.OutputTitle,
OutputCopyright: s.OutputCopyright,
OutputStatusCode: s.OutputStatusCode,
OutputLocation: s.OutputLocation,
OutputContentLength: s.OutputContentLength,
Expand Down Expand Up @@ -199,6 +201,7 @@ type Options struct {
VHostInput bool
Smuggling bool
ExtractTitle bool
ExtractCopyright bool
StatusCode bool
Location bool
ContentLength bool
Expand Down Expand Up @@ -354,6 +357,7 @@ func ParseOptions() *Options {
flagSet.BoolVarP(&options.OutputLinesCount, "line-count", "lc", false, "display response body line count"),
flagSet.BoolVarP(&options.OutputWordsCount, "word-count", "wc", false, "display response body word count"),
flagSet.BoolVar(&options.ExtractTitle, "title", false, "display page title"),
flagSet.BoolVar(&options.ExtractCopyright, "copyright", false, "display any copyright dates"),
flagSet.DynamicVarP(&options.ResponseBodyPreviewSize, "body-preview", "bp", 100, "display first N characters of response body"),
flagSet.BoolVarP(&options.OutputServerHeader, "web-server", "server", false, "display server name"),
flagSet.BoolVarP(&options.TechDetect, "tech-detect", "td", false, "display technology in use based on wappalyzer dataset"),
Expand Down
17 changes: 17 additions & 0 deletions runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ func New(options *Options) (*Runner, error) {
runner.options.protocol = httpx.HTTPorHTTPS
scanopts.VHost = options.VHost
scanopts.OutputTitle = options.ExtractTitle
scanopts.OutputCopyright = options.ExtractCopyright
scanopts.OutputStatusCode = options.StatusCode
scanopts.OutputLocation = options.Location
scanopts.OutputContentLength = options.ContentLength
Expand Down Expand Up @@ -1761,6 +1762,21 @@ retry:
builder.WriteRune(']')
}

var copyright string
if httpx.CanHaveTitleTag(resp.GetHeaderPart("Content-Type", ";")) {
copyright = httpx.ExtractCopyright(resp) // This will return a space-delimited string of years
}

if scanopts.OutputCopyright && copyright != "" {
builder.WriteString(" [")
if !scanopts.OutputWithNoColor {
builder.WriteString(aurora.Cyan(copyright).String())
} else {
builder.WriteString(copyright)
}
builder.WriteRune(']')
}

var bodyPreview string
if r.options.ResponseBodyPreviewSize > 0 && resp != nil {
bodyPreview = string(resp.Data)
Expand Down Expand Up @@ -2204,6 +2220,7 @@ retry:
Location: resp.GetHeaderPart("Location", ";"),
ContentType: resp.GetHeaderPart("Content-Type", ";"),
Title: title,
Copyright: copyright,
str: builder.String(),
VHost: isvhost,
WebServer: serverHeader,
Expand Down
1 change: 1 addition & 0 deletions runner/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Result struct {
Input string `json:"input,omitempty" csv:"input"`
Location string `json:"location,omitempty" csv:"location"`
Title string `json:"title,omitempty" csv:"title"`
Copyright string `json:"copyright,omitempty" csv:"copyright"`
str string
Scheme string `json:"scheme,omitempty" csv:"scheme"`
Error string `json:"error,omitempty" csv:"error"`
Expand Down

0 comments on commit 980b4e1

Please sign in to comment.