diff --git a/pkg/detectors/jdbc/jdbc.go b/pkg/detectors/jdbc/jdbc.go index a424d7072..7a27c8320 100644 --- a/pkg/detectors/jdbc/jdbc.go +++ b/pkg/detectors/jdbc/jdbc.go @@ -50,7 +50,8 @@ var _ detectors.Detector = (*Scanner)(nil) var _ detectors.CustomFalsePositiveChecker = (*Scanner)(nil) var ( - keyPat = regexp.MustCompile(`(?i)jdbc:[\w]{3,10}:[^\s"'<>,(){}[\]&]{10,512}`) + // Matches typical JDBC connection strings amd ingores any special character at the end + keyPat = regexp.MustCompile(`(?i)jdbc:[\w]{3,10}:[^\s"'<>,{}[\]]{10,511}[A-Za-z0-9]`) ) // Keywords are used for efficiently pre-filtering chunks. diff --git a/pkg/detectors/jdbc/jdbc_integration_test.go b/pkg/detectors/jdbc/jdbc_integration_test.go index c525b1cbe..f1a390029 100644 --- a/pkg/detectors/jdbc/jdbc_integration_test.go +++ b/pkg/detectors/jdbc/jdbc_integration_test.go @@ -29,8 +29,8 @@ func TestJdbcVerified(t *testing.T) { postgresUser := gofakeit.Username() postgresPass := gofakeit.Password(true, true, true, false, false, 10) postgresDB := gofakeit.Word() - postgresContainer, err := postgres.RunContainer(ctx, - testcontainers.WithImage("postgres:13-alpine"), + postgresContainer, err := postgres.Run(ctx, + "postgres:13-alpine", postgres.WithDatabase(postgresDB), postgres.WithUsername(postgresUser), postgres.WithPassword(postgresPass), @@ -56,8 +56,8 @@ func TestJdbcVerified(t *testing.T) { mysqlUser := gofakeit.Username() mysqlPass := gofakeit.Password(true, true, true, false, false, 10) mysqlDatabase := gofakeit.Word() - mysqlC, err := mysql.RunContainer(ctx, - mysql.WithDatabase(mysqlDatabase), + mysqlC, err := mysql.Run(ctx, + "mysql:8.0.36", mysql.WithUsername(mysqlUser), mysql.WithPassword(mysqlPass), ) @@ -79,8 +79,8 @@ func TestJdbcVerified(t *testing.T) { sqlServerPass := gofakeit.Password(true, true, true, false, false, 10) sqlServerDatabase := "master" - mssqlContainer, err := mssql.RunContainer(ctx, - testcontainers.WithImage("mcr.microsoft.com/azure-sql-edge"), + mssqlContainer, err := mssql.Run(ctx, + "mcr.microsoft.com/azure-sql-edge", mssql.WithAcceptEULA(), mssql.WithPassword(sqlServerPass), ) @@ -125,6 +125,10 @@ func TestJdbcVerified(t *testing.T) { Verified: true, Redacted: fmt.Sprintf("jdbc:postgresql://%s:%s/%s?sslmode=disable&password=%s&user=%s", postgresHost, postgresPort.Port(), postgresDB, strings.Repeat("*", len(postgresPass)), postgresUser), + AnalysisInfo: map[string]string{ + "connection_string": fmt.Sprintf("jdbc:postgresql://%s:%s/%s?sslmode=disable&password=%s&user=%s", + postgresHost, postgresPort.Port(), postgresDB, postgresPass, postgresUser), + }, }, }, wantErr: false, @@ -143,6 +147,10 @@ func TestJdbcVerified(t *testing.T) { Verified: true, Redacted: fmt.Sprintf(`jdbc:mysql://%s:%s@tcp(%s:%s)/%s`, mysqlUser, strings.Repeat("*", len(mysqlPass)), mysqlHost, mysqlPort.Port(), mysqlDatabase), + AnalysisInfo: map[string]string{ + "connection_string": fmt.Sprintf(`jdbc:mysql://%s:%s@tcp(%s:%s)/%s`, + mysqlUser, mysqlPass, mysqlHost, mysqlPort.Port(), mysqlDatabase), + }, }, }, wantErr: false, @@ -161,6 +169,10 @@ func TestJdbcVerified(t *testing.T) { Verified: true, Redacted: fmt.Sprintf("jdbc:sqlserver://odbc:server=%s;port=%s;database=%s;password=%s", sqlServerHost, sqlServerPort.Port(), sqlServerDatabase, strings.Repeat("*", len(sqlServerPass))), + AnalysisInfo: map[string]string{ + "connection_string": fmt.Sprintf("jdbc:sqlserver://odbc:server=%s;port=%s;database=%s;password=%s", + sqlServerHost, sqlServerPort.Port(), sqlServerDatabase, sqlServerPass), + }, }, }, wantErr: false, diff --git a/pkg/detectors/jdbc/jdbc_test.go b/pkg/detectors/jdbc/jdbc_test.go index 14649c7ff..2bb3c642f 100644 --- a/pkg/detectors/jdbc/jdbc_test.go +++ b/pkg/detectors/jdbc/jdbc_test.go @@ -40,6 +40,8 @@ func TestJdbc_Pattern(t *testing.T) { jdbc:mysql:localhost:3306/mydatabase jdbc:sqlserver://x.x.x.x:1433;databaseName=MY-DB;user=MY-USER;password=MY-PASSWORD;encrypt=false jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks + (jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb) + jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser& $ProjectFileDir$ @@ -50,6 +52,8 @@ func TestJdbc_Pattern(t *testing.T) { "jdbc:mysql:localhost:3306/mydatabase", "jdbc:sqlserver://x.x.x.x:1433;databaseName=MY-DB;user=MY-USER;password=MY-PASSWORD;encrypt=false", "jdbc:sqlserver://localhost:1433;databaseName=AdventureWorks", + "jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb", + "jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser", }, }, { @@ -64,8 +68,10 @@ func TestJdbc_Pattern(t *testing.T) { "jdbc:oracle:thin:@host:1521:db", "jdbc:mysql://host:3306/db,other_param", "jdbc:db2://host:50000/db?param=1" - ] - }`, + "jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser" + "jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb" + ] + }`, want: []string{ "jdbc:postgresql://localhost:5432/mydb", "jdbc:mysql://user:pass@host:3306/db?param=1", @@ -73,6 +79,8 @@ func TestJdbc_Pattern(t *testing.T) { "jdbc:oracle:thin:@host:1521:db", "jdbc:mysql://host:3306/db", "jdbc:db2://host:50000/db?param=1", + "jdbc:postgresql://localhost:1521/testdb?sslmode=disable&password=testpassword&user=testuser", + "jdbc:mysql://testuser:testpassword@tcp(localhost:1521)/testdb", }, }, { diff --git a/pkg/detectors/jdbc/mysql_integration_test.go b/pkg/detectors/jdbc/mysql_integration_test.go index 602065c4c..1861c3e5d 100644 --- a/pkg/detectors/jdbc/mysql_integration_test.go +++ b/pkg/detectors/jdbc/mysql_integration_test.go @@ -23,7 +23,8 @@ func TestMySQL(t *testing.T) { ctx := context.Background() - mysqlC, err := mysql.RunContainer(ctx, + mysqlC, err := mysql.Run(ctx, + "mysql:8.0.36", mysql.WithDatabase(mysqlDatabase), mysql.WithUsername(mysqlUser), mysql.WithPassword(mysqlPass), diff --git a/pkg/detectors/jdbc/postgres_integration_test.go b/pkg/detectors/jdbc/postgres_integration_test.go index 0aeb20889..f3fcb57be 100644 --- a/pkg/detectors/jdbc/postgres_integration_test.go +++ b/pkg/detectors/jdbc/postgres_integration_test.go @@ -6,7 +6,6 @@ package jdbc import ( "context" "fmt" - "log" "testing" "time" @@ -34,8 +33,8 @@ func TestPostgres(t *testing.T) { t.Log("dbName: ", dbName) ctx := context.Background() - postgresContainer, err := postgres.RunContainer(ctx, - testcontainers.WithImage("postgres:13-alpine"), + postgresContainer, err := postgres.Run(ctx, + "postgres:13-alpine", postgres.WithDatabase(dbName), postgres.WithUsername(user), postgres.WithPassword(pass), @@ -56,10 +55,6 @@ func TestPostgres(t *testing.T) { if err != nil { t.Fatal(err) } - - if err != nil { - log.Fatalf("failed to start container: %s", err) - } defer postgresContainer.Terminate(ctx) tests := []struct { diff --git a/pkg/detectors/jdbc/sqlserver.go b/pkg/detectors/jdbc/sqlserver.go index 4633e92a0..fc6ac1a91 100644 --- a/pkg/detectors/jdbc/sqlserver.go +++ b/pkg/detectors/jdbc/sqlserver.go @@ -73,6 +73,11 @@ func parseSqlServer(ctx logContext.Context, subname string) (JDBC, error) { continue } + // incase there is a bridge between jdbc and some driver like "odbc", and conn string looks like this odbc:server + if split := strings.Split(key, ":"); len(split) > 1 { + key = split[1] + } + switch strings.ToLower(key) { case "password", "spring.datasource.password", "pwd": password = value diff --git a/pkg/detectors/jdbc/sqlserver_integration_test.go b/pkg/detectors/jdbc/sqlserver_integration_test.go index 684445ed9..d8aa37b57 100644 --- a/pkg/detectors/jdbc/sqlserver_integration_test.go +++ b/pkg/detectors/jdbc/sqlserver_integration_test.go @@ -10,8 +10,8 @@ import ( "github.com/brianvoe/gofakeit/v7" "github.com/stretchr/testify/assert" - "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mssql" + logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context" ) func TestSqlServer(t *testing.T) { @@ -21,8 +21,8 @@ func TestSqlServer(t *testing.T) { sqlServerPass := gofakeit.Password(true, true, true, false, false, 10) sqlServerDB := "master" - mssqlContainer, err := mssql.RunContainer(ctx, - testcontainers.WithImage("mcr.microsoft.com/azure-sql-edge"), + mssqlContainer, err := mssql.Run(ctx, + "mcr.microsoft.com/azure-sql-edge", mssql.WithAcceptEULA(), mssql.WithPassword(sqlServerPass), ) @@ -61,7 +61,7 @@ func TestSqlServer(t *testing.T) { want: result{pingOk: true, pingDeterminate: true}, }, { - input: "//server=badhost;user id=sa;database=master;password=", + input: fmt.Sprintf("//server=badhost;user id=sa;database=master;password=%s", sqlServerPass), want: result{pingOk: false, pingDeterminate: false}, }, { @@ -81,7 +81,8 @@ func TestSqlServer(t *testing.T) { } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { - j, err := parseSqlServer(tt.input) + ctx := logContext.AddLogger(context.Background()) + j, err := ParseSqlServer(ctx, tt.input) if err != nil { got := result{parseErr: true} diff --git a/pkg/detectors/jdbc/sqlserver_test.go b/pkg/detectors/jdbc/sqlserver_test.go index 32bfbae23..d2f09447d 100644 --- a/pkg/detectors/jdbc/sqlserver_test.go +++ b/pkg/detectors/jdbc/sqlserver_test.go @@ -118,6 +118,42 @@ func TestParseSqlServerUserIgnoredBug2(t *testing.T) { } } +func TestParseSqlServerWithJdbcAndOdbcBridgeString(t *testing.T) { + subname := "//odbc:server=localhost;port=1433;database=testdb;password=testpassword" + + wantHost := "localhost" + wantPort := "1433" + wantPassword := "testpassword" + wantDatabase := "testdb" + + ctx := logContext.AddLogger(context.Background()) + + j, err := parseSqlServer(ctx, subname) + if err != nil { + t.Fatalf("parseSqlServer() error = %v", err) + } + + if j == nil { + t.Fatalf("parseSqlServer() returned nil, expected valid connection.") + } + + sqlServerConn, ok := j.(*SqlServerJDBC) + if !ok { + t.Fatalf("parseSqlServer() returned unexpected type %T, expected *SqlServerJDBC", j) + } + + if sqlServerConn.Host != wantHost+":"+wantPort { + t.Errorf("Host mismatch. Got: %s, Want: %s", sqlServerConn.Host, wantHost+":"+wantPort) + } + + if sqlServerConn.Password != wantPassword { + t.Errorf("Password mismatch. Got: %s, Want: %s", sqlServerConn.Password, wantPassword) + } + + if sqlServerConn.Database != wantDatabase { + t.Errorf("Database mismatch. Got: %s, Want: %s", sqlServerConn.Database, wantDatabase) + } +} func TestSQLServerHandler_ParseJDBCURL(t *testing.T) { tests := []struct { name string