-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathssh2new.lua
1975 lines (1739 loc) · 73 KB
/
ssh2new.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- ssh2new.lua (c) 2010, 2017 by Lucio Andrés Illanes Albornoz <[email protected]>
-- ssh2new.lua
-- A non-comprehensive RFC 425{2,3}-compliant client implementation
-- of the Secure Shell (SSH) protocol version 2 for use with Nmap as an NSE
-- script superseding the stock `ssh2.lua' script as distributed with at
-- least Nmap v5.00, additionally implementing generic authentication
-- support and the functionality required to be afforded to it.
--
-- Consult the documentation as present in and produced from the
-- below NSE fields for further information.
--
-- Licensed under the terms of the MIT license. Refer to either `LICENSE' as
-- distributed alongside this file or[1] if unavailable.
--
-- [1] <http://www.opensource.org/licenses/mit-license.php>
--
--{{{ NSE fields
--@version 1.0
--@author Lucio Andrés Illanes Albornoz --<[email protected]>
--@license MIT
--
--{{{ Description
--[[
Tested with the <code>pkgsrc Nmap v5.35DC1</code> on <code>NetBSD/i386 v5.1</code>.
Requires the accompanying set of diffs to have been applied to Nmap v5.00+.
Originally based on the stock <code>Nmap v5.35DC1 ssh2.lua</code> script.
A non-exhaustive list of things explicitely not supported by this script:
* The legacy SSH{,1} protocol or any mode compatible with it
* Reasonably secure Diffie-Hellman key exchanging
* Mandatory Key Re-Exchange as per RFC 4253 Section 9
* non-CBC block cipher modes (including CTR and SDCTR)
* Mitigation of the CBC plaintext recovery side-channel attack described in VU#958563
* RFC 4254 Channels, Interactive Sessions, X11, and TCP/IP Forwarding
* The RFC 4252 Host-Based and Public Key authentication methods
* Kerberos and GSSAPI authentication
Relevant documents and specifications:
* RFC 2409 -- The Internet Key Exchange (IKE)
* RFC 2631 -- Diffie-Hellman Key Agreement Method
* RFC 3629 -- UTF-8, a transformation format of ISO 10646
* RFC 4250 -- The Secure Shell (SSH) Protocol Assigned Numbers
* RFC 4251 -- The Secure Shell (SSH) Protocol Architecture
* RFC 4252 -- The Secure Shell (SSH) Authentication Protocol
* RFC 4253 -- The Secure Shell (SSH) Transport Layer Protocol
* RFC 4256 -- Generic Message Exchange Authentication for the Secure Shell Protocol (SSH)
* RFC 4344 -- SSH Transport Layer Encryption Modes
* RFC 4345 -- Improved Arcfour Modes for SSH
* RFC 4419 -- Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer Protocol
* draft-ietf-ipsec-ciph-cast128-cbc-00.txt -- The ESP CAST128-CBC Algorithm
* OpenSSH Security Advisory: cbc.adv -- <http://www.openssh.com/txt/cbc.adv>
* CPNI Vulnerability Advisory SSH -- <http://www.cpni.gov.uk/Docs/Vulnerability_Advisory_SSH.txt>
* PuTTY wish ssh2-cbc-weakness -- <http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/ssh2-cbc-weakness.html>
Alternative SSH{,2} brute scanners and related libraries:
* THC-Hydra -- <http://freeworld.thc.org/thc-hydra/>
* Ncrack -- <http://sock-raw.org/papers/openssh_library>
* metasploit -- <http://www.metasploit.com/modules/auxiliary/scanner/ssh/ssh_login>
* paramiko: SSH2 protocol for python -- <http://www.lag.net/paramiko/>
* Net::SSH software suite -- <http://net-ssh.rubyforge.org/>
* Greg Sabino Mullane / Net-SSH-Perl -- <http://search.cpan.org/dist/Net-SSH-Perl/>
* libssh -- <http://www.libssh.org/>
* libssh2 -- <http://www.libssh2.org/>
* sshscan, sshteam, 55hb, pscan{,2}, hscan, [ ... ]
SSH{,2} brute force mitigation monitors:
* Sshguard -- <http://www.sshguard.net/>
* sshd_sentry -- <http://linuxmafia.com/pub/linux/security/sshd_sentry/>
* sshdfilter -- <http://www.csc.liv.ac.uk/~greg/sshdfilter/>
* ipt_recent -- <http://www.snowman.net/projects/ipt_recent/>
* BlockHosts -- <http://www.aczoom.com/cms/blockhosts>
* DenyHosts -- <http://denyhosts.sourceforge.net/>
* BlockSSHD -- <http://blocksshd.sourceforge.net/>
* Ssh-faker -- <http://www.pkts.ca/ssh-faker.shtml>
* Shellter -- <http://shellter.sourceforge.net/>
* sshutout -- <http://www.techfinesse.com/sshutout/sshutout.html>
* Fail2ban -- <http://www.fail2ban.org/>
* pam-abl -- <http://sourceforge.net/projects/pam-abl/>
* sshban -- <http://nixbit.com/cat/internet/log-analyzers/sshban/>
* Tattle -- <http://www.securiteam.com/tools/5JP0520G0Q.html>
* sshit -- <http://anp.ath.cx/sshit/>
--]]
--}}}
--}}}
--
module(... or "ssh2new", package.seeall); SSH2 = { };
-- {{{ Public API status tables
-- Last occurred error = error [format] string constants, severity
Error = {
["none"] = { "(No error)", },
["receive"] = { "socket:receive(): %s", },
["receive_buf"] = { "socket:receive_buf(): %s", },
["send"] = { "socket:send(): %s", },
["disconnect"] = { "Disconnected: [%d] `%s'; terminated SSH session.", },
["digest"] = { "openssl.digest: %s", },
["ident"] = { "Remote SSH server does not speak SSH2(?), got: %s", },
["ident2"] = { "Invalid identification string `%s' received from remote server.", },
["algorithm"] = { "Unable to negotiate an algorithm for /%s/ (offered: `%s';) terminated SSH session.", },
["kexdh_reply"] = { "Missing one of K_S, f, or signature in the SSH_MSG_KEXDH_REPLY packet sent by the remote server.", },
["ctx_init"] = { "openssl:ctx_init(): %s", },
["packetlen"] = { "Packet length %d above 35000.", },
["mac"] = { "Corrupted MAC on input.", },
["gotunimp"] = { "Received SSH_MSG_UNIMPLEMENTED in response to packet #%d", },
["expected"] = { "Unexpected message byte %02X (expected %02X.)", },
["alreadyconn"] = { "Already connecting or connected.", },
["wantservice"] = { "Expected `%s' SSH_MSG_SERVICE_ACCEPT packet, got `%s' instead.", },
["notconn"] = { "Not connected.", },
["uauthreqd"] = { "Already requested the `ssh-userauth' service.", },
["uauthfail"] = { "Got SSH_MSG_USERAUTH_FAILURE (authentications that may continue: `%s')", },
["uauthnotreq"] = { "The `ssh-userauth' service must be requested prior to attempting authentication.", },
["expected2"] = { "Unexpected message byte %02X.", },
["unknown_key"] = { "Unknown public key type `%s'.", },
["do_vexchg"] = { "Tried to re-do version exchange; terminated SSH session.", },
["do_kexinit"] = { "Tried to re-do kexinit; terminated SSH session.", },
["do_kex"] = { "Tried to re-do key exchange; terminated SSH session.", },
["do_newkeys"] = { "Tried to re-do SSH_MSG_NEWKEYS; terminated SSH session.", },
["notimpl"] = { "Operation not implemented.", },
["unknownauth"] = { "Unknown authentication method `%s'; terminated SSH session.", },
["wantbytes"] = { "Expected to receive %d bytes, got %d bytes instead.", },
}
-- SSH connection and authentication status constants
Status = {
-- Boolean flags
Connecting = 0x002,
Connected = 0x003,
-- Connection phase
DoneVersionExchange = 0x010,
DoneKexInit = 0x020,
DoneKeyExchange = 0x030,
DoneNewKeys = 0x040,
RequestedUserAuth = 0x050,
-- Authentication status
AuthFailureContinue = 0x100,
AuthFailurePermanent = 0x200,
AuthFailure = 0x300,
}
-- }}}
-- {{{ Public SSH message constants
-- Subset of relevant SSH transport layer and authentication protocol
-- message numbers.
MSG = {
-- [cf. RFC 4253 Section 12 (`Summary of Message Numbers')]
SSH_MSG_DISCONNECT = 1,
SSH_MSG_IGNORE = 2,
SSH_MSG_UNIMPLEMENTED = 3,
SSH_MSG_DEBUG = 4,
SSH_MSG_SERVICE_REQUEST = 5,
SSH_MSG_SERVICE_ACCEPT = 6,
SSH_MSG_KEXINIT = 20,
SSH_MSG_NEWKEYS = 21,
-- [cf. RFC 4419 Diffie-Hellman Group Exchange for the Secure Shell (SSH)
-- Transport Layer Protocol]
SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30,
SSH_MSG_KEX_DH_GEX_GROUP = 31,
SSH_MSG_KEX_DH_GEX_INIT = 32,
SSH_MSG_KEX_DH_GEX_REPLY = 33,
SSH_MSG_KEX_DH_GEX_REQUEST = 34,
-- [cf. RFC 4253 Section 8 (`Diffie-Hellman Key Exchange')]
SSH_MSG_KEXDH_INIT = 30,
SSH_MSG_KEXDH_REPLY = 31,
-- [cf. RFC 4252 Section 6 (`Authentication Protocol Message Numbers')]
SSH_MSG_USERAUTH_REQUEST = 50,
SSH_MSG_USERAUTH_FAILURE = 51,
SSH_MSG_USERAUTH_SUCCESS = 52,
SSH_MSG_USERAUTH_BANNER = 53,
-- [cf. RFC 4256 Section 5 (`IANA Considerations')]
SSH_MSG_USERAUTH_INFO_REQUEST = 60,
SSH_MSG_USERAUTH_INFO_RESPONSE = 61,
}
-- }}}
-- {{{ Internal debugging message tables
-- Debugging [format] string and debug level constants
local Debug = {
["PACKET"] = { ["lvl"] = 1, ["pfx"] = "from: %-6s enc: %-5s len: %-4d msg: %-2d", },
["PACKETHEX"] = { ["lvl"] = 2, ["pfx"] = "from: %-6s enc: %-5s len: %-4d msg: %-2d\n%s", },
["MSGDBG"] = { ["lvl"] = 1, ["pfx"] = "msg : %s", },
["MSGIGN"] = { ["lvl"] = 2, ["pfx"] = "data: %s", },
["UNIMPL"] = { ["lvl"] = 1, ["pfx"] = "seq#: %d", },
["KEY"] = { ["lvl"] = 1, ["pfx"] = "char: %c size: %d bytes: %s", },
["DISCONNECT"] = { ["lvl"] = 1, ["pfx"] = "msg : %s", },
["KEX_INIT"] = { ["lvl"] = 1, ["pfx"] = "type: %-15s keysz: %-3s dgstsz: %-3s blocksz: %-3s discard: %-4s choice: %s", },
["AUTHSUCC"] = { ["lvl"] = 1, ["pfx"] = "", },
["AUTHFAIL"] = { ["lvl"] = 1, ["pfx"] = "cont: %s", },
["AUTHNEXT"] = { ["lvl"] = 1, ["pfx"] = "cont: %s", },
["BANNER"] = { ["lvl"] = 1, ["pfx"] = "text: %s", },
["INFOREQ"] = { ["lvl"] = 1, ["pfx"] = "name: %s insn: %s prompt[1]: %s", },
["AUTHPASS"] = { ["lvl"] = 1, ["pfx"] = "user: %s pass: %s", },
["AUTHKBD"] = { ["lvl"] = 1, ["pfx"] = "user: %s pass: %s", },
}
-- }}}
-- {{{ Internal algorithm name translation and parameter table
-- Fixup table matching the qualified names of the various algorithms
-- (including, but not limited to, block ciphers and hash functions) specified
-- by RFC 4253, possibly explicitely specifying key length, mode of operation,
-- and digest size, resp. within the name itself against their corresponding
-- OpenSSL equivalents and {digest, key} {length, size}s. Additionally includes
-- the key exchange methods specified by RFC 4253 and RFC 4419 as special-case
-- variants, without relying on an OpenSSL implementation of either.
local Algorithms = {
-- RFC-mandated Comment
-- {{{ [6.3] Encryption [ciphers]
["3des-cbc"] = { -- REQUIRED three-key 3DES in CBC mode
["name"] = "des-ede3-cbc",
["key_size"] = 24,
["block_size"] = 8,
},
["blowfish-cbc"] = { -- OPTIONAL Blowfish in CBC mode
["name"] = "bf-cbc",
["key_size"] = 16,
["block_size"] = 8,
},
["aes256-cbc"] = {
["name"] = "aes-256-cbc", -- OPTIONAL AES in CBC mode, with a 256-bit key
["key_size"] = 32,
["block_size"] = 16,
},
["aes192-cbc"] = {
["name"] = "aes-192-cbc", -- OPTIONAL AES with a 192-bit key
["key_size"] = 24,
["block_size"] = 16,
},
["aes128-cbc"] = { -- OPTIONAL AES with a 128-bit key
["name"] = "aes-128-cbc",
["key_size"] = 16,
["block_size"] = 16,
},
["arcfour"] = { -- OPTIONAL the ARCFOUR stream cipher with a 128-bit key
["name"] = "rc4",
["key_size"] = 16,
["block_size"] = 8,
},
["arcfour128"] = { --[RFC 4345 Section 4 (`Algorithm Definitions')
["name"] = "rc4",
["key_size"] = 16,
["block_size"] = 8,
["discard"] = 1536,
},
["arcfour256"] = { --[RFC 4345 Section 4 (`Algorithm Definitions')
["name"] = "rc4",
["key_size"] = 32,
["block_size"] = 8,
["discard"] = 1536,
},
["cast128-cbc"] = { --[The ESP CAST128-CBC Algorithm -- <draft-ietf-ipsec-ciph-cast128-cbc-00.txt>]
["name"] = "cast5-cbc",
["key_size"] = 16,
["block_size"] = 8,
},
-- }}}
-- {{{ [6.4] Data integrity (MAC algorithm digests)
["hmac-sha1"] = { -- REQUIRED HMAC-SHA1 (digest length = key length = 20)
["name"] = "sha1",
["digest_size"] = 20,
["key_size"] = 20,
},
["hmac-sha1-96"] = { -- RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
["name"] = "sha1",
["digest_size"] = 12,
["key_size"] = 20,
},
["hmac-md5"] = { -- OPTIONAL HMAC-MD5 (digest length = key length = 16)
["name"] = "md5",
["digest_size"] = 16,
["key_size"] = 16,
},
["hmac-md5-96"] = { -- OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
["name"] = "md5",
["digest_size"] = 12,
["key_size"] = 16,
},
-- }}}
-- {{{ [6.5] Key Exchange Methods
["diffie-hellman-group1-sha1"] -- REQUIRED
= {
["name"] = "diffie-hellman-group1-sha1",
-- First Oakley Default MODP Group 1024-bit generator
-- and prime number as specified in RFC 2409 Section 6.1
-- (ibid) and RFC 4253 Section 8.1 (ibid.)
["H"] = "SHA1",
["g"] = openssl.bignum_dec2bn("2"),
["p"] = openssl.bignum_hex2bn(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020"
.. "BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE135"
.. "6D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5"
.. "A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF"),
},
["diffie-hellman-group14-sha1"] -- REQUIRED
= {
["name"] = "diffie-hellman-group14-sha1",
-- Oakley MODP Group 14 2048-bit generator and prime
-- number as specified in RFC 3526 (ibid) and RFC 4253
-- Section 8.2 (ibid.)
["H"] = "SHA1",
["g"] = openssl.bignum_dec2bn("2"),
["p"] = openssl.bignum_hex2bn(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020"
.. "BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE135"
.. "6D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5"
.. "A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55"
.. "D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966"
.. "D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B"
.. "2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D22"
.. "61898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"),
},
["diffie-hellman-group-exchange-sha1"] -- RFC 4419 Section 4.1
= {
["H"] = "SHA1",
["name"] = "diffie-hellman-group-exchange-sha1",
},
["diffie-hellman-group-exchange-sha256"] -- RFC 4419 Section 4.2
= {
["H"] = "SHA256",
["name"] = "diffie-hellman-group-exchange-sha256",
},
-- }}}
-- {{{ [6.6] Public Key Algorithms
-- [cf. [FIPS-180-2] and [RFC3447] (`RSASSA-PKCS1-v1_5.')]
["ssh-dss"] = { -- REQUIRED sign Raw DSS Key
["name"] = "DSS1",
["digest_size"] = 20,
},
["ssh-rsa"] = { -- RECOMMENDED sign Raw RSA Key
["name"] = "RSA-SHA1",
["digest_size"] = 20,
},
-- }}}
}
-- }}}
-- {{{ Internal SSH server bug constants and identification string pattern table
-- XXX document
local BUG = {
-- {{{ From compat.c [as of OpenSSH NetBSD_Secure_Shell-20080403]
SSH_BUG_BIGENDIANAES = 2, -- XXX add
SSH_BUG_DERIVEKEY = 4,
SSH_BUG_FIRSTKEX = 7,
SSH_BUG_HMAC = 9,
SSH_BUG_NOREKEY = 12,
SSH_BUG_RSASIGMD5 = 20, -- XXX add
SSH_BUG_SIGBLOB = 22, -- XXX add
SSH_OLD_DHGEX = 24,
-- }}}
-- {{{ From SSH.C [as of PuTTY v0.60 (Release)]
BUG_SSH2_DERIVEKEY = 30,
BUG_SSH2_HMAC = 31,
BUG_SSH2_REKEY = 33, -- XXX add
BUG_SSH2_RSA_PADDING = 34, -- XXX add
-- }}}
}
-- Do note that the patterns within each table element are Lua patterns as
-- described in[1] and neither regular expressions nor globs.
-- [1] <http://www.lua.org/manual/5.1/manual.html#5.4.1>
local COMPAT = {
-- {{{ From compat.c [as of OpenSSH NetBSD_Secure_Shell-20080403]
{
["patterns"] = {
"^OpenSSH-2%.0.*", "^OpenSSH-2%.1.*",
"^OpenSSH_2%.1.*", "^OpenSSH_2%.2.*",
},
["bugs"] = { BUG.SSH_OLD_DHGEX, BUG.SSH_BUG_NOREKEY, },
},
{
["patterns"] = { "^OpenSSH_2%.3%.0.*", },
["bugs"] = {
BUG.SSH_BUG_BIGENDIANAES, BUG.SSH_OLD_DHGEX,
BUG.SSH_BUG_NOREKEY,
},
},
{
["patterns"] = { "^OpenSSH_2%.3%..*", },
["bugs"] = {
BUG.SSH_BUG_BIGENDIANAES, BUG.SSH_OLD_DHGEX,
BUG.SSH_BUG_NOREKEY,
},
},
{
["patterns"] = {
"^OpenSSH_2%.5%.0p1.*", "^OpenSSH_2%.5%.1p1.*",
},
["bugs"] = {
BUG.SSH_BUG_BIGENDIANAES, BUG.SSH_OLD_DHGEX,
BUG.SSH_BUG_NOREKEY,
},
},
{
["patterns"] = {
"^OpenSSH_2%.5%.0.*", "^OpenSSH_2%.5%.1.*",
"^OpenSSH_2%.5%.2.*",
},
["bugs"] = {
BUG.SSH_OLD_DHGEX, BUG.SSH_BUG_NOREKEY,
},
},
{
["patterns"] = { "^OpenSSH_2%.5%.3.*", },
["bugs"] = { BUG.SSH_BUG_NOREKEY, },
},
{
["patterns"] = { "^Sun_SSH_1%.0.*", },
["bugs"] = { BUG.SSH_BUG_NOREKEY, },
},
{
["patterns"] = { "^2%.1%.0.*", },
["bugs"] = {
BUG.SSH_BUG_SIGBLOB, BUG.SSH_BUG_HMAC,
BUG.SSH_BUG_RSASIGMD5, BUG.SSH_BUG_FIRSTKEX,
},
},
{
["patterns"] = { "^2%.1 .*", },
["bugs"] = {
BUG.SSH_BUG_SIGBLOB, BUG.SSH_BUG_HMAC,
BUG.SSH_BUG_RSASIGMD5, BUG.SSH_BUG_FIRSTKEX,
},
},
{
["patterns"] = {
"^2%.0%.13.*", "^2%.0%.14.*", "^2%.0%.15.*",
"^2%.0%.16.*", "^2%.0%.17.*", "^2%.0%.18.*",
"^2%.0%.19.*",
},
["bugs"] = {
BUG.SSH_BUG_SIGBLOB, BUG.SSH_BUG_HMAC,
BUG.SSH_BUG_RSASIGMD5, BUG.SSH_BUG_FIRSTKEX,
},
},
{
["patterns"] = { "^2%.0%.11.*", "^2%.0%.12.*", },
["bugs"] = {
BUG.SSH_BUG_SIGBLOB, BUG.SSH_BUG_HMAC,
BUG.SSH_BUG_RSASIGMD5, BUG.SSH_BUG_FIRSTKEX,
},
},
{
["patterns"] = { "^2%.0%..*", },
["bugs"] = {
BUG.SSH_BUG_SIGBLOB, BUG.SSH_BUG_HMAC,
BUG.SSH_BUG_RSASIGMD5, BUG.SSH_BUG_DERIVEKEY,
BUG.SSH_BUG_FIRSTKEX,
},
},
{
["patterns"] = { "^2%.2%.0.*", "^2%.3%.0.*", },
["bugs"] = {
BUG.SSH_BUG_HMAC, BUG.SSH_BUG_RSASIGMD5,
BUG.SSH_BUG_FIRSTKEX,
},
},
{
["patterns"] = { "^2%.3%..*", },
["bugs"] = { BUG.SSH_BUG_RSASIGMD5, BUG.SSH_BUG_FIRSTKEX, },
},
{
["patterns"] = { "^2%..*", },
["bugs"] = { BUG.SSH_BUG_FIRSTKEX, },
},
-- }}}
-- {{{ From SSH.C [as of PuTTY v0.60 (Release)]
{
["patterns"] = {
"^.* VShell", "^2%.1%.0.*", "^2%.0%..*",
"^2%.2%.0.*", "^2%.3%.0.*", "^2%.1 .*",
},
["bugs"] = { BUG.BUG_SSH2_HMAC, },
},
{
["patterns"] = { "^.* VShell", "^2%.0%.0.*", "^2%.0%.10.*", },
["bugs"] = { BUG.BUG_SSH2_DERIVEKEY, },
},
{
["patterns"] = {
"^OpenSSH_2%.[5-9].*", "^OpenSSH_3%.[0-2].*",
},
["bugs"] = { BUG.BUG_SSH2_RSA_PADDING, },
},
{
["patterns"] = {
"^DigiSSH_2%.0", "^OpenSSH_2%.[0-4].*",
"^OpenSSH_2%.5%.[0-3].*",
"^Sun_SSH_1%.0", "^Sun_SSH_1%.0%.1",
"^WeOnlyDo-.*",
},
["bugs"] = { BUG.BUG_SSH2_REKEY, },
},
-- }}}
}
-- }}}
-- {{{ Public error handling and debugging functions
--- Convenience wrapper setting the string describing the last detected and
-- occured error as detected to the file and line whence the latter
-- originated and the supplied error string for a given <code>Session</code>.
--@param error_type An index into the global <code>Error</code> string table identifying its [format] string.
--@param ... The optional arguments to format into the <code>Error</code> [format] string.
--@return The formatted error string.
function SSH2:set_last_error(error_type, ...)
local status, _
local error = "(Unknown error)"
local file = debug.getinfo(2, "S").source
local line = debug.getinfo(2, "l").currentline
-- [cf. <http://lua-users.org/wiki/FileLineMacros>]
if(Error[error_type])
then status, _ = pcall(string.format, Error[error_type], unpack(arg))
if status then error = _ end
end
file = string.gsub(file, "(.*/)(.*)", "%2") or file
self.last_error = file .. ":" .. line .. " " .. error
return self.last_error
end
--- Convenience wrapper allowing for structured debugging output.
--@param dbg_type An index into the global <code>Debug</code> prefix [format] string and level table.
--@param ... The optional arguments to format into the [format] string.
function SSH2:print_debug(dbg_type, ...)
local file = debug.getinfo(2, "S").source
local line = debug.getinfo(2, "l").currentline
-- [cf. <http://lua-users.org/wiki/FileLineMacros>]
local clock = os.date(self.timestamp_fmt, nmap.clock_ms() / 1000)
local pfx = Debug[dbg_type]["pfx"]
assert(Debug[dbg_type])
file = string.gsub(file, "(.*/)(.*)", "%2") or file
stdnse.print_debug(
Debug[dbg_type]["lvl"],
"%-8s.%04d %-10s:%-5s r:%-3s w:%-3s %-10s" .. pfx,
clock, nmap.clock_ms() % 1000, file, line,
tostring(self.seq.r), tostring(self.seq.w),
dbg_type, unpack(arg))
end
--- Dumps a hexadecimal representation of a raw packet including its
-- corresponding sequence number, and a prefix indicating its direction.
--@param packet Decrypted, not necessarily authenticated, SSH2 packet.
--@param from_server <code>true</code> if <code>packet</code> received from server, <code>false</code> if sent by us.
--@param encrypted <code>true</code> if <code>packet</code> is encrypted, <code>false</code> otherwise
function SSH2:dump_packet(packet, from_server, encrypted)
local _
local pfx, seq, msg, packet_hex, dbg_type
local packet_hex = ""
if from_server then pfx = "Server" else pfx = "Client" end
if(not encrypted)
then _, _, _, msg = bin.unpack(">Icc", packet)
else msg = 0
end
if(Debug["PACKETHEX"]["lvl"] <= nmap.debugging())
then local offset
for nbyte=1, packet:len()
do offset, _ = bin.unpack("H1", packet, offset)
packet_hex = (packet_hex or "") .. _ .. " "
if((nbyte % 8) == 0)
then packet_hex = packet_hex .. " "
end
if((nbyte % 16) == 0)
then packet_hex = packet_hex .. "\n"
end
end
dbg_type = "PACKETHEX"
else dbg_type = "PACKET"
end
self:print_debug(
dbg_type, pfx,
tostring(encrypted), packet:len(), msg, packet_hex)
end
--- Dumps a hexadecimal representation of an IV or a key.
--@param Kc The IV or the key's identifying character byte.
--@param K The raw IV or key.
--@see <code>RFC 4253 Section 7.2 (`Output from Key Exchange')</code>
function SSH2:dump_key(Kc, K)
local Klen = K:len()
local _, K = bin.unpack("H" .. K:len(), K)
self:print_debug("KEY", Kc:byte(), Klen, K)
end
-- }}}
-- {{{ Public wrapper functions
--- XXX document
function SSH2:TIMEOUT_SET()
if(self.timeout)
then self.last_IO = nmap.clock_ms()
self.socket:set_timeout(self.timeout)
end
end
--- XXX document
function SSH2:TIMEOUT_UPD()
if(self.timeout)
then self.timeout = self.timeout - (nmap.clock_ms() - self.last_IO)
self.last_IO = nmap.clock_ms()
end
end
--- Convenience wrapper around the <code>receive_buf</code> NSE function
-- adjusting the corresponding I/O timeout value in accordance both with
-- its current value for the corresponding <code>Session</code> and
-- thereafter, the delta between the former and the time spent blocking
-- for network I/O and receiving data.
--@param nbytes The exact amount of bytes to receive, possibly blocking in the process.
--@return Either <code>true</code> and the received raw <code>packet</code> or <code>false</code> and an <code>error string</code>.
function SSH2:receive_buf(nbytes)
local packet
self:TIMEOUT_SET()
status, packet = self.socket:receive_buf(match.numbytes(nbytes))
self:TIMEOUT_UPD()
if(not status)
then return false, self:set_last_error("receive_buf", packet)
end
if(nbytes ~= packet:len())
then return false,
self:set_last_error("wantbytes", nbytes, packet:len())
else return true, packet
end
end
--- Handles a received <code>SSH_MSG_DISCONNECT</code> packet by printing
-- the <code>reason</code> string and integer accompanying the former given
-- an appropriate debugging level.
--@param payload The packet's <code>payload</code>, including message byte.
--@see <code>RFC 4253 Section 11.1 (`Disconnect Message')</code>
--@return <code>false</code>, allowing for convenient <code>return</code> idioms.
function SSH2:handle_disconnect(payload)
local reason_code, reason, msg, last_error
_, _, reason_code, reason, _ = bin.unpack(">cIaa", payload)
last_error = self:set_last_error("disconnect", reason_code, reason)
self:print_debug("DISCONNECT", self.last_error);
self:destroy(); return false, last_error;
end
--- XXX document
function SSH2:destroy()
self.status = { }; if self.socket then self.socket:close() end;
self.ctx = { }; self.seq = { }; self.kex = { }; self.kexinit = { };
end
-- }}}
-- {{{ Public {de,en}cryption, integrity, and key derivation functions
--- Computes and truncates the MAC of a decrypted, either received, or to be
-- sent packet, employing the corresponding digest function and length as
-- negotiated during connection establishment, and the sequence number
-- corresponding to the packet's direction.
--@param packet Raw, unencrypted packet.
--@param client_to_server <code>true</code> if the passed <code>packet</code> shall afterwards be sent to the server, <code>false</code> if it was received by the server.
--@see <code>RFC 4253 Section 6.4 (`Data Integrity')</code>
--@return Either <code>true</code> and the computed <code>MAC</code> or <code>false</code> and an <code>error string</code>.
function SSH2:compute_mac(packet, client_to_server)
local _
local malg, mlen, seq
-- Obtain the previously initialised HMAC context, SSH2 mandated digest
-- size, and current packet sequence number corresponding to the packet
-- direction as specified by /client_to_server/.
if(not client_to_server)
then malg = "mac_S"; mlen = self.kex[malg]["digest_size"];
malg = self.ctx[malg]; seq = self.seq.r;
else malg = "mac_C"; mlen = self.kex[malg]["digest_size"];
malg = self.ctx[malg]; seq = self.seq.w;
end
-- Compute the MAC of the specified sequence number and raw packet,
-- returning the possibly truncated bytes from the beginning of the
-- former given successful computation, or /nil/ otherwise.
local mac = malg:hmac(bin.pack(">IA", seq, packet))
_, mac = bin.unpack("A" .. mlen, mac)
return mac
end
--- Derives, via truncation or concatenation, a single key from the secrets
-- shared by both the <code>server</code> and the <code>client</code>,
-- resulting from earlier <code>key exchange</code> procedures.
--@param key_size The target key's size, as mandated by the correspondingly negotiatioed algorithm and key size.
--@param Kc The key's character byte as per <code>RFC 4253 Section 7.2 (`Output from Key Exchange')</code>.
--@see <code>RFC 4253 Section 7.2 (`Output from Key Exchange')</code>
--@return Either <code>true</code> and the specified amount of bytes from the beginning of the derived key or <code>false</code> and an <code>error string</code>.
function SSH2:derive_key(key_size, Kc)
local status, _, msg, K, H, key, Hm, _key
K = self.kexinit["K"]; H = self.kexinit["H"];
key = self.kexinit["H"]; Kc = Kc:byte();
-- Derive the initial key by computing the hash of the concatenated
-- shared secrets, the session key, and the key character byte
-- as specified, initialising the temporary table of computed
-- hashes with the former as its first entry.
Hm = self.kex["kex"]["H"]
-- [cf. kex.c:480, 494 from NetBSD_Secure_Shell-20080403
-- regarding SSH_BUG_DERIVEKEY]
if(not self.bugs[BUG.SSH_BUG_DERIVEKEY])
then msg = bin.pack(">AAcA", K, H, Kc, key)
else msg = bin.pack(">AcA", H, Kc, key)
end
status, key = pcall(openssl.digest, Hm, msg)
if(not status)
then return false, set_last_error("digest", key)
else _key = { key, }
end
-- Extend the key given a specified key size exceeding the output
-- of the hash function by employing the scheme detailed in [ibid],
-- appending the resulting hash to the above initialised temporary
-- table with each iteration.
while key:len() < key_size
do local Kn_new = table.maxn(_key) + 1
key = ""
-- Concatenate the shared secrets and every single hash
-- computed priorly, and calculate the hash of the resulting
-- sequence.
-- [cf. kex.c:480, 494 from NetBSD_Secure_Shell-20080403
-- regarding SSH_BUG_DERIVEKEY]
if(not self.bugs[BUG.SSH_BUG_DERIVEKEY])
then _key[Kn_new] = bin.pack(">AA", K, H)
else _key[Kn_new] = bin.pack(">A", H)
end
for Kn=1,(Kn_new - 1),1
do _key[Kn_new] = bin.pack(">AA", _key[Kn_new], _key[Kn])
end
status, _key[Kn_new] = pcall(openssl.digest, Hm, _key[Kn_new])
if(not status)
then return false,
self:set_last_error("digest", _key[Kn_new])
end
for Kn=1,(Kn_new - 1),1
do key = bin.pack(">AA", key, _key[Kn])
end
end
-- Return the specified amount of bytes from the beginning of the
-- derived key, possibly implicitly truncating.
_, key = bin.unpack("A" .. key_size, key)
return true, key
end
-- }}}
-- {{{ Internal wrapper functions
--- XXX document
--@param ip XXX document
--@param port XXX document
--@param timeout XXX document
--@param socket XXX document
--@param timestamp_fmt XXX document
--@return XXX
function new_session(ip, port, timeout, socket, timestamp_fmt)
local S = setmetatable({}, {__index = SSH2})
S.status = { }; S.ip = ip; S.port = port;
S.timeout = timeout; S.t0 = 0; S.socket = socket;
S.bugs = { }; S.ctx = { };
if not timestamp_fmt then S.timestamp_fmt = "%H:%M:%S" end
-- RFC 4253 Section 6.4 (`Data Integrity')
S.seq = { ["r"] = 0, ["w"] = 0, }
-- RFC 4253 Section 7.1 (`Algorithm Negotiation')
-- RFC 4253 Section 7.2 (`Output from Key Exchange')
S.kex = { }
-- RFC 4253 Section 8 (`Diffie-Hellman Key Exchange')
S.kexinit = { }
return true, S
end
--- XXX document
--@param ssh_name XXX document
--@return XXX
function new_algorithm(ssh_name)
return setmetatable({}, {__index = Algorithms[ssh_name]})
end
--- Extracts the parameters identifying the supplied public key according to the
-- encoding and algorithm indicated by the first SSH2 BPP /string/ contained
-- within it, preceding the actual key data.
--@param public_key The raw <code>public key</code> in format specified in <code>RFC 4253</code>.
--@see <code>RFC 4253 Section 6.6 (`Public Key Algorithms')</code>
--@return Either <code>true</code>, the <code>public key format identifier</code> (e.g. <code>ssh-rsa</code>,) the amount of bits in the <code>public key's public exponent</code>, and the key's actual parameters, or <code>false</code> and an <code>error string</code>.
local extract_public_key = function(public_key)
local key_type, bits; key = { };
_, key_type = bin.unpack(">a", public_key)
if("ssh-dss" == key_type)
then _, _, key["p"], key["q"], key["g"], key["y"]
= bin.unpack(">aaaaa", public_key)
bits = openssl.bignum_bin2bn(p):num_bits()
elseif("ssh-rsa" == key_type)
then _, _, key["e"], key["n"]
= bin.unpack(">aaa", public_key)
bits = openssl.bignum_bin2bn(n):num_bits()
else return false, S:set_last_error("unknown_key", key_type)
end
for f, _ in pairs(key) do key[f] = openssl.bignum_mpi2bn(key[f]) end
return true, key_type, bits, key
end
--- XXX document
--@param msg XXX document
--@see <code>RFC 4251 Section 9.2 (`Control Character Filtering')</code>
--@return XXX document
local function sanitise(msg)
return msg:gsub("[\r\n]", "")
end
-- }}}
-- {{{ [4.2] Version exchange and compatibility mode determination
--- Performs the <code>Protocol Version Exchange</code> with the remote SSH
-- server, possibly enabling compatibility modes given known bugs present in
-- the remote SSH implementation in accordance with the former's
-- <code>identification string</code>.
--@param ident_C The <code>identification string</code> excluding any terminating <code>CR LF</code> characters to send to the remote server.
--@see <code>RFC 4253 Section 4.2 (`Protocol Version Exchange')</code>
--@return Either <code>true</code> or <code>false</code> and an <code>error string</code>.
function SSH2:do_version_exchange(ident_C)
local status, ident_S
-- Ensure that the current Session is in the correct connection phase.
if(not ((self.status[Status.Connecting])
and (not self.status[Status.DoneVersionExchange])))
then self:destroy()
return false, self:set_last_error("do_vexchg")
end
repeat
self:TIMEOUT_SET()
status, ident_S = self.socket:receive()
self:TIMEOUT_UPD()
if(not status)
then self:destroy()
return false, self:set_last_error("receive", ident_S)
end
-- `The server MAY send other lines of data before sending the
-- version string. Each line SHOULD be terminated by a
-- Carriage Return and Line Feed. Such lines MUST NOT begin
-- with "SSH-", [ ... ]'
if(string.find(ident_S, "SSH-", 1, true))
then
-- Ignore non-SSH2 speaking SSH(?) daemons; send our
-- client identification string given a protocol
-- version of either 2.0 or 1.99, with the latter
-- being equivalent to the former given legacy SSH
-- compatibility [cf. RFC 4253 Section 5.1].
if( (nil == string.find(ident_S, "2.0", 5, true))
and (nil == string.find(ident_S, "1.99", 5, true)))
then self:destroy()
return false,
self:set_last_error("ident", ident_S)
else status,
self.last_error
= self.socket:send(ident_C .. "\r\n")
if(not status)
then self:destroy()
return false,
self:set_last_error(
"send", self.last_error)
end
end
-- Store both identification strings bar their
-- optionally terminating CR-LF pairs in the
-- session table, since they are required in later
-- stages of the SSH2 connection setup.
self.kexinit["V_C"] = ident_C
self.kexinit["V_S"] = ident_S:gsub("[\r\n]", "")
-- Determine whether to and do, if applying, enable the
-- compatibility mode{,s} corresponding to the remote
-- SSH implementation as identified by its
-- identification string by iteratively toggling each
-- known bug given an implementation matching the
-- corresponding pattern.
-- Do note that Lua's lack of binary operators
-- and enumerations makes this, as usual, ludicrous to
-- accomplish.
local version_S = ident_S:match(
"^SSH%-%d+%.%d+%-([^\n]+)")
if(not version_S)
then self:destroy()
return false,
self:set_last_error("ident2", ident_S)
end
for _, Tp in pairs(COMPAT)
do for _, pattern in pairs(Tp["patterns"])
do if(version_S:match(pattern))
then for _, bug in pairs(Tp["bugs"])
do self.bugs[bug] = true
end
end
end
end
self.status[Status.DoneVersionExchange] = true
return true
end
until false
end
-- }}}
-- {{{ [4.2] Key exchange algorithm negotiation
--- Initiate the key exchange by and do negotiate the algorithms aswell as their
-- parameters to employ immediately after the former procedure has successfully
-- finished, attempting to coalesce the remote SSH server's offers with the
-- corresponding equivalents as supported by this script itself, the underlying
-- OpenSSL implementation on the host system, and, optionally, the choice
-- preferred by the caller.
-- Do note that the `none' cipher and MAC algorithm when offered by
-- the server are not supported.
--@param preferred_algorithm An optionally specified table indicating one single algorithm per category that should, if possible, be preferred during algorithm negotiation, with the according categories being: <code>encryption_C</code>, <code>encryption_S</code>, <code>mac_C</code>, and <code>mac_S</code>.
--@see <code>RFC 4253 Section 4.2 (`Protocol Version Exchange')</code>
--@return Either <code>true</code> or <code>false</code> and an <code>error string</code>.
function SSH2:do_kex_init(preferred_algorithm)
local status, _
local kexinit_S, kexinit_C, offset_S, first_kex_packet_follows
-- Ensure that the current Session is in the correct connection phase.
if(not ((self.status[Status.Connecting])
and (not self.status[Status.DoneKexInit])))
then self:destroy()
return false, self:set_last_error("do_kexinit")
end