ssh2shell 2.0.12 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ssh2shell.js +93 -30
- package/package.json +1 -1
- package/src/ssh2shell.coffee +126 -24
package/lib/ssh2shell.js
CHANGED
|
@@ -453,9 +453,13 @@
|
|
|
453
453
|
};
|
|
454
454
|
|
|
455
455
|
SSH2Shell.prototype._sshConnect = function() {
|
|
456
|
-
var option, ref, safeOption, safeValue, shellControlChars, shellQuote, sshFlags, sshOptions, value;
|
|
456
|
+
var option, ref, safeHost, safeOption, safePort, safeUser, safeValue, shellControlChars, shellQuote, sshFlags, sshOptions, value;
|
|
457
457
|
sshFlags = "-x";
|
|
458
458
|
sshOptions = "";
|
|
459
|
+
shellQuote = function(value) {
|
|
460
|
+
return "'" + String(value).replace(/'/g, "'\\''") + "'";
|
|
461
|
+
};
|
|
462
|
+
shellControlChars = /[;&|`$\n\r]/;
|
|
459
463
|
if (this.sshObj.server.ssh) {
|
|
460
464
|
if (this.sshObj.server.ssh.forceProtocolVersion) {
|
|
461
465
|
sshFlags += this.sshObj.server.ssh.forceProtocolVersion;
|
|
@@ -473,30 +477,54 @@
|
|
|
473
477
|
sshFlags += "v";
|
|
474
478
|
}
|
|
475
479
|
if (this.sshObj.server.ssh.cipherSpec) {
|
|
476
|
-
|
|
480
|
+
if (shellControlChars.test(this.sshObj.server.ssh.cipherSpec)) {
|
|
481
|
+
this.emit('error', "Malicious characters detected in SSH cipherSpec.", "Security Alert", true);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
sshOptions += " -c " + shellQuote(this.sshObj.server.ssh.cipherSpec);
|
|
477
485
|
}
|
|
478
486
|
if (this.sshObj.server.ssh.escape) {
|
|
479
|
-
|
|
487
|
+
if (shellControlChars.test(this.sshObj.server.ssh.escape)) {
|
|
488
|
+
this.emit('error', "Malicious characters detected in SSH escape sequence.", "Security Alert", true);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
sshOptions += " -e " + shellQuote(this.sshObj.server.ssh.escape);
|
|
480
492
|
}
|
|
481
493
|
if (this.sshObj.server.ssh.logFile) {
|
|
482
|
-
|
|
494
|
+
if (shellControlChars.test(this.sshObj.server.ssh.logFile)) {
|
|
495
|
+
this.emit('error', "Malicious characters detected in SSH logFile path.", "Security Alert", true);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
sshOptions += " -E " + shellQuote(this.sshObj.server.ssh.logFile);
|
|
483
499
|
}
|
|
484
500
|
if (this.sshObj.server.ssh.configFile) {
|
|
485
|
-
|
|
501
|
+
if (shellControlChars.test(this.sshObj.server.ssh.configFile)) {
|
|
502
|
+
this.emit('error', "Malicious characters detected in SSH configFile path.", "Security Alert", true);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
sshOptions += " -F " + shellQuote(this.sshObj.server.ssh.configFile);
|
|
486
506
|
}
|
|
487
507
|
if (this.sshObj.server.ssh.identityFile) {
|
|
488
|
-
|
|
508
|
+
if (shellControlChars.test(this.sshObj.server.ssh.identityFile)) {
|
|
509
|
+
this.emit('error', "Malicious characters detected in SSH identityFile path.", "Security Alert", true);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
sshOptions += " -i " + shellQuote(this.sshObj.server.ssh.identityFile);
|
|
489
513
|
}
|
|
490
514
|
if (this.sshObj.server.ssh.loginName) {
|
|
491
|
-
|
|
515
|
+
if (shellControlChars.test(this.sshObj.server.ssh.loginName)) {
|
|
516
|
+
this.emit('error', "Malicious characters detected in SSH loginName.", "Security Alert", true);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
sshOptions += " -l " + shellQuote(this.sshObj.server.ssh.loginName);
|
|
492
520
|
}
|
|
493
521
|
if (this.sshObj.server.ssh.macSpec) {
|
|
494
|
-
|
|
522
|
+
if (shellControlChars.test(this.sshObj.server.ssh.macSpec)) {
|
|
523
|
+
this.emit('error', "Malicious characters detected in SSH macSpec.", "Security Alert", true);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
sshOptions += " -m " + shellQuote(this.sshObj.server.ssh.macSpec);
|
|
495
527
|
}
|
|
496
|
-
shellQuote = function(value) {
|
|
497
|
-
return "'" + String(value).replace(/'/g, "'\\''") + "'";
|
|
498
|
-
};
|
|
499
|
-
shellControlChars = /[;&|`$\n\r]/;
|
|
500
528
|
ref = this.sshObj.server.ssh.options || {};
|
|
501
529
|
for (option in ref) {
|
|
502
530
|
value = ref[option];
|
|
@@ -510,10 +538,13 @@
|
|
|
510
538
|
}
|
|
511
539
|
}
|
|
512
540
|
sshOptions += ' -o "StrictHostKeyChecking=no"';
|
|
513
|
-
|
|
541
|
+
safePort = String(this.sshObj.server.port || 22).replace(/[^0-9]/g, "");
|
|
542
|
+
sshOptions += " -p " + safePort;
|
|
543
|
+
safeUser = shellQuote(this.sshObj.server.userName);
|
|
544
|
+
safeHost = shellQuote(this.sshObj.server.host);
|
|
514
545
|
this.sshObj.sshAuth = false;
|
|
515
546
|
this.sshObj.sshToNextHost = true;
|
|
516
|
-
this.command = "ssh " + sshFlags + " " + sshOptions + " " +
|
|
547
|
+
this.command = "ssh " + sshFlags + " " + sshOptions + " " + safeUser + "@" + safeHost;
|
|
517
548
|
if (this.sshObj.debug) {
|
|
518
549
|
this.emit('msg', this.sshObj.server.host + ": SSH command: connect");
|
|
519
550
|
}
|
|
@@ -715,7 +746,7 @@
|
|
|
715
746
|
};
|
|
716
747
|
|
|
717
748
|
SSH2Shell.prototype._loadDefaults = function() {
|
|
718
|
-
var ref, ref1, ref2, ref3, ref4, ref5, ref6;
|
|
749
|
+
var cleanPass, cleanPrompt, cleanStandard, cleanText, escapeRegex, ref, ref1, ref2, ref3, ref4, ref5, ref6, safeAsciiPattern, safeColorPattern;
|
|
719
750
|
if (this.sshObj.msg && this.sshObj.msg.send && typeof this.sshObj.msg.send === 'function') {
|
|
720
751
|
this.sshObj.msg = this.sshObj.msg.send;
|
|
721
752
|
}
|
|
@@ -779,21 +810,19 @@
|
|
|
779
810
|
if (!this.sshObj.passPromptText) {
|
|
780
811
|
this.sshObj.passPromptText = "Password";
|
|
781
812
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
if (!this.sshObj.asciiFilter) {
|
|
786
|
-
this.sshObj.asciiFilter = "[^\r\n\x20-\x7e]";
|
|
813
|
+
this.sshObj.enter = /^[\r\n]+$/.test(this.sshObj.enter || "") ? this.sshObj.enter : "\n";
|
|
814
|
+
if (!this.sshObj.textColorFilter) {
|
|
815
|
+
this.sshObj.textColorFilter = "\\x1b\\[[0-9;]*m";
|
|
787
816
|
}
|
|
788
817
|
if (this.sshObj.disableColorFilter !== true) {
|
|
789
818
|
this.sshObj.disableColorFilter = false;
|
|
790
819
|
}
|
|
820
|
+
if (!this.sshObj.asciiFilter) {
|
|
821
|
+
this.sshObj.asciiFilter = "[^\r\n\x20-\x7e]";
|
|
822
|
+
}
|
|
791
823
|
if (this.sshObj.disableASCIIFilter !== true) {
|
|
792
824
|
this.sshObj.disableASCIIFilter = false;
|
|
793
825
|
}
|
|
794
|
-
if (!this.sshObj.textColorFilter) {
|
|
795
|
-
this.sshObj.textColorFilter = "(\[{1}[0-9;]+m{1})";
|
|
796
|
-
}
|
|
797
826
|
if (!this.sshObj.exitCommands) {
|
|
798
827
|
this.sshObj.exitCommands = [];
|
|
799
828
|
}
|
|
@@ -812,20 +841,52 @@
|
|
|
812
841
|
}
|
|
813
842
|
this.idleTime = (ref2 = this.sshObj.idleTimeOut) != null ? ref2 : 5000;
|
|
814
843
|
this.dataIdleTime = (ref3 = this.sshObj.dataIdleTime) != null ? ref3 : 500;
|
|
844
|
+
escapeRegex = function(str) {
|
|
845
|
+
return String(str).replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
|
|
846
|
+
};
|
|
815
847
|
if (!this.asciiFilter) {
|
|
816
|
-
|
|
848
|
+
if (this.sshObj.asciiFilter === "[^\r\n\x20-\x7e]") {
|
|
849
|
+
this.asciiFilter = /[^\t\r\n\x20-\x7e]/g;
|
|
850
|
+
} else {
|
|
851
|
+
safeAsciiPattern = /^\[\^?[a-zA-Z0-9\s\\x00-\\x7f\-\[\]]+\]$/;
|
|
852
|
+
if (safeAsciiPattern.test(this.sshObj.asciiFilter)) {
|
|
853
|
+
this.asciiFilter = new RegExp(this.sshObj.asciiFilter, "g");
|
|
854
|
+
} else {
|
|
855
|
+
if (this.sshObj.debug) {
|
|
856
|
+
this.emit('msg', (this.sshObj.server.host + ": AsciiFilter: incorrect regular expression reverting to default: ") + this.sshObj.asciiFilter);
|
|
857
|
+
}
|
|
858
|
+
this.asciiFilter = /[^\t\r\n\x20-\x7e]/g;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
817
861
|
}
|
|
818
862
|
if (!this.textColorFilter) {
|
|
819
|
-
|
|
863
|
+
if (this.sshObj.textColorFilter === "(\[{1}[0-9;]+m{1})") {
|
|
864
|
+
this.textColorFilter = /\x1b\[[0-9;]*m/g;
|
|
865
|
+
} else {
|
|
866
|
+
safeColorPattern = /^(\\x1b|\\033)?\\?\[[0-9;]*\*?[a-zA-Z]$/;
|
|
867
|
+
if (safeColorPattern.test(this.sshObj.textColorFilter)) {
|
|
868
|
+
this.textColorFilter = new RegExp(this.sshObj.textColorFilter, "g");
|
|
869
|
+
} else {
|
|
870
|
+
if (this.sshObj.debug) {
|
|
871
|
+
this.emit('msg', (this.sshObj.server.host + ": TextColorFilter: incorrect regular expression reverting to default: ") + this.sshObj.textColorFilter);
|
|
872
|
+
}
|
|
873
|
+
this.textColorFilter = /\x1b\[[0-9;]*m/g;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
820
876
|
}
|
|
821
877
|
if (!this.passwordPrompt) {
|
|
822
|
-
|
|
878
|
+
cleanText = escapeRegex(this.sshObj.passPromptText || "Password");
|
|
879
|
+
cleanPrompt = escapeRegex(this.sshObj.passwordPrompt || ":");
|
|
880
|
+
this.passwordPrompt = new RegExp(cleanText + "[^\\n]*" + cleanPrompt + "\\s?$", "i");
|
|
823
881
|
}
|
|
824
882
|
if (!this.passphrasePrompt) {
|
|
825
|
-
|
|
883
|
+
cleanText = escapeRegex(this.sshObj.passPromptText || "Password");
|
|
884
|
+
cleanPass = escapeRegex(this.sshObj.passphrasePrompt || ":");
|
|
885
|
+
this.passphrasePrompt = new RegExp(cleanText + "[^\\n]*" + cleanPass + "\\s?$", "i");
|
|
826
886
|
}
|
|
827
887
|
if (!this.standardPrompt) {
|
|
828
|
-
|
|
888
|
+
cleanStandard = escapeRegex(this.sshObj.standardPrompt || ">$%#");
|
|
889
|
+
this.standardPrompt = new RegExp("[" + cleanStandard + "]\\s?$");
|
|
829
890
|
}
|
|
830
891
|
this.sshObj.onCommandProcessing = (ref4 = this.sshObj.onCommandProcessing) != null ? ref4 : (function(_this) {
|
|
831
892
|
return function(command, response, sshObj, stream) {};
|
|
@@ -846,8 +907,10 @@
|
|
|
846
907
|
if (_this.sshObj.verbose) {
|
|
847
908
|
_this.emit('msg', _this.sshObj.server.host + ": Timeout command: " + command + " response: " + response);
|
|
848
909
|
}
|
|
849
|
-
_this.emit("error", _this.sshObj.server.host + ": Command timed out after " + (_this.idleTime / 1000) + " seconds", "Timeout", true, function(err, type) {
|
|
850
|
-
|
|
910
|
+
return _this.emit("error", _this.sshObj.server.host + ": Command timed out after " + (_this.idleTime / 1000) + " seconds", "Timeout", true, function(err, type) {
|
|
911
|
+
_this.sshObj.sessionText += _this._buffer;
|
|
912
|
+
return _this._buffer = "";
|
|
913
|
+
});
|
|
851
914
|
};
|
|
852
915
|
})(this);
|
|
853
916
|
if (typeof this.sshObj.onKeyboardInteractive === 'function') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ssh2shell",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.14",
|
|
4
4
|
"author": "cmp-202",
|
|
5
5
|
"description": "A wrapper class for ssh2 to run multiple sequential commands in an SSH shell session, handle command responses, and tunnel through to other hosts using nested host objects",
|
|
6
6
|
"main": "./lib/ssh2shell.js",
|
package/src/ssh2shell.coffee
CHANGED
|
@@ -314,36 +314,93 @@ class SSH2Shell extends Stream
|
|
|
314
314
|
|
|
315
315
|
_sshConnect: =>
|
|
316
316
|
|
|
317
|
-
#
|
|
317
|
+
# Add ssh commandline options from host.server.ssh
|
|
318
318
|
sshFlags = "-x"
|
|
319
319
|
sshOptions = ""
|
|
320
|
+
|
|
321
|
+
# 1. Helper function to ensure individual values are safely wrapped
|
|
322
|
+
# to prevent spaces or characters from escaping parameter flags
|
|
323
|
+
shellQuote = (value) -> "'" + String(value).replace(/'/g, "'\\''") + "'"
|
|
324
|
+
|
|
325
|
+
# 2. Strong control whitelist pattern ensuring basic parameters
|
|
326
|
+
# don't contain command separators or layout manipulation tokens
|
|
327
|
+
shellControlChars = /[;&|`$\n\r]/
|
|
328
|
+
|
|
320
329
|
if @sshObj.server.ssh
|
|
330
|
+
# Safely handle protocol configurations
|
|
321
331
|
sshFlags += @sshObj.server.ssh.forceProtocolVersion if @sshObj.server.ssh.forceProtocolVersion
|
|
322
332
|
sshFlags += @sshObj.server.ssh.forceAddressType if @sshObj.server.ssh.forceAddressType
|
|
323
333
|
sshFlags += "T" if @sshObj.server.ssh.disablePseudoTTY
|
|
324
334
|
sshFlags += "t" if @sshObj.server.ssh.forcePseudoTTY
|
|
325
335
|
sshFlags += "v" if @sshObj.server.ssh.verbose
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
336
|
+
|
|
337
|
+
# 3. Intercept and safely escape single parameter properties
|
|
338
|
+
if @sshObj.server.ssh.cipherSpec
|
|
339
|
+
if shellControlChars.test(@sshObj.server.ssh.cipherSpec)
|
|
340
|
+
@.emit 'error', "Malicious characters detected in SSH cipherSpec.", "Security Alert", true
|
|
341
|
+
return
|
|
342
|
+
sshOptions += " -c " + shellQuote(@sshObj.server.ssh.cipherSpec)
|
|
343
|
+
|
|
344
|
+
if @sshObj.server.ssh.escape
|
|
345
|
+
if shellControlChars.test(@sshObj.server.ssh.escape)
|
|
346
|
+
@.emit 'error', "Malicious characters detected in SSH escape sequence.", "Security Alert", true
|
|
347
|
+
return
|
|
348
|
+
sshOptions += " -e " + shellQuote(@sshObj.server.ssh.escape)
|
|
349
|
+
|
|
350
|
+
if @sshObj.server.ssh.logFile
|
|
351
|
+
if shellControlChars.test(@sshObj.server.ssh.logFile)
|
|
352
|
+
@.emit 'error', "Malicious characters detected in SSH logFile path.", "Security Alert", true
|
|
353
|
+
return
|
|
354
|
+
sshOptions += " -E " + shellQuote(@sshObj.server.ssh.logFile)
|
|
355
|
+
|
|
356
|
+
if @sshObj.server.ssh.configFile
|
|
357
|
+
if shellControlChars.test(@sshObj.server.ssh.configFile)
|
|
358
|
+
@.emit 'error', "Malicious characters detected in SSH configFile path.", "Security Alert", true
|
|
359
|
+
return
|
|
360
|
+
sshOptions += " -F " + shellQuote(@sshObj.server.ssh.configFile)
|
|
361
|
+
|
|
362
|
+
if @sshObj.server.ssh.identityFile
|
|
363
|
+
if shellControlChars.test(@sshObj.server.ssh.identityFile)
|
|
364
|
+
@.emit 'error', "Malicious characters detected in SSH identityFile path.", "Security Alert", true
|
|
365
|
+
return
|
|
366
|
+
sshOptions += " -i " + shellQuote(@sshObj.server.ssh.identityFile)
|
|
367
|
+
|
|
368
|
+
if @sshObj.server.ssh.loginName
|
|
369
|
+
if shellControlChars.test(@sshObj.server.ssh.loginName)
|
|
370
|
+
@.emit 'error', "Malicious characters detected in SSH loginName.", "Security Alert", true
|
|
371
|
+
return
|
|
372
|
+
sshOptions += " -l " + shellQuote(@sshObj.server.ssh.loginName)
|
|
373
|
+
|
|
374
|
+
if @sshObj.server.ssh.macSpec
|
|
375
|
+
if shellControlChars.test(@sshObj.server.ssh.macSpec)
|
|
376
|
+
@.emit 'error', "Malicious characters detected in SSH macSpec.", "Security Alert", true
|
|
377
|
+
return
|
|
378
|
+
sshOptions += " -m " + shellQuote(@sshObj.server.ssh.macSpec)
|
|
379
|
+
|
|
380
|
+
# Validate custom option blocks safely
|
|
335
381
|
for option, value of (@sshObj.server.ssh.options or {})
|
|
336
382
|
if shellControlChars.test(option) or shellControlChars.test(value)
|
|
337
|
-
@.emit 'error',"Malicious characters detected in SSH options.","Security Alert",true
|
|
383
|
+
@.emit 'error', "Malicious characters detected in SSH options.", "Security Alert", true
|
|
338
384
|
return
|
|
339
385
|
safeOption = String(option).replace(/[^a-zA-Z0-9]/g, "")
|
|
340
386
|
safeValue = shellQuote(value)
|
|
341
387
|
sshOptions += " -o #{safeOption}=#{safeValue}"
|
|
388
|
+
|
|
389
|
+
# Wrap target dynamic properties to ensure security boundaries hold up
|
|
342
390
|
sshOptions += ' -o "StrictHostKeyChecking=no"'
|
|
343
|
-
|
|
391
|
+
|
|
392
|
+
# Sanitize user context fields going straight into the string command
|
|
393
|
+
safePort = String(@sshObj.server.port or 22).replace(/[^0-9]/g, "")
|
|
394
|
+
sshOptions += " -p #{safePort}"
|
|
395
|
+
|
|
396
|
+
safeUser = shellQuote(@sshObj.server.userName)
|
|
397
|
+
safeHost = shellQuote(@sshObj.server.host)
|
|
398
|
+
|
|
344
399
|
@sshObj.sshAuth = false
|
|
345
400
|
@sshObj.sshToNextHost = true
|
|
346
|
-
|
|
401
|
+
|
|
402
|
+
# 4. Perfectly combined secure connection command string
|
|
403
|
+
@command = "ssh #{sshFlags} #{sshOptions} #{safeUser}@#{safeHost}"
|
|
347
404
|
@.emit 'msg', "#{@sshObj.server.host}: SSH command: connect" if @sshObj.debug
|
|
348
405
|
@_runCommand(@command)
|
|
349
406
|
|
|
@@ -475,7 +532,7 @@ class SSH2Shell extends Stream
|
|
|
475
532
|
@.emit 'msg', "#{@sshObj.server.host}: Load Defaults" if @sshObj.debug
|
|
476
533
|
@command = ""
|
|
477
534
|
@_buffer = ""
|
|
478
|
-
@sshObj.sshToNextHost
|
|
535
|
+
@sshObj.sshToNextHost = false
|
|
479
536
|
@sshObj.connectedMessage = "Connected" unless @sshObj.connectedMessage
|
|
480
537
|
@sshObj.readyMessage = "Ready" unless @sshObj.readyMessage
|
|
481
538
|
@sshObj.closedMessage = "Closed" unless @sshObj.closedMessage
|
|
@@ -490,11 +547,11 @@ class SSH2Shell extends Stream
|
|
|
490
547
|
@sshObj.passwordPrompt = ":" unless @sshObj.passwordPrompt
|
|
491
548
|
@sshObj.passphrasePrompt = ":" unless @sshObj.passphrasePrompt
|
|
492
549
|
@sshObj.passPromptText = "Password" unless @sshObj.passPromptText
|
|
493
|
-
@sshObj.enter =
|
|
494
|
-
@sshObj.
|
|
550
|
+
@sshObj.enter = if /^[\r\n]+$/.test(@sshObj.enter or "") then @sshObj.enter else "\n"
|
|
551
|
+
@sshObj.textColorFilter = "\\x1b\\[[0-9;]*m" unless @sshObj.textColorFilter
|
|
495
552
|
@sshObj.disableColorFilter = false unless @sshObj.disableColorFilter is true
|
|
553
|
+
@sshObj.asciiFilter = "[^\r\n\x20-\x7e]" unless @sshObj.asciiFilter
|
|
496
554
|
@sshObj.disableASCIIFilter = false unless @sshObj.disableASCIIFilter is true
|
|
497
|
-
@sshObj.textColorFilter = "(\[{1}[0-9;]+m{1})" unless @sshObj.textColorFilter
|
|
498
555
|
@sshObj.exitCommands = [] unless @sshObj.exitCommands
|
|
499
556
|
@sshObj.pwSent = false
|
|
500
557
|
@sshObj.sshAuth = false
|
|
@@ -505,12 +562,56 @@ class SSH2Shell extends Stream
|
|
|
505
562
|
@sshObj.pty = true unless @sshObj.pty
|
|
506
563
|
@idleTime = @sshObj.idleTimeOut ? 5000
|
|
507
564
|
@dataIdleTime = @sshObj.dataIdleTime ? 500
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
565
|
+
|
|
566
|
+
escapeRegex = (str) -> String(str).replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
|
|
567
|
+
|
|
568
|
+
# 1. Compile ASCII Filter with Safe Fallback
|
|
569
|
+
unless @asciiFilter
|
|
570
|
+
if @sshObj.asciiFilter is "[^\r\n\x20-\x7e]"
|
|
571
|
+
@asciiFilter = /[^\t\r\n\x20-\x7e]/g
|
|
572
|
+
else
|
|
573
|
+
# Only allow basic structural character classes/ranges; reject dangerous loops (+, *, ?, nested groups)
|
|
574
|
+
safeAsciiPattern = /^\[\^?[a-zA-Z0-9\s\\x00-\\x7f\-\[\]]+\]$/
|
|
575
|
+
|
|
576
|
+
if safeAsciiPattern.test(@sshObj.asciiFilter)
|
|
577
|
+
@asciiFilter = new RegExp(@sshObj.asciiFilter, "g")
|
|
578
|
+
else
|
|
579
|
+
# Graceful Fallback: Warn the user via your debug pipeline and run the safe internal fallback
|
|
580
|
+
if @sshObj.debug
|
|
581
|
+
@emit 'msg', "#{@sshObj.server.host}: AsciiFilter: incorrect regular expression reverting to default: " + @sshObj.asciiFilter
|
|
582
|
+
|
|
583
|
+
# Force set the safe pre-compiled literal so CodeQL passes statically
|
|
584
|
+
@asciiFilter = /[^\t\r\n\x20-\x7e]/g
|
|
585
|
+
|
|
586
|
+
# 2. Compile Text Color Filter with Safe Fallback
|
|
587
|
+
unless @textColorFilter
|
|
588
|
+
if @sshObj.textColorFilter is "(\[{1}[0-9;]+m{1})"
|
|
589
|
+
@textColorFilter = /\x1b\[[0-9;]*m/g
|
|
590
|
+
else
|
|
591
|
+
# Match standard ANSI styles safely, rejecting any ReDoS structures
|
|
592
|
+
safeColorPattern = /^(\\x1b|\\033)?\\?\[[0-9;]*\*?[a-zA-Z]$/
|
|
593
|
+
|
|
594
|
+
if safeColorPattern.test(@sshObj.textColorFilter)
|
|
595
|
+
@textColorFilter = new RegExp(@sshObj.textColorFilter, "g")
|
|
596
|
+
else
|
|
597
|
+
if @sshObj.debug
|
|
598
|
+
@emit 'msg', "#{@sshObj.server.host}: TextColorFilter: incorrect regular expression reverting to default: " + @sshObj.textColorFilter
|
|
599
|
+
@textColorFilter = /\x1b\[[0-9;]*m/g
|
|
600
|
+
|
|
601
|
+
# 3. Prompts: Fully sanitized at runtime via escapeRegex (never need fallbacks because they are cast as pure literals)
|
|
602
|
+
unless @passwordPrompt
|
|
603
|
+
cleanText = escapeRegex(@sshObj.passPromptText or "Password")
|
|
604
|
+
cleanPrompt = escapeRegex(@sshObj.passwordPrompt or ":")
|
|
605
|
+
@passwordPrompt = new RegExp("#{cleanText}[^\\n]*#{cleanPrompt}\\s?$", "i")
|
|
606
|
+
|
|
607
|
+
unless @passphrasePrompt
|
|
608
|
+
cleanText = escapeRegex(@sshObj.passPromptText or "Password")
|
|
609
|
+
cleanPass = escapeRegex(@sshObj.passphrasePrompt or ":")
|
|
610
|
+
@passphrasePrompt = new RegExp("#{cleanText}[^\\n]*#{cleanPass}\\s?$", "i")
|
|
611
|
+
|
|
612
|
+
unless @standardPrompt
|
|
613
|
+
cleanStandard = escapeRegex(@sshObj.standardPrompt or ">$%#")
|
|
614
|
+
@standardPrompt = new RegExp("[#{cleanStandard}]\\s?$")
|
|
514
615
|
|
|
515
616
|
@sshObj.onCommandProcessing = @sshObj.onCommandProcessing ? ( command, response, sshObj, stream ) =>
|
|
516
617
|
|
|
@@ -523,7 +624,8 @@ class SSH2Shell extends Stream
|
|
|
523
624
|
@.emit 'msg', "#{@sshObj.server.host}: Timeout command: #{command} response: #{response}" if @sshObj.verbose
|
|
524
625
|
|
|
525
626
|
@.emit "error", "#{@sshObj.server.host}: Command timed out after #{@.idleTime/1000} seconds", "Timeout", true, (err, type)=>
|
|
526
|
-
|
|
627
|
+
@sshObj.sessionText += @_buffer
|
|
628
|
+
@_buffer = ""
|
|
527
629
|
|
|
528
630
|
@.on "keyboard-interactive", @sshObj.onKeyboardInteractive if typeof @sshObj.onKeyboardInteractive == 'function'
|
|
529
631
|
@.on "error", @sshObj.onError if typeof @sshObj.onError == 'function'
|