Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions cmd/integration-test/javascript.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ var jsTestcases = []TestCaseInfo{
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
{Path: "protocols/javascript/oracle-auth-test.yaml", TestCase: &javascriptOracleAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}},
}

var (
redisResource *dockertest.Resource
sshResource *dockertest.Resource
oracleResource *dockertest.Resource
vncResource *dockertest.Resource
pool *dockertest.Pool
defaultRetry = 3
)
Expand Down Expand Up @@ -110,6 +112,39 @@ func (j *javascriptOracleAuthTest) Execute(filePath string) error {
tempPort := oracleResource.GetPort("1521/tcp")
finalURL := "localhost:" + tempPort
defer purge(oracleResource)

errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
//let ssh server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}

type javascriptVncPassBrute struct{}

func (j *javascriptVncPassBrute) Execute(filePath string) error {
if vncResource == nil || pool == nil {
// skip test as vnc is not running
return nil
}
tempPort := vncResource.GetPort("5900/tcp")
finalURL := "localhost:" + tempPort
defer purge(vncResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
Expand Down Expand Up @@ -216,4 +251,22 @@ func init() {
if err := oracleResource.Expire(30); err != nil {
log.Printf("Could not expire Oracle resource: %s", err)
}

// setup a temporary vnc server
vncResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "dorowu/ubuntu-desktop-lxde-vnc",
Tag: "latest",
Env: []string{
"VNC_PASSWORD=mysecret",
},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start resource: %s", err)
return
}
// by default expire after 30 sec
if err := vncResource.Expire(30); err != nil {
log.Printf("Could not expire resource: %s", err)
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ require (
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/alexsnet/go-vnc v0.1.0 // indirect
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Make go-vnc a direct dependency and tidy the module.

The package is imported directly in pkg/js/libs/vnc/vnc.go, so it should not be marked as indirect. This can cause drift and tooling confusion.

Apply this minimal change and then run go mod tidy:

-	github.com/alexsnet/go-vnc v0.1.0 // indirect
+	github.com/alexsnet/go-vnc v0.1.0

Run the following to verify and clean up:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
go mod tidy -v
go mod why -m github.com/alexsnet/go-vnc || true

Length of output: 24910


Remove indirect marker for github.com/alexsnet/go-vnc and tidy module

The output of your verification confirms that pkg/js/libs/vnc/vnc.go imports github.com/alexsnet/go-vnc directly:

# github.com/alexsnet/go-vnc
github.com/projectdiscovery/nuclei/v3/pkg/js/libs/vnc
github.com/alexsnet/go-vnc

Accordingly, update your go.mod:

-	github.com/alexsnet/go-vnc v0.1.0 // indirect
+	github.com/alexsnet/go-vnc v0.1.0

Then run:

go mod tidy

to ensure the module file is clean and accurate.

🤖 Prompt for AI Agents
In go.mod around line 147, the dependency github.com/alexsnet/go-vnc is marked
as // indirect but is imported directly by pkg/js/libs/vnc/vnc.go; remove the
indirect marker by making the dependency direct (e.g., run go get
github.com/alexsnet/go-vnc@v0.1.0 or update the requirement entry so it is not
commented as indirect) and then run go mod tidy to clean up go.mod/go.sum and
commit the updated files.

github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAu
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alexsnet/go-vnc v0.1.0 h1:vBCwPNy79WEL8V/Z5A0ngEFCvTWBAjmS048lkR2rdmY=
github.com/alexsnet/go-vnc v0.1.0/go.mod h1:bbRsg41Sh3zvrnWsw+REKJVGZd8Of2+S0V1G0ZaBhlU=
github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=
github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
Expand Down
38 changes: 38 additions & 0 deletions integration_tests/protocols/javascript/vnc-pass-brute.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
id: vnc-password-test

info:
name: VNC Password Authentication Test
author: pdteam
severity: high
description: |
Tests VNC authentication with correct and incorrect passwords.
metadata:
shodan-query: product:"vnc"
tags: js,network,vnc,authentication

javascript:
- pre-condition: |
isPortOpen(Host,Port)

code: |
let vnc = require('nuclei/vnc');
let client = new vnc.VNCClient();
client.Connect(Host, Port, Password);

args:
Host: "{{Host}}"
Port: "5900"
Password: "{{passwords}}"
payloads:
passwords:
- ""
- root
- password
- admin
- mysecret
stop-at-first-match: true

matchers:
- type: dsl
dsl:
- "success == true"
1 change: 1 addition & 0 deletions pkg/js/generated/go/libvnc/vnc.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func init() {

// Objects / Classes
"IsVNCResponse": gojs.GetClassConstructor[lib_vnc.IsVNCResponse](&lib_vnc.IsVNCResponse{}),
"VNCClient": gojs.GetClassConstructor[lib_vnc.VNCClient](&lib_vnc.VNCClient{}),
},
).Register()
}
Expand Down
31 changes: 31 additions & 0 deletions pkg/js/generated/ts/vnc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,34 @@ export interface IsVNCResponse {
Banner?: string,
}

/**
* VNCClient is a client for VNC servers.
* @example
* ```javascript
* const vnc = require('nuclei/vnc');
* const client = new vnc.VNCClient();
* ```
*/
export class VNCClient {


// Constructor of VNCClient
constructor() {}

/**
* Connect connects to VNC server using given password.
* If connection and authentication is successful, it returns true.
* If connection or authentication is unsuccessful, it returns false and error.
* The connection is closed after the function returns.
* @example
* ```javascript
* const vnc = require('nuclei/vnc');
* const client = new vnc.VNCClient();
* const connected = client.Connect('acme.com', 5900, 'password');
* ```
*/
public Connect(host: string, port: number, password: string): boolean | null {
return null;
}
}

87 changes: 85 additions & 2 deletions pkg/js/libs/vnc/vnc.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"strconv"
"time"

vnclib "github.com/alexsnet/go-vnc"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc"
vncplugin "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
stringsutil "github.com/projectdiscovery/utils/strings"
)

type (
Expand All @@ -24,8 +26,89 @@ type (
IsVNC bool
Banner string
}

// VNCClient is a client for VNC servers.
// @example
// ```javascript
// const vnc = require('nuclei/vnc');
// const client = new vnc.VNCClient();
// const connected = client.Connect('acme.com', 5900, 'password');
// log(toJSON(connected));
// ```
VNCClient struct{}
)

// Connect connects to VNC server using given password.
// If connection and authentication is successful, it returns true.
// If connection or authentication is unsuccessful, it returns false and error.
// The connection is closed after the function returns.
// @example
// ```javascript
// const vnc = require('nuclei/vnc');
// const client = new vnc.VNCClient();
// const connected = client.Connect('acme.com', 5900, 'password');
// ```
func (c *VNCClient) Connect(ctx context.Context, host string, port int, password string) (bool, error) {
executionId := ctx.Value("executionId").(string)
return connect(executionId, host, port, password)
}

// connect attempts to authenticate with a VNC server using the given password
func connect(executionId string, host string, port int, password string) (bool, error) {
if host == "" || port <= 0 {
return false, fmt.Errorf("invalid host or port")
}
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}

Comment on lines +61 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Include port in network-policy check to handle IPv6 and port-scoped rules.

Passing only the host can misclassify IPv6 literals and bypass/over-block port-scoped policies. Use host:port.

-	if !protocolstate.IsHostAllowed(executionId, host) {
+	addr := net.JoinHostPort(host, strconv.Itoa(port))
+	if !protocolstate.IsHostAllowed(executionId, addr) {
 		// host is not valid according to network policy
-		return false, protocolstate.ErrHostDenied.Msgf(host)
+		return false, protocolstate.ErrHostDenied.Msgf(addr)
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
addr := net.JoinHostPort(host, strconv.Itoa(port))
if !protocolstate.IsHostAllowed(executionId, addr) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(addr)
}
🤖 Prompt for AI Agents
In pkg/js/libs/vnc/vnc.go around lines 61 to 65, the network-policy check
currently passes only the host which misclassifies IPv6 literals and ignores
port-scoped rules; change the check to pass the host with port (host:port)
instead — format IPv6 literals with brackets (e.g. [ipv6]:port) when composing
the address, then call protocolstate.IsHostAllowed(executionId, hostWithPort)
and return protocolstate.ErrHostDenied.Msgf(hostWithPort) on denial so policy
evaluations correctly consider both address and port.

dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}

Comment on lines +66 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Nil-check Fastdialer to avoid nil deref.

Dialers may be present but Fastdialer nil during early init or in restricted contexts.

 	dialer := protocolstate.GetDialersWithId(executionId)
 	if dialer == nil {
 		return false, fmt.Errorf("dialers not initialized for %s", executionId)
 	}
+	if dialer.Fastdialer == nil {
+		return false, fmt.Errorf("fastdialer not initialized for %s", executionId)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
if dialer.Fastdialer == nil {
return false, fmt.Errorf("fastdialer not initialized for %s", executionId)
}
🤖 Prompt for AI Agents
In pkg/js/libs/vnc/vnc.go around lines 66 to 70, the code checks dialer for nil
but does not verify that dialer.Fastdialer is non-nil; add a nil-check for
dialer.Fastdialer immediately after confirming dialer exists and before any use
to avoid a nil pointer dereference, and return an appropriate error (e.g., false
and fmt.Errorf("fastdialer not initialized for %s", executionId)) or handle the
missing fastdialer case gracefully (log/propagate) consistent with surrounding
error handling.

conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()

// Set connection timeout
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))

Comment on lines +71 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Propagate context and set a timeout for dialing; avoid context.TODO.

Use a bounded context for the dial and reuse the same for the handshake; deadline on the net.Conn alone doesn’t cancel the dialing attempt.

-	conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	addr := net.JoinHostPort(host, strconv.Itoa(port))
+	conn, err := dialer.Fastdialer.Dial(ctx, "tcp", addr)
 	if err != nil {
 		return false, err
 	}
 	defer func() {
 		_ = conn.Close()
 	}()
 
 	// Set connection timeout
-	_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
+	_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
// Set connection timeout
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
// Propagate a 10s timeout into the dial (so the dial itself will be canceled if it hangs)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
addr := net.JoinHostPort(host, strconv.Itoa(port))
conn, err := dialer.Fastdialer.Dial(ctx, "tcp", addr)
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
// Also set a deadline on the established connection for the handshake/read/write
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
🤖 Prompt for AI Agents
In pkg/js/libs/vnc/vnc.go around lines 71 to 81, replace the use of context.TODO
and the lone conn.SetDeadline with a bounded context that is used for dialing
and the subsequent handshake: create a ctx, cancel :=
context.WithTimeout(parentCtxOrBackground, 10*time.Second) and defer cancel(),
call dialer.Fastdialer.Dial(ctx, ...) so the dial can be canceled by the
timeout, reuse that same ctx when performing the VNC handshake, and if you still
set a conn deadline derive it from ctx.Deadline() (if present) rather than
hardcoding time.Now().Add(...); ensure the cancel() is deferred immediately
after ctx creation.

// Create VNC client config with password
vncConfig := vnclib.NewClientConfig(password)

// Attempt to connect and authenticate
c, err := vnclib.Connect(context.TODO(), conn, vncConfig)
if err != nil {
// Check for specific authentication errors
if isAuthError(err) {
return false, nil // Authentication failed, but connection succeeded
}
return false, err // Connection or other error
}
Comment on lines +85 to +93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Reuse the same bounded context for the VNC handshake.

Avoid context.TODO so the handshake obeys the same timeout/cancellation.

-	c, err := vnclib.Connect(context.TODO(), conn, vncConfig)
+	c, err := vnclib.Connect(ctx, conn, vncConfig)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Attempt to connect and authenticate
c, err := vnclib.Connect(context.TODO(), conn, vncConfig)
if err != nil {
// Check for specific authentication errors
if isAuthError(err) {
return false, nil // Authentication failed, but connection succeeded
}
return false, err // Connection or other error
}
// Attempt to connect and authenticate
c, err := vnclib.Connect(ctx, conn, vncConfig)
if err != nil {
// Check for specific authentication errors
if isAuthError(err) {
return false, nil // Authentication failed, but connection succeeded
}
return false, err // Connection or other error
}
🤖 Prompt for AI Agents
In pkg/js/libs/vnc/vnc.go around lines 85 to 93, the handshake call uses
context.TODO(), which ignores the caller's timeout/cancellation; replace
context.TODO() with the function's context parameter (ctx) so the vnclib.Connect
call uses the same bounded context; if the function currently lacks a
context.Context parameter, add one to the signature and propagate it through all
callers so the VNC connect/handshake obeys the caller's timeout and
cancellation.

if c != nil {
_ = c.Close()
}

return true, nil
}

// isAuthError checks if the error is an authentication failure
func isAuthError(err error) bool {
if err == nil {
return false
}

// Check for common VNC authentication error messages
errStr := err.Error()
return stringsutil.ContainsAnyI(errStr, "authentication", "auth", "password", "invalid", "failed")
}

// IsVNC checks if a host is running a VNC server.
// It returns a boolean indicating if the host is running a VNC server
// and the banner of the VNC server.
Expand Down Expand Up @@ -57,7 +140,7 @@ func isVNC(executionId string, host string, port int) (IsVNCResponse, error) {
_ = conn.Close()
}()

vncPlugin := vnc.VNCPlugin{}
vncPlugin := vncplugin.VNCPlugin{}
service, err := vncPlugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return resp, err
Expand Down
Loading