masterzen/winrm

Problem with WinRM connection if the host requires first time login password change

Opened this issue · 3 comments

I am trying to establish winrm connection to the host which has a first time login password change requirement. Is there an option to stdin when the host requests password change?. I am using the below code structure and fails during createShelll() call stating unknown error Post "http://host_ip:5986/wsman": net/http: timeout awaiting response headers

package main

import (
  "github.com/masterzen/winrm"
  _"fmt"
  _"bytes"
  "os"
  "strconv"
  "log"
  _"strings"
)

type Connection struct {
    username    string
    password    string
    newPassword string
    host        string
    port        int
}

func (conn *Connection) Connect() (*winrm.Client, error) {
    endpoint := winrm.NewEndpoint(conn.host, conn.port, false, false, nil, nil, nil, 0)
    params := winrm.DefaultParameters
    params.TransportDecorator = func() winrm.Transporter { return &winrm.ClientNTLM{} }

    client, err := winrm.NewClientWithParameters(endpoint, conn.username, conn.password, params)
    if err != nil {
        panic(err)
    }
    return client, err
}

func (conn *Connection) PasswordReset(client *winrm.Client) (*winrm.Shell, error) {
    shell, err := client.CreateShell()
    if err != nil {
      log.Fatal(err)
    }
    return shell, err
}

func main() {
    if (len(os.Args) < 5) {
        log.Fatal("\nNot enough arguments for ssh password reset")
    }
    port, err := strconv.Atoi(os.Args[5])
    if err != nil {
        log.Fatal(err)
    }
    conn := &Connection{
        username:       os.Args[1],
        password:       os.Args[2],
        newPassword:    os.Args[3],
        host:           os.Args[4],
        port:           port,
    }
    client, err := conn.Connect()

    _, err = conn.PasswordReset(client)
    if err != nil {
        log.Fatal(err)
    }
}

The host has the below winrm setting and winrm connection works fine (ex: using terraform) for the same host with different user - the user does not require password change.

$Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "${hostname}"
New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint -Force
winrm quickconfig -q
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
winrm set winrm/config/service/auth '@{Basic="true"}' 
winrm set winrm/config/listener?Address=*+Transport=HTTPS '@{Port="5986";Hostname="${hostname}";CertificateThumbprint='"$($Cert.Thumbprint)"'}' 
netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow

I can achieve the password change for unix systems by reading the stdout and stdin using golang.org/x/crypto/ssh. Checking if there is something similar I can do with winrm.

Hi @Dineshk77 ,

Thanks for reporting this issue. Unfortunately I don't think you'll be able to solve this issue by reading stdout/stdin. I'm not sure, but I believe that this password change is part of the authentication negotiation in NTLM.

Can you try another winrm client implementation (ie the ruby one for instance) to check that it is indeed an issue in this project?

But I think it would be easier for your use-case to use certificate authentication and not rely on passwords at all. See https://www.hurryupandwait.io/blog/certificate-password-less-based-authentication-in-winrm for more information.

Hi @masterzen

I tried with one of the ruby project -https://github.com/WinRb/WinRM

I couldn't make a successful connection using the user account which requires password rest. However I was able to connect with another domain account with the below code and it worked.

require 'winrm'
opts = { 
  endpoint: 'http://ip:5985/wsman',
  user: 'domain_account',
  password: '******'
}
conn = WinRM::Connection.new(opts)
shell = conn.shell(:powershell) do |shell|
  output = shell.run('ipconfig') do |stdout, stderr|
    STDOUT.print stdout
    STDERR.print stderr
  end
  puts "The script exited with exit code #{output.exitcode}"
end

However I can't do the same using below winrm Golang code. Am I missing something?

package main

import (
	"github.com/masterzen/winrm"
	"os"
	"log"
)

func main() {
	endpoint := winrm.NewEndpoint("ip", 5985, false, false, nil, nil, nil, 0)
	client, err := winrm.NewClient(endpoint, "domain_account", "******")
	if err != nil {
		panic(err)
	}
	status, err := client.Run("ipconfig", os.Stdout, os.Stderr)
	log.Println(status)
	log.Println(err)
}

Error states: http response error: 401 - invalid content type

The HTTP error 401 means that the winrm server didn't authorize the request. Unfortunately this is hard to troubleshoot.
First check that the password is indeed correct, if it's the case, then you might need to use the NTLM transport. Check this project readme for a code example.