Skip to content

Commit

Permalink
adding postgres detector, and also changing our default username and …
Browse files Browse the repository at this point in the history
…password match
  • Loading branch information
Chair authored and Chair committed Nov 10, 2023
1 parent 76a0468 commit bdc8b2d
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 8 deletions.
5 changes: 3 additions & 2 deletions pkg/common/patterns.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type RegexState struct {
compiledRegex *regexp.Regexp
}


// Custom Regex functions
func BuildRegex(pattern string, specialChar string, length int) string {
return fmt.Sprintf(`\b([%s%s]{%s})\b`, pattern, specialChar, strconv.Itoa(length))
Expand Down Expand Up @@ -58,14 +59,14 @@ func (r RegexState) Matches(data []byte) []string {

// UsernameRegexCheck constructs an username usernameRegex pattern from a given pattern of excluded characters.
func UsernameRegexCheck(pattern string) RegexState {
raw := fmt.Sprintf(`(?im)(?:user|usr)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:%+v]{4,40})\b`, pattern)
raw := fmt.Sprintf(`(?im)(?:user|usr)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:%+v'"\s]{4,40})`, pattern)

return RegexState{regexp.MustCompile(raw)}
}

// PasswordRegexCheck constructs an username usernameRegex pattern from a given pattern of excluded characters.
func PasswordRegexCheck(pattern string) RegexState {
raw := fmt.Sprintf(`(?im)(?:pass|password)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:%+v]{4,40})`, pattern)
raw := fmt.Sprintf(`(?im)(?:pass)\S{0,40}?[:=\s]{1,3}[ '"=]{0,1}([^:%+v'"\s]{4,40})`, pattern)

return RegexState{regexp.MustCompile(raw)}
}
170 changes: 170 additions & 0 deletions pkg/detectors/postgres/postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package postgres

import (
"context"
"database/sql"
"net/url"
"regexp"
"strings"
"fmt"
"time"

"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)

type Scanner struct{}

var _ detectors.Detector = (*Scanner)(nil)

var (
// URI pattern for PostgreSQL connection string
uriPat = regexp.MustCompile(`\b(?i)postgresql://[\S]+\b`)

// Separate patterns for username, password, and hostname
hostnamePat = regexp.MustCompile(`(?i)(?:host|server).{0,40}?(\b[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\b)`)


// You might want to customize these patterns based on common practices in your codebases
)

func (s Scanner) Keywords() []string {
return []string{"postgres", "psql", "pghost"}
}

func verifyPostgres(pgURL *url.URL) error {
// Extract the necessary components
username := ""
password := ""
if pgURL.User != nil {
username = pgURL.User.Username()
password, _ = pgURL.User.Password()
}
hostname := pgURL.Hostname()

// Handle custom port
port := pgURL.Port()
if port == "" {
port = "5432" // Default PostgreSQL port
}

// Handle SSL mode
sslmode := "disable" // Default to disable
queryParams := pgURL.Query()
if sslQuery, ok := queryParams["sslmode"]; ok && len(sslQuery) > 0 {
sslmode = sslQuery[0]
}

// Construct the PostgreSQL connection string
connStr := fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=%s", username, password, hostname, port, sslmode)

// Open a connection to the database
db, err := sql.Open("postgres", connStr)
if err != nil {
return err
}
defer db.Close()

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()


// Try to establish a connection
err = db.PingContext(ctx)
if err != nil {
return err
}

// If we reach here, the credentials are valid
return nil
}

func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)

// Check for inline connection strings
uriMatches := uriPat.FindAllString(dataStr, -1)
for _, uri := range uriMatches {
pgURL, err := url.Parse(uri)
if err != nil {
continue
}

// PostgreSQL URLs might not always have the userinfo (username:password) part
if pgURL.User != nil {
username := pgURL.User.Username()
password, _ := pgURL.User.Password()
hostname := pgURL.Hostname()
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Postgres,
Raw: []byte(strings.Join([]string{hostname, username, password}, "\t")),
}
if verify {
verificationErr:= verifyPostgres(pgURL)
s1.Verified = verificationErr == nil
}
results = append(results, s1)
}
}

// Check for separate components
usernameRegexState := common.UsernameRegexCheck("")
usernameMatches := usernameRegexState.Matches(data)

passwordRegexState := common.PasswordRegexCheck("") // No explicit character exclusions by Snowflake for passwords
passwordMatches := passwordRegexState.Matches(data)
hostnameMatches := hostnamePat.FindAllStringSubmatch(dataStr, -1)

// Combine the separate components into potential credentials
for _, username := range usernameMatches {
if len(username) < 2 {
continue
}
for _, hostname := range hostnameMatches {
if len(hostname) < 2 {
continue
}
result := false
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Postgres,
}
for _, password := range passwordMatches {
if len(password) < 2 {
continue
}

// Since we're combining these, we should probably also ensure that the total length does not exceed the 255 character limit for hostnames
combinedLength := len(username) + len(password) + len(hostname[1])
if combinedLength > 255 {
continue // Skip if the combined length is too long
}
s1.Raw = []byte(strings.Join([]string{hostname[1], username, password}, "\t"))
result = true
postgresURL := url.URL{
Scheme: "postgresql",
User: url.UserPassword(username, password),
Host: fmt.Sprintf("%s:%s", hostname[1], "5432"),
}
if verify {
verificationErr:= verifyPostgres(&postgresURL)
s1.Verified = verificationErr == nil
break
}
}
if result {
results = append(results, s1)
}
}
}


// Verification could be done here if necessary

return results, nil
}

func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Postgres
}

2 changes: 2 additions & 0 deletions pkg/engine/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/positionstack"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postageapp"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postbacks"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postgres"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/posthog"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postman"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/postmark"
Expand Down Expand Up @@ -1491,6 +1492,7 @@ func DefaultDetectors() []detectors.Detector {
speechtextai.Scanner{},
databox.Scanner{},
postbacks.Scanner{},
postgres.Scanner{},
collect2.Scanner{},
uclassify.Scanner{},
holistic.Scanner{},
Expand Down
16 changes: 10 additions & 6 deletions pkg/pb/detectorspb/detectors.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions proto/detectors.proto
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,7 @@ enum DetectorType {
Overloop = 965;
Ngrok = 966;
Replicate = 967;
Postgres = 968;
}

message Result {
Expand Down

0 comments on commit bdc8b2d

Please sign in to comment.