I wrote a service to retrieve windows credential from windows with its target name. I wrote two ways to get credential.the code works fine in my machine(windows 11) for both ways but it does not find credential in a windows server.
Code= Windows Service(background service writen in .Net 8) run as administrator
getting Credential:
here is how did I do that:
calling CredRead:
private static string? ReadCredential(string target, Func<CredentialEnum.CREDENTIAL, string> readFunc)
{
if (string.IsNullOrWhiteSpace(target))
{
throw new ArgumentException($"{nameof(target)} cannot be null or empty.", nameof(target));
}
nint credPtr = nint.Zero;
try
{
target = target.Trim();
bool success = CredRead(target, CredentialTypeEnum.CRED_TYPE.GENERIC, 0, out credPtr);
if (success)
{
CredentialEnum.CREDENTIAL credential = Marshal.PtrToStructure<CredentialEnum.CREDENTIAL>(credPtr);
return readFunc(credential);
}
}
catch (Exception ex)
{
Log.Error(ex, "Error reading credential");
}
finally
{
if (credPtr != nint.Zero)
{
CredFree(credPtr);
credPtr = nint.Zero;
}
}
return null;
}
CredRead:
//https://learn.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credreada
[DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredRead(string target, CredentialTypeEnum.CRED_TYPE type, int reservedFlag, out nint credentialPtr);
calling CreadMan.ps1 script:
private async Task ExecuteCredManCommand(string psCommand, CustomerHost host)
{
string scriptPath = GetScriptFullPath("CredMan.ps1");
psCommand = $"&{scriptPath + " " + $"-GetCred -Target '{host.CredentialName.Trim()}' -CredType Generic"}";
StringBuilder stdOutBuffer = new StringBuilder();
StringBuilder stdErrBuffer = new StringBuilder();
var result = Cli.Wrap("powershell")
.WithArguments($"-Command \"{psCommand}\"")
.WithValidation(CommandResultValidation.None)
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer))
.ListenAsync();
int exitCode = 0;
await foreach (var cmdEvent in result)
{
if (cmdEvent is ExitedCommandEvent exited)
{
exitCode = exited.ExitCode;
_logger.LogInformation($"Process exited; Code: {exited.ExitCode}");
}
}
// Parse the output to retrieve UserName and Password
string output = stdOutBuffer.ToString();
string[] lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
_logger.LogInformation($"CredMan Output: {lines}");
foreach (var line in lines)
{
//" UserName : k-is\\administrator"
if (line.Trim().StartsWith("UserName"))
{
host.UserName = line.Split(':')[1].Trim();
}
else if (line.Trim().StartsWith("Password"))
{
host.Password = line.Split(':')[1].Trim();
}
}
}
CredMan function which return error on Server: error is "Write-Host "Credential for '$Target' as '$CredType' type was not found.""
#region Reading selected credential
if($GetCred)
{
if(-not $Target)
{
Write-Host "You must supply a target URI."
return
}
# may be [PsUtils.CredMan+Credential] or [Management.Automation.ErrorRecord]
[Object] $Cred = Read-Creds $Target $CredType
if($null -eq $Cred)
{
Write-Host "Credential for '$Target' as '$CredType' type was not found."
return
}
if($Cred -is [Management.Automation.ErrorRecord])
{
return $Cred
}
[String] $CredStr = @"
Found credentials as:
UserName : $($Cred.UserName)
Password : $($Cred.CredentialBlob)
Target : $($Cred.TargetName.Substring($Cred.TargetName.IndexOf("=")+1))
Updated : $([String]::Format("{0:yyyy-MM-dd HH:mm:ss}", $Cred.LastWritten.ToUniversalTime())) UTC
Comment : $($Cred.Comment)
"@
Write-Host $CredStr
}
#endregion
When I run the CredMan.ps1 in powershell ISE it return the given Credential successfully. the powershell code is :
cd pathofcredman
.\CredMan.ps1 -GetCred -Target NameOfTarget -CredType Generic
I will appriciate if anyone can help me how to go forward
As hier said the problem is that Service is running as "local System account" and even it has the admin power but the windows credential does not allow it to access credential. for accessing credential, the user should be the same user as creator of credential. the easy way is to right click on service in "Windows Services" and say to run service as the user... but the correct way is to define a service Account for doing that job.