-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsecurepass.html
262 lines (234 loc) · 17.7 KB
/
securepass.html
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
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
<link rel="stylesheet" href="style.css">
<script>
function copyClipboard() {
var elm = document.getElementById("code");
// for Internet Explorer
if(document.body.createTextRange) {
var range = document.body.createTextRange();
range.moveToElementText(elm);
range.select();
document.execCommand("Copy");
}
else if(window.getSelection) {
// other browsers
var selection = window.getSelection();
var range = document.createRange();
range.selectNodeContents(elm);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand("Copy");
}
} </script>
</head>
<body class="body">
<div class="table-cell-main">
<h1 class="heading">
A Better Implementation of SecureString Passwords for Powershell Modules
</h1>
<div class="table-cell">
<button class="codebutton" id="codebtn" type="button" onclick="copyClipboard()"><span class="copy">Copy CodeBlock</span></button>
<pre>
<code>
<p class="codeblock" id="code"><span class="highlight">Function</span> <span class="function">Test-Function</span>{
[<span class="function">CmdletBinding</span>(<span class="mtk55">DefaultParameterSetName</span>=<span class="string">'None'</span>)]
<span class="conditional">param</span>(
[<span class="function">Parameter</span>(<span class="mtk55">Mandatory</span>=<span class="highlight">$True</span>, <span class="mtk55">Position</span>=<span class="function">1</span>, <span class="mtk55">ParameterSetName</span>=<span class="string">'Peer'</span>)]
[<span class="highlight">ArgumentCompleter</span>({
<span class="conditional">param</span> ( <span class="mtk55">$commandName</span>,
<span class="mtk55">$parameterName</span>,
<span class="mtk55">$wordToComplete</span>,
<span class="mtk55">$commandAst</span>,
<span class="mtk55">$fakeBoundParameters</span> )
<span class="conditional">if</span>([<span class="highlight">string</span>]::IsNullOrEmpty(<span class="mtk55">$fakeBoundParameters</span><span class="function">.PassPhrase</span>)){
<span class="mtk55">$</span><span class="highlight">Script</span><span class="mtk55">:PassPhrase</span> = ([<span class="highlight">System.Management.Automation.PSCredential</span>]::new(<span class="string">' '</span>, <span class="highlight">$(</span><span class="function">Read-Host</span> -AsSecureString))).GetNetworkCredential().Password
<span class="mtk55">$count</span> = <span class="string">'~'</span> * (<span class="mtk55">$PassPhrase</span><span class="function">.length</span> / <span class="function">2</span> )
<span class="mtk55">$</span><span class="highlight">Script</span><span class="mtk55">:ReturnableValue</span> = <span class="string">"</span><span class="highlight">$(</span><span class="mtk55">$count</span> + <span class="string">"SECUREPASSWORD"</span> + <span class="mtk55">$count</span><span class="highlight">)</span><span class="string">"</span>
<span class="conditional">return</span> <span class="mtk55">$</span><span class="highlight">Script</span><span class="mtk55">:ReturnableValue</span>
}
})]
[<span class="highlight">String</span>]<span class="mtk55">$PassPhrase</span> = <span class="mtk55">$</span><span class="highlight">Script</span><span class="mtk55">:ReturnableValue</span>,
[<span class="function">parameter</span>(DontShow)]
<span class="highlight">$VerbosePreference</span>,
[<span class="function">parameter</span>(DontShow)]
<span class="highlight">$WarningPreference</span>,
[<span class="function">parameter</span>(DontShow)]
<span class="highlight">$ConfirmPreference</span>,
[<span class="function">parameter</span>(DontShow)]
<span class="highlight">$DebugPreference</span>
)
<span class="conditional">if</span>(<span class="mtk55">$PassPhrase</span> -notmatch <span class="mtk55">$</span><span class="highlight">Script</span><span class="mtk55">:ReturnableValue</span>){
<span class="function">Write-Warning</span> <span class="string">'SecureString form of password is mandatory. Please use tab-completor with the parameter and try again.'</span>
<span class="conditional">break</span>;
}
}</p>
</code>
</pre>
</div>
<div class="table-cell">
<span class="paragraph">
To get a better understanding of the code above, lets take a deep dive into road that let here...
</span>
</div>
<div class="table-cell">
<span class="paragraph">
To start off, a minor backstory!<br /><br />
As I started learning how to use <span class="function">Register-ArgumentCompletor</span> as well as dynamic parameters back in the day, I was always curious about if passwords could be efficiently and securely integrated using them. So, I started researching and basically going into a rabbit hole of searching forums, including <span class="forums">Reddit</span>, where I asked many times if this could be done, but could not find a solid answer. It was the usual "Its not possible" or "The only way is to use <span class="function">Read-Host [-AsSecureString]</span> and specify it after the module or script executes". Eventually I gave up and time moved on and I started having a better understaing of Powershell. I then got into making modules, one of which led me back to this rabbit hole. One of the parameters of the module is for a password and well... here we go again. But as I researched more this time, I stumbled across a guide on whats called <span class="highlight">ArgumentCompletor</span>, which is similar to <span class="function">Register-ArgumentCompletor</span>, but allows me to encapsulate the functionality into my code instead of making it external to the code itself. More great info can be found by > <a class="reference" href="https://powershell.one/powershell-internals/attributes/auto-completion">Dr. Tobias Weltner</a> <. With the information found there, I started messing around with stuff and started stumbling onto what eventually became a successful implentation.
<br />
<br />
Lets get started!
<br />
<br />
To save some time, I have created the structure already and as a side note, for this took work we must
<br />
<br />
{TAB} TO ACTIVATE AND {ENTER} TO LEAVE THE ARGUMENTCOMPLETOR.
</span>
</div>
<img class="img" src="./images/nofakebound.png"><br />
<div class="table-cell">
<span class="paragraph">
If we test the function above, and try to use tab completion, the end result will be this...
</span>
</div>
<img class="img" src="./images/result1.png"><br />
<div class="table-cell">
<span class="paragraph">
The reason is because if you look closely at this line of the code
<br />
<br />
<span class="conditional">if</span>([<span class="netclass">string</span>]::IsNullOrEmpty(<span class="variable">$PassPhrase</span>)){}
<br />
<br />
we can see that the parameter itself is being passed into the argument. This is wrong because the parameter itself does not have a value contained until AFTER the module has started running. So what actually needs to happen is we need to make use of what is known as <span class="variable">$fakeBoundParameters</span>. These are initialized inside of the argument container and can hold a value as the argument is executed. So, with this new bound, lets try the code again and see what the result is...
</span>
</div>
<img class="img" src="./images/passphrasenoreturn.png"><br />
<div class="table-cell">
<span class="paragraph">
We can see that the tab completion now works, but the result returned is the same...
</span>
</div>
<img class="img" src="./images/result1.png"><br />
<div class="table-cell">
<span class="paragraph">
This is because we need to have a statement return to the console, like so...
</span>
</div>
<img class="img" src="./images/passphrasewithreturn.png"><br />
<div class="table-cell">
<span class="paragraph">
But, if we run this, our returned result for <span class="variable">$PassPhrase</span> is...
</span>
</div>
<img class="img" src="./images/result2.png"><br />
<div class="table-cell">
<span class="paragraph">
This is because whatever value is returned to the console, becomes the value for our given variable <span class="variable">$PassPhrase</span>. So, all we need to do is give it another variable to contain the result in as pass THAT as the returnable value instead, like so...
</span>
</div>
<img class="img" src="./images/somearbitraryvar.png"><br />
<div class="table-cell">
<span class="paragraph">
That didn't seem to work, it just came back with a <span class="variable">$Null</span> result... but why?
<br />
<br />
Well, this is because the container that we passed the code into <span class="variable">$SomeArbitraryVar</span> is only able to be utilized within that argumentcompletor codeblock. This is useful in general incase you need to hold onto data passed within it, to use with other statements used within the same container, but this isnt useful for use because we need to be able to use it outside of the ArgumentCompletor itself. So, why not use a Global scope so we can break it free from its containment? Well, we could, but Global scopes are highly frowned upon for their security risks and possibilities of breaking objects of other scripts and such. So, what we need is something that can allow us to free our variable from its immediate containment, but still stay within the confinds of the script or session its executed in. The scope we will use for this is the <span class="variable">$Script:</span> Scope. More information on scopes by > <a class="reference" href="https://adamtheautomator.com/powershell-scopes/">Tyler Muir</a> <.
<br />
<br />
So now, lets test this...
<br />
<br /> As a side note, we can actually use the parameters name itself because the regular <span class="variable">$PassPhrase</span> and the elevated <span class="variable">$Script:</span><span class="property">PassPhrase</span> are used as 2 separate containers for our code.
<br />
<br />
Lets watch!
</span>
</div>
<img class="img" src="./images/securestringasterisksshowing.png"><br />
<div class="table-cell">
<span class="paragraph">
That worked! We can see in the codeblock that we passed both variables to return after running the module, but something weird happened to the console as the value returned to the console. As you can see we returned the
<br />
<span class="property">---SecureString---</span><br />value but it left additional asterisks at the end. This leads me to believe that when the value is returned, the console cannot clear itself before returning the given value, resulting in something that looks like a console glitch. But this is merely due to the way the console is designed. So why not <span class="function">Clear-Host</span> before returning the value? Well, because this implementation will have a glitch type effect as well. So what we need is a way to have the returned value be longer than the securestring itself. This is where we can introduce math and get a little creative.
<br />
<br />
Lets watch!
</span>
</div>
<img class="img" src="./images/securestringresultmath.png"><br />
<div class="table-cell">
<span class="paragraph">
We can see that the really long securestring that we passed, returns a value that has now removed all the left over asterisks. To break this down into a simple calculation, I momentarily decrypted the securestring to be able to get the length of it in totalbytes, then divided in half, then multipled it by the - [hyphen/subtract] symbol. Once I contained it in a <span class="variable">$count</span> variable, I returned the value using whats called a sub-expression operator <span class="variable">$()</span>. Simple definition is that this operator will calculate anything within it, then pass it back as a variable. Its extremely useful for calculations, scriptblocks and much more.<br />
So now we have a general understanding of how to implement this. But how does it pan out in a real world example and what measures need to be in place to ensure that the module takes this implentation seriously? Lets dive a little deeper as we are not quite at the end of the journey presented in the first image of this guide.<br />
We can run the code below which is just a plain-text value as see how its presented to the console, as well as the end result compared to our elevated version of the variable.
</span>
</div>
<div class="table-cell">
<span class="paragraph">
<span class="function">Test-Function</span> <span class="paramater">-PassPhrase</span> <span class="value">'test123'</span>
</span>
</div>
<img class="img" src="./images/ranasplaintext.png"><br />
<div class="table-cell">
<span class="paragraph">
From what we can see here, the value of <span class="variable">$PassPhrase</span> returned, but <span class="variable">$Script:</span><span class="property">PassPhrase</span> did not, it returned a <span class="variable">$Null</span> result. This is because instead of using tab completion, we specified a plain-text value, thus, the tab completion never containing a value. This isnt necessary a big deal if youre using argumentcompletors that dont require securestrings or need security or something of the like. But if security is a must, then we must put a stop in place so the script cannot execute without there being a secure-string value.
<br />
We need to create an if statement to produce this effect. But first, we need to take the value returned to the console and place that into a variable itself so we have something to compare in the if statement. Then, we will return that value, but also add it as the default <span class="variable">$PassPhrase</span> value. This is to ensure that if the end user types a plain-text value, it will replace the default of our secure-string returned value, thus triggering the if statement. Incase its the first time that is ran, we will also add an -or statement with it as the elevated variable may not have a value yet.
</span>
</div>
<img class="img" src="./images/testingifwarning.png"><br />
<div class="table-cell">
<span class="paragraph">
From the execution, we can see that a plan-text value AND a secure-string value were passed, but our warning still executed and broke the script, even though <span class="variable">$PassPhrase</span> and <span class="variable">$Script:</span><span class="property">PassPhrase</span> should have matched. So what happened? Well, without going into too much boring detail, I went into a rabbithole of testing and learned that there are certain limitations to this method of secure-string passing. One of them being that certain characters that are passed to the console have an impact on the overall result. Both variables should match (I even used the -match operator) as they are constructed the same way. They are both strings and all, but what I learned is that they return differently. Heres an example of the hyphen symbol returned to the console and exceuted in the function.
</span>
</div>
<img class="img" src="./images/resultsarediff.png"><br />
<div class="table-cell">
<span class="paragraph">
As you can see <span class="variable">$PassPhrase</span> returns no outside quotes but <span class="variable">$Script:</span><span class="property">ReturnableValue</span> does. My best guess on this is that <span class="variable">$PassPhrase</span> executes the additional quotes as part of the executed code, while <span class="variable">$Script:</span><span class="property">ReturnableValue</span> executes the additional quotes as part of the returned value. Even though they are both string values, they have different operating commands. So whats the solution? Remove the additional quotes on the outside of the value, down to single (double) quotes.
</span>
</div>
<img class="img" src="./images/executesproperly.png"><br />
<div class="table-cell">
<span class="paragraph">
We can see that it executes properly now, but if you look closely, you can see that in
<br />
<span class="function">Test-Function</span> -PassPhrase ----SECURE-PASSWORD----
<br />
----SECURE-PASSWORD---- has the same color as the -PassPhrase parameter itself. I thought this was odd so I did some testing with other symbols and oddly enough I was not surprised with the results.
</span>
</div>
<img class="img" src="./images/othersymbols.png"><br />
<div class="table-cell">
<span class="paragraph">
So what is happening here? Well, it spelled out clearly in the errors themselves. Some symbols are being passed back to the variable as quantifiers (definers), instead of as strings. So, with this in mind, we now understand that only certain characters are safe to use for our if statement becasue they define as part of the string and not as part of actual code. (~ and -).<br />
With this knowledge we are now ready to execute a live example of this implentation working properly. Ill be back once I have set this up...
</span>
</div>
<div class="table-cell">
<span class="paragraph somecode">
Writing some code.....
</span>
</div>
<div class="table-cell">
<span class="paragraph">
Okay, I'm Back!<br />
Let's Test this!
</span>
</div>
<img class="img" src="./images/final.png"><br />
<div class="table-cell">
<span class="paragraph">
And there we have it!
<br />
<br />
A safe and effective way to implement securestring passwords into modules and placing a security measure to ensure your module enforces its use.
</span>
</div>
</div>
</body>
</html>