@@ -272,9 +272,9 @@ public static async Task<string> GetChallengeToken(string requestUri, HttpClient
272
272
null ) ;
273
273
}
274
274
275
- var headerValue = authenticateHeaderValues . FirstOrDefault ( ) ;
275
+ var wwwHeader = authenticateHeaderValues . FirstOrDefault ( ) ;
276
276
277
- if ( string . IsNullOrEmpty ( headerValue ) || ! headerValue . Contains ( '=' ) )
277
+ if ( string . IsNullOrEmpty ( wwwHeader ) || ! wwwHeader . Contains ( '=' ) )
278
278
{
279
279
throw new ServerManagedIdentityTokenException (
280
280
ManagedIdentityErrorCodes . ServerManagedIdentityTokenChallengeFailed ,
@@ -283,31 +283,17 @@ public static async Task<string> GetChallengeToken(string requestUri, HttpClient
283
283
}
284
284
285
285
// Value in the header is: "Basic realm=<secret file path>"
286
- var secretFilePath = headerValue . Split ( '=' ) [ 1 ] ;
286
+ var secretFilePath = wwwHeader . Split ( new char [ ] { '=' } , StringSplitOptions . RemoveEmptyEntries ) . LastOrDefault ( ) ;
287
287
288
- var expectedSecretFileLocation = Environment . GetEnvironmentVariable ( "ProgramData" ) + HybridSecretFileDirectory ;
289
-
290
- // Validate the secret file path received is from the expected predefined directory and is of expected .key file extension.
291
- // This ensures we are not redirected by some malicious process listening on localhost:40342 into a bad secret file.
292
- if ( string . IsNullOrEmpty ( secretFilePath ) ||
293
- ! secretFilePath . Contains ( expectedSecretFileLocation ) ||
294
- ! secretFilePath . Contains ( ".key" ) )
295
- {
296
- throw new ServerManagedIdentityTokenException (
297
- ManagedIdentityErrorCodes . ServerManagedIdentityTokenChallengeFailed ,
298
- StorageSyncResources . AgentMI_InvalidSecretFileError ,
299
- null ) ;
300
- }
301
-
302
- if ( File . Exists ( secretFilePath ) )
288
+ if ( IsSecretFilePathValid ( secretFilePath ) )
303
289
{
304
290
challengeToken = File . ReadAllText ( secretFilePath ) ;
305
291
}
306
292
else
307
293
{
308
294
throw new ServerManagedIdentityTokenException (
309
295
ManagedIdentityErrorCodes . ServerManagedIdentityTokenChallengeFailed ,
310
- StorageSyncResources . AgentMI_MissingSecretFilePathOnServerError ,
296
+ StorageSyncResources . AgentMI_InvalidSecretFileError ,
311
297
null ) ;
312
298
}
313
299
}
@@ -349,6 +335,59 @@ public static async Task<string> GetChallengeToken(string requestUri, HttpClient
349
335
return challengeToken ;
350
336
}
351
337
338
+ /// <summary>
339
+ /// Validate the secret file path received is from the expected predefined directory and is of expected .key file extension.
340
+ /// This ensures we are not redirected by some malicious process listening on localhost:40342 into a bad secret file.
341
+ /// </summary>
342
+ /// <param name="secretFilePath"></param>
343
+ /// <returns></returns>
344
+ private static bool IsSecretFilePathValid ( string secretFilePath )
345
+ {
346
+ // Check if the secret file path is null or empty
347
+ if ( string . IsNullOrEmpty ( secretFilePath ) )
348
+ {
349
+ return false ;
350
+ }
351
+
352
+ // Normalize the path to prevent path traversal attacks
353
+ string normalizedTokenLocation = Path . GetFullPath ( secretFilePath ) ;
354
+
355
+ string allowedFolder ;
356
+
357
+ // Expected form: %ProgramData%\AzureConnectedMachineAgent\Tokens\<guid>.key
358
+ var programData = Environment . GetEnvironmentVariable ( "ProgramData" ) ;
359
+
360
+ if ( string . IsNullOrEmpty ( programData ) )
361
+ {
362
+ // If ProgramData is not found, try to manually construct it using SystemDrive
363
+ var systemDrive = Environment . GetEnvironmentVariable ( "SystemDrive" ) ;
364
+
365
+ if ( string . IsNullOrEmpty ( systemDrive ) )
366
+ {
367
+ throw new ServerManagedIdentityTokenException (
368
+ ManagedIdentityErrorCodes . ServerManagedIdentityTokenChallengeFailed ,
369
+ StorageSyncResources . AgentMI_ProgramDataNotFoundError ,
370
+ null ) ;
371
+ }
372
+ else
373
+ {
374
+ programData = Path . Combine ( systemDrive , "ProgramData" ) ;
375
+ }
376
+ }
377
+
378
+ allowedFolder = Path . GetFullPath ( Path . Combine ( programData , "AzureConnectedMachineAgent" , "Tokens" ) ) ;
379
+
380
+ // Ensure the secret file is within the allowed tokens folder, exists, and ends with .key
381
+ if ( ! normalizedTokenLocation . StartsWith ( allowedFolder + Path . DirectorySeparatorChar , StringComparison . OrdinalIgnoreCase ) ||
382
+ ! File . Exists ( normalizedTokenLocation ) ||
383
+ ! Path . GetFileName ( normalizedTokenLocation ) . EndsWith ( ".key" , StringComparison . OrdinalIgnoreCase ) )
384
+ {
385
+ return false ;
386
+ }
387
+
388
+ return true ;
389
+ }
390
+
352
391
/// <summary>
353
392
/// Gets the Server Type from the the StorageSync registry path. Default to <see cref="LocalServerType.HybridServer"/>
354
393
/// Not using ServerManagedIdentityProvider.GetServerType because it does not necessarily do a direct registry key read.
0 commit comments