Skip to content

Commit c3b7e10

Browse files
AndrewTravisEz13
andauthored
Merged PR 20126: Fix for partial PowerShell module search paths, that can be resolved to CWD locations (PowerShell#17231)
The problem is .NET will return empty strings for special folders that don't exist in some accounts (like System account), and the module path code appends path locations without first checking if the root path is non-empty. This results in partial paths in the PSModulePath list, which are then interpreted by .NET file APIs as rooted in the current working directory. And this in turn can allow low privilege users to drop modules in locations that higher privilege accounts will load from, thus gaining escalated privilege code execution. These changes detect this non-rooted condition and prevents partial paths from being included in search lists. Cherry picked from !17201 Co-authored-by: Travis Plunk <[email protected]>
1 parent 956c2ef commit c3b7e10

File tree

3 files changed

+44
-7
lines changed

3 files changed

+44
-7
lines changed

src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -970,7 +970,8 @@ internal static string GetPersonalModulePath()
970970
#if UNIX
971971
return Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES);
972972
#else
973-
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Utils.ModuleDirectory);
973+
string myDocumentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
974+
return string.IsNullOrEmpty(myDocumentsPath) ? null : Path.Combine(myDocumentsPath, Utils.ModuleDirectory);
974975
#endif
975976
}
976977

@@ -1189,7 +1190,18 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM
11891190
currentProcessModulePath = hkcuUserModulePath; // = EVT.User
11901191
}
11911192

1192-
currentProcessModulePath += Path.PathSeparator;
1193+
if (string.IsNullOrEmpty(currentProcessModulePath))
1194+
{
1195+
if (currentProcessModulePath is null)
1196+
{
1197+
currentProcessModulePath = string.Empty;
1198+
}
1199+
}
1200+
else
1201+
{
1202+
currentProcessModulePath += Path.PathSeparator;
1203+
}
1204+
11931205
if (string.IsNullOrEmpty(hklmMachineModulePath)) // EVT.Machine does Not exist
11941206
{
11951207
currentProcessModulePath += CombineSystemModulePaths(); // += (SharedModulePath + $PSHome\Modules)
@@ -1210,11 +1222,23 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM
12101222
// personalModulePath
12111223
// sharedModulePath
12121224
// systemModulePath
1213-
currentProcessModulePath = AddToPath(currentProcessModulePath, personalModulePathToUse, 0);
1214-
int insertIndex = PathContainsSubstring(currentProcessModulePath, personalModulePathToUse) + personalModulePathToUse.Length + 1;
1215-
currentProcessModulePath = AddToPath(currentProcessModulePath, sharedModulePath, insertIndex);
1216-
insertIndex = PathContainsSubstring(currentProcessModulePath, sharedModulePath) + sharedModulePath.Length + 1;
1217-
currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, insertIndex);
1225+
int insertIndex = 0;
1226+
if (!string.IsNullOrEmpty(personalModulePathToUse))
1227+
{
1228+
currentProcessModulePath = AddToPath(currentProcessModulePath, personalModulePathToUse, insertIndex);
1229+
insertIndex = PathContainsSubstring(currentProcessModulePath, personalModulePathToUse) + personalModulePathToUse.Length + 1;
1230+
}
1231+
1232+
if (!string.IsNullOrEmpty(sharedModulePath))
1233+
{
1234+
currentProcessModulePath = AddToPath(currentProcessModulePath, sharedModulePath, insertIndex);
1235+
insertIndex = PathContainsSubstring(currentProcessModulePath, sharedModulePath) + sharedModulePath.Length + 1;
1236+
}
1237+
1238+
if (!string.IsNullOrEmpty(systemModulePathToUse))
1239+
{
1240+
currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, insertIndex);
1241+
}
12181242
}
12191243

12201244
return currentProcessModulePath;

src/System.Management.Automation/resources/PathUtilsStrings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@
138138
<data name="ExportPSSession_ErrorDirectoryExists" xml:space="preserve">
139139
<value>The directory '{0}' already exists. Use the -Force parameter if you want to overwrite the directory and files within the directory.</value>
140140
</data>
141+
<data name="ExportPSSession_ErrorModuleNameOrPath" xml:space="preserve">
142+
<value>The -OutputModule parameter does not resolve to a path, and a user module path cannot be found for the provided name.</value>
143+
</data>
141144
<data name="ExportPSSession_CannotCreateOutputDirectory" xml:space="preserve">
142145
<value>Cannot create the module {0} due to the following: {1}. Use a different argument for the -OutputModule parameter and retry.</value>
143146
<comment>{StrContains="OutputModule"}

src/System.Management.Automation/utils/PathUtils.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,16 @@ internal static DirectoryInfo CreateModuleDirectory(PSCmdlet cmdlet, string modu
367367
if (string.IsNullOrEmpty(rootedPath))
368368
{
369369
string personalModuleRoot = ModuleIntrinsics.GetPersonalModulePath();
370+
if (string.IsNullOrEmpty(personalModuleRoot))
371+
{
372+
cmdlet.ThrowTerminatingError(
373+
new ErrorRecord(
374+
new ArgumentException(PathUtilsStrings.ExportPSSession_ErrorModuleNameOrPath),
375+
"ExportPSSession_ErrorModuleNameOrPath",
376+
ErrorCategory.InvalidArgument,
377+
cmdlet));
378+
}
379+
370380
rootedPath = Path.Combine(personalModuleRoot, moduleNameOrPath);
371381
}
372382

0 commit comments

Comments
 (0)