mirror of
https://github.com/projectdiscovery/nuclei
synced 2026-06-08 16:50:47 +00:00
3537030e1f
* fix(js): prevent pool slot starvation under load Zombie goroutines from timed-out JS executions held pool slots indefinitely: Add() blocked with context.Background(), and defer Done() only ran when the goroutine eventually completed. Under load, both pools (80 pooled + 20 non-pooled slots) filled with zombies, silently dropping all subsequent matches. Three changes fix slot lifecycle management: 1. Propagate the 20s deadline context into ExecuteProgram (compiler.go) so both execution paths can respect the deadline. 2. Replace Add() with AddWithContext(ctx) in both pool.go and non-pool.go so goroutines waiting for a slot fail fast when the deadline expires instead of blocking indefinitely. 3. Add a watchdog goroutine that releases the slot when the deadline expires, even if the zombie is still running. An atomic.Bool ensures exactly one Done() call between the watchdog and the normal defer path. * adding context * refactor(js): derived the ctx to remaining tractable deadline leaks (#7302) * refactor(js): use `context.Background` as default instead Signed-off-by: Dwi Siswanto <git@dw1.io> * refactor(js): derived the ctx to remaining tractable deadline leaks Signed-off-by: Dwi Siswanto <git@dw1.io> * test(js): add `NucleiJS.Context` tests Signed-off-by: Dwi Siswanto <git@dw1.io> * fix(cmd): context param exclusion in memoization hash The memoization template condition for excluding context parameters from hash keys was incorrect. The memoize package represents context.Context types as "&{context Context}" (AST string representation), not "context.Context". Signed-off-by: Dwi Siswanto <git@dw1.io> * chore(js): memogen'ed Signed-off-by: Dwi Siswanto <git@dw1.io> --------- Signed-off-by: Dwi Siswanto <git@dw1.io> * fix(js): hangs in checkRDPEncryption by bounding socket I/O Add `setConnDeadlineFromContext` helper to set deadlines on conns derived from context timeouts. Move conn cleanup out of loop-scoped defers to make sure immediate cleanup per probe attempt. Signed-off-by: Dwi Siswanto <git@dw1.io> --------- Signed-off-by: Dwi Siswanto <git@dw1.io> Co-authored-by: Niek den Breeje <AuditeMarlow@users.noreply.github.com> Co-authored-by: Dwi Siswanto <25837540+dwisiswant0@users.noreply.github.com> Co-authored-by: Dwi Siswanto <git@dw1.io>
205 lines
5.6 KiB
Go
205 lines
5.6 KiB
Go
package oracle
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
|
|
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/oracledb"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
|
goora "github.com/sijms/go-ora/v2"
|
|
)
|
|
|
|
type (
|
|
// IsOracleResponse is the response from the IsOracle function.
|
|
// this is returned by IsOracle function.
|
|
// @example
|
|
// ```javascript
|
|
// const oracle = require('nuclei/oracle');
|
|
// const isOracle = oracle.IsOracle('acme.com', 1521);
|
|
// ```
|
|
IsOracleResponse struct {
|
|
IsOracle bool
|
|
Banner string
|
|
}
|
|
// Client is a client for Oracle database.
|
|
// Internally client uses oracle/godror driver.
|
|
// @example
|
|
// ```javascript
|
|
// const oracle = require('nuclei/oracle');
|
|
// const client = new oracle.OracleClient();
|
|
// ```
|
|
OracleClient struct {
|
|
connector *goora.OracleConnector
|
|
}
|
|
)
|
|
|
|
// IsOracle checks if a host is running an Oracle server
|
|
// @example
|
|
// ```javascript
|
|
// const oracle = require('nuclei/oracle');
|
|
// const isOracle = oracle.IsOracle('acme.com', 1521);
|
|
// log(toJSON(isOracle));
|
|
// ```
|
|
func (c *OracleClient) IsOracle(ctx context.Context, host string, port int) (IsOracleResponse, error) {
|
|
executionId := ctx.Value("executionId").(string)
|
|
return memoizedisOracle(ctx, executionId, host, port)
|
|
}
|
|
|
|
// @memo
|
|
func isOracle(ctx context.Context, executionId string, host string, port int) (IsOracleResponse, error) {
|
|
resp := IsOracleResponse{}
|
|
|
|
dialer := protocolstate.GetDialersWithId(executionId)
|
|
if dialer == nil {
|
|
return IsOracleResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
|
|
}
|
|
|
|
timeout := 5 * time.Second
|
|
conn, err := dialer.Fastdialer.Dial(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
defer func() {
|
|
_ = conn.Close()
|
|
}()
|
|
|
|
oracledbPlugin := oracledb.ORACLEPlugin{}
|
|
service, err := oracledbPlugin.Run(conn, timeout, plugins.Target{Host: host})
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
if service == nil {
|
|
return resp, nil
|
|
}
|
|
resp.Banner = service.Version
|
|
resp.Banner = service.Metadata().(plugins.ServiceOracle).Info
|
|
resp.IsOracle = true
|
|
return resp, nil
|
|
}
|
|
|
|
func (c *OracleClient) oracleDbInstance(ctx context.Context, connStr string, executionId string) (*goora.OracleConnector, error) {
|
|
if c.connector == nil {
|
|
connector := goora.NewConnector(connStr)
|
|
oraConnector, ok := connector.(*goora.OracleConnector)
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to cast connector to OracleConnector")
|
|
}
|
|
c.connector = oraConnector
|
|
}
|
|
|
|
// Refresh the dialer on every call so the connector uses the current
|
|
// execution context instead of a stale or already-canceled one.
|
|
c.connector.Dialer(&oracleCustomDialer{executionId: executionId, ctx: ctx})
|
|
|
|
return c.connector, nil
|
|
}
|
|
|
|
// Connect connects to an Oracle database
|
|
// @example
|
|
// ```javascript
|
|
// const oracle = require('nuclei/oracle');
|
|
// const client = new oracle.OracleClient;
|
|
// client.Connect('acme.com', 1521, 'XE', 'user', 'password');
|
|
// ```
|
|
func (c *OracleClient) Connect(ctx context.Context, host string, port int, serviceName string, username string, password string) (bool, error) {
|
|
connStr := goora.BuildUrl(host, port, serviceName, username, password, nil)
|
|
|
|
return c.ConnectWithDSN(ctx, connStr)
|
|
}
|
|
|
|
func (c *OracleClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {
|
|
executionId := ctx.Value("executionId").(string)
|
|
|
|
connector, err := c.oracleDbInstance(ctx, dsn, executionId)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
db := sql.OpenDB(connector)
|
|
defer func() {
|
|
_ = db.Close()
|
|
}()
|
|
|
|
db.SetMaxOpenConns(1)
|
|
db.SetMaxIdleConns(0)
|
|
|
|
// Test the connection
|
|
err = db.PingContext(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// ExecuteQuery connects to MS SQL database using given credentials and executes a query.
|
|
// It returns the results of the query or an error if something goes wrong.
|
|
// @example
|
|
// ```javascript
|
|
// const oracle = require('nuclei/oracle');
|
|
// const client = new oracle.OracleClient;
|
|
// const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT @@version');
|
|
// log(to_json(result));
|
|
// ```
|
|
func (c *OracleClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {
|
|
if host == "" || port <= 0 {
|
|
return nil, fmt.Errorf("invalid host or port")
|
|
}
|
|
|
|
isOracleResp, err := c.IsOracle(ctx, host, port)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !isOracleResp.IsOracle {
|
|
return nil, fmt.Errorf("not a oracle service")
|
|
}
|
|
|
|
connStr := goora.BuildUrl(host, port, dbName, username, password, nil)
|
|
|
|
return c.ExecuteQueryWithDSN(ctx, connStr, query)
|
|
}
|
|
|
|
// ExecuteQueryWithDSN executes a query on an Oracle database using a DSN
|
|
// @example
|
|
// ```javascript
|
|
// const oracle = require('nuclei/oracle');
|
|
// const client = new oracle.OracleClient;
|
|
// const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');
|
|
// log(to_json(result));
|
|
// ```
|
|
func (c *OracleClient) ExecuteQueryWithDSN(ctx context.Context, dsn string, query string) (*utils.SQLResult, error) {
|
|
executionId := ctx.Value("executionId").(string)
|
|
|
|
connector, err := c.oracleDbInstance(ctx, dsn, executionId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db := sql.OpenDB(connector)
|
|
defer func() {
|
|
_ = db.Close()
|
|
}()
|
|
|
|
db.SetMaxOpenConns(1)
|
|
db.SetMaxIdleConns(0)
|
|
|
|
rows, err := db.QueryContext(ctx, query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data, err := utils.UnmarshalSQLRows(rows)
|
|
if err != nil {
|
|
if data != nil && len(data.Rows) > 0 {
|
|
return data, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return data, nil
|
|
}
|