mirror of
https://github.com/anotherhadi/iknowyou.git
synced 2026-04-12 00:47:26 +02:00
init
This commit is contained in:
137
back/internal/tools/crtsh/tool.go
Normal file
137
back/internal/tools/crtsh/tool.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package crtsh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/anotherhadi/iknowyou/internal/tools"
|
||||
)
|
||||
|
||||
const (
|
||||
name = "crt.sh"
|
||||
description = "SSL/TLS certificate transparency log search via crt.sh — enumerates subdomains and certificates issued for a domain."
|
||||
link = "https://crt.sh"
|
||||
icon = ""
|
||||
)
|
||||
|
||||
type Runner struct{}
|
||||
|
||||
func New() tools.ToolRunner {
|
||||
return &Runner{}
|
||||
}
|
||||
|
||||
func (r *Runner) Name() string { return name }
|
||||
func (r *Runner) Description() string { return description }
|
||||
func (r *Runner) Link() string { return link }
|
||||
func (r *Runner) Icon() string { return icon }
|
||||
|
||||
func (r *Runner) InputTypes() []tools.InputType {
|
||||
return []tools.InputType{tools.InputTypeDomain}
|
||||
}
|
||||
|
||||
type crtEntry struct {
|
||||
IssuerName string `json:"issuer_name"`
|
||||
CommonName string `json:"common_name"`
|
||||
NameValue string `json:"name_value"`
|
||||
NotBefore string `json:"not_before"`
|
||||
NotAfter string `json:"not_after"`
|
||||
EntryTimestamp string `json:"entry_timestamp"`
|
||||
}
|
||||
|
||||
func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error {
|
||||
defer close(out)
|
||||
|
||||
params := url.Values{}
|
||||
params.Set("q", "%."+target)
|
||||
params.Set("output", "json")
|
||||
apiURL := "https://crt.sh/?" + params.Encode()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)
|
||||
if err != nil {
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; crtsh-scanner/1.0)")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"}
|
||||
} else {
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: err.Error()}
|
||||
}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to read response"}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
msg := fmt.Sprintf("API error %d", resp.StatusCode)
|
||||
if resp.StatusCode == http.StatusBadGateway || resp.StatusCode == http.StatusServiceUnavailable || resp.StatusCode == http.StatusGatewayTimeout {
|
||||
msg = fmt.Sprintf("crt.sh is temporarily unavailable (%d), try again later", resp.StatusCode)
|
||||
}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: msg}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
|
||||
var entries []crtEntry
|
||||
if err := json.Unmarshal(body, &entries); err != nil {
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "failed to parse response"}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: 0}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deduplicate subdomains from name_value fields
|
||||
seen := make(map[string]struct{})
|
||||
for _, e := range entries {
|
||||
for _, line := range strings.Split(e.NameValue, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" && !strings.HasPrefix(line, "*") {
|
||||
seen[line] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subdomains := make([]string, 0, len(seen))
|
||||
for s := range seen {
|
||||
subdomains = append(subdomains, s)
|
||||
}
|
||||
sort.Strings(subdomains)
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("Found %d unique subdomains across %d certificate entries\n\n", len(subdomains), len(entries)))
|
||||
for _, s := range subdomains {
|
||||
sb.WriteString(s + "\n")
|
||||
}
|
||||
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: strings.TrimSpace(sb.String())}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: len(subdomains)}
|
||||
out <- tools.Event{Tool: name, Type: tools.EventTypeDone}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user