├── README.md
├── go.mod
├── go.sum
├── main.go
└── winrmntlm
├── encryption.go
└── ntlmssp
├── authenticate_message.go
├── avpair.go
├── bindings.go
├── challenge_message.go
├── client.go
├── crypto.go
├── domain_posix.go
├── domain_windows.go
├── flags.go
├── http
├── client.go
├── crypto.go
├── mime.go
└── util.go
├── message_header.go
├── negotiate_message.go
├── ntlm.go
├── payload.go
├── security.go
├── unicode.go
├── util.go
├── version.go
├── version_posix.go
├── version_windows.go
└── workstation.go
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # winrm-PTH
4 | Golang implement WinRM client with PTH (pass the hash)
5 |
6 | #### Table of Contents
7 |
8 | -
9 | Info
10 |
11 | - How it works?
12 | - Screenshots
13 | - References
14 | - References
15 |
16 |
17 | ## Info
18 |
19 | This is an example of Golang implement WinRM client with PTH.
20 |
21 | (back to top)
22 |
23 | ## How it works?
24 |
25 | In [ntlmssp](https://github.com/bodgit/ntlmssp), it always encodes the password to md4 hash in function `ntowfV1`, then we can make a simple logic detection to check if the user passes a hash into it, with that "patch", we can give hash into `ntlmssp` to make the auth structures.
26 |
27 | The [winrm](https://github.com/masterzen/winrm) always use `DefaultClientCompatibilityLevel`, so we don't need to do lots of changes in `ntlmssp`.
28 |
29 | Why do we need `encryption.go`?
30 |
31 | - Because in Windows, winrm is always set `AllowUnencrypted` to false, and the library [winrm](https://github.com/masterzen/winrm) only support `AllowUnencrypted`, which means we can't auth into `winrm` without `winrm set winrm/config/service @{AllowUnencrypted="true"}`, this is not make sense.
32 | - With `encryption.go`, we can auth into `winrm` even without `winrm set winrm/config/service @{AllowUnencrypted="true"}`
33 |
34 | (back to top)
35 |
36 | ## Screenshots
37 | - Without `AllowUnencrypted` in PTH
38 | 
39 |
40 | (back to top)
41 |
42 | ## Known bugs
43 | - Hash + AllowUnencrypted="true" == Not working
44 |
45 | ## References
46 | - https://github.com/CalypsoSys/winrmntlm
47 | - https://github.com/masterzen/winrm
48 | - https://github.com/CalypsoSys/bobwinrm
49 | - https://github.com/bodgit/ntlmssp
50 |
51 | (back to top)
52 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/bodgit/windows v1.0.1
7 | github.com/go-logr/logr v1.3.0
8 | github.com/hashicorp/go-cleanhttp v0.5.2
9 | github.com/masterzen/winrm v0.0.0-20231227165926-e811dad5ac77
10 | github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde
11 | golang.org/x/crypto v0.17.0
12 | golang.org/x/text v0.14.0
13 | )
14 |
15 | require (
16 | github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
17 | github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 // indirect
18 | github.com/bodgit/ntlmssp v0.0.0-20231122144230-2b2bca29f22b // indirect
19 | github.com/gofrs/uuid v4.4.0+incompatible // indirect
20 | github.com/hashicorp/go-uuid v1.0.3 // indirect
21 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect
22 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
23 | github.com/jcmturner/gofork v1.7.6 // indirect
24 | github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
25 | github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
26 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect
27 | github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
28 | golang.org/x/net v0.18.0 // indirect
29 | )
30 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
2 | github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
3 | github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 h1:w0E0fgc1YafGEh5cROhlROMWXiNoZqApk2PDN0M1+Ns=
4 | github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
5 | github.com/bodgit/ntlmssp v0.0.0-20231122144230-2b2bca29f22b h1:/rxp4dz0wtk+sumWvQpaUft0yM+YqPegRgKxqU3MXBE=
6 | github.com/bodgit/ntlmssp v0.0.0-20231122144230-2b2bca29f22b/go.mod h1:t46HDKvw4bCyAsVTayprrRiC9UItu18q9Zo29vTGN10=
7 | github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
8 | github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12 | github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
13 | github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
14 | github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
15 | github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
16 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
17 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
18 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
19 | github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
20 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
21 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
22 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
23 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
24 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
25 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
26 | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
27 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
28 | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
29 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
30 | github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
31 | github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
32 | github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
33 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
34 | github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
35 | github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
36 | github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
37 | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
38 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
39 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
40 | github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg=
41 | github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
42 | github.com/masterzen/winrm v0.0.0-20231227165926-e811dad5ac77 h1:psY7rHKhnfqjTEgkleIYpF1vVxVfYsUYFTO/cL5Z6xM=
43 | github.com/masterzen/winrm v0.0.0-20231227165926-e811dad5ac77/go.mod h1:otHfftEJdo9JWGoq9GcJRaeNLp/uhqNq8JOk5lL+8Ks=
44 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
45 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
47 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
48 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
49 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
50 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
51 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
52 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
53 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
54 | github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0=
55 | github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8=
56 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
57 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
58 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
59 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
60 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
61 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
62 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
63 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
64 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
65 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
66 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
67 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
68 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
69 | golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
70 | golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
71 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
72 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
73 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
74 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
75 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
76 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
77 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
78 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
79 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
80 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
81 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
82 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
83 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
84 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
85 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
86 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
87 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
88 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
89 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
90 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
91 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
92 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
93 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
94 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
95 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
96 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
97 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
98 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 |
8 | "main/winrmntlm"
9 |
10 | "github.com/masterzen/winrm"
11 | )
12 |
13 | func main() {
14 | runExec_winrmntlm("192.168.183.253", 5985, false, "administrator", "e91d2eafde47de62c6c49a012b3a6af1") // works // unsupported action
15 | }
16 |
17 | func runExec_winrmntlm(address string, port int, https bool, userName string, password string) {
18 | endpoint := winrm.NewEndpoint(address, port, https, true, nil, nil, nil, 0)
19 |
20 | params := winrm.DefaultParameters
21 | enc, _ := winrmntlm.NewEncryption("ntlm", userName, password, endpoint, true) // true is means if password is hash, else false
22 | params.TransportDecorator = func() winrm.Transporter { return enc }
23 |
24 | client, err := winrm.NewClientWithParameters(endpoint, userName, password, params)
25 | if err != nil {
26 | fmt.Println(err)
27 | }
28 |
29 | exitCode, err := client.RunWithContext(context.Background(), "ipconfig /all", os.Stdout, os.Stderr)
30 | fmt.Printf("%d\n%v\nn", exitCode, err)
31 | if err != nil {
32 | _ = exitCode
33 | fmt.Println(err)
34 | } else {
35 | fmt.Println("Command Test Ok")
36 | }
37 |
38 | wmiQuery := `select * from Win32_ComputerSystem`
39 | psCommand := fmt.Sprintf(`$FormatEnumerationLimit=-1; Get-WmiObject -Query "%s" | Out-String -Width 4096`, wmiQuery)
40 | stdOut, stdErr, exitCode, _ := client.RunPSWithContext(context.Background(), psCommand)
41 |
42 | fmt.Println(stdOut)
43 | fmt.Println(stdErr)
44 | fmt.Println(exitCode)
45 | }
46 |
--------------------------------------------------------------------------------
/winrmntlm/encryption.go:
--------------------------------------------------------------------------------
1 | package winrmntlm
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "fmt"
8 | "io"
9 |
10 | "net/http"
11 | "net/url"
12 | "strconv"
13 | "strings"
14 |
15 | "main/winrmntlm/ntlmssp"
16 |
17 | ntlmhttp "main/winrmntlm/ntlmssp/http"
18 |
19 | "github.com/masterzen/winrm"
20 | "github.com/masterzen/winrm/soap"
21 | )
22 |
23 | type Encryption struct {
24 | username string
25 | password string
26 | pass_is_hash bool
27 | endpoint *winrm.Endpoint
28 | ntlm *winrm.ClientNTLM
29 | protocol string
30 | protocolString []byte
31 | httpClient *http.Client
32 | ntlmClient *ntlmssp.Client
33 | ntlmhttp *ntlmhttp.Client
34 | }
35 |
36 | const (
37 | sixTenKB = 16384
38 | mimeBoundary = "--Encrypted Boundary"
39 | defaultCipher = "RC4-HMAC-NTLM"
40 | boundaryLength = len(mimeBoundary)
41 | )
42 |
43 | /*
44 | Encrypted Message Types
45 | When using Encryption, there are three options available
46 |
47 | 1. Negotiate/SPNEGO
48 |
49 | 2. Kerberos
50 |
51 | 3. CredSSP
52 |
53 | protocol: The protocol string used for the particular auth protocol
54 |
55 | The auth protocol used, will determine the wrapping and unwrapping method plus
56 | the protocol string to use. Currently only NTLM is supported
57 |
58 | based on the python code from https://pypi.org/project/pywinrm/
59 |
60 | see https://github.com/diyan/pywinrm/blob/master/winrm/encryption.py
61 |
62 | uses the most excellent NTLM library from https://github.com/bodgit/ntlmssp
63 | */
64 | func NewEncryption(protocol string, username string, password string, endpoint *winrm.Endpoint, pass_is_hash bool) (*Encryption, error) {
65 | encryption := &Encryption{
66 | username: username,
67 | password: password,
68 | pass_is_hash: pass_is_hash,
69 | endpoint: endpoint,
70 | ntlm: &winrm.ClientNTLM{},
71 | protocol: protocol,
72 | }
73 |
74 | switch protocol {
75 | case "ntlm":
76 | encryption.protocolString = []byte("application/HTTP-SPNEGO-session-encrypted")
77 | return encryption, nil
78 | /* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation
79 | case "credssp":
80 | encryption.protocolString = []byte("application/HTTP-CredSSP-session-encrypted")
81 | case "kerberos": // kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation
82 | encryption.protocolString = []byte("application/HTTP-SPNEGO-session-encrypted")
83 | */
84 | }
85 |
86 | return nil, fmt.Errorf("Encryption for protocol '%s' not supported in winrm", protocol)
87 | }
88 |
89 | func (e *Encryption) Transport(endpoint *winrm.Endpoint) error {
90 | e.httpClient = &http.Client{}
91 | return e.ntlm.Transport(endpoint)
92 | }
93 |
94 | func (e *Encryption) Post(client *winrm.Client, message *soap.SoapMessage) (string, error) {
95 | var userName, domain string
96 | if strings.Contains(e.username, "@") {
97 | parts := strings.Split(e.username, "@")
98 | domain = parts[1]
99 | userName = parts[0]
100 | } else if strings.Contains(e.username, "\\") {
101 | parts := strings.Split(e.username, "\\")
102 | domain = parts[0]
103 | userName = parts[1]
104 | } else {
105 | userName = e.username
106 | }
107 |
108 | e.ntlmClient, _ = ntlmssp.NewClient(ntlmssp.SetUserInfo(userName, e.password, e.pass_is_hash), ntlmssp.SetDomain(domain), ntlmssp.SetVersion(ntlmssp.DefaultVersion()))
109 | e.ntlmhttp, _ = ntlmhttp.NewClient(e.httpClient, e.ntlmClient)
110 |
111 | var err error
112 | if err = e.PrepareRequest(client, e.url()); err == nil {
113 | return e.PrepareEncryptedRequest(client, e.url(), []byte(message.String()))
114 | } else {
115 | return e.ntlm.Post(client, message)
116 | }
117 | }
118 |
119 | func (e *Encryption) url() string {
120 | var scheme string
121 | if e.endpoint.HTTPS {
122 | scheme = "https"
123 | } else {
124 | scheme = "http"
125 | }
126 |
127 | return fmt.Sprintf("%s://%s:%d/wsman", scheme, e.endpoint.Host, e.endpoint.Port)
128 | }
129 |
130 | func (e *Encryption) PrepareRequest(client *winrm.Client, endpoint string) error {
131 | req, err := http.NewRequest("POST", endpoint, nil)
132 | if err != nil {
133 | return err
134 | }
135 |
136 | req.Header.Set("User-Agent", "WinRM client")
137 | req.Header.Set("Content-Length", "0")
138 | req.Header.Set("Content-Type", "application/soap+xml;charset=UTF-8")
139 | req.Header.Set("Connection", "Keep-Alive")
140 |
141 | resp, err := e.ntlmhttp.Do(req)
142 | if err != nil {
143 | return fmt.Errorf("unknown error %w", err)
144 | }
145 |
146 | if resp.StatusCode != 200 {
147 | return fmt.Errorf("http error %d", resp.StatusCode)
148 | }
149 |
150 | return nil
151 | }
152 |
153 | /*
154 | Creates a prepared request to send to the server with an encrypted message
155 | and correct headers
156 |
157 | :param endpoint: The endpoint/server to prepare requests to
158 | :param message: The unencrypted message to send to the server
159 | :return: A prepared request that has an decrypted message
160 | */
161 | func (e *Encryption) PrepareEncryptedRequest(client *winrm.Client, endpoint string, message []byte) (string, error) {
162 | url, err := url.Parse(endpoint)
163 | if err != nil {
164 | return "", err
165 | }
166 | host := strings.Split(url.Hostname(), ":")[0]
167 |
168 | var content_type string
169 | var encrypted_message []byte
170 |
171 | if e.protocol == "credssp" && len(message) > sixTenKB {
172 | content_type = "multipart/x-multi-encrypted"
173 | encrypted_message = []byte{}
174 | message_chunks := [][]byte{}
175 | for i := 0; i < len(message); i += sixTenKB {
176 | message_chunks = append(message_chunks, message[i:i+sixTenKB])
177 | }
178 | for _, message_chunk := range message_chunks {
179 | encrypted_chunk := e.encryptMessage(message_chunk, host)
180 | encrypted_message = append(encrypted_message, encrypted_chunk...)
181 | }
182 | } else {
183 | content_type = "multipart/encrypted"
184 | encrypted_message = e.encryptMessage(message, host)
185 | }
186 |
187 | encrypted_message = append(encrypted_message, []byte(mimeBoundary)...)
188 | encrypted_message = append(encrypted_message, []byte("--\r\n")...)
189 |
190 | req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(encrypted_message))
191 | if err != nil {
192 | return "", err
193 | }
194 |
195 | req.Header.Set("User-Agent", "WinRM client")
196 | req.Header.Set("Connection", "Keep-Alive")
197 | req.Header.Set("Content-Length", fmt.Sprintf("%d", len(encrypted_message)))
198 | req.Header.Set("Content-Type", fmt.Sprintf(`%s;protocol="%s";boundary="Encrypted Boundary"`, content_type, e.protocolString))
199 |
200 | resp, err := e.ntlmhttp.Do(req)
201 | if err != nil {
202 | return "", fmt.Errorf("unknown error %w", err)
203 | }
204 |
205 | body, err := e.ParseEncryptedResponse(resp)
206 |
207 | return string(body), err
208 | }
209 |
210 | /*
211 | Takes in the encrypted response from the server and decrypts it
212 |
213 | :param response: The response that needs to be decrytped
214 | :return: The unencrypted message from the server
215 | */
216 | func (e *Encryption) ParseEncryptedResponse(response *http.Response) ([]byte, error) {
217 | contentType := response.Header.Get("Content-Type")
218 | if strings.Contains(contentType, fmt.Sprintf(`protocol="%s"`, e.protocolString)) {
219 | return e.decryptResponse(response, response.Request.URL.Hostname())
220 | }
221 | body, err := io.ReadAll(response.Body)
222 | response.Body.Close()
223 | if err != nil {
224 | return nil, err
225 | }
226 | return body, nil
227 | }
228 |
229 | func (e *Encryption) encryptMessage(message []byte, host string) []byte {
230 | encryptedStream, _ := e.buildMessage(message, host)
231 |
232 | messagePayload := bytes.Join([][]byte{
233 | []byte(mimeBoundary),
234 | []byte("\r\n"),
235 | []byte(fmt.Sprintf("\tContent-Type: %s\r\n", string(e.protocolString))),
236 | []byte(fmt.Sprintf("\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=%d\r\n", len(message))),
237 | []byte(mimeBoundary),
238 | []byte("\r\n"),
239 | []byte("\tContent-Type: application/octet-stream\r\n"),
240 | encryptedStream,
241 | }, []byte{})
242 |
243 | return messagePayload
244 | }
245 |
246 | func deleteEmpty(b [][]byte) [][]byte {
247 | var r [][]byte
248 | for _, by := range b {
249 | if len(by) != 0 {
250 | r = append(r, by)
251 | }
252 | }
253 | return r
254 | }
255 |
256 | // tried using pkg.go.dev/mime/multipart here but parsing fails with with
257 | // because in the header we have "\tContent-Type: application/HTTP-SPNEGO-session-encrypted\r\n"
258 | // on call to textproto.ReadMIMEHeader
259 | // because of "The first line cannot start with a leading space."
260 | func (e *Encryption) decryptResponse(response *http.Response, host string) ([]byte, error) {
261 | body, _ := io.ReadAll(response.Body)
262 | parts := deleteEmpty(bytes.Split(body, []byte(fmt.Sprintf("%s\r\n", mimeBoundary))))
263 | var message []byte
264 |
265 | for i := 0; i < len(parts); i += 2 {
266 | header := parts[i]
267 | payload := parts[i+1]
268 |
269 | expectedLengthStr := bytes.SplitAfter(header, []byte("Length="))[1]
270 | expectedLength, err := strconv.Atoi(string(bytes.TrimSpace(expectedLengthStr)))
271 | if err != nil {
272 | return nil, err
273 | }
274 |
275 | // remove the end MIME block if it exists
276 | if bytes.HasSuffix(payload, []byte(fmt.Sprintf("%s--\r\n", mimeBoundary))) {
277 | payload = payload[:len(payload)-boundaryLength-4]
278 | }
279 | encryptedData := bytes.ReplaceAll(payload, []byte("\tContent-Type: application/octet-stream\r\n"), []byte{})
280 | decryptedMessage, err := e.decryptMessage(encryptedData, host)
281 | if err != nil {
282 | return nil, err
283 | }
284 |
285 | actualLength := int(len(decryptedMessage))
286 | if actualLength != expectedLength {
287 | return nil, errors.New("encrypted length from server does not match the expected size, message has been tampered with")
288 | }
289 |
290 | message = append(message, decryptedMessage...)
291 | }
292 |
293 | return message, nil
294 | }
295 |
296 | func (e *Encryption) decryptMessage(encryptedData []byte, host string) ([]byte, error) {
297 | switch e.protocol {
298 | case "ntlm":
299 | return e.decryptNtlmMessage(encryptedData, host)
300 | /* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation
301 | case "credssp":
302 | return e.decryptCredsspMessage(encryptedData, host)
303 | case "kerberos":
304 | return e.decryptKerberosMessage(encryptedData, host)
305 | */
306 | default:
307 | return nil, errors.New("Encryption for protocol " + e.protocol + " not supported in pywinrm")
308 | }
309 | }
310 |
311 | func (e *Encryption) decryptNtlmMessage(encryptedData []byte, host string) ([]byte, error) {
312 | signatureLength := int(binary.LittleEndian.Uint32(encryptedData[:4]))
313 | signature := encryptedData[4 : signatureLength+4]
314 | encryptedMessage := encryptedData[signatureLength+4:]
315 |
316 | message, err := e.ntlmClient.SecuritySession().Unwrap(encryptedMessage, signature)
317 | if err != nil {
318 | return nil, err
319 | }
320 | return message, nil
321 | }
322 |
323 | /* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation
324 | func (e *Encryption) decryptCredsspMessage(encryptedData []byte, host string) ([]byte, error) {
325 | // // TODO
326 | // encryptedMessage := encryptedData[4:]
327 |
328 | // credsspContext, ok := e.session.Auth.Contexts()[host]
329 | // if !ok {
330 | // return nil, fmt.Errorf("credssp context not found for host: %s", host)
331 | // }
332 |
333 | // message, err := credsspContext.Unwrap(encryptedMessage)
334 | // if err != nil {
335 | // return nil, err
336 | // }
337 | // return message, nil
338 | }
339 |
340 | func (enc *Encryption) decryptKerberosMessage(encryptedData []byte, host string) ([]byte, error) {
341 | // //TODO
342 | // signatureLength := binary.LittleEndian.Uint32(encryptedData[0:4])
343 | // signature := encryptedData[4 : 4+signatureLength]
344 | // encryptedMessage := encryptedData[4+signatureLength:]
345 |
346 | // message, err := enc.session.Auth.UnwrapWinrm(host, encryptedMessage, signature)
347 | // if err != nil {
348 | // return nil, err
349 | // }
350 |
351 | // return message, nil
352 | }
353 | */
354 |
355 | func (e *Encryption) buildMessage(encryptedData []byte, host string) ([]byte, error) {
356 | switch e.protocol {
357 | case "ntlm":
358 | return e.buildNTLMMessage(encryptedData, host)
359 | /* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation
360 | case "credssp":
361 | return e.buildCredSSPMessage(encryptedData, host)
362 | case "kerberos":
363 | return e.buildKerberosMessage(encryptedData, host)
364 | */
365 | default:
366 | return nil, errors.New("Encryption for protocol " + e.protocol + " not supported in pywinrm")
367 | }
368 | }
369 |
370 | func (enc *Encryption) buildNTLMMessage(message []byte, host string) ([]byte, error) {
371 | if enc.ntlmClient.SecuritySession() == nil {
372 | return nil, nil
373 | }
374 | sealedMessage, signature, err := enc.ntlmClient.SecuritySession().Wrap(message)
375 | if err != nil {
376 | return nil, err
377 | }
378 |
379 | buf := new(bytes.Buffer)
380 | if err = binary.Write(buf, binary.LittleEndian, uint32(len(signature))); err != nil {
381 | return nil, err
382 | }
383 |
384 | buf.Write(signature)
385 | buf.Write(sealedMessage)
386 |
387 | return buf.Bytes(), nil
388 | }
389 |
390 | /* credssp and kerberos is currently unimplemented, leave holder for future to keep in sync with python implementation
391 | func (e *Encryption) buildCredSSPMessage(message []byte, host string) ([]byte, error) {
392 | // //TODO
393 | // context := e.session.Auth.Contexts[host]
394 | // sealedMessage := context.Wrap(message)
395 |
396 | // cipherNegotiated := context.TLSConnection.ConnectionState().CipherSuite.Name
397 | // trailerLength := e.getCredSSPTrailerLength(len(message), cipherNegotiated)
398 |
399 | // trailer := make([]byte, 4)
400 | // binary.LittleEndian.PutUint32(trailer, uint32(trailerLength))
401 |
402 | // return append(trailer, sealedMessage...), nil
403 | }
404 |
405 | func (e *Encryption) buildKerberosMessage(message []byte, host string) ([]byte, error) {
406 | // //TODO
407 | // sealedMessage, signature := e.session.Auth.WrapWinrm(host, message)
408 |
409 | // signatureLength := make([]byte, 4)
410 | // binary.LittleEndian.PutUint32(signatureLength, uint32(len(signature)))
411 |
412 | // return append(append(signatureLength, signature...), sealedMessage...), nil
413 | }
414 |
415 | func (e *Encryption) getCredSSPTrailerLength(messageLength int, cipherSuite string) int {
416 | var trailerLength int
417 |
418 | if match, _ := regexp.MatchString("^.*-GCM-[\\w\\d]*$", cipherSuite); match {
419 | trailerLength = 16
420 | } else {
421 | hashAlgorithm := cipherSuite[strings.LastIndex(cipherSuite, "-")+1:]
422 | var hashLength int
423 |
424 | if hashAlgorithm == "MD5" {
425 | hashLength = 16
426 | } else if hashAlgorithm == "SHA" {
427 | hashLength = 20
428 | } else if hashAlgorithm == "SHA256" {
429 | hashLength = 32
430 | } else if hashAlgorithm == "SHA384" {
431 | hashLength = 48
432 | } else {
433 | hashLength = 0
434 | }
435 |
436 | prePadLength := messageLength + hashLength
437 | paddingLength := 0
438 |
439 | if strings.Contains(cipherSuite, "RC4") {
440 | paddingLength = 0
441 | } else if strings.Contains(cipherSuite, "DES") || strings.Contains(cipherSuite, "3DES") {
442 | paddingLength = 8 - (prePadLength % 8)
443 |
444 | } else {
445 | // AES is a 128 bit block cipher
446 | paddingLength = 16 - (prePadLength % 16)
447 | }
448 |
449 | trailerLength = (prePadLength + paddingLength) - messageLength
450 | }
451 | return trailerLength
452 | }
453 | */
454 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/authenticate_message.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "sort"
10 | "unsafe"
11 | )
12 |
13 | var (
14 | errInvalidAuthenticateMessage = errors.New("invalid NTLM authenticate message")
15 | )
16 |
17 | type authenticateMessageFields struct {
18 | messageHeader
19 | LmChallengeResponseFields payload
20 | NtChallengeResponseFields payload
21 | DomainNameFields payload
22 | UserNameFields payload
23 | WorkstationFields payload
24 | EncryptedRandomSessionKeyFields payload
25 | NegotiateFlags uint32
26 | }
27 |
28 | func (m *authenticateMessageFields) IsValid() bool {
29 | return m.messageHeader.IsValid() && m.MessageType == ntLmAuthenticate
30 | }
31 |
32 | type authenticateMessage struct {
33 | authenticateMessageFields
34 | LmChallengeResponse []uint8
35 | NtChallengeResponse []uint8
36 | DomainName string
37 | UserName string
38 | Workstation string
39 | EncryptedRandomSessionKey []uint8
40 | Version *Version
41 | MIC []uint8
42 |
43 | // These aren't transmitted, but necessary in order to compute the MIC
44 | ExportedSessionKey []uint8
45 | TargetInfo targetInfo
46 | }
47 |
48 | func (m *authenticateMessage) UpdateMIC(preamble []byte) error {
49 | if v, ok := m.TargetInfo.Get(msvAvFlags); ok {
50 | flags := binary.LittleEndian.Uint32(v)
51 | if flags&msvAvFlagMICProvided != 0 {
52 | // Add an all-zero MIC first
53 | m.MIC = zeroBytes(16)
54 |
55 | b, err := m.Marshal()
56 | if err != nil {
57 | return err
58 | }
59 |
60 | // Now compute the actual MIC
61 | m.MIC = hmacMD5(m.ExportedSessionKey, concat(preamble, b))
62 | }
63 | }
64 |
65 | return nil
66 | }
67 |
68 | func (m *authenticateMessage) Marshal() ([]byte, error) {
69 | offset := int(unsafe.Sizeof(m.authenticateMessageFields) + unsafe.Sizeof(*m.Version))
70 | if m.MIC != nil {
71 | offset += len(m.MIC)
72 | }
73 |
74 | var domainName, userName, workstation []byte
75 | if ntlmsspNegotiateUnicode.IsSet(m.NegotiateFlags) {
76 | var err error
77 | domainName, err = utf16FromString(m.DomainName)
78 | if err != nil {
79 | return nil, err
80 | }
81 | userName, err = utf16FromString(m.UserName)
82 | if err != nil {
83 | return nil, err
84 | }
85 | workstation, err = utf16FromString(m.Workstation)
86 | if err != nil {
87 | return nil, err
88 | }
89 | } else {
90 | domainName = []byte(m.DomainName)
91 | userName = []byte(m.UserName)
92 | workstation = []byte(m.Workstation)
93 | }
94 |
95 | // Microsoft MS-NLMP spec doesn't write out the payloads in the same
96 | // order as they are defined in the struct. Copy the behaviour just so
97 | // the various test values can be used
98 |
99 | m.DomainNameFields = newPayload(len(domainName), &offset)
100 | m.UserNameFields = newPayload(len(userName), &offset)
101 | m.WorkstationFields = newPayload(len(workstation), &offset)
102 |
103 | m.LmChallengeResponseFields = newPayload(len(m.LmChallengeResponse), &offset)
104 | m.NtChallengeResponseFields = newPayload(len(m.NtChallengeResponse), &offset)
105 |
106 | m.EncryptedRandomSessionKeyFields = newPayload(len(m.EncryptedRandomSessionKey), &offset)
107 |
108 | var version = &Version{}
109 |
110 | m.NegotiateFlags = ntlmsspNegotiateVersion.Unset(m.NegotiateFlags)
111 | if m.Version != nil {
112 | m.NegotiateFlags = ntlmsspNegotiateVersion.Set(m.NegotiateFlags)
113 | version = m.Version
114 | }
115 |
116 | b := bytes.Buffer{}
117 | if err := binary.Write(&b, binary.LittleEndian, &m.authenticateMessageFields); err != nil {
118 | return nil, err
119 | }
120 |
121 | v, err := version.marshal()
122 | if err != nil {
123 | return nil, err
124 | }
125 | b.Write(v)
126 |
127 | if m.MIC != nil {
128 | b.Write(m.MIC)
129 | }
130 |
131 | b.Write(domainName)
132 | b.Write(userName)
133 | b.Write(workstation)
134 |
135 | b.Write(m.LmChallengeResponse)
136 | b.Write(m.NtChallengeResponse)
137 |
138 | b.Write(m.EncryptedRandomSessionKey)
139 |
140 | return b.Bytes(), nil
141 | }
142 |
143 | func (m *authenticateMessage) Unmarshal(b []byte) error {
144 | reader := bytes.NewReader(b)
145 |
146 | err := binary.Read(reader, binary.LittleEndian, &m.authenticateMessageFields)
147 | if err != nil {
148 | return err
149 | }
150 |
151 | if !m.authenticateMessageFields.IsValid() {
152 | return errInvalidAuthenticateMessage
153 | }
154 |
155 | var offsets []int
156 |
157 | for _, x := range []struct {
158 | payload *payload
159 | bytes *[]uint8
160 | }{
161 | {
162 | &m.LmChallengeResponseFields,
163 | &m.LmChallengeResponse,
164 | },
165 | {
166 | &m.NtChallengeResponseFields,
167 | &m.NtChallengeResponse,
168 | },
169 | {
170 | &m.EncryptedRandomSessionKeyFields,
171 | &m.EncryptedRandomSessionKey,
172 | },
173 | } {
174 | offsets = append(offsets, int(x.payload.Offset))
175 |
176 | if x.payload.Len > 0 {
177 | *x.bytes, err = x.payload.ReadValue(reader)
178 | if err != nil {
179 | return err
180 | }
181 | }
182 | }
183 |
184 | for _, x := range []struct {
185 | payload *payload
186 | string *string
187 | }{
188 | {
189 | &m.DomainNameFields,
190 | &m.DomainName,
191 | },
192 | {
193 | &m.UserNameFields,
194 | &m.UserName,
195 | },
196 | {
197 | &m.WorkstationFields,
198 | &m.Workstation,
199 | },
200 | } {
201 | offsets = append(offsets, int(x.payload.Offset))
202 |
203 | if x.payload.Len > 0 {
204 | *x.string, err = x.payload.ReadString(reader, ntlmsspNegotiateUnicode.IsSet(m.NegotiateFlags))
205 | if err != nil {
206 | return err
207 | }
208 | }
209 | }
210 |
211 | if ntlmsspNegotiateVersion.IsSet(m.NegotiateFlags) {
212 | v := Version{}
213 | if err := v.unmarshal(reader); err != nil {
214 | return err
215 | }
216 | m.Version = &v
217 | } else {
218 | // Seek past the version
219 | if _, err := reader.Seek(int64(unsafe.Sizeof(*m.Version)), io.SeekCurrent); err != nil {
220 | return err
221 | }
222 | }
223 |
224 | pos, err := reader.Seek(0, io.SeekCurrent)
225 | if err != nil {
226 | return err
227 | }
228 |
229 | // Sort the offsets into ascending order
230 | sort.Ints(offsets)
231 |
232 | // If there's a gap between the end of the version where we are and
233 | // the first payload offset then treat the gap as a MIC
234 | if length := offsets[0] - int(pos); length > 0 {
235 | m.MIC = make([]byte, length)
236 | n, err := reader.Read(m.MIC)
237 | if err != nil {
238 | return err
239 | }
240 | if n != length {
241 | return fmt.Errorf("expected %d bytes, only read %d", length, n)
242 | }
243 | }
244 |
245 | return nil
246 | }
247 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/avpair.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "encoding/gob"
7 | "fmt"
8 | )
9 |
10 | type avID uint16
11 |
12 | const (
13 | msvAvEOL avID = iota
14 | msvAvNbComputerName
15 | msvAvNbDomainName
16 | msvAvDNSComputerName
17 | msvAvDNSDomainName
18 | msvAvDNSTreeName
19 | msvAvFlags
20 | msvAvTimestamp
21 | msvAvSingleHost
22 | msvAvTargetName
23 | msvChannelBindings
24 | )
25 |
26 | const (
27 | msvAvFlagAuthenticationConstrained uint32 = 1 << iota
28 | msvAvFlagMICProvided
29 | msvAvFlagUntrustedSPNSource
30 | )
31 |
32 | type avPair struct {
33 | ID avID
34 | Length uint16
35 | }
36 |
37 | type targetInfo struct {
38 | Pairs map[avID][]uint8
39 | Order []avID
40 | }
41 |
42 | func newTargetInfo() targetInfo {
43 | return targetInfo{
44 | Pairs: make(map[avID][]uint8),
45 | Order: []avID{},
46 | }
47 | }
48 |
49 | func (t *targetInfo) Get(id avID) ([]uint8, bool) {
50 | v, ok := t.Pairs[id]
51 | return v, ok
52 | }
53 |
54 | func (t *targetInfo) Set(id avID, value []uint8) {
55 | if id == msvAvEOL {
56 | return
57 | }
58 | if _, ok := t.Get(id); !ok {
59 | t.Order = append(t.Order, id)
60 | }
61 | t.Pairs[id] = value
62 | }
63 |
64 | func (t *targetInfo) Del(id avID) {
65 | delete(t.Pairs, id)
66 | j := 0
67 | for _, n := range t.Order {
68 | if n != id {
69 | t.Order[j] = n
70 | j++
71 | }
72 | }
73 | t.Order = t.Order[:j]
74 | }
75 |
76 | func (t *targetInfo) Len() int {
77 | return len(t.Pairs)
78 | }
79 |
80 | func init() {
81 | gob.Register(targetInfo{})
82 | }
83 |
84 | func (t *targetInfo) Clone() (*targetInfo, error) {
85 | b := bytes.Buffer{}
86 | enc := gob.NewEncoder(&b)
87 | dec := gob.NewDecoder(&b)
88 | if err := enc.Encode(*t); err != nil {
89 | return nil, err
90 | }
91 | var copy targetInfo
92 | if err := dec.Decode(©); err != nil {
93 | return nil, err
94 | }
95 | return ©, nil
96 | }
97 |
98 | func (t *targetInfo) Marshal() ([]byte, error) {
99 | b := bytes.Buffer{}
100 |
101 | for _, k := range t.Order {
102 | if k == msvAvEOL {
103 | continue
104 | }
105 |
106 | v := t.Pairs[k]
107 |
108 | if err := binary.Write(&b, binary.LittleEndian, &avPair{k, uint16(len(v))}); err != nil {
109 | return nil, err
110 | }
111 |
112 | b.Write(v)
113 | }
114 |
115 | // Append required MsvAvEOL pair
116 | if err := binary.Write(&b, binary.LittleEndian, &avPair{msvAvEOL, 0}); err != nil {
117 | return nil, err
118 | }
119 |
120 | return b.Bytes(), nil
121 | }
122 |
123 | func (t *targetInfo) Unmarshal(b []byte) error {
124 | reader := bytes.NewReader(b)
125 |
126 | for {
127 | var pair avPair
128 |
129 | if err := binary.Read(reader, binary.LittleEndian, &pair); err != nil {
130 | return err
131 | }
132 |
133 | if pair.ID == msvAvEOL {
134 | break
135 | }
136 |
137 | value := make([]byte, pair.Length)
138 | n, err := reader.Read(value)
139 | if err != nil {
140 | return err
141 | }
142 | if n != int(pair.Length) {
143 | return fmt.Errorf("expected %d bytes, only read %d", pair.Length, n)
144 | }
145 |
146 | t.Set(pair.ID, value)
147 | }
148 |
149 | return nil
150 | }
151 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/bindings.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | )
7 |
8 | const (
9 | // TLSServerEndPoint is defined in RFC 5929 and stores the hash of the server certificate.
10 | TLSServerEndPoint string = "tls-server-end-point"
11 | )
12 |
13 | // ChannelBindings models the GSS-API channel bindings defined in RFC 2744.
14 | type ChannelBindings struct {
15 | InitiatorAddrtype uint32
16 | InitiatorAddress []uint8
17 | AcceptorAddrtype uint32
18 | AcceptorAddress []uint8
19 | ApplicationData []uint8
20 | }
21 |
22 | func (c *ChannelBindings) marshal() ([]byte, error) {
23 | b := bytes.Buffer{}
24 |
25 | if err := binary.Write(&b, binary.LittleEndian, &c.InitiatorAddrtype); err != nil {
26 | return nil, err
27 | }
28 |
29 | length := uint32(len(c.InitiatorAddress))
30 | if err := binary.Write(&b, binary.LittleEndian, &length); err != nil {
31 | return nil, err
32 | }
33 |
34 | b.Write(c.InitiatorAddress)
35 |
36 | if err := binary.Write(&b, binary.LittleEndian, &c.AcceptorAddrtype); err != nil {
37 | return nil, err
38 | }
39 |
40 | length = uint32(len(c.AcceptorAddress))
41 | if err := binary.Write(&b, binary.LittleEndian, &length); err != nil {
42 | return nil, err
43 | }
44 |
45 | b.Write(c.AcceptorAddress)
46 |
47 | length = uint32(len(c.ApplicationData))
48 | if err := binary.Write(&b, binary.LittleEndian, &length); err != nil {
49 | return nil, err
50 | }
51 |
52 | b.Write(c.ApplicationData)
53 |
54 | return b.Bytes(), nil
55 | }
56 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/challenge_message.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "unsafe"
8 | )
9 |
10 | var (
11 | errInvalidChallengeMessage = errors.New("invalid NTLM challenge message")
12 | )
13 |
14 | type challengeMessageFields struct {
15 | messageHeader
16 | TargetNameFields payload
17 | NegotiateFlags uint32
18 | ServerChallenge [8]uint8
19 | _ [8]uint8
20 | TargetInfoFields payload
21 | }
22 |
23 | func (m *challengeMessageFields) IsValid() bool {
24 | return m.messageHeader.IsValid() && m.MessageType == ntLmChallenge
25 | }
26 |
27 | type challengeMessage struct {
28 | challengeMessageFields
29 | TargetName string
30 | TargetInfo targetInfo
31 | Version *Version
32 | }
33 |
34 | func (m *challengeMessage) Marshal() ([]byte, error) {
35 | offset := int(unsafe.Sizeof(m.challengeMessageFields) + unsafe.Sizeof(*m.Version))
36 |
37 | m.NegotiateFlags = ntlmsspRequestTarget.Unset(m.NegotiateFlags)
38 | if m.TargetName != "" {
39 | m.NegotiateFlags = ntlmsspRequestTarget.Set(m.NegotiateFlags)
40 | }
41 |
42 | m.NegotiateFlags = ntlmsspNegotiateTargetInfo.Unset(m.NegotiateFlags)
43 | if m.TargetInfo.Len() > 0 {
44 | m.NegotiateFlags = ntlmsspNegotiateTargetInfo.Set(m.NegotiateFlags)
45 | }
46 |
47 | var targetName []byte
48 | if ntlmsspNegotiateUnicode.IsSet(m.NegotiateFlags) {
49 | var err error
50 | targetName, err = utf16FromString(m.TargetName)
51 | if err != nil {
52 | return nil, err
53 | }
54 | } else {
55 | targetName = []byte(m.TargetName)
56 | }
57 |
58 | m.TargetNameFields = newPayload(len(targetName), &offset)
59 |
60 | targetInfo, err := m.TargetInfo.Marshal()
61 | if err != nil {
62 | return nil, err
63 | }
64 |
65 | m.TargetInfoFields = newPayload(len(targetInfo), &offset)
66 |
67 | var version = &Version{}
68 |
69 | m.NegotiateFlags = ntlmsspNegotiateVersion.Unset(m.NegotiateFlags)
70 | if m.Version != nil {
71 | m.NegotiateFlags = ntlmsspNegotiateVersion.Set(m.NegotiateFlags)
72 | version = m.Version
73 | }
74 |
75 | b := bytes.Buffer{}
76 | if err := binary.Write(&b, binary.LittleEndian, &m.challengeMessageFields); err != nil {
77 | return nil, err
78 | }
79 |
80 | v, err := version.marshal()
81 | if err != nil {
82 | return nil, err
83 | }
84 | b.Write(v)
85 |
86 | b.Write(targetName)
87 | b.Write(targetInfo)
88 |
89 | return b.Bytes(), nil
90 | }
91 |
92 | func (m *challengeMessage) Unmarshal(b []byte) error {
93 | reader := bytes.NewReader(b)
94 |
95 | err := binary.Read(reader, binary.LittleEndian, &m.challengeMessageFields)
96 | if err != nil {
97 | return err
98 | }
99 |
100 | if !m.challengeMessageFields.IsValid() {
101 | return errInvalidChallengeMessage
102 | }
103 |
104 | if ntlmsspRequestTarget.IsSet(m.NegotiateFlags) && m.TargetNameFields.Len > 0 {
105 | m.TargetName, err = m.TargetNameFields.ReadString(reader, ntlmsspNegotiateUnicode.IsSet(m.NegotiateFlags))
106 | if err != nil {
107 | return err
108 | }
109 | }
110 |
111 | m.TargetInfo = newTargetInfo()
112 |
113 | if ntlmsspNegotiateTargetInfo.IsSet(m.NegotiateFlags) && m.TargetInfoFields.Len > 0 {
114 | v, err := m.TargetInfoFields.ReadValue(reader)
115 | if err != nil {
116 | return err
117 | }
118 |
119 | if err := m.TargetInfo.Unmarshal(v); err != nil {
120 | return err
121 | }
122 | }
123 |
124 | if ntlmsspNegotiateVersion.IsSet(m.NegotiateFlags) {
125 | v := Version{}
126 | if err := v.unmarshal(reader); err != nil {
127 | return err
128 | }
129 | m.Version = &v
130 | }
131 |
132 | return nil
133 | }
134 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/client.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | type lmCompatibilityLevel int
9 |
10 | const (
11 | DefaultClientCompatibilityLevel lmCompatibilityLevel = 3
12 | )
13 |
14 | type Client struct {
15 | compatibilityLevel lmCompatibilityLevel
16 | defaultFlags uint32
17 | domain string
18 | password string
19 | pass_is_hash bool
20 | username string
21 | workstation string
22 | version *Version
23 |
24 | negotiatedFlags uint32
25 | negotiateMessage []byte
26 | challengeMessage []byte
27 | complete bool
28 | securitySession *SecuritySession
29 | }
30 |
31 | func realClientChallenge() ([]byte, error) {
32 | return nonce(8)
33 | }
34 |
35 | func realExportedSessionKey() ([]byte, error) {
36 | return nonce(16)
37 | }
38 |
39 | var (
40 | generateClientChallenge = realClientChallenge
41 | generateExportedSessionKey = realExportedSessionKey
42 | )
43 |
44 | func NewClient(options ...func(*Client) error) (*Client, error) {
45 | c := &Client{}
46 |
47 | // Set the defaults
48 | if err := c.SetOption(SetCompatibilityLevel(DefaultClientCompatibilityLevel)); err != nil {
49 | return nil, err
50 | }
51 |
52 | if err := c.SetOption(options...); err != nil {
53 | return nil, err
54 | }
55 |
56 | return c, nil
57 | }
58 |
59 | func defaultClientFlags() uint32 {
60 | flags := uint32(0)
61 |
62 | flags = ntlmsspNegotiateUnicode.Set(flags)
63 | flags = ntlmsspNegotiateSign.Set(flags)
64 | flags = ntlmsspNegotiateSeal.Set(flags)
65 | flags = ntlmsspNegotiateAlwaysSign.Set(flags)
66 | flags = ntlmsspNegotiateTargetInfo.Set(flags)
67 | flags = ntlmsspNegotiate128.Set(flags)
68 | flags = ntlmsspNegotiateKeyExch.Set(flags)
69 | flags = ntlmsspNegotiate56.Set(flags)
70 |
71 | return flags
72 | }
73 |
74 | func (c *Client) SetOption(options ...func(*Client) error) error {
75 | for _, option := range options {
76 | if err := option(c); err != nil {
77 | return err
78 | }
79 | }
80 | return nil
81 | }
82 |
83 | func SetCompatibilityLevel(level lmCompatibilityLevel) func(*Client) error {
84 | return func(c *Client) error {
85 | flags := defaultClientFlags()
86 |
87 | // Ideally these should be constants if there's official naming for them
88 | switch level {
89 | case 0: // LM Auth and NTLMv1 Auth
90 | flags = ntlmsspNegotiateLMKey.Set(flags)
91 | flags = ntlmsspNegotiateNTLM.Set(flags)
92 | case 1: // LM Auth and NTLMv1 Auth with Extended Session Security (NTLM2)
93 | flags = ntlmsspNegotiateNTLM.Set(flags)
94 | flags = ntlmsspNegotiateExtendedSessionsecurity.Set(flags)
95 | case 2: // NTLMv1 Auth with Extended Session Security (NTLM2)
96 | fallthrough
97 | case 3, 4, 5: // NTLMv2 Auth
98 | flags = ntlmsspNegotiateExtendedSessionsecurity.Set(flags)
99 | default:
100 | return fmt.Errorf("invalid compatibility level(0-5): %d", level)
101 | }
102 |
103 | c.compatibilityLevel = level
104 | c.defaultFlags = flags
105 |
106 | return nil
107 | }
108 | }
109 |
110 | func SetDomain(domain string) func(*Client) error {
111 | return func(c *Client) error {
112 | c.domain = strings.TrimSpace(domain)
113 | return nil
114 | }
115 | }
116 |
117 | func SetUserInfo(username, password string, pass_is_hash bool) func(*Client) error {
118 | return func(c *Client) error {
119 | c.username = username
120 | c.password = password
121 | c.pass_is_hash = pass_is_hash
122 | return nil
123 | }
124 | }
125 |
126 | func SetWorkstation(workstation string) func(*Client) error {
127 | return func(c *Client) error {
128 | c.workstation = strings.TrimSpace(workstation)
129 | return nil
130 | }
131 | }
132 |
133 | func SetVersion(version *Version) func(*Client) error {
134 | return func(c *Client) error {
135 | c.version = version
136 | return nil
137 | }
138 | }
139 |
140 | func (c *Client) Authenticate(input []byte, bindings *ChannelBindings) ([]byte, error) {
141 | if input != nil {
142 | return c.processChallengeMessage(input, bindings)
143 | }
144 | return c.newNegotiateMessage()
145 | }
146 |
147 | func (c *Client) newNegotiateMessage() ([]byte, error) {
148 | m := &negotiateMessage{
149 | negotiateMessageFields: negotiateMessageFields{
150 | messageHeader: newMessageHeader(ntLmNegotiate),
151 | NegotiateFlags: c.defaultFlags,
152 | },
153 | DomainName: c.domain,
154 | Workstation: c.workstation,
155 | Version: c.version,
156 | }
157 |
158 | b, err := m.Marshal()
159 | if err != nil {
160 | return nil, err
161 | }
162 |
163 | // Store the message bytes in case we need to generate a MIC
164 | c.negotiateMessage = b
165 |
166 | return b, nil
167 | }
168 |
169 | func (c *Client) processChallengeMessage(input []byte, bindings *ChannelBindings) ([]byte, error) {
170 | cm := &challengeMessage{}
171 | if err := cm.Unmarshal(input); err != nil {
172 | return nil, err
173 | }
174 |
175 | // Store the message bytes in case we need to generate a MIC
176 | c.challengeMessage = input
177 |
178 | c.negotiatedFlags = cm.NegotiateFlags
179 | if ntlmsspNegotiateUnicode.IsSet(c.negotiatedFlags) {
180 | c.negotiatedFlags = ntlmNegotiateOEM.Unset(c.negotiatedFlags)
181 | }
182 |
183 | // Set anonymous flag
184 | if c.username == "" && c.password == "" {
185 | c.negotiatedFlags = ntlmsspAnonymous.Set(c.negotiatedFlags)
186 | }
187 |
188 | clientChallenge, err := generateClientChallenge()
189 | if err != nil {
190 | return nil, err
191 | }
192 |
193 | lmChallengeResponse, err := lmChallengeResponse(c.negotiatedFlags, c.compatibilityLevel, clientChallenge, c.username, c.password, c.domain, c.pass_is_hash, cm)
194 | if err != nil {
195 | return nil, err
196 | }
197 |
198 | targetInfo, err := cm.TargetInfo.Clone()
199 | if err != nil {
200 | return nil, err
201 | }
202 |
203 | ntChallengeResponse, keyExchangeKey, err := ntChallengeResponse(c.negotiatedFlags, c.compatibilityLevel, clientChallenge, c.username, c.password, c.domain, c.pass_is_hash, cm, lmChallengeResponse, *targetInfo, bindings)
204 | if err != nil {
205 | return nil, err
206 | }
207 |
208 | var encryptedRandomSessionKey, exportedSessionKey []byte
209 |
210 | if ntlmsspNegotiateKeyExch.IsSet(c.negotiatedFlags) {
211 | exportedSessionKey, err = generateExportedSessionKey()
212 | if err != nil {
213 | return nil, err
214 | }
215 |
216 | encryptedRandomSessionKey, err = encryptRC4K(keyExchangeKey, exportedSessionKey)
217 | if err != nil {
218 | return nil, err
219 | }
220 | } else {
221 | exportedSessionKey = keyExchangeKey
222 | }
223 |
224 | if ntlmsspNegotiateSeal.IsSet(c.negotiatedFlags) || ntlmsspNegotiateSign.IsSet(c.negotiatedFlags) {
225 | c.securitySession, err = newSecuritySession(c.negotiatedFlags, exportedSessionKey, sourceClient)
226 | if err != nil {
227 | return nil, err
228 | }
229 | }
230 |
231 | am := &authenticateMessage{
232 | authenticateMessageFields: authenticateMessageFields{
233 | messageHeader: newMessageHeader(ntLmAuthenticate),
234 | NegotiateFlags: c.negotiatedFlags,
235 | },
236 | LmChallengeResponse: lmChallengeResponse,
237 | NtChallengeResponse: ntChallengeResponse,
238 | DomainName: c.domain,
239 | UserName: c.username,
240 | Workstation: c.workstation,
241 | EncryptedRandomSessionKey: encryptedRandomSessionKey,
242 | Version: c.version,
243 | // Needed for computing the MIC
244 | ExportedSessionKey: exportedSessionKey,
245 | TargetInfo: *targetInfo,
246 | }
247 |
248 | if err := am.UpdateMIC(concat(c.negotiateMessage, c.challengeMessage)); err != nil {
249 | return nil, err
250 | }
251 |
252 | b, err := am.Marshal()
253 | if err != nil {
254 | return nil, err
255 | }
256 |
257 | // Mark transaction as complete
258 | c.complete = true
259 |
260 | return b, nil
261 | }
262 |
263 | func (c *Client) Complete() bool {
264 | return c.complete
265 | }
266 |
267 | func (c *Client) SecuritySession() *SecuritySession {
268 | return c.securitySession
269 | }
270 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/crypto.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "crypto/des"
6 | "crypto/hmac"
7 | "crypto/md5"
8 | "crypto/rand"
9 | "crypto/rc4"
10 | "encoding/binary"
11 | "errors"
12 | "hash/crc32"
13 |
14 | "golang.org/x/crypto/md4"
15 | )
16 |
17 | func nonce(length int) ([]byte, error) {
18 | result := make([]byte, length)
19 | if _, err := rand.Read(result); err != nil {
20 | return nil, err
21 | }
22 |
23 | return result, nil
24 | }
25 |
26 | func createDESKey(b []byte) ([]byte, error) {
27 | if len(b) < 7 {
28 | return nil, errors.New("need at least 7 bytes")
29 | }
30 |
31 | key := zeroBytes(8)
32 |
33 | key[0] = b[0]
34 | key[1] = b[0]<<7 | b[1]>>1
35 | key[2] = b[1]<<6 | b[2]>>2
36 | key[3] = b[2]<<5 | b[3]>>3
37 | key[4] = b[3]<<4 | b[4]>>4
38 | key[5] = b[4]<<3 | b[5]>>5
39 | key[6] = b[5]<<2 | b[6]>>6
40 | key[7] = b[6] << 1
41 |
42 | // Calculate odd parity
43 | for i, x := range key {
44 | key[i] = (x & 0xfe) | ((((x >> 1) ^ (x >> 2) ^ (x >> 3) ^ (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7)) ^ 0x01) & 0x01)
45 | }
46 |
47 | return key, nil
48 | }
49 |
50 | func encryptDES(k, d []byte) ([]byte, error) {
51 | key, err := createDESKey(k)
52 | if err != nil {
53 | return nil, err
54 | }
55 |
56 | cipher, err := des.NewCipher(key)
57 | if err != nil {
58 | return nil, err
59 | }
60 |
61 | result := make([]byte, len(d))
62 | cipher.Encrypt(result, d)
63 |
64 | return result, nil
65 | }
66 |
67 | func encryptDESL(k, d []byte) ([]byte, error) {
68 | b := bytes.Buffer{}
69 |
70 | padded := zeroPad(k, 21)
71 |
72 | for _, i := range []int{0, 7, 14} {
73 | result, err := encryptDES(padded[i:], d)
74 | if err != nil {
75 | return nil, err
76 | }
77 |
78 | if _, err := b.Write(result); err != nil {
79 | return nil, err
80 | }
81 | }
82 |
83 | return b.Bytes(), nil
84 | }
85 |
86 | func initRC4(k []byte) (*rc4.Cipher, error) {
87 | cipher, err := rc4.NewCipher(k)
88 | if err != nil {
89 | return nil, err
90 | }
91 | return cipher, nil
92 | }
93 |
94 | func encryptRC4(cipher *rc4.Cipher, d []byte) []byte {
95 | result := make([]byte, len(d))
96 | cipher.XORKeyStream(result, d)
97 | return result
98 | }
99 |
100 | func encryptRC4K(k, d []byte) ([]byte, error) {
101 | cipher, err := initRC4(k)
102 | if err != nil {
103 | return nil, err
104 | }
105 | return encryptRC4(cipher, d), nil
106 | }
107 |
108 | func hashMD4(b []byte) []byte {
109 | md4 := md4.New()
110 | md4.Write(b)
111 |
112 | return md4.Sum(nil)
113 | }
114 |
115 | func hashMD5(b []byte) []byte {
116 | md5 := md5.New()
117 | md5.Write(b)
118 |
119 | return md5.Sum(nil)
120 | }
121 |
122 | func hmacMD5(k, m []byte) []byte {
123 | mac := hmac.New(md5.New, k)
124 | mac.Write(m)
125 |
126 | return mac.Sum(nil)
127 | }
128 |
129 | func hashCRC32(b []byte) []byte {
130 | checksum := make([]byte, 4)
131 | binary.LittleEndian.PutUint32(checksum, crc32.ChecksumIEEE(b))
132 | return checksum
133 | }
134 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/domain_posix.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package ntlmssp
4 |
5 | // DefaultDomain returns the Windows domain that the host is joined to. This
6 | // will never be successful on non-Windows as there's no standard API.
7 | func DefaultDomain() (string, error) {
8 | return "", nil
9 | }
10 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/domain_windows.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "syscall"
5 | "unsafe"
6 | )
7 |
8 | // DefaultDomain returns the Windows domain that the host is joined to. This
9 | // will never be successful on non-Windows as there's no standard API.
10 | func DefaultDomain() (string, error) {
11 | var domain *uint16
12 | var status uint32
13 | err := syscall.NetGetJoinInformation(nil, &domain, &status)
14 | if err != nil {
15 | return "", err
16 | }
17 | defer syscall.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))
18 |
19 | // Not joined to a domain
20 | if status != syscall.NetSetupDomainName {
21 | return "", nil
22 | }
23 |
24 | return syscall.UTF16ToString((*[1024]uint16)(unsafe.Pointer(domain))[:]), nil
25 | }
26 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/flags.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import "strings"
4 |
5 | type negotiateFlag uint32
6 |
7 | const (
8 | ntlmsspNegotiateUnicode negotiateFlag = 1 << iota
9 | ntlmNegotiateOEM
10 | ntlmsspRequestTarget
11 | _
12 | ntlmsspNegotiateSign
13 | ntlmsspNegotiateSeal
14 | ntlmsspNegotiateDatagram
15 | ntlmsspNegotiateLMKey
16 | _
17 | ntlmsspNegotiateNTLM
18 | _
19 | ntlmsspAnonymous
20 | ntlmsspNegotiateOEMDomainSupplied
21 | ntlmsspNegotiateOEMWorkstationSupplied
22 | _
23 | ntlmsspNegotiateAlwaysSign
24 | ntlmsspTargetTypeDomain
25 | ntlmsspTargetTypeServer
26 | _
27 | ntlmsspNegotiateExtendedSessionsecurity
28 | ntlmsspNegotiateIdentity
29 | _
30 | ntlmsspRequestNonNTSessionKey
31 | ntlmsspNegotiateTargetInfo
32 | _
33 | ntlmsspNegotiateVersion
34 | _
35 | _
36 | _
37 | ntlmsspNegotiate128
38 | ntlmsspNegotiateKeyExch
39 | ntlmsspNegotiate56
40 | )
41 |
42 | func (f negotiateFlag) Set(flags uint32) uint32 {
43 | return flags | uint32(f)
44 | }
45 |
46 | func (f negotiateFlag) IsSet(flags uint32) bool {
47 | return (flags & uint32(f)) != 0
48 | }
49 |
50 | func (f negotiateFlag) Unset(flags uint32) uint32 {
51 | return flags & ^uint32(f)
52 | }
53 |
54 | func (f negotiateFlag) String() string {
55 | strings := map[negotiateFlag]string{
56 | ntlmsspNegotiateUnicode: "NTLMSSP_NEGOTIATE_UNICODE",
57 | ntlmNegotiateOEM: "NTLM_NEGOTIATE_OEM",
58 | ntlmsspRequestTarget: "NTLMSSP_REQUEST_TARGET",
59 | ntlmsspNegotiateSign: "NTLMSSP_NEGOTIATE_SIGN",
60 | ntlmsspNegotiateSeal: "NTLMSSP_NEGOTIATE_SEAL",
61 | ntlmsspNegotiateDatagram: "NTLMSSP_NEGOTIATE_DATAGRAM",
62 | ntlmsspNegotiateLMKey: "NTLMSSP_NEGOTIATE_LM_KEY",
63 | ntlmsspNegotiateNTLM: "NTLMSSP_NEGOTIATE_NTLM",
64 | ntlmsspAnonymous: "NTLMSSP_ANONYMOUS",
65 | ntlmsspNegotiateOEMDomainSupplied: "NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED",
66 | ntlmsspNegotiateOEMWorkstationSupplied: "NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED",
67 | ntlmsspNegotiateAlwaysSign: "NTLMSSP_NEGOTIATE_ALWAYS_SIGN",
68 | ntlmsspTargetTypeDomain: "NTLMSSP_TARGET_TYPE_DOMAIN",
69 | ntlmsspTargetTypeServer: "NTLMSSP_TARGET_TYPE_SERVER",
70 | ntlmsspNegotiateExtendedSessionsecurity: "NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY",
71 | ntlmsspNegotiateIdentity: "NTLMSSP_NEGOTIATE_IDENTITY",
72 | ntlmsspRequestNonNTSessionKey: "NTLMSSP_REQUEST_NON_NT_SESSION_KEY",
73 | ntlmsspNegotiateTargetInfo: "NTLMSSP_NEGOTIATE_TARGET_INFO",
74 | ntlmsspNegotiateVersion: "NTLMSSP_NEGOTIATE_VERSION",
75 | ntlmsspNegotiate128: "NTLMSSP_NEGOTIATE_128",
76 | ntlmsspNegotiateKeyExch: "NTLMSSP_NEGOTIATE_KEY_EXCH",
77 | ntlmsspNegotiate56: "NTLMSSP_NEGOTIATE_56",
78 | }
79 |
80 | return strings[f]
81 | }
82 |
83 | func flagsToString(flags uint32) string {
84 | var set []string
85 |
86 | for _, f := range [...]negotiateFlag{
87 | ntlmsspNegotiateUnicode,
88 | ntlmNegotiateOEM,
89 | ntlmsspRequestTarget,
90 | ntlmsspNegotiateSign,
91 | ntlmsspNegotiateSeal,
92 | ntlmsspNegotiateDatagram,
93 | ntlmsspNegotiateLMKey,
94 | ntlmsspNegotiateNTLM,
95 | ntlmsspAnonymous,
96 | ntlmsspNegotiateOEMDomainSupplied,
97 | ntlmsspNegotiateOEMWorkstationSupplied,
98 | ntlmsspNegotiateAlwaysSign,
99 | ntlmsspTargetTypeDomain,
100 | ntlmsspTargetTypeServer,
101 | ntlmsspNegotiateExtendedSessionsecurity,
102 | ntlmsspNegotiateIdentity,
103 | ntlmsspRequestNonNTSessionKey,
104 | ntlmsspNegotiateTargetInfo,
105 | ntlmsspNegotiateVersion,
106 | ntlmsspNegotiate128,
107 | ntlmsspNegotiateKeyExch,
108 | ntlmsspNegotiate56,
109 | } {
110 | if f.IsSet(flags) {
111 | set = append(set, f.String())
112 | }
113 | }
114 |
115 | return strings.Join(set, " | ")
116 | }
117 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/http/client.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "encoding/binary"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "net/http"
11 | "net/http/cookiejar"
12 | "net/textproto"
13 | "net/url"
14 | "strings"
15 |
16 | "main/winrmntlm/ntlmssp"
17 |
18 | "github.com/go-logr/logr"
19 | "github.com/hashicorp/go-cleanhttp"
20 | )
21 |
22 | var (
23 | httpAuthenticateHeader = textproto.CanonicalMIMEHeaderKey("WWW-Authenticate")
24 | )
25 |
26 | type Client struct {
27 | http *http.Client
28 | ntlm *ntlmssp.Client
29 | encryption bool
30 | sendCBT bool
31 | logger logr.Logger
32 | }
33 |
34 | type teeReadCloser struct {
35 | io.Reader
36 | io.Closer
37 | }
38 |
39 | func NewClient(httpClient *http.Client, ntlmClient *ntlmssp.Client, options ...func(*Client) error) (*Client, error) {
40 | if httpClient == nil {
41 | httpClient = cleanhttp.DefaultClient()
42 | }
43 | if httpClient.Jar == nil {
44 | httpClient.Jar, _ = cookiejar.New(nil)
45 | }
46 | if httpClient.Transport != nil && httpClient.Transport.(*http.Transport).DisableKeepAlives {
47 | return nil, errors.New("NTLM cannot work without keepalives")
48 | }
49 |
50 | // FIXME CheckRedirect
51 |
52 | if ntlmClient == nil {
53 | domain, err := ntlmssp.DefaultDomain()
54 | if err != nil {
55 | return nil, err
56 | }
57 |
58 | workstation, err := ntlmssp.DefaultWorkstation()
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | ntlmClient, _ = ntlmssp.NewClient(ntlmssp.SetDomain(domain), ntlmssp.SetWorkstation(workstation), ntlmssp.SetVersion(ntlmssp.DefaultVersion()))
64 | }
65 |
66 | c := &Client{
67 | http: httpClient,
68 | ntlm: ntlmClient,
69 | logger: logr.Discard(),
70 | }
71 |
72 | if err := c.SetOption(options...); err != nil {
73 | return nil, err
74 | }
75 |
76 | return c, nil
77 | }
78 |
79 | func (c *Client) SetOption(options ...func(*Client) error) error {
80 | for _, option := range options {
81 | if err := option(c); err != nil {
82 | return err
83 | }
84 | }
85 | return nil
86 | }
87 |
88 | func SendCBT(value bool) func(*Client) error {
89 | return func(c *Client) error {
90 | c.sendCBT = value
91 | return nil
92 | }
93 | }
94 |
95 | func Encryption(value bool) func(*Client) error {
96 | return func(c *Client) error {
97 | c.encryption = value
98 | return nil
99 | }
100 | }
101 |
102 | func Logger(logger logr.Logger) func(*Client) error {
103 | return func(c *Client) error {
104 | c.logger = logger
105 | return nil
106 | }
107 | }
108 |
109 | func (c *Client) wrap(req *http.Request) error {
110 | if session := c.ntlm.SecuritySession(); c.ntlm.Complete() && c.encryption && session != nil && req.Body != nil {
111 |
112 | contentType := req.Header.Get(contentTypeHeader)
113 | if contentType == "" {
114 | return errors.New("no Content-Type header")
115 | }
116 |
117 | body, err := io.ReadAll(req.Body)
118 | if err != nil {
119 | return err
120 | }
121 |
122 | sealed, signature, err := session.Wrap(body)
123 | if err != nil {
124 | return err
125 | }
126 |
127 | length := make([]byte, 4)
128 | binary.LittleEndian.PutUint32(length, uint32(len(signature)))
129 |
130 | body, newContentType, err := Wrap(concat(length, signature, sealed), contentType)
131 | if err != nil {
132 | return err
133 | }
134 |
135 | req.Body = io.NopCloser(bytes.NewBuffer(body))
136 | req.Header.Set(contentTypeHeader, newContentType)
137 | }
138 |
139 | return nil
140 | }
141 |
142 | func (c *Client) unwrap(resp *http.Response) error {
143 | if session := c.ntlm.SecuritySession(); c.ntlm.Complete() && c.encryption && session != nil && resp.Body != nil {
144 |
145 | contentType := resp.Header.Get(contentTypeHeader)
146 | if contentType == "" {
147 | return errors.New("no Content-Type header")
148 | }
149 |
150 | sealed, err := io.ReadAll(resp.Body)
151 | if err != nil {
152 | return err
153 | }
154 | defer resp.Body.Close()
155 |
156 | data, newContentType, err := Unwrap(sealed, contentType)
157 | if err != nil {
158 | return err
159 | }
160 |
161 | length := binary.LittleEndian.Uint32(data[:4])
162 |
163 | signature := make([]byte, length)
164 | copy(signature, data[4:4+length])
165 |
166 | body, err := session.Unwrap(data[4+length:], signature)
167 | if err != nil {
168 | return err
169 | }
170 |
171 | resp.Body = io.NopCloser(bytes.NewBuffer(body))
172 | resp.Header.Set(contentTypeHeader, newContentType)
173 | resp.Header.Set("Content-Length", fmt.Sprint(len(body)))
174 | }
175 |
176 | return nil
177 | }
178 |
179 | func (c *Client) Do(req *http.Request) (resp *http.Response, err error) {
180 |
181 | // Potentially replace the request body with a signed and sealed copy
182 | if err := c.wrap(req); err != nil {
183 | return nil, err
184 | }
185 |
186 | var body bytes.Buffer
187 |
188 | if req.Body != nil {
189 | tr := io.TeeReader(req.Body, &body)
190 | req.Body = teeReadCloser{tr, req.Body}
191 | }
192 |
193 | c.logger.Info("request", req)
194 |
195 | resp, err = c.http.Do(req)
196 | if err != nil {
197 | return nil, err
198 | }
199 |
200 | if c.ntlm.Complete() || resp.StatusCode != http.StatusUnauthorized {
201 | // Potentially unseal and check signature
202 | if err := c.unwrap(resp); err != nil {
203 | return nil, err
204 | }
205 |
206 | return resp, nil
207 | }
208 |
209 | for i := 0; i < 2; i++ {
210 |
211 | ok, input, err := isAuthenticationMethod(resp.Header, "Negotiate")
212 | if err != nil {
213 | return nil, err
214 | }
215 |
216 | if !ok {
217 | return resp, nil
218 | }
219 |
220 | var cbt *ntlmssp.ChannelBindings
221 |
222 | if c.sendCBT && resp.TLS != nil {
223 | cbt = generateChannelBindings(resp.TLS.PeerCertificates[0]) // Presume it's the first one?
224 | }
225 |
226 | b, err := c.ntlm.Authenticate(input, cbt)
227 | if err != nil {
228 | return nil, err
229 | }
230 | req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(b))
231 |
232 | if req.Body != nil {
233 | req.Body = io.NopCloser(&body)
234 | }
235 |
236 | c.logger.Info("request", req)
237 |
238 | resp, err = c.http.Do(req)
239 | if err != nil {
240 | return nil, err
241 | }
242 |
243 | if resp.StatusCode != http.StatusUnauthorized {
244 | return resp, nil
245 | }
246 | }
247 |
248 | return resp, nil
249 | }
250 |
251 | func isAuthenticationMethod(headers http.Header, method string) (bool, []byte, error) {
252 | if h, ok := headers[httpAuthenticateHeader]; ok {
253 | for _, x := range h {
254 | if x == method {
255 | return true, nil, nil
256 | }
257 | if strings.HasPrefix(x, method+" ") {
258 | parts := strings.SplitN(x, " ", 2)
259 | if len(parts) < 2 {
260 | return true, nil, errors.New("malformed " + method + " header value")
261 | }
262 | b, err := base64.StdEncoding.DecodeString(parts[1])
263 | return true, b, err
264 | }
265 | }
266 | }
267 | return false, nil, nil
268 | }
269 |
270 | func (c *Client) Get(url string) (resp *http.Response, err error) {
271 | req, err := http.NewRequest("GET", url, nil)
272 | if err != nil {
273 | return nil, err
274 | }
275 | return c.Do(req)
276 | }
277 |
278 | func (c *Client) Head(url string) (resp *http.Response, err error) {
279 | req, err := http.NewRequest("HEAD", url, nil)
280 | if err != nil {
281 | return nil, err
282 | }
283 | return c.Do(req)
284 | }
285 |
286 | func (c *Client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
287 | req, err := http.NewRequest("POST", url, body)
288 | if err != nil {
289 | return nil, err
290 | }
291 | req.Header.Set("Content-Type", contentType)
292 | return c.Do(req)
293 | }
294 |
295 | func (c *Client) PostForm(url string, data url.Values) (resp *http.Response, err error) {
296 | return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
297 | }
298 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/http/crypto.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "crypto"
5 | "crypto/x509"
6 | "hash"
7 |
8 | "main/winrmntlm/ntlmssp"
9 | )
10 |
11 | func signatureAlgorithmHash(algo x509.SignatureAlgorithm) crypto.Hash {
12 | for _, details := range []struct {
13 | algo x509.SignatureAlgorithm
14 | hash crypto.Hash
15 | }{
16 | {x509.MD2WithRSA, crypto.Hash(0)},
17 | {x509.MD5WithRSA, crypto.MD5},
18 | {x509.SHA1WithRSA, crypto.SHA1},
19 | {x509.SHA256WithRSA, crypto.SHA256},
20 | {x509.SHA384WithRSA, crypto.SHA384},
21 | {x509.SHA512WithRSA, crypto.SHA512},
22 | {x509.DSAWithSHA1, crypto.SHA1},
23 | {x509.DSAWithSHA256, crypto.SHA256},
24 | {x509.ECDSAWithSHA1, crypto.SHA1},
25 | {x509.ECDSAWithSHA256, crypto.SHA256},
26 | {x509.ECDSAWithSHA384, crypto.SHA384},
27 | {x509.ECDSAWithSHA512, crypto.SHA512},
28 | {x509.SHA256WithRSAPSS, crypto.SHA256},
29 | {x509.SHA384WithRSAPSS, crypto.SHA384},
30 | {x509.SHA512WithRSAPSS, crypto.SHA512},
31 | } {
32 | if details.algo == algo {
33 | return details.hash
34 | }
35 | }
36 | return crypto.Hash(0)
37 | }
38 |
39 | func generateCertificateHash(cert *x509.Certificate) []byte {
40 | algorithm := signatureAlgorithmHash(cert.SignatureAlgorithm)
41 |
42 | var hash hash.Hash
43 |
44 | switch algorithm {
45 | case crypto.Hash(0):
46 | return nil
47 | case crypto.MD5, crypto.SHA1:
48 | hash = crypto.SHA256.New()
49 | default:
50 | hash = algorithm.New()
51 | }
52 |
53 | hash.Write(cert.Raw)
54 |
55 | return hash.Sum(nil)
56 | }
57 |
58 | func generateChannelBindings(cert *x509.Certificate) *ntlmssp.ChannelBindings {
59 | b := generateCertificateHash(cert)
60 | if b == nil {
61 | return nil
62 | }
63 |
64 | return &ntlmssp.ChannelBindings{
65 | ApplicationData: concat([]byte(ntlmssp.TLSServerEndPoint+":"), b),
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/http/mime.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "mime/multipart"
10 | "net/textproto"
11 | "strings"
12 |
13 | "github.com/tidwall/transform"
14 | )
15 |
16 | const (
17 | mimeBoundary string = "Encrypted Boundary"
18 | mimeProtocol string = "application/HTTP-SPNEGO-session-encrypted"
19 | dashBoundary string = "--" + mimeBoundary
20 | contentTypeHeader string = "Content-Type"
21 | contentTypeValue string = "multipart/encrypted;protocol=\"" + mimeProtocol + "\";boundary=\"" + mimeBoundary + "\""
22 | octetStream string = "application/octet-stream"
23 | )
24 |
25 | func isHeader(line []byte) bool {
26 | b := make([]byte, len(line))
27 | copy(b, line)
28 | b = append(b, '\r', '\n')
29 | r := textproto.NewReader(bufio.NewReader(bytes.NewBuffer(b)))
30 | _, err := r.ReadMIMEHeader()
31 | if err != nil {
32 | return false
33 | }
34 | return true
35 | }
36 |
37 | func toWSMV(r io.Reader) io.Reader {
38 | br := bufio.NewReader(r)
39 | return transform.NewTransformer(func() ([]byte, error) {
40 | for {
41 | line, err := br.ReadBytes('\n')
42 | if err != nil {
43 | return nil, err
44 | }
45 |
46 | switch {
47 | case bytes.Equal(line, []byte{'\r', '\n'}):
48 | break
49 | case bytes.HasPrefix(line, []byte(dashBoundary)):
50 | return line, nil
51 | case isHeader(line):
52 | return append([]byte{'\t'}, line...), nil
53 | default:
54 | next, err := br.Peek(len(dashBoundary))
55 | if err != nil {
56 | return nil, err
57 | }
58 | if bytes.Equal(next, []byte(dashBoundary)) {
59 | return line[:len(line)-2], nil
60 | }
61 | return line, nil
62 | }
63 | }
64 | })
65 | }
66 |
67 | func fromWSMV(r io.Reader) io.Reader {
68 | br := bufio.NewReader(r)
69 | header := false
70 | return transform.NewTransformer(func() ([]byte, error) {
71 | for {
72 | line, err := br.ReadBytes('\n')
73 | if err != nil {
74 | return nil, err
75 | }
76 |
77 | switch {
78 | case bytes.HasPrefix(line, []byte{'\t'}) && isHeader(line[1:]):
79 | header = true
80 | return line[1:], nil
81 | default:
82 | if header {
83 | line = append([]byte{'\r', '\n'}, line...)
84 | header = false
85 | }
86 | if bytes.Contains(line[1:], []byte(dashBoundary)) {
87 | for i := 0; i < len(line); i++ {
88 | if bytes.HasPrefix(line[i:], []byte(dashBoundary)) {
89 | line = append(line[:i], append([]byte{'\r', '\n'}, line[i:]...)...)
90 | break
91 | }
92 | }
93 | }
94 | return line, nil
95 | }
96 | }
97 | })
98 | }
99 |
100 | func Wrap(input []byte, ct string) ([]byte, string, error) {
101 | b := &bytes.Buffer{}
102 |
103 | writer := multipart.NewWriter(b)
104 | if err := writer.SetBoundary(mimeBoundary); err != nil {
105 | return nil, "", err
106 | }
107 |
108 | header := make(textproto.MIMEHeader)
109 | header.Set(contentTypeHeader, mimeProtocol)
110 | // Using Set() will canonicalise this to "Originalcontent" which may cause problems
111 | header["OriginalContent"] = []string{fmt.Sprintf("type=%s;Length=%d", ct, len(input))}
112 |
113 | if _, err := writer.CreatePart(header); err != nil {
114 | return nil, "", err
115 | }
116 |
117 | body := make(textproto.MIMEHeader)
118 | body.Set(contentTypeHeader, octetStream)
119 |
120 | part, err := writer.CreatePart(body)
121 | if err != nil {
122 | return nil, "", err
123 | }
124 |
125 | if _, err := io.Copy(part, bytes.NewBuffer(input)); err != nil {
126 | return nil, "", err
127 | }
128 |
129 | writer.Close()
130 |
131 | output, err := io.ReadAll(toWSMV(b))
132 | if err != nil {
133 | return nil, "", err
134 | }
135 |
136 | return output, contentTypeValue, nil
137 | }
138 |
139 | func Unwrap(input []byte, ct string) ([]byte, string, error) {
140 | if ct != contentTypeValue {
141 | return nil, "", errors.New("incorrect Content-Type value")
142 | }
143 |
144 | reader := multipart.NewReader(fromWSMV(bytes.NewBuffer(input)), mimeBoundary)
145 | output := bytes.Buffer{}
146 |
147 | var originalContent string
148 |
149 | Loop:
150 | for i := 0; true; i++ {
151 | part, err := reader.NextPart()
152 | switch err {
153 | case nil:
154 | break
155 | case io.EOF:
156 | break Loop
157 | default:
158 | return nil, "", err
159 | }
160 |
161 | switch i {
162 | case 0:
163 | if part.Header.Get(contentTypeHeader) != mimeProtocol {
164 | return nil, "", errors.New("incorrect Content-Type value")
165 | }
166 | if originalContent = part.Header.Get("OriginalContent"); originalContent == "" {
167 | return nil, "", errors.New("missing OriginalContent header")
168 | }
169 | case 1:
170 | if part.Header.Get(contentTypeHeader) != octetStream {
171 | return nil, "", errors.New("incorrect Content-Type value")
172 | }
173 | if _, err := output.ReadFrom(part); err != nil {
174 | return nil, "", err
175 | }
176 | default:
177 | return nil, "", errors.New("additional MIME parts encountered")
178 | }
179 | }
180 |
181 | // TODO Better way of parsing this
182 | parts := strings.Split(originalContent, ";")
183 |
184 | return output.Bytes(), parts[0][5:] + ";" + parts[1], nil
185 | }
186 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/http/util.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import "bytes"
4 |
5 | func concat(bs ...[]byte) []byte {
6 | return bytes.Join(bs, nil)
7 | }
8 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/message_header.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import "bytes"
4 |
5 | const (
6 | ntLmNegotiate uint32 = iota + 1
7 | ntLmChallenge
8 | ntLmAuthenticate
9 | )
10 |
11 | type messageHeader struct {
12 | Signature [8]byte
13 | MessageType uint32
14 | }
15 |
16 | var signature = [8]byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}
17 |
18 | func (h messageHeader) IsValid() bool {
19 | return bytes.Equal(h.Signature[:], signature[:]) &&
20 | h.MessageType >= ntLmNegotiate && h.MessageType <= ntLmAuthenticate
21 | }
22 |
23 | func newMessageHeader(messageType uint32) messageHeader {
24 | return messageHeader{signature, messageType}
25 | }
26 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/negotiate_message.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "unsafe"
8 | )
9 |
10 | var (
11 | errInvalidNegotiateMessage = errors.New("invalid NTLM negotiate message")
12 | )
13 |
14 | type negotiateMessageFields struct {
15 | messageHeader
16 | NegotiateFlags uint32
17 | DomainNameFields payload
18 | WorkstationFields payload
19 | }
20 |
21 | func (m *negotiateMessageFields) IsValid() bool {
22 | return m.messageHeader.IsValid() && m.MessageType == ntLmNegotiate
23 | }
24 |
25 | type negotiateMessage struct {
26 | negotiateMessageFields
27 | DomainName string
28 | Workstation string
29 | Version *Version
30 | }
31 |
32 | func (m *negotiateMessage) Marshal() ([]byte, error) {
33 | offset := int(unsafe.Sizeof(m.negotiateMessageFields) + unsafe.Sizeof(*m.Version))
34 |
35 | m.NegotiateFlags = ntlmsspNegotiateOEMDomainSupplied.Unset(m.NegotiateFlags)
36 | if m.DomainName != "" {
37 | m.NegotiateFlags = ntlmsspNegotiateOEMDomainSupplied.Set(m.NegotiateFlags)
38 | }
39 |
40 | m.NegotiateFlags = ntlmsspNegotiateOEMWorkstationSupplied.Unset(m.NegotiateFlags)
41 | if m.Workstation != "" {
42 | m.NegotiateFlags = ntlmsspNegotiateOEMWorkstationSupplied.Set(m.NegotiateFlags)
43 | }
44 |
45 | // Always OEM
46 | m.DomainNameFields = newPayload(len(m.DomainName), &offset)
47 | m.WorkstationFields = newPayload(len(m.Workstation), &offset)
48 |
49 | var version = &Version{}
50 |
51 | m.NegotiateFlags = ntlmsspNegotiateVersion.Unset(m.NegotiateFlags)
52 | if m.Version != nil {
53 | m.NegotiateFlags = ntlmsspNegotiateVersion.Set(m.NegotiateFlags)
54 | version = m.Version
55 | }
56 |
57 | b := bytes.Buffer{}
58 | if err := binary.Write(&b, binary.LittleEndian, &m.negotiateMessageFields); err != nil {
59 | return nil, err
60 | }
61 |
62 | v, err := version.marshal()
63 | if err != nil {
64 | return nil, err
65 | }
66 | b.Write(v)
67 |
68 | // Always OEM
69 | // XXX Should they be forced uppercase?
70 | b.WriteString(m.DomainName + m.Workstation)
71 |
72 | return b.Bytes(), nil
73 | }
74 |
75 | func (m *negotiateMessage) Unmarshal(b []byte) error {
76 | reader := bytes.NewReader(b)
77 |
78 | err := binary.Read(reader, binary.LittleEndian, &m.negotiateMessageFields)
79 | if err != nil {
80 | return err
81 | }
82 |
83 | if !m.negotiateMessageFields.IsValid() {
84 | return errInvalidNegotiateMessage
85 | }
86 |
87 | if ntlmsspNegotiateOEMDomainSupplied.IsSet(m.NegotiateFlags) && m.DomainNameFields.Len > 0 {
88 | m.DomainName, err = m.DomainNameFields.ReadString(reader, false) // Always OEM
89 | if err != nil {
90 | return err
91 | }
92 | }
93 |
94 | if ntlmsspNegotiateOEMWorkstationSupplied.IsSet(m.NegotiateFlags) && m.WorkstationFields.Len > 0 {
95 | m.Workstation, err = m.WorkstationFields.ReadString(reader, false) // Always OEM
96 | if err != nil {
97 | return err
98 | }
99 | }
100 |
101 | if ntlmsspNegotiateVersion.IsSet(m.NegotiateFlags) {
102 | v := Version{}
103 | if err := v.unmarshal(reader); err != nil {
104 | return err
105 | }
106 | m.Version = &v
107 | }
108 |
109 | return nil
110 | }
111 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/ntlm.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "encoding/hex"
7 | "strings"
8 | "time"
9 |
10 | "github.com/bodgit/windows"
11 | "golang.org/x/crypto/md4"
12 | )
13 |
14 | const (
15 | lmCiphertext string = "KGS!@#$%"
16 | )
17 |
18 | func realCurrentTime() ([]byte, error) {
19 | ft := windows.NsecToFiletime(time.Now().UnixNano())
20 |
21 | b := bytes.Buffer{}
22 | if err := binary.Write(&b, binary.LittleEndian, ft); err != nil {
23 | return nil, err
24 | }
25 |
26 | return b.Bytes(), nil
27 | }
28 |
29 | var currentTime = realCurrentTime
30 |
31 | func lmowfV1(password string) ([]byte, error) {
32 | b := bytes.Buffer{}
33 |
34 | padded := zeroPad([]byte(strings.ToUpper(password)), 14)
35 |
36 | for _, i := range []int{0, 7} {
37 | result, err := encryptDES(padded[i:], []byte(lmCiphertext))
38 | if err != nil {
39 | return nil, err
40 | }
41 |
42 | b.Write(result)
43 | }
44 |
45 | return b.Bytes(), nil
46 | }
47 |
48 | func ntowfV1(password string) ([]byte, error) {
49 | b, err := utf16FromString(password)
50 | if err != nil {
51 | return nil, err
52 | }
53 | return hashMD4(b), nil
54 | }
55 |
56 | func ntowfV2(username, password, domain string, pass_is_hash bool) ([]byte, error) {
57 | var k []byte
58 | m, err := utf16FromString(strings.ToUpper(username) + domain)
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | if pass_is_hash {
64 | k, err = hex.DecodeString(password)
65 | } else {
66 | k, err = ntowfV1(password)
67 | }
68 |
69 | if err != nil {
70 | return nil, err
71 | }
72 |
73 | return hmacMD5(k, m), nil
74 | }
75 |
76 | func lmV1WithSessionSecurityResponse(clientChallenge []byte) []byte {
77 | return zeroPad(clientChallenge, 24)
78 | }
79 |
80 | func lmV1Response(password string, serverChallenge []byte) ([]byte, error) {
81 | lmHash, err := lmowfV1(password)
82 | if err != nil {
83 | return nil, err
84 | }
85 | return encryptDESL(lmHash, serverChallenge)
86 | }
87 |
88 | func lmV2Response(username, password, domain string, pass_is_hash bool, serverChallenge, clientChallenge []byte) ([]byte, error) {
89 | ntlmHash, err := ntowfV2(username, password, domain, pass_is_hash)
90 | if err != nil {
91 | return nil, err
92 | }
93 | return concat(hmacMD5(ntlmHash, concat(serverChallenge, clientChallenge)), clientChallenge), nil
94 | }
95 |
96 | func ntlmV1Response(password string, serverChallenge []byte) ([]byte, []byte, error) {
97 | ntlmHash, err := ntowfV1(password)
98 | if err != nil {
99 | return nil, nil, err
100 | }
101 |
102 | response, err := encryptDESL(ntlmHash, serverChallenge)
103 | if err != nil {
104 | return nil, nil, err
105 | }
106 |
107 | return response, hashMD4(ntlmHash), nil
108 | }
109 |
110 | func ntlm2Response(password string, serverChallenge, clientChallenge []byte) ([]byte, []byte, error) {
111 | ntlmHash, err := ntowfV1(password)
112 | if err != nil {
113 | return nil, nil, err
114 | }
115 |
116 | response, err := encryptDESL(ntlmHash, hashMD5(concat(serverChallenge, clientChallenge))[:8])
117 | if err != nil {
118 | return nil, nil, err
119 | }
120 |
121 | return response, hashMD4(ntlmHash), nil
122 | }
123 |
124 | func ntlmV2Temp(timestamp []byte, clientChallenge []byte, targetInfo targetInfo) ([]byte, error) {
125 | b, err := targetInfo.Marshal()
126 | if err != nil {
127 | return nil, err
128 | }
129 |
130 | return concat([]byte{0x01}, []byte{0x01}, zeroBytes(6), timestamp, clientChallenge, zeroBytes(4), b, zeroBytes(4)), nil
131 | }
132 |
133 | func ntlmV2Response(username, password, domain string, pass_is_hash bool, serverChallenge, clientChallenge []byte, timestamp []byte, targetInfo targetInfo) ([]byte, []byte, error) {
134 | ntlmHash, err := ntowfV2(username, password, domain, pass_is_hash)
135 | if err != nil {
136 | return nil, nil, err
137 | }
138 |
139 | temp, err := ntlmV2Temp(timestamp, clientChallenge, targetInfo)
140 | if err != nil {
141 | return nil, nil, err
142 | }
143 |
144 | ntProofStr := hmacMD5(ntlmHash, concat(serverChallenge, temp))
145 |
146 | return concat(ntProofStr, temp), hmacMD5(ntlmHash, ntProofStr), nil
147 | }
148 |
149 | func ntlmV1ExchangeKey(flags uint32, sessionBaseKey []byte, serverChallenge []byte, lmChallengeResponse []byte, lmHash []byte) ([]byte, error) {
150 | switch {
151 | case ntlmsspNegotiateExtendedSessionsecurity.IsSet(flags):
152 | return hmacMD5(sessionBaseKey, concat(serverChallenge, lmChallengeResponse[:8])), nil
153 | case ntlmsspNegotiateLMKey.IsSet(flags):
154 | b := bytes.Buffer{}
155 |
156 | for _, k := range [][]byte{lmHash[:7], concat(lmHash[7:8], bytes.Repeat([]byte{0xbd}, 6))} {
157 | result, err := encryptDES(k, lmChallengeResponse[:8])
158 | if err != nil {
159 | return nil, err
160 | }
161 |
162 | b.Write(result)
163 | }
164 |
165 | return b.Bytes(), nil
166 | case ntlmsspRequestNonNTSessionKey.IsSet(flags):
167 | return zeroPad(lmHash[:8], 16), nil
168 | default:
169 | return sessionBaseKey, nil
170 | }
171 | }
172 |
173 | func lmChallengeResponse(flags uint32, level lmCompatibilityLevel, clientChallenge []byte, username, password, domain string, pass_is_hash bool, cm *challengeMessage) ([]byte, error) {
174 | switch {
175 | case ntlmsspAnonymous.IsSet(flags):
176 | return zeroBytes(1), nil
177 | case ntlmsspNegotiateExtendedSessionsecurity.IsSet(flags) && level < 3:
178 | // LMv1 with session security
179 | return lmV1WithSessionSecurityResponse(clientChallenge), nil
180 | case level < 2:
181 | // LMv1 response
182 | return lmV1Response(password, cm.ServerChallenge[:])
183 | case level == 2:
184 | // NTLMv1 response
185 | response, _, err := ntlmV1Response(password, cm.ServerChallenge[:])
186 | return response, err
187 | default:
188 | // LMv2 response
189 | if _, ok := cm.TargetInfo.Get(msvAvTimestamp); ok {
190 | return zeroBytes(24), nil
191 | }
192 | return lmV2Response(username, password, domain, pass_is_hash, cm.ServerChallenge[:], clientChallenge)
193 | }
194 | }
195 |
196 | func ntChallengeResponse(flags uint32, level lmCompatibilityLevel, clientChallenge []byte, username, password, domain string, pass_is_hash bool, cm *challengeMessage, lmChallengeResponse []byte, targetInfo targetInfo, channelBindings *ChannelBindings) ([]byte, []byte, error) {
197 | switch {
198 | case ntlmsspAnonymous.IsSet(flags):
199 | return []byte{}, zeroBytes(md4.Size), nil
200 | case level < 3:
201 | var response, sessionBaseKey []byte
202 | var err error
203 |
204 | if ntlmsspNegotiateExtendedSessionsecurity.IsSet(flags) {
205 | // NTLMv1 authentication with NTLM2
206 | response, sessionBaseKey, err = ntlm2Response(password, cm.ServerChallenge[:], clientChallenge)
207 | } else {
208 | // NTLMv1 authentication
209 | response, sessionBaseKey, err = ntlmV1Response(password, cm.ServerChallenge[:])
210 | }
211 | if err != nil {
212 | return nil, nil, err
213 | }
214 |
215 | lmHash, err := lmowfV1(password)
216 | if err != nil {
217 | return nil, nil, err
218 | }
219 |
220 | keyExchangeKey, err := ntlmV1ExchangeKey(flags, sessionBaseKey, cm.ServerChallenge[:], lmChallengeResponse, lmHash)
221 | if err != nil {
222 | return nil, nil, err
223 | }
224 |
225 | return response, keyExchangeKey, nil
226 | default:
227 | // NTLMv2 authentication
228 | timestamp, ok := targetInfo.Get(msvAvTimestamp)
229 | if ok {
230 | var flags uint32
231 | if v, ok := targetInfo.Get(msvAvFlags); ok {
232 | flags = binary.LittleEndian.Uint32(v)
233 | flags |= msvAvFlagMICProvided
234 | } else {
235 | flags = msvAvFlagMICProvided
236 | }
237 | v := make([]byte, 4)
238 | binary.LittleEndian.PutUint32(v, flags)
239 | targetInfo.Set(msvAvFlags, v)
240 | } else {
241 | var err error
242 | timestamp, err = currentTime()
243 | if err != nil {
244 | return nil, nil, err
245 | }
246 | }
247 |
248 | if channelBindings != nil {
249 | b, err := channelBindings.marshal()
250 | if err != nil {
251 | return nil, nil, err
252 | }
253 | targetInfo.Set(msvChannelBindings, hashMD5(b))
254 | }
255 |
256 | return ntlmV2Response(username, password, domain, pass_is_hash, cm.ServerChallenge[:], clientChallenge, timestamp, targetInfo)
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/payload.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | )
8 |
9 | type payload struct {
10 | Len uint16
11 | MaxLen uint16
12 | Offset uint32
13 | }
14 |
15 | func newPayload(length int, offset *int) payload {
16 | p := payload{
17 | Len: uint16(length),
18 | MaxLen: uint16(length),
19 | Offset: uint32(*offset),
20 | }
21 |
22 | *offset += length
23 |
24 | return p
25 | }
26 |
27 | func (p *payload) ReadValue(reader *bytes.Reader) ([]byte, error) {
28 | // Remember where we are
29 | pos, err := reader.Seek(0, io.SeekCurrent)
30 | if err != nil {
31 | return nil, err
32 | }
33 |
34 | // Seek to where the payload is
35 | _, err = reader.Seek(int64(p.Offset), io.SeekStart)
36 | if err != nil {
37 | return nil, err
38 | }
39 |
40 | // Read the value
41 | b := make([]byte, p.Len)
42 | n, err := reader.Read(b)
43 | if err != nil {
44 | return nil, err
45 | }
46 | if n != int(p.Len) {
47 | return nil, fmt.Errorf("expected %d bytes, only read %d", p.Len, n)
48 | }
49 |
50 | // Seek back to where we were
51 | _, err = reader.Seek(pos, io.SeekStart)
52 | if err != nil {
53 | return nil, err
54 | }
55 |
56 | return b, nil
57 | }
58 |
59 | func (p *payload) ReadString(reader *bytes.Reader, utf16 bool) (string, error) {
60 | b, err := p.ReadValue(reader)
61 | if err != nil {
62 | return "", err
63 | }
64 |
65 | if utf16 {
66 | s, err := utf16ToString(b)
67 | if err != nil {
68 | return "", err
69 | }
70 | return s, nil
71 | }
72 |
73 | return string(b), nil
74 | }
75 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/security.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "crypto/rc4"
6 | "encoding/binary"
7 | "errors"
8 | )
9 |
10 | type SecuritySession struct {
11 | negotiateFlags, outgoingSeqNum, incomingSeqNum uint32
12 | outgoingSigningKey, incomingSigningKey []byte
13 | outgoingHandle, incomingHandle *rc4.Cipher
14 | }
15 |
16 | var (
17 | clientSigning = concat([]byte("session key to client-to-server signing key magic constant"), []byte{0x00})
18 | serverSigning = concat([]byte("session key to server-to-client signing key magic constant"), []byte{0x00})
19 | clientSealing = concat([]byte("session key to client-to-server sealing key magic constant"), []byte{0x00})
20 | serverSealing = concat([]byte("session key to server-to-client sealing key magic constant"), []byte{0x00})
21 | )
22 |
23 | type securitySource int
24 |
25 | const (
26 | sourceClient securitySource = iota
27 | sourceServer
28 | )
29 |
30 | func newSecuritySession(flags uint32, exportedSessionKey []byte, source securitySource) (*SecuritySession, error) {
31 | s := &SecuritySession{
32 | negotiateFlags: flags,
33 | }
34 |
35 | clientSealingKey := sealKey(s.negotiateFlags, exportedSessionKey, clientSealing)
36 | serverSealingKey := sealKey(s.negotiateFlags, exportedSessionKey, serverSealing)
37 |
38 | var err error
39 |
40 | switch source {
41 | case sourceClient:
42 | s.outgoingSigningKey = signKey(exportedSessionKey, clientSigning)
43 | s.incomingSigningKey = signKey(exportedSessionKey, serverSigning)
44 |
45 | s.outgoingHandle, err = initRC4(clientSealingKey)
46 | if err != nil {
47 | return nil, err
48 | }
49 |
50 | s.incomingHandle, err = initRC4(serverSealingKey)
51 | if err != nil {
52 | return nil, err
53 | }
54 | case sourceServer:
55 | s.outgoingSigningKey = signKey(exportedSessionKey, serverSigning)
56 | s.incomingSigningKey = signKey(exportedSessionKey, clientSigning)
57 |
58 | s.outgoingHandle, err = initRC4(serverSealingKey)
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | s.incomingHandle, err = initRC4(clientSealingKey)
64 | if err != nil {
65 | return nil, err
66 | }
67 | default:
68 | return nil, errors.New("unknown source")
69 | }
70 |
71 | return s, nil
72 | }
73 |
74 | func (s *SecuritySession) Wrap(b []byte) ([]byte, []byte, error) {
75 | m := append(b[:0:0], b...)
76 | switch {
77 | case ntlmsspNegotiateSeal.IsSet(s.negotiateFlags):
78 | m = encryptRC4(s.outgoingHandle, m)
79 | fallthrough
80 | case ntlmsspNegotiateSign.IsSet(s.negotiateFlags):
81 | // Signature is always created on the original unencrypted message
82 | signature, err := calculateSignature(b, s.negotiateFlags, s.outgoingSigningKey, s.outgoingSeqNum, s.outgoingHandle)
83 | if err != nil {
84 | return nil, nil, err
85 | }
86 |
87 | s.outgoingSeqNum++
88 |
89 | return m, signature, nil
90 | default:
91 | return m, nil, nil
92 | }
93 | }
94 |
95 | func (s *SecuritySession) Unwrap(b, signature []byte) ([]byte, error) {
96 | m := append(b[:0:0], b...)
97 | switch {
98 | case ntlmsspNegotiateSeal.IsSet(s.negotiateFlags):
99 | m = encryptRC4(s.incomingHandle, m)
100 | fallthrough
101 | case ntlmsspNegotiateSign.IsSet(s.negotiateFlags):
102 | // Signature is checked after any decryption
103 | expected, err := calculateSignature(m, s.negotiateFlags, s.incomingSigningKey, s.incomingSeqNum, s.incomingHandle)
104 | if err != nil {
105 | return nil, err
106 | }
107 |
108 | offset := 8
109 | if ntlmsspNegotiateExtendedSessionsecurity.IsSet(s.negotiateFlags) {
110 | offset = 4
111 | }
112 |
113 | // Compare the checksum portion of the signature
114 | if !bytes.Equal(signature[offset:12], expected[offset:12]) {
115 | return nil, errors.New("checksum does not match")
116 | }
117 |
118 | // Compare the sequence number portion of the signature
119 | if !bytes.Equal(signature[12:16], expected[12:16]) {
120 | return nil, errors.New("sequence number does not match")
121 | }
122 |
123 | s.incomingSeqNum++
124 |
125 | fallthrough
126 | default:
127 | return m, nil
128 | }
129 | }
130 |
131 | func signKey(exportedSessionKey, constant []byte) []byte {
132 | return hashMD5(concat(exportedSessionKey, constant))
133 | }
134 |
135 | func sealKey(flags uint32, exportedSessionKey, constant []byte) []byte {
136 | switch {
137 | case ntlmsspNegotiateExtendedSessionsecurity.IsSet(flags) && ntlmsspNegotiate128.IsSet(flags):
138 | // NTLM2 with 128-bit key
139 | return hashMD5(concat(exportedSessionKey, constant))
140 | case ntlmsspNegotiateExtendedSessionsecurity.IsSet(flags) && ntlmsspNegotiate56.IsSet(flags):
141 | // NTLM2 with 56-bit key
142 | return hashMD5(concat(exportedSessionKey[:7], constant))
143 | case ntlmsspNegotiateExtendedSessionsecurity.IsSet(flags):
144 | // NTLM2 with 40-bit key
145 | return hashMD5(concat(exportedSessionKey[:5], constant))
146 | case ntlmsspNegotiateLMKey.IsSet(flags) && ntlmsspNegotiate56.IsSet(flags):
147 | // NTLM1 with 56-bit key
148 | return concat(exportedSessionKey[:7], []byte{0xa0})
149 | case ntlmsspNegotiateLMKey.IsSet(flags):
150 | // NTLM1 with 40-bit key
151 | return concat(exportedSessionKey[:5], []byte{0xe5, 0x38, 0xb0})
152 | default:
153 | return exportedSessionKey
154 | }
155 | }
156 |
157 | func calculateSignature(message []byte, flags uint32, signingKey []byte, sequenceNumber uint32, handle *rc4.Cipher) ([]byte, error) {
158 | var version uint32 = 1
159 |
160 | b := bytes.Buffer{}
161 | if err := binary.Write(&b, binary.LittleEndian, &version); err != nil {
162 | return nil, err
163 | }
164 |
165 | seqNum := make([]byte, 4)
166 | binary.LittleEndian.PutUint32(seqNum, sequenceNumber)
167 |
168 | switch {
169 | case ntlmsspNegotiateExtendedSessionsecurity.IsSet(flags):
170 | checksum := hmacMD5(signingKey, concat(seqNum, message))[:8]
171 |
172 | if ntlmsspNegotiateKeyExch.IsSet(flags) {
173 | checksum = encryptRC4(handle, checksum)
174 | }
175 |
176 | // Checksum
177 | b.Write(checksum)
178 |
179 | // Sequence Number
180 | b.Write(seqNum)
181 | default:
182 | // The MS-NLMP specification doesn't match the examples. The
183 | // examples write out the encrypted random pad in the signature
184 | // whereas the pseudo code writes out zeroes.
185 |
186 | // RandomPad of 0
187 | _ = encryptRC4(handle, zeroBytes(4))
188 | b.Write(zeroBytes(4))
189 |
190 | // Checksum
191 | b.Write(encryptRC4(handle, hashCRC32(message)))
192 |
193 | encryptedSeqNum := encryptRC4(handle, zeroBytes(4))
194 |
195 | for i := 0; i < 4; i++ {
196 | encryptedSeqNum[i] ^= seqNum[i]
197 | }
198 |
199 | // Sequence Number
200 | b.Write(encryptedSeqNum)
201 | }
202 |
203 | return b.Bytes(), nil
204 | }
205 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/unicode.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import "golang.org/x/text/encoding/unicode"
4 |
5 | func utf16FromString(s string) ([]byte, error) {
6 | b, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder().Bytes([]byte(s))
7 | if err != nil {
8 | return nil, err
9 | }
10 |
11 | return b, nil
12 | }
13 |
14 | func utf16ToString(b []byte) (string, error) {
15 | s, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder().Bytes(b)
16 | if err != nil {
17 | return "", err
18 | }
19 |
20 | return string(s), nil
21 | }
22 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/util.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import "bytes"
4 |
5 | func concat(bs ...[]byte) []byte {
6 | return bytes.Join(bs, nil)
7 | }
8 |
9 | func zeroBytes(length int) []byte {
10 | return make([]byte, length, length)
11 | }
12 |
13 | func zeroPad(s []byte, length int) []byte {
14 | d := zeroBytes(length)
15 | copy(d, s)
16 |
17 | return d
18 | }
19 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/version.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "unsafe"
7 | )
8 |
9 | const (
10 | // NTLMSSPRevisionW2K3 is the current revision of NTLM.
11 | NTLMSSPRevisionW2K3 uint8 = 0x0f
12 | )
13 |
14 | // Version models the NTLM version passed in the various messages.
15 | type Version struct {
16 | ProductMajorVersion uint8
17 | ProductMinorVersion uint8
18 | ProductBuild uint16
19 | _ [3]uint8
20 | NTLMRevisionCurrent uint8
21 | }
22 |
23 | func (v *Version) marshal() ([]byte, error) {
24 | dest := make([]byte, 0, unsafe.Sizeof(Version{}))
25 | buffer := bytes.NewBuffer(dest)
26 |
27 | if err := binary.Write(buffer, binary.LittleEndian, v); err != nil {
28 | return nil, err
29 | }
30 |
31 | return buffer.Bytes(), nil
32 | }
33 |
34 | func (v *Version) unmarshal(reader *bytes.Reader) error {
35 | if err := binary.Read(reader, binary.LittleEndian, v); err != nil {
36 | return err
37 | }
38 |
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/version_posix.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package ntlmssp
4 |
5 | // DefaultVersion returns a pointer to a NTLM Version struct for the OS which
6 | // will be populated on Windows or nil otherwise.
7 | func DefaultVersion() *Version {
8 | return nil
9 | }
10 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/version_windows.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "syscall"
5 | )
6 |
7 | // DefaultVersion returns a pointer to a NTLM Version struct for the OS which
8 | // will be populated on Windows or nil otherwise.
9 | func DefaultVersion() *Version {
10 | dll := syscall.MustLoadDLL("kernel32.dll")
11 | p := dll.MustFindProc("GetVersion")
12 | v, _, _ := p.Call()
13 |
14 | return &Version{
15 | ProductMajorVersion: uint8(v),
16 | ProductMinorVersion: uint8(v >> 8),
17 | ProductBuild: uint16(v >> 16),
18 | NTLMRevisionCurrent: NTLMSSPRevisionW2K3,
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/winrmntlm/ntlmssp/workstation.go:
--------------------------------------------------------------------------------
1 | package ntlmssp
2 |
3 | import (
4 | "os"
5 | "strings"
6 | )
7 |
8 | // DefaultWorkstation returns the current workstation name.
9 | func DefaultWorkstation() (string, error) {
10 | hostname, err := os.Hostname()
11 | if err != nil {
12 | return "", err
13 | }
14 |
15 | return strings.ToUpper(hostname), nil
16 | }
17 |
--------------------------------------------------------------------------------