Skip to content

Commit f2a12fe

Browse files
authored
Merge pull request #486 from edoardottt/devel
v1.0.0
2 parents 608b917 + 4069f0d commit f2a12fe

File tree

11 files changed

+411
-177
lines changed

11 files changed

+411
-177
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<br>
44
</h1>
55

6-
<h4 align="center">Use favicon.ico to improve your target recon phase</h4>
6+
<h4 align="center">Use favicons to improve your target recon phase</h4>
77

88
<h6 align="center"> Coded with 💙 by edoardottt </h6>
99

go.mod

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,65 @@ module github.com/edoardottt/favirecon
33
go 1.23.0
44

55
require (
6+
github.com/PuerkitoBio/goquery v1.10.3
67
github.com/edoardottt/golazy v0.1.4
78
github.com/projectdiscovery/goflags v0.1.74
89
github.com/projectdiscovery/gologger v1.1.54
910
github.com/projectdiscovery/mapcidr v1.1.34
10-
github.com/projectdiscovery/utils v0.4.17
11+
github.com/projectdiscovery/utils v0.4.18
1112
github.com/stretchr/testify v1.10.0
1213
github.com/twmb/murmur3 v1.1.8
1314
go.uber.org/ratelimit v0.3.1
1415
)
1516

1617
require (
17-
github.com/STARRY-S/zip v0.2.1 // indirect
18+
github.com/STARRY-S/zip v0.2.3 // indirect
1819
github.com/andybalholm/brotli v1.1.1 // indirect
20+
github.com/andybalholm/cascadia v1.3.3 // indirect
1921
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
2022
github.com/aymerick/douceur v0.2.0 // indirect
21-
github.com/benbjohnson/clock v1.3.0 // indirect
23+
github.com/benbjohnson/clock v1.3.5 // indirect
2224
github.com/bodgit/plumbing v1.3.0 // indirect
23-
github.com/bodgit/sevenzip v1.6.0 // indirect
25+
github.com/bodgit/sevenzip v1.6.1 // indirect
2426
github.com/bodgit/windows v1.0.1 // indirect
25-
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
27+
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect
2628
github.com/davecgh/go-spew v1.1.1 // indirect
2729
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
2830
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
2931
github.com/gorilla/css v1.0.1 // indirect
30-
github.com/hashicorp/errwrap v1.1.0 // indirect
31-
github.com/hashicorp/go-multierror v1.1.1 // indirect
3232
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
3333
github.com/json-iterator/go v1.1.12 // indirect
34-
github.com/klauspost/compress v1.17.11 // indirect
34+
github.com/klauspost/compress v1.18.0 // indirect
3535
github.com/klauspost/pgzip v1.2.6 // indirect
3636
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
3737
github.com/mattn/go-isatty v0.0.20 // indirect
38-
github.com/mholt/archives v0.1.0 // indirect
38+
github.com/mholt/archives v0.1.1 // indirect
3939
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
40-
github.com/miekg/dns v1.1.56 // indirect
40+
github.com/miekg/dns v1.1.65 // indirect
41+
github.com/minio/minlz v1.0.0 // indirect
4142
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4243
github.com/modern-go/reflect2 v1.0.2 // indirect
43-
github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 // indirect
44-
github.com/pierrec/lz4/v4 v4.1.21 // indirect
44+
github.com/nwaples/rardecode/v2 v2.1.1 // indirect
45+
github.com/pierrec/lz4/v4 v4.1.22 // indirect
4546
github.com/pkg/errors v0.9.1 // indirect
4647
github.com/pmezard/go-difflib v1.0.0 // indirect
4748
github.com/projectdiscovery/blackrock v0.0.1 // indirect
4849
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
49-
github.com/sorairolake/lzip-go v0.3.5 // indirect
50+
github.com/sorairolake/lzip-go v0.3.7 // indirect
51+
github.com/spf13/afero v1.14.0 // indirect
5052
github.com/therootcompany/xz v1.0.1 // indirect
51-
github.com/tidwall/gjson v1.14.4 // indirect
53+
github.com/tidwall/gjson v1.18.0 // indirect
5254
github.com/tidwall/match v1.1.1 // indirect
5355
github.com/tidwall/pretty v1.2.1 // indirect
5456
github.com/ulikunitz/xz v0.5.12 // indirect
5557
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
56-
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
57-
golang.org/x/mod v0.17.0 // indirect
58-
golang.org/x/net v0.38.0 // indirect
59-
golang.org/x/sync v0.12.0 // indirect
60-
golang.org/x/sys v0.31.0 // indirect
61-
golang.org/x/text v0.23.0 // indirect
62-
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
58+
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
59+
golang.org/x/mod v0.24.0 // indirect
60+
golang.org/x/net v0.39.0 // indirect
61+
golang.org/x/sync v0.13.0 // indirect
62+
golang.org/x/sys v0.32.0 // indirect
63+
golang.org/x/text v0.24.0 // indirect
64+
golang.org/x/tools v0.32.0 // indirect
6365
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
6466
gopkg.in/yaml.v3 v3.0.1 // indirect
6567
)

go.sum

Lines changed: 86 additions & 41 deletions
Large diffs are not rendered by default.

pkg/favirecon/db.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@
684684
"1749354953": "Vantara Pentaho Business Analytics Server",
685685
"-175177211": "VMware vCenter Converter Standalone",
686686
"175178334": "Nas4free",
687+
"1752776796": "OpenAI",
687688
"-175283071": "Dell",
688689
"1756605828": "WebCTRL",
689690
"1757638479": "Bossgoo",
@@ -1100,6 +1101,7 @@
11001101
"430582574": "SmartPing",
11011102
"-43161126": "Slack",
11021103
"432733105": "Pi Star",
1104+
"-434501501": "bit.ly",
11031105
"-435817905": "Cambium Networks",
11041106
"-438482901": "Moodle",
11051107
"440258421": "Dolibarr",
@@ -1132,6 +1134,7 @@
11321134
"517158172": "D-Link (router/network)",
11331135
"-519765377": "Parallels Plesk Panel",
11341136
"-520888198": "Blue Iris (Webcam)",
1137+
"-525583313": "OpenAI",
11351138
"-532394952": "CX",
11361139
"538323054": "NethServer Enterprise",
11371140
"538585915": "Lenel",

pkg/favirecon/favirecon.go

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package favirecon
88

99
import (
1010
"bufio"
11+
"errors"
1112
"fmt"
1213
"os"
1314
"sync"
@@ -126,6 +127,23 @@ func pushInput(r *Runner) {
126127
close(r.Input)
127128
}
128129

130+
/*
131+
Try /favicon.ico first. Most common and lightweight check.
132+
Accept it only if:
133+
- Status is 200.
134+
- Content-Type is an image.
135+
- Body length is > 0 (some sites return 200 but empty).
136+
If valid, hash and lookup. ✅ Done.
137+
138+
Fallback to parsing <link rel="icon" ...> in the HTML <head>:
139+
- Fetch original input URL (not with /favicon.ico appended).
140+
- Parse the HTML.
141+
- Extract: rel=icon, rel=shortcut icon, rel=apple-touch-icon
142+
If href is:
143+
- A relative path → resolve against base URL.
144+
- A full URL → use as-is.
145+
- Data URL → decode and hash directly.
146+
*/
129147
func execute(r *Runner) {
130148
defer r.InWg.Done()
131149

@@ -137,38 +155,52 @@ func execute(r *Runner) {
137155
go func() {
138156
defer r.InWg.Done()
139157

158+
client, err := customClient(&r.Options)
159+
if err != nil {
160+
gologger.Error().Msgf("%s", err)
161+
162+
return
163+
}
164+
140165
for value := range r.Input {
141-
targetURL, err := PrepareURL(value)
166+
faviconURL, err := PrepareURL(value)
142167
if err != nil {
143168
gologger.Error().Msgf("%s", err)
144169

145-
return
170+
continue
146171
}
147172

148173
rl.Take()
149174

150-
client, err := customClient(&r.Options)
175+
found, result, err := getFavicon(faviconURL, r.UserAgent, client)
151176
if err != nil {
152-
gologger.Error().Msgf("%s", err)
153-
154-
return
177+
if errors.Is(err, ErrFaviconNotFound) {
178+
gologger.Debug().Msgf("%s for url %s", err.Error(), value)
179+
} else {
180+
gologger.Error().Msgf("%s", err)
181+
}
155182
}
156183

157-
result, err := getFavicon(targetURL, r.UserAgent, client)
158-
if err != nil {
159-
gologger.Error().Msgf("%s", err)
184+
if !found {
185+
gologger.Debug().Msgf("Fallback to HTML parsing for %s", value)
160186

161-
return
187+
faviconURL, result, err = extractFaviconFromHTML(value, r.UserAgent, client)
188+
if err != nil {
189+
gologger.Debug().Msgf("Favicon not found for %s: %s", value, err)
190+
continue
191+
}
162192
}
163193

164-
found, err := CheckFavicon(result, r.Options.Hash, targetURL)
194+
foundDB, err := CheckFavicon(result, r.Options.Hash, faviconURL)
165195
if err != nil {
166196
if r.Options.Verbose {
167197
gologger.Error().Msgf("%s", err)
168198
}
169-
} else {
170-
r.Output <- output.Found{URL: targetURL, Name: found, Hash: result}
199+
200+
continue
171201
}
202+
203+
r.Output <- output.Found{URL: value, Name: foundDB, Hash: result}
172204
}
173205
}()
174206
}

pkg/favirecon/html.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
favirecon - Use favicon.ico to improve your target recon phase. Quickly detect technologies, WAF, exposed panels, known services.
3+
4+
This repository is under MIT License https://github.com/edoardottt/favirecon/blob/main/LICENSE
5+
*/
6+
7+
package favirecon
8+
9+
import (
10+
"encoding/base64"
11+
"errors"
12+
"net/http"
13+
"strings"
14+
15+
"github.com/PuerkitoBio/goquery"
16+
)
17+
18+
var (
19+
ErrFaviconNotFound = errors.New("favicon not found")
20+
ErrFaviconLinkTagNotFound = errors.New("no favicon link tag found")
21+
ErrHTMLNotFetched = errors.New("failed to fetch HTML")
22+
ErrInvalidDataURI = errors.New("invalid data URI")
23+
)
24+
25+
func extractFaviconFromHTML(pageURL, ua string, client *http.Client) (string, string, error) {
26+
req, err := http.NewRequest(http.MethodGet, pageURL, nil)
27+
if err != nil {
28+
return "", "", err
29+
}
30+
31+
req.Header.Add("User-Agent", ua)
32+
33+
resp, err := client.Do(req)
34+
if err != nil {
35+
return "", "", err
36+
}
37+
defer resp.Body.Close()
38+
39+
if resp.StatusCode != http.StatusOK {
40+
return "", "", ErrHTMLNotFetched
41+
}
42+
43+
doc, err := goquery.NewDocumentFromReader(resp.Body)
44+
if err != nil {
45+
return "", "", err
46+
}
47+
48+
var faviconHref string
49+
50+
doc.Find("link").EachWithBreak(func(i int, s *goquery.Selection) bool {
51+
rel, _ := s.Attr("rel")
52+
href, ok := s.Attr("href")
53+
54+
if ok && strings.Contains(strings.ToLower(rel), "icon") {
55+
faviconHref = href
56+
return false // break loop
57+
}
58+
59+
return true
60+
})
61+
62+
if faviconHref == "" {
63+
return "", "", ErrFaviconLinkTagNotFound
64+
}
65+
66+
// handle base64 data
67+
if strings.HasPrefix(faviconHref, "data:image") {
68+
base64Data := strings.SplitN(faviconHref, ",", 2)
69+
if len(base64Data) != 2 {
70+
return "", "", ErrInvalidDataURI
71+
}
72+
73+
decoded, err := base64.StdEncoding.DecodeString(base64Data[1])
74+
if err != nil {
75+
return "", "", err
76+
}
77+
78+
return faviconHref, GetFaviconHash(decoded), nil
79+
}
80+
81+
faviconURL := resolveURL(pageURL, faviconHref)
82+
83+
found, favicon, err := getFavicon(faviconURL, ua, client)
84+
if err != nil {
85+
return faviconURL, "", err
86+
}
87+
88+
if !found {
89+
return "", "", ErrFaviconNotFound
90+
}
91+
92+
return faviconURL, favicon, nil
93+
}

0 commit comments

Comments
 (0)