6 Commits

Author SHA1 Message Date
Evan Hosinski 93a5ce069d Update filenames 2025-05-15 21:22:21 -04:00
Evan Hosinski 85f315dafe Updated photos
blurred content
2025-05-15 21:19:32 -04:00
Ar1ste1a e05b54fb29 Updated README.md added readme photos 2025-05-15 21:14:57 -04:00
Ar1ste1a 7a6ae01254 Updated README.md 2025-05-15 21:11:24 -04:00
Ar1ste1a 02ef2f78fb Added Makefile 2025-05-15 17:24:45 -04:00
Evan Hosinski 473bdbff0b Added enhanced logging for specific errors related to dehash tied into the apiu calls. Added logs subcommand. 2025-05-15 16:38:14 -04:00
9 changed files with 539 additions and 112 deletions
+1
View File
@@ -1 +1,2 @@
.idea/* .idea/*
build/*
-1
View File
@@ -1 +0,0 @@
txt
Binary file not shown.

After

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

+53
View File
@@ -0,0 +1,53 @@
# Makefile for Dehasher
# Go command
GO=go
# Binary name
BINARY_NAME=dehasher
# Build directory
BUILD_DIR=build/bin
# Platforms to build for
PLATFORMS=linux darwin windows
# Architecture to build for
ARCHS=amd64 arm64
# Version info from git tag or default
VERSION=$(shell git describe --tags 2>/dev/null || echo "v1.0.1")
.PHONY: all clean build build-all
# Default target
all: clean build-all
# Clean build artifacts
clean:
rm -rf $(BUILD_DIR)
mkdir -p $(BUILD_DIR)
# Build for current platform
build:
$(GO) build -o $(BUILD_DIR)/$(BINARY_NAME) -ldflags "-X main.version=$(VERSION)" dehasher.go
# Build for all platforms
build-all: clean
@for platform in $(PLATFORMS); do \
for arch in $(ARCHS); do \
echo "Building for $$platform/$$arch..."; \
GOOS=$$platform GOARCH=$$arch $(GO) build -o $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch -ldflags "-X main.version=$(VERSION)" dehasher.go; \
if [ "$$platform" = "windows" ]; then \
mv $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch $(BUILD_DIR)/$(BINARY_NAME)-$$platform-$$arch.exe; \
fi; \
done; \
done
# Install locally
install: build
cp $(BUILD_DIR)/$(BINARY_NAME) /usr/local/bin/
# Run tests
test:
$(GO) test ./...
+218 -92
View File
@@ -1,69 +1,36 @@
# Dehasher # 🚀 Dehasher
## A cli tool built for interaction with the Dehash API ### A CLI tool for seamless interaction with the Dehashed API
<div align="center"> ---
<img src="https://img.wanman.io/fUSu0/SaCUyEMe87.png/raw" style="width: 350px; height: auto" alt="Ar1ste1a" title="Ar1ste1a Offensive Security">
</div>
# Features ## 🌟 Features
- Output Format Control - **Output Format Control**: JSON, YAML, XML, and TEXT support.
- Request Limiting - **Regex & Wildcard Matching**: Flexible query options.
- Record Limiting - **Local Database Storage**: Default or custom paths.
- Regular Expression Handling - **Database Querying**: Raw SQL and filtered queries.
- Exact Match Handling - **Enhanced Logging**: Easy log parsing and rotation.
- Error Handling - **Error Handling**: Intelligent API error management.
- Credential Dumping - **WhoIs Lookups**: Domain, IP, MX, NS, and more.
- Intelligent Token Usage - **Subdomain Scanning**: Identify subdomains.
- Database Path Configuration - **Robust Logging**: Detailed logs for debugging.
# Options - **API Key Management**: Securely store and manage API keys.
- **Formatted Output**: Easy to read and understand.
- **Intuitive Database Querying**: Query for specific information.
```bash-session ---
usage: Dehasher [-h --help] {-k --key} {-a --authorized-email} [-h --help] [-m --max-records] [-r --max-requests] [-B --print-balance] [-X --exact-match] [-R --regex-match] [-t --list-tokens] [-o --output-file-name] [-T --output-txt] [-J --output-json] [-Y --output-yaml] [-x --output-xml] [-U --username-query] [-E --email-query] [-I --ip-address-query] [-P --password-query] [-Q --hashed-password-query] [-N --name-query] [-C --creds-only]
Dehashed Tool ## 📦 Installation
options: Clone the repository and build the tool:
-h --help show this help message and exit ```bash
-m --max-records Maximum amount of records to return git clone https://github.com/Ar1ste1a/Dehasher.git
-r --max-requests Maximum number of requests to make cd Dehasher
-B --print-balance Print remaining balance after requests go build dehasher.go
-X --exact-match Use Exact Matching on fields
-R --regex-match Use Regex Matching on fields
-t --list-tokens List the number of tokens remaining
-o --output-file-name File to output results to
-T --output-txt Output to text file
-J --output-json Output to JSON file
-Y --output-yaml Output to YAML file
-x --output-xml Output to XML file
-U --username-query Username Query
-E --email-query Email Query
-I --ip-address-query IP Address Query
-P --password-query Password Query
-Q --hashed-password-query Hashed Password Query
-N --name-query Name Query
-C --creds-only Return Credentials Only
-k --key API Key
-a --authorized-email Email to pair with key for authentication
--local-db Use local database in current directory
v1.0
``` ```
# Sample Run <hr></hr>
```bash-session
-k ddq<redacted> -a ar1ste1a@<redacted> -E @example.com -C -o example_creds
Making 3 Requests for 10000 Records (30000 Total)
[*] Performing Request...
[*] Retrieved 60 Records
[-] Not Enough Entries, ending queries
[+] Discovered 60 Records
[*] Writing entries file: example_creds.json
[*] Success
``` ## 🔰 Getting Started
# Getting Started
To begin, clone the repository To begin, clone the repository
``` bash-session ``` bash-session
@@ -72,13 +39,26 @@ cd Dehasher
go build dehasher.go go build dehasher.go
``` ```
# Database Configuration <hr></hr>
## 🛠️ Initial Setup
Dehasher requires an API key from Dehashed. Set it up with:
```bash
ar1ste1a@kali:~$ dehasher set-key <redacted>
```
<hr></hr>
## 🗄️ Database Configuration
Dehasher supports two database storage options: Dehasher supports two database storage options:
1. **Default Path** (default): Stores the database at `~/.local/share/Dehasher/db/dehashed.sqlite` 1. **Default Path** (default): Stores the database at `~/.local/share/Dehasher/db/dehashed.sqlite`
2. **Local Path**: Stores the database in the current directory as `./dehasher.sqlite` 2. **Local Path**: Stores the database in the current directory as `./dehasher.sqlite`
The **Local Path** option allows for separate databases for different projects or engagements.
To configure the database location: To configure the database location:
```bash ```bash
@@ -89,61 +69,207 @@ To configure the database location:
./dehasher set-local-db false ./dehasher set-local-db false
``` ```
You can also specify the database location when running commands: <hr></hr>
```bash ## 🔍 Crafting Queries
# Use local database for this command only
./dehasher -k YOUR_API_KEY -a YOUR_EMAIL -E @example.com --local-db
```
# Crafting a query ### Simple Query
Dehasher can be used simply for example to query for credentials matching a given email domain.
## Simple Query
``` go ``` go
# Provide credentials for emails matching @target.com # Provide credentials for emails matching @target.com
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E @target.com dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E @target.com
``` ```
## Simple Credentials Query ### Simple Credentials Query
Dehasher can also be used to return only credentials for a given query.
``` go ``` go
# Provide credentials for emails matching @target.com # Provide credentials for emails matching @target.com
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E @target.com -C dehasher -E @target.com -C
``` ```
## Simple Query Returning Balance ### Multiple Match Query
Dehasher is capable of handling multiple queries for the same field.
This is useful for when you want to search for multiple domains, or multiple usernames.
``` go ``` go
# Provide credentials for emails matching @target.com # Provide credentials for emails matching @target.com and @target2.com
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E @target.com -C -B dehasher -E @target.com,@target2.com -C
``` ```
## Regex Query ### Wildcard Query
Dehasher is capable of handling wildcard queries.
A wildcard query cannot begin with a wildcard.
This is a limitation of the Dehashed API.
An asterisk can be used to denote multiple characters, and a question mark can be used to denote a single character.
``` go
# Provide credentials for emails matching @target.com and @target2.com
dehasher -E @target?.com -C -W
```
### Regex Query
Dehasher is capable of handling regex queries.
Simply denote regex queries with the `-R` flag.
Place all regex queries in quotes with the corresponding query flag in single quotes.
``` go ``` go
# Return matches for emails matching this given regex query # Return matches for emails matching this given regex query
# -R e: Specify the '-E' field as a regex entry dehasher -R -e '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com'
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -E '[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?@target.com' -C -B -R e
``` ```
## Exact Match Query ### Output Text (default JSON)
``` go Dehasher is capable of handling output formats.
# Return matches for usernames exactly matching "admin" The default output format is JSON.
# -X u: Specify the '-U' field as an exact match entry To change the output format, use the `-f` flag.
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -C -B -U admin -X u Dehasher currently supports JSON, YAML, XML, and TEXT output formats.
```
## Output Text (default JSON)
``` go ``` go
# Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt' # Return matches for usernames exactly matching "admin" and write to text file 'admins_file.txt'
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -C -B -U admin -X u -T -o admins_file dehasher -U admin -o admins_file -f txt
``` ```
## Output YAML <hr></hr>
``` go
# Return matches for usernames exactly matching "admin" and write to yaml file 'admins_file.yaml' ## 🌐 WhoIs Lookups
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -C -B -U admin -X u -Y -o admins_file Dehasher supports WHOIS lookups, history searches, reverse WHOIS searches, IP lookups, MX lookups, NS lookups, and subdomain scans.
The WhoIs Lookups require a separate API Credit from the Dehashed API.
### Domain Lookup
```bash
# Perform a WHOIS lookup for example.com
dehasher whois -d example.com
``` ```
## Output XML ### History Lookup
``` go History Lookups require 25 credits.
# Return matches for usernames exactly matching "admin" and write to xml file 'admins_file.xml' This is a Dehashed API limitation.
dehasher -k ddq<redacted> -a ar1ste1a@domain.tld -C -B -U admin -X u -x -o admins_file ```bash
# Perform a WHOIS history search for example.com
dehasher whois -d example.com -H
``` ```
### Reverse WHOIS Lookup
```bash
# Perform a reverse WHOIS lookup for example.com
dehasher whois -I example.com
```
### IP Lookup
```bash
# Perform a reverse IP lookup for 8.8.8.8
dehasher whois -i 8.8.8.8
```
### MX Lookup
```bash
# Perform a reverse MX lookup for google.com
dehasher whois -m google.com
```
### NS Lookup
```bash
# Perform a reverse NS lookup for google.com
dehasher whois -n google.com
```
### Subdomain Scan
```bash
# Perform a WHOIS subdomain scan for google.com
dehasher whois -d google.com -s
```
<hr></hr>
## 📊 Database Querying
Dehasher stores query results in a local database.
This database can be queried for previous results.
This database also includes WhoIs Information and Subdomain Scan results, but does **not** include historical lookups.
## Simple Query
![Alt text](.img/simple_query_db.png "Simple Query")
Dehasher supports querying the database for previous results.
This is useful for when you want to query for specific information.
```bash
# Query the database for all results containing the word 'admin' in the username
dehasher query -t results -q "username LIKE '%admin%'"
```
## Raw SQL Queries
![Alt text](.img/raw_query_db.png "Raw Query")
Dehasher also supports raw SQL queries. This is useful for when you want to query for specific information.
```bash
# Query the database for all results containing the word 'admin' in the username
dehasher query -r "SELECT * FROM results WHERE username LIKE '%admin%'"
```
## Query Options
Dehasher supports a number of query options. These options can be used to filter the results of a query.
```bash
# Query the database for all results containing the word 'admin' in the username
dehasher query -t results -q "username LIKE '%admin%'" -n username,email,password
```
## Listing Tables and Columns
Dehasher supports listing all available tables and columns.
This is useful for when you want to query for specific information.
```bash
# List all available tables and columns
dehasher query -a
```
The current tables available for query are:
- results
- creds
- whois
- subdomains
- history
- runs
---
# Exporting Results
Dehasher supports exporting results to a file.
This is useful for when you want to requery for specific information without touching the Dehashed API.
The export subcommand supports all the same options as the query subcommand.
The export subcommand also supports file naming and output format control.
```bash
# Export all results containing the word 'admin' in the username to a text file
dehasher export -t results -q "username LIKE '%admin%'" -o admins_file -f txt
```
## 🐛 Debugging
Dehasher uses the `zap` logging library for logging. The logs are stored in `~/.local/share/Dehasher/logs`.
The logs can be easily queried from the Dehasher CLI.
```bash
# Show the last 10 logs
dehasher logs -l 10
# Show logs from the last 24 hours
dehasher logs -s "24 hours ago"
# Show logs from the last 24 hours with a severity of error or fatal
dehasher logs -s "24 hours ago" -v error,fatal
```
## 🎉 Sample Run
```bash
ar1ste1a@kali:~$ dehasher api -D <redacted>.com -o <redacted> -f json
Making 3 Requests for 10000 Records (30000 Total)
[*] Querying Dehashed API...
[*] Performing Request...
[+] Retrieved 2740 Records
[-] Not Enough Entries, ending queries
[+] Discovered 10 Credentials
[*] Writing entries to file: <redacted>.json
[*] Success
[*] Completing Process
```
## 🤝 Contributing
Contributions are welcome! Submit a pull request to help improve Dehasher.
<div align="center">
<img src="https://img.wanman.io/fUSu0/jUtovIFE52.png/raw" style="width: 350px; height: auto" alt="Ar1ste1a" title="Ar1ste1a Offensive Security">
</div>
## **Release The Kraken**
+70 -4
View File
@@ -16,8 +16,8 @@ func init() {
// Add flags specific to export command // Add flags specific to export command
exportCmd.Flags().IntVarP(&exportLimitRows, "limit", "l", 100, "Limit number of results") exportCmd.Flags().IntVarP(&exportLimitRows, "limit", "l", 100, "Limit number of results")
exportCmd.Flags().BoolVarP(&exportListAll, "list-all", "a", false, "List all columns") exportCmd.Flags().BoolVarP(&exportListAll, "list-all", "a", false, "List all tables and their columns")
exportCmd.Flags().StringVarP(&exportTableName, "table", "t", "", "Table to export") exportCmd.Flags().StringVarP(&exportTableName, "table", "t", "", "Table to export (results, creds, whois, subdomains, history, runs)")
exportCmd.Flags().StringVarP(&exportNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')") exportCmd.Flags().StringVarP(&exportNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')")
exportCmd.Flags().StringVarP(&exportColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')") exportCmd.Flags().StringVarP(&exportColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')")
exportCmd.Flags().StringVarP(&exportUserQuery, "user-query", "q", "", "User query to execute") exportCmd.Flags().StringVarP(&exportUserQuery, "user-query", "q", "", "User query to execute")
@@ -50,6 +50,12 @@ var (
Use: "export", Use: "export",
Short: "Export database to file", Short: "Export database to file",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// If list-all flag is set, list all tables and columns
if exportListAll {
listAvailableTables()
return
}
fmt.Println("[*] Exporting database...") fmt.Println("[*] Exporting database...")
// If Raw Query is set, execute it and export // If Raw Query is set, execute it and export
@@ -59,11 +65,65 @@ var (
return return
} }
// Validate table name
if exportTableName == "" {
fmt.Println("[!] Error: Table name is required. Use -t or --table to specify a table.")
fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return
}
if !isValidTable(exportTableName) {
fmt.Printf("[!] Error: Unknown table '%s'.\n", exportTableName)
fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return
}
// Validate columns if specified
if exportColumns != "" {
columns := strings.Split(exportColumns, ",")
invalidColumns := validateColumns(exportTableName, columns)
if len(invalidColumns) > 0 {
fmt.Printf("[!] Error: Invalid column(s) for table '%s': %s\n",
exportTableName, strings.Join(invalidColumns, ", "))
fmt.Println("[*] Available columns for this table:")
for i := 0; i < len(availableTables[exportTableName]); i += 5 {
end := i + 5
if end > len(availableTables[exportTableName]) {
end = len(availableTables[exportTableName])
}
fmt.Printf(" %s\n", strings.Join(availableTables[exportTableName][i:end], ", "))
}
return
}
}
// Validate not-null fields if specified
if exportNotNull != "" {
notNullFields := strings.Split(exportNotNull, ",")
invalidFields := validateColumns(exportTableName, notNullFields)
if len(invalidFields) > 0 {
fmt.Printf("[!] Error: Invalid not-null field(s) for table '%s': %s\n",
exportTableName, strings.Join(invalidFields, ", "))
fmt.Println("[*] Available columns for this table:")
for i := 0; i < len(availableTables[exportTableName]); i += 5 {
end := i + 5
if end > len(availableTables[exportTableName]) {
end = len(availableTables[exportTableName])
}
fmt.Printf(" %s\n", strings.Join(availableTables[exportTableName][i:end], ", "))
}
return
}
}
// Determine which table to query based on the tableTypeDBQuery parameter // Determine which table to query based on the tableTypeDBQuery parameter
table := sqlite.GetTable(exportTableName) table := sqlite.GetTable(exportTableName)
if table == sqlite.UnknownTable { if table == sqlite.UnknownTable {
fmt.Printf("Error: Unknown table type '%s'.\n", exportTableName) fmt.Printf("[!] Error: Unknown table type '%s'.\n", exportTableName)
cmd.Help() fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return return
} }
@@ -99,6 +159,12 @@ func exportTableQuery(table sqlite.Table) {
// Get the object for the table // Get the object for the table
object := table.Object() object := table.Object()
// Check if object is nil (invalid table)
if object == nil {
fmt.Printf("[!] Error: Table '%s' is not valid or does not exist.\n", exportTableName)
return
}
// Query the database // Query the database
db := sqlite.GetDB() db := sqlite.GetDB()
query := db.Model(object).Select(columns) query := db.Model(object).Select(columns)
+189 -7
View File
@@ -7,28 +7,137 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"go.uber.org/zap" "go.uber.org/zap"
"os"
"strings" "strings"
) )
// Map of available tables and their columns
var availableTables = map[string][]string{
"results": {
"id", "created_at", "updated_at", "deleted_at", "dehashed_id", "email", "ip_address", "username",
"password", "hashed_password", "hash_type", "name", "vin", "license_plate", "url", "social",
"cryptocurrency_address", "address", "phone", "company", "database_name",
},
"creds": {
"id", "created_at", "updated_at", "deleted_at", "email", "username", "password",
},
"whois": {
"id", "created_at", "updated_at", "deleted_at", "contact_email", "created_date",
"domain_name", "domain_name_ext", "expires_date", "status", "whois_server",
},
"subdomains": {
"id", "created_at", "updated_at", "deleted_at", "domain", "first_seen", "last_seen",
},
"history": {
"id", "created_at", "updated_at", "deleted_at", "domain_name", "domain_type",
"registrar_name", "whois_server", "created_date_iso8601", "updated_date_iso8601", "expires_date_iso8601",
},
"runs": {
"id", "created_at", "updated_at", "deleted_at", "max_records", "max_requests", "starting_page",
"output_format", "output_file", "regex_match", "wildcard_match", "username_query", "email_query",
"ip_query", "pass_query", "hash_query", "name_query", "domain_query", "vin_query", "license_plate_query",
"address_query", "phone_query", "social_query", "crypto_address_query", "print_balance", "creds_only",
},
}
// Function to list available tables and their columns
func listAvailableTables() {
fmt.Println("Available tables and columns:")
// Prepare data for pretty.Table
headers := []string{"Table", "Columns"}
var tableRows [][]string
// Sort tables alphabetically for consistent output
var tableNames []string
for tableName := range availableTables {
tableNames = append(tableNames, tableName)
}
// Simple bubble sort for table names
for i := 0; i < len(tableNames)-1; i++ {
for j := 0; j < len(tableNames)-i-1; j++ {
if tableNames[j] > tableNames[j+1] {
tableNames[j], tableNames[j+1] = tableNames[j+1], tableNames[j]
}
}
}
// Create rows for the table
for _, tableName := range tableNames {
columns := availableTables[tableName]
// Format columns with line breaks for better readability
var formattedColumns string
for i := 0; i < len(columns); i += 5 {
end := i + 5
if end > len(columns) {
end = len(columns)
}
if i > 0 {
formattedColumns += "\n"
}
formattedColumns += strings.Join(columns[i:end], ", ")
}
tableRows = append(tableRows, []string{tableName, formattedColumns})
}
// Display the table
pretty.Table(headers, tableRows)
}
// Function to validate table name
func isValidTable(tableName string) bool {
_, exists := availableTables[tableName]
return exists
}
// Function to validate column names for a specific table
func validateColumns(tableName string, columns []string) []string {
if tableName == "" || columns == nil || len(columns) == 0 || columns[0] == "*" {
return nil
}
tableColumns, exists := availableTables[tableName]
if !exists {
return []string{fmt.Sprintf("Table '%s' does not exist", tableName)}
}
var invalidColumns []string
for _, col := range columns {
valid := false
for _, tableCol := range tableColumns {
if col == tableCol {
valid = true
break
}
}
if !valid {
invalidColumns = append(invalidColumns, col)
}
}
return invalidColumns
}
func init() { func init() {
// Add whois command to root command // Add whois command to root command
rootCmd.AddCommand(queryCmd) rootCmd.AddCommand(queryCmd)
// Add flags specific to whois command // Add flags specific to whois command
queryCmd.Flags().StringVarP(&dbQueryTableName, "table", "t", "", "Table to query (results, creds, whois, subdomains, history, query_options)") queryCmd.Flags().StringVarP(&dbQueryTableName, "table", "t", "", "Table to query (results, creds, whois, subdomains, history, runs)")
queryCmd.Flags().IntVarP(&dbQueryLimitRows, "limit", "l", 100, "Limit number of results") queryCmd.Flags().IntVarP(&dbQueryLimitRows, "limit", "l", 100, "Limit number of results")
queryCmd.Flags().StringVarP(&dbQueryNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')") queryCmd.Flags().StringVarP(&dbQueryNotNull, "not-null", "n", "", "Filter for non-null values (comma-separated list, e.g., 'password,email')")
queryCmd.Flags().StringVarP(&dbQueryColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')") queryCmd.Flags().StringVarP(&dbQueryColumns, "columns", "c", "", "Columns to display in output (comma-separated list, e.g., 'username,email,password')")
queryCmd.Flags().StringVarP(&dbQueryUserQuery, "user-query", "q", "", "User query to execute") queryCmd.Flags().StringVarP(&dbQueryUserQuery, "user-query", "q", "", "User query to execute")
queryCmd.Flags().StringVarP(&dbQueryRawQuery, "raw-query", "r", "", "Raw SQL query to execute") queryCmd.Flags().StringVarP(&dbQueryRawQuery, "raw-query", "r", "", "Raw SQL query to execute")
queryCmd.Flags().BoolVarP(&dbQueryListAll, "list-all", "a", false, "List all columns") queryCmd.Flags().BoolVarP(&dbQueryListAll, "list-all", "a", false, "List all tables and their columns")
// Add mutually exclusive flags to query and raw-query // Add mutually exclusive flags to query and raw-query
// Cannot use query and raw-query at the same time // Cannot use query and raw-query at the same time
queryCmd.MarkFlagsMutuallyExclusive("user-query", "raw-query") queryCmd.MarkFlagsMutuallyExclusive("user-query", "raw-query")
// Raw query does not require a table // Raw query does not require a table
queryCmd.MarkFlagsMutuallyExclusive("user-query", "table") queryCmd.MarkFlagsMutuallyExclusive("raw-query", "table")
// List all columns does not require a query or raw-query // List all columns does not require a query or raw-query
queryCmd.MarkFlagsMutuallyExclusive("raw-query", "list-all") queryCmd.MarkFlagsMutuallyExclusive("raw-query", "list-all")
} }
@@ -47,20 +156,81 @@ var (
Short: "Query the database", Short: "Query the database",
Long: `Query the database for various information.`, Long: `Query the database for various information.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// If list-all flag is set, list all tables and columns
if dbQueryListAll {
listAvailableTables()
return
}
// If Raw Query is set, execute it and return // If Raw Query is set, execute it and return
if dbQueryRawQuery != "" { if dbQueryRawQuery != "" {
fmt.Println("[*] Executing Raw Query...") fmt.Println("[*] Executing Raw Query...")
rawDBQuery() rawDBQuery()
os.Exit(1) return
}
// Validate table name
if dbQueryTableName == "" {
fmt.Println("[!] Error: Table name is required. Use -t or --table to specify a table.")
fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return
}
if !isValidTable(dbQueryTableName) {
fmt.Printf("[!] Error: Unknown table '%s'.\n", dbQueryTableName)
fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return
}
// Validate columns if specified
if dbQueryColumns != "" {
columns := strings.Split(dbQueryColumns, ",")
invalidColumns := validateColumns(dbQueryTableName, columns)
if len(invalidColumns) > 0 {
fmt.Printf("[!] Error: Invalid column(s) for table '%s': %s\n",
dbQueryTableName, strings.Join(invalidColumns, ", "))
fmt.Println("[*] Available columns for this table:")
for i := 0; i < len(availableTables[dbQueryTableName]); i += 5 {
end := i + 5
if end > len(availableTables[dbQueryTableName]) {
end = len(availableTables[dbQueryTableName])
}
fmt.Printf(" %s\n", strings.Join(availableTables[dbQueryTableName][i:end], ", "))
}
return
}
}
// Validate not-null fields if specified
if dbQueryNotNull != "" {
notNullFields := strings.Split(dbQueryNotNull, ",")
invalidFields := validateColumns(dbQueryTableName, notNullFields)
if len(invalidFields) > 0 {
fmt.Printf("[!] Error: Invalid not-null field(s) for table '%s': %s\n",
dbQueryTableName, strings.Join(invalidFields, ", "))
fmt.Println("[*] Available columns for this table:")
for i := 0; i < len(availableTables[dbQueryTableName]); i += 5 {
end := i + 5
if end > len(availableTables[dbQueryTableName]) {
end = len(availableTables[dbQueryTableName])
}
fmt.Printf(" %s\n", strings.Join(availableTables[dbQueryTableName][i:end], ", "))
}
return
}
} }
// Determine which table to query based on the tableTypeDBQuery parameter // Determine which table to query based on the tableTypeDBQuery parameter
table := sqlite.GetTable(dbQueryTableName) table := sqlite.GetTable(dbQueryTableName)
if table == sqlite.UnknownTable { if table == sqlite.UnknownTable {
fmt.Printf("Error: Unknown table type '%s'.\n", dbQueryTableName) fmt.Printf("[!] Error: Unknown table type '%s'.\n", dbQueryTableName)
cmd.Help() fmt.Println("[*] Available tables: results, creds, whois, subdomains, history, runs")
fmt.Println("[*] Use --list-all to see all tables and their columns.")
return return
} }
fmt.Println("[*] Querying Database...") fmt.Println("[*] Querying Database...")
tableQuery(table) tableQuery(table)
}, },
@@ -93,6 +263,12 @@ func tableQuery(table sqlite.Table) {
// Get the object for the table // Get the object for the table
object := table.Object() object := table.Object()
// Check if object is nil (invalid table)
if object == nil {
fmt.Printf("[!] Error: Table '%s' is not valid or does not exist.\n", dbQueryTableName)
return
}
// Query the database // Query the database
db := sqlite.GetDB() db := sqlite.GetDB()
query := db.Model(object).Select(columns) query := db.Model(object).Select(columns)
@@ -114,6 +290,7 @@ func tableQuery(table sqlite.Table) {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error executing query: %v\n", err) fmt.Printf("[!] Error executing query: %v\n", err)
return
} }
defer rows.Close() defer rows.Close()
@@ -125,6 +302,7 @@ func tableQuery(table sqlite.Table) {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error getting columns from query: %v\n", err) fmt.Printf("[!] Error getting columns from query: %v\n", err)
return
} }
// Prepare data for pretty.Table // Prepare data for pretty.Table
@@ -144,6 +322,7 @@ func tableQuery(table sqlite.Table) {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error scanning row from query: %v\n", err) fmt.Printf("[!] Error scanning row from query: %v\n", err)
continue
} }
// Convert row values to strings // Convert row values to strings
@@ -202,6 +381,7 @@ func rawDBQuery() {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error executing raw query: %v\n", err) fmt.Printf("[!] Error executing raw query: %v\n", err)
return
} }
defer rows.Close() defer rows.Close()
@@ -212,6 +392,7 @@ func rawDBQuery() {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error getting columns from raw query: %v\n", err) fmt.Printf("[!] Error getting columns from raw query: %v\n", err)
return
} }
// Prepare data for pretty.Table // Prepare data for pretty.Table
@@ -231,6 +412,7 @@ func rawDBQuery() {
zap.Error(err), zap.Error(err),
) )
fmt.Printf("[!] Error scanning row from raw query: %v\n", err) fmt.Printf("[!] Error scanning row from raw query: %v\n", err)
continue
} }
// Convert row values to strings // Convert row values to strings
+8 -8
View File
@@ -85,20 +85,20 @@ func (dh *Dehasher) setQueries() {
// Start starts the querying process // Start starts the querying process
func (dh *Dehasher) Start() { func (dh *Dehasher) Start() {
fmt.Println("[*] Querying Dehashed API...") fmt.Printf("[*] Querying Dehashed API...\n")
for i := 0; i < dh.options.MaxRequests; i++ { for i := 0; i < dh.options.MaxRequests; i++ {
fmt.Printf("\n\t[*] Performing Request...") fmt.Printf(" [*] Performing Request...\n")
count, err := dh.client.Search(*dh.request) count, err := dh.client.Search(*dh.request)
if err != nil { if err != nil {
// Check if it's a DehashError // Check if it's a DehashError
if dhErr, ok := err.(*DehashError); ok { if dhErr, ok := err.(*DehashError); ok {
fmt.Printf("\n\t[!] Dehashed API Error: %s (Code: %d)", dhErr.Message, dhErr.Code) fmt.Printf(" [!] Dehashed API Error: %s (Code: %d)\n", dhErr.Message, dhErr.Code)
zap.L().Error("dehashed_api_error", zap.L().Error("dehashed_api_error",
zap.String("message", dhErr.Message), zap.String("message", dhErr.Message),
zap.Int("code", dhErr.Code), zap.Int("code", dhErr.Code),
) )
} else { } else {
fmt.Printf("\n\t[!] Error performing request: %v", err) fmt.Printf(" [!] Error performing request: %v\n", err)
zap.L().Error("request_error", zap.L().Error("request_error",
zap.String("message", "failed to perform request"), zap.String("message", "failed to perform request"),
zap.Error(err), zap.Error(err),
@@ -108,11 +108,11 @@ func (dh *Dehasher) Start() {
} }
if count < dh.options.MaxRecords { if count < dh.options.MaxRecords {
fmt.Printf("\n\t\t[+] Retrieved %d Records", count) fmt.Printf(" [+] Retrieved %d records\n", count)
fmt.Printf("\n[-] Not Enough Entries, ending queries") fmt.Printf(" [-] Not enough entries, ending queries\n")
break break
} else { } else {
fmt.Printf("\n\t\t[+] Retrieved %d Records", dh.options.MaxRecords) fmt.Printf(" [+] Retrieved %d records\n", dh.options.MaxRecords)
} }
dh.request.Page = dh.getNextPage() dh.request.Page = dh.getNextPage()
@@ -171,7 +171,7 @@ func (dh *Dehasher) parseResults() {
zap.L().Info("extracting_credentials") zap.L().Info("extracting_credentials")
results := dh.client.GetResults() results := dh.client.GetResults()
creds := results.ExtractCredentials() creds := results.ExtractCredentials()
fmt.Printf("\n\t[*] Discovered %d Credentials", len(creds)) fmt.Printf("\n\t[+] Discovered %d Credentials", len(creds))
err := sqlite.StoreCreds(creds) err := sqlite.StoreCreds(creds)
if err != nil { if err != nil {
zap.L().Error("store_creds", zap.L().Error("store_creds",