Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Usage Question] impersonation for kerberos authenticated users #117

Open
giuliohome opened this issue Nov 12, 2021 · 9 comments
Open

[Usage Question] impersonation for kerberos authenticated users #117

giuliohome opened this issue Nov 12, 2021 · 9 comments

Comments

@giuliohome
Copy link

giuliohome commented Nov 12, 2021

Only a support/help question.
Let say I have a user authenticated via kerberos SSO.
Now my node backend is running under system user and it has no access to a certain network folder.
The authenticated user instead has access to such network folder, so I want to impersonate the user; question: how to do that?
Should I use the access token from sso? I can't find an example, a tutorial or more instructions.
I've tried to look at the source code here.
The point is that I see that SSO.ts is doing sspi.ImpersonateSecurityContext(this.serverContextHandle); but to do that it is using a serverContextHandle that is kept private(*)! I would be tempted to fork and modify the code at that point (here, conceptually, I should be able to open the shared folder as the impersonated user, correct?), but it seems complex and before doing that, I would rather gather a better overall understanding. Also because I see also sspi.OpenThreadToken() immediately after the impersonation: is that needed for the impersonation (maybe not, I guess the user is already impersonated here, correct?) or just to save the access token? I guess it is for the latter goal, but, again, as I said before, I miss the usage of this access token.

(*) Well, it is passed via contructor from the auth.ts that in turn is gathering the serverSecurityContext.contextHandle, basically from sspi.AcceptSecurityContext(input) where the input is more or less the Kerberos authorization token... ok, but I believe I'm not supposed to repeat all that procedure (starting from Kerberos token and passing it to AcceptSecurityContext) again in my usage code: that would mean that saving the access token is useless, so I'm not considering this option.

@giuliohome
Copy link
Author

giuliohome commented Nov 12, 2021

I think I have to call ImpersonateLoggedOnUser with the with sso.user.accessToken in input.

Will try (if I find an example for node.js: this package export appears to be a good fit, I'm going to install it) and close the issue, if it works.
Thanks

@giuliohome
Copy link
Author

I'm getting

0|node.js express sso  | TypeError: 'handle' should be an External returned from logonUser()
0|node.js express sso  |     at F:\Apps\ng\angular-sso-example\back\src\server.ts:62:3

for this code

const { impersonateLoggedOnUser,  revertToSelf} = require('F:\\Apps\\ng\\angular-sso-example\\back\\build\\Release\\users.node');
const accessToken = req?.session?.sso?.user?.accessToken;
impersonateLoggedOnUser(accessToken);

@giuliohome
Copy link
Author

giuliohome commented Nov 12, 2021

The two libraries have different ways to treat the handles.
I think I have to deserialize the handle from this utility in such a way that is expected from here...

edit

Ehm.... this is what you use to retrieve a HANDLE from a string, I'll try to modify the other lib users.cc accordingly...

@giuliohome
Copy link
Author

giuliohome commented Nov 12, 2021

Now the access token is correctly transformed into a napi value and viceversa (code below), but still ImpersonateLoggedOnUser throws invalid handle.

std::string p2s(void *ptr) {
  std::stringstream s;
  s << "0x" << std::setfill('0') << std::setw(sizeof(ULONG_PTR) * 2) << std::hex
     << ptr;
  std::string result = s.str();
  return result;
}

Value impersonateLoggedOnUser(CallbackInfo const& info) {
    auto env = info.Env();
	// return info[0];
	
	HANDLE token =
		s2p(info[0].As<Napi::String>().Utf8Value());
	
	Value ret_handle = External<void>::New(env, token, [](Env env, HANDLE handle) {
			CloseHandle(handle);
		});
	
	auto handle = get_handle(env, 
		ret_handle
	);
	
    if (!ImpersonateLoggedOnUser(handle)) {
		throw createWindowsError(env, GetLastError(), "ImpersonateLoggedOnUser");
    }
	
	//std::string str = p2s(handle);
	//return Napi::String::New(env, str);
	return ret_handle;
}

giuliohome added a commit to giuliohome/native-users-node that referenced this issue Nov 12, 2021
@giuliohome
Copy link
Author

giuliohome commented Nov 13, 2021

I have recompiled the library to have access to the private server context handle.
So now I can run again all the sequence

        const getServerHandle = (req : any) => req?.session?.sso?.serverContextHandle
        const input = getServerHandle(req);
	sspi.ImpersonateSecurityContext(input);
	const new_access_token = sspi.OpenThreadToken();
	impersonateLoggedOnUser(new_access_token);

and in this case I'm getting a different error: 'Access is denied.'

asked on SO, but it has been closed.

In conclusion, the results of my tests of reusing serverContextHandle and OpenThreadToken are

  1. impersonateLoggedOnUser : 'Access is denied.'
  2. sqlite connection: 'SQLITE_CANTOPEN: unable to open database file'
  3. ms sql with windows auth: "[Microsoft][SQL Server Native Client 11.0][SQL Server]Login failed for user 'domain\owner'."

giuliohome added a commit to giuliohome/angular-sso-example that referenced this issue Nov 14, 2021
giuliohome added a commit to giuliohome/angular-sso-example that referenced this issue Nov 14, 2021
giuliohome added a commit to giuliohome/angular-sso-example that referenced this issue Nov 14, 2021
giuliohome added a commit to giuliohome/angular-sso-example that referenced this issue Nov 14, 2021
@giuliohome
Copy link
Author

Unfortunately also MS SQL windows authentication fails because it sees the process owner user and not the impersonated SSPI token.
Sharing this last commit for future reference and review.

@giuliohome
Copy link
Author

giuliohome commented Nov 14, 2021

I doubt that other debug details are needed. All this boils down to saying that Kerberos ticket is only valid to authenticate Alice to Bob but can't be used for Bob to impersonate Alice (typically for windows authentication on ms sql server). I only see 2 possible answers: either 1) yes, it's by design (hopefully that is true IMO) or 2) no, one could be able to use Kerberos ticket to impersonateLoggedOnUser or to connect via windows auth to a SQL Server and the like... In either case it can be answered, in principle, without further details about my debug environment. My conclusion written here.

giuliohome added a commit to giuliohome/angular-sso-example that referenced this issue Nov 16, 2021
@giuliohome
Copy link
Author

giuliohome commented Nov 16, 2021

Solved with this code!

In the context of SSPI Kerberos access token (typically from single sign on), the access denied from impersonateLoggedOnUser is solved in C++ by setting DWORD flags = MAXIMUM_ALLOWED; in OpenThreadToken. At that point ImpersonateLoggedOnUser will accept the returned token (without errors like access denied) and Kerberos single sign on impersonation can be achieved via CreateProcessAsUser. However the impersonation is not possible with an elevated user, I think... (and for sure refresh group policies with 'allow logon')

Will post a PR

#include "../../misc.h"
#include <fstream>

namespace myAddon {
	
// Proof of concept as Kerberos SSPI impersonated user 	
void testImpersponation(HANDLE userToken) {
	
  // Create and open a text file
  std::ofstream MyFile("test_SSPI.bat");

  // Write to the file
  MyFile << "whoami > whoami.txt"; 

  // Close the file
  MyFile.close(); // check if file owner is the impersonated user
	
  STARTUPINFO si = { sizeof(STARTUPINFO) };
  PROCESS_INFORMATION pi = {0};

  wchar_t wszCommand[]=L"cmd.exe /C test_SSPI.bat";
  /* Unicode version of CreateProcess modifies its command parameter... Ansi doesn't.
     Apparently this is not classed as a bug ???? */
  if(!CreateProcessAsUser(userToken,NULL,wszCommand,NULL,NULL,FALSE,CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi))
  {
      //CloseHandle(hToken);
      fprintf(stderr,"CreateProcess returned error %d\n",GetLastError());
      return;
  }
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread)
}
	

void e_ImpersonateSecurityContext(const Napi::CallbackInfo &info) {
  Napi::Env env = info.Env();

  if (info.Length() < 1) {
    throw Napi::Error::New(
        env,
        "ImpersonateSecurityContext: Wrong number of arguments. "
        "ImpersonateSecurityContext(serverContextHandle: string)");
  }

  Napi::String serverContextHandleString = info[0].As<Napi::String>();
  CtxtHandle serverContextHandle =
      SecHandleUtil::deserialize(serverContextHandleString.Utf8Value());

  SECURITY_STATUS secStatus = ImpersonateSecurityContext(&serverContextHandle);
  if (secStatus != SEC_E_OK) {
    throw Napi::Error::New(env,
                           "Cannot ImpersonateSecurityContext: secStatus = " +
                               plf::error_msg(secStatus));
  }

  HANDLE userToken;

  DWORD flags = MAXIMUM_ALLOWED; // TOKEN_QUERY | TOKEN_QUERY_SOURCE;

  BOOL status = OpenThreadToken(GetCurrentThread(), flags, TRUE, &userToken);
  if (status == FALSE) {
      throw Napi::Error::New(env, "OpenThreadToken: error. " + plf::error_msg());
  }


   HANDLE duplicatedToken;
  BOOL statusDupl = DuplicateTokenEx(userToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &duplicatedToken);
  if (statusDupl == FALSE) {
      throw Napi::Error::New(env, "DuplicateTokenEx: error. " + plf::error_msg());
  } 
  


  if (!ImpersonateLoggedOnUser(duplicatedToken)) {
      throw Napi::Error::New(env, "C++ ImpersonateLoggedOnUser: error. " + plf::error_msg());
  }

  testImpersponation(duplicatedToken);

  
  RevertToSelf();
  CloseHandle(duplicatedToken);
}

}  // namespace myAddon

The above proof of concept and impersonation test has been moved now to the other library as written below.

giuliohome added a commit to giuliohome/node-expose-sspi that referenced this issue Nov 16, 2021
@giuliohome
Copy link
Author

now the impersonation test has been moved to the other library native-users-node PR by passing the server handle to the session:
it looks good to me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant