Path: blob/dev/pkg/js/libs/postgres/postgres.go
2070 views
package postgres12import (3"context"4"database/sql"5"fmt"6"net"7"strings"8"time"910"github.com/go-pg/pg"11"github.com/praetorian-inc/fingerprintx/pkg/plugins"12postgres "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/postgresql"13utils "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"14"github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap" //nolint:staticcheck // need to call init15_ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap" //nolint:staticcheck16"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"17)1819type (20// PGClient is a client for Postgres database.21// Internally client uses go-pg/pg driver.22// @example23// ```javascript24// const postgres = require('nuclei/postgres');25// const client = new postgres.PGClient;26// ```27PGClient struct{}28)2930// IsPostgres checks if the given host and port are running Postgres database.31// If connection is successful, it returns true.32// If connection is unsuccessful, it returns false and error.33// @example34// ```javascript35// const postgres = require('nuclei/postgres');36// const isPostgres = postgres.IsPostgres('acme.com', 5432);37// ```38func (c *PGClient) IsPostgres(ctx context.Context, host string, port int) (bool, error) {39executionId := ctx.Value("executionId").(string)40// todo: why this is exposed? Service fingerprint should be automatic41return memoizedisPostgres(executionId, host, port)42}4344// @memo45func isPostgres(executionId string, host string, port int) (bool, error) {46timeout := 10 * time.Second4748dialer := protocolstate.GetDialersWithId(executionId)49if dialer == nil {50return false, fmt.Errorf("dialers not initialized for %s", executionId)51}5253conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))54if err != nil {55return false, err56}57defer func() {58_ = conn.Close()59}()6061_ = conn.SetDeadline(time.Now().Add(timeout))6263plugin := &postgres.POSTGRESPlugin{}64service, err := plugin.Run(conn, timeout, plugins.Target{Host: host})65if err != nil {66return false, err67}68if service == nil {69return false, nil70}71return true, nil72}7374// Connect connects to Postgres database using given credentials.75// If connection is successful, it returns true.76// If connection is unsuccessful, it returns false and error.77// The connection is closed after the function returns.78// @example79// ```javascript80// const postgres = require('nuclei/postgres');81// const client = new postgres.PGClient;82// const connected = client.Connect('acme.com', 5432, 'username', 'password');83// ```84func (c *PGClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {85ok, err := c.IsPostgres(ctx, host, port)86if err != nil {87return false, err88}89if !ok {90return false, fmt.Errorf("not a postgres service")91}92executionId := ctx.Value("executionId").(string)93return memoizedconnect(executionId, host, port, username, password, "postgres")94}9596// ExecuteQuery connects to Postgres database using given credentials and database name.97// and executes a query on the db.98// If connection is successful, it returns the result of the query.99// @example100// ```javascript101// const postgres = require('nuclei/postgres');102// const client = new postgres.PGClient;103// const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');104// log(to_json(result));105// ```106func (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {107ok, err := c.IsPostgres(ctx, host, port)108if err != nil {109return nil, err110}111if !ok {112return nil, fmt.Errorf("not a postgres service")113}114115executionId := ctx.Value("executionId").(string)116117return memoizedexecuteQuery(executionId, host, port, username, password, dbName, query)118}119120// @memo121func executeQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {122if !protocolstate.IsHostAllowed(executionId, host) {123// host is not valid according to network policy124return nil, protocolstate.ErrHostDenied.Msgf(host)125}126127target := net.JoinHostPort(host, fmt.Sprintf("%d", port))128129connStr := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable&executionId=%s", username, password, target, dbName, executionId)130db, err := sql.Open(pgwrap.PGWrapDriver, connStr)131if err != nil {132return nil, err133}134defer func() {135_ = db.Close()136}()137138rows, err := db.Query(query)139if err != nil {140return nil, err141}142resp, err := utils.UnmarshalSQLRows(rows)143if err != nil {144return nil, err145}146return resp, nil147}148149// ConnectWithDB connects to Postgres database using given credentials and database name.150// If connection is successful, it returns true.151// If connection is unsuccessful, it returns false and error.152// The connection is closed after the function returns.153// @example154// ```javascript155// const postgres = require('nuclei/postgres');156// const client = new postgres.PGClient;157// const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');158// ```159func (c *PGClient) ConnectWithDB(ctx context.Context, host string, port int, username, password, dbName string) (bool, error) {160ok, err := c.IsPostgres(ctx, host, port)161if err != nil {162return false, err163}164if !ok {165return false, fmt.Errorf("not a postgres service")166}167168executionId := ctx.Value("executionId").(string)169170return memoizedconnect(executionId, host, port, username, password, dbName)171}172173// @memo174func connect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {175if host == "" || port <= 0 {176return false, fmt.Errorf("invalid host or port")177}178179if !protocolstate.IsHostAllowed(executionId, host) {180// host is not valid according to network policy181return false, protocolstate.ErrHostDenied.Msgf(host)182}183184target := net.JoinHostPort(host, fmt.Sprintf("%d", port))185186ctx, cancel := context.WithCancel(context.Background())187defer cancel()188189dialer := protocolstate.GetDialersWithId(executionId)190if dialer == nil {191return false, fmt.Errorf("dialers not initialized for %s", executionId)192}193194db := pg.Connect(&pg.Options{195Addr: target,196User: username,197Password: password,198Database: dbName,199Dialer: func(network, addr string) (net.Conn, error) {200return dialer.Fastdialer.Dial(context.Background(), network, addr)201},202IdleCheckFrequency: -1,203}).WithContext(ctx).WithTimeout(10 * time.Second)204defer func() {205_ = db.Close()206}()207208_, err := db.Exec("select 1")209if err != nil {210switch true {211case strings.Contains(err.Error(), "connect: connection refused"):212fallthrough213case strings.Contains(err.Error(), "no pg_hba.conf entry for host"):214fallthrough215case strings.Contains(err.Error(), "network unreachable"):216fallthrough217case strings.Contains(err.Error(), "reset"):218fallthrough219case strings.Contains(err.Error(), "i/o timeout"):220return false, err221}222return false, nil223}224return true, nil225}226227228