How to get a unique identification from harddisk, motherboard etc from a web-application. I know that this is not possible to do directly.
I´m working in a company with a lot of personal information and the employees use a web-application, that I have developed in aspx.net, of course with login and https, but because of new rules from the government, this is not secure enough anymore.
So to solved this, the company have decided that we have to make some kind of whitelisting of computer, that means that every computer have to use a unique key (GUID) based on the computer's hardware to login to our web application.
But how to do that from web (aspx.net, javascript, jquery), can I make some kind of App (or a Winform program), and next time our employees try to login, they have to install the App/application, and then the aspx.net application can call this program and return the GUID to the login service (WCF-service), how to do that?
I have been working with c#, Winforms, Silverlight, aspx.net, javasript, jquery etc. for many years, but this year I have to find out how to developing Apps (cross platform), so if you have an example/link in other than those mentioned language, this would be fine too
I suggest taking a look at this Retrieving Hardware Identifiers in C# with WMI.
By using classes such as ManagementObject and ManagementClass you will be able to get the properties of your computer's hardware.
Note that the processor ID itself is not unique, you might want to consider a combination of hardware IDs as your unique key.
ex:
/// <summary>
/// Return processorId from first CPU in machine
/// </summary>
/// <returns>[string] ProcessorId</returns>
public string GetCPUId()
{
string cpuInfo = String.Empty;
string temp=String.Empty;
ManagementClass mc = new ManagementClass("Win32_Processor");
ManagementObjectCollection moc = mc.GetInstances();
foreach(ManagementObject mo in moc)
{
if(cpuInfo==String.Empty)
{// only return cpuInfo from first CPU
cpuInfo = mo.Properties["ProcessorId"].Value.ToString();
}
}
return cpuInfo;
}
Then you can create your own browser application to access your company's website. Fetch these IDs and pass them for security purposes.
Related
I'm working on a service (C#) that receives session-change notifications (specifically SessionLogon). The only piece of information I get with that notification is SessionId.
My ultimate goal is to check the logon user's profile (local/roaming AppData/MyCorp/MyApp folder) for a particular setting, and perform a task if it's there.
I need to go from SessionId to something I can map to a local user profile, either directly to a User Account SID or to something that can be mapped to a SID, (e.g. "<domain>\<username>", etc).
The solutions I've found on SO depend upon Windows Terminal Services (WTS) APIs (e.g. WTSQuerySessionInformation), but Remote Desktop Services isn't available on Windows 10 Home edition, so that's a non-starter.
Does anyone know how to map from SessionId to a local user account that doesn't involve WTS APIs?
(EDIT #1) CLARIFICATION:
In .NET, the ServiceBase class has an OnSessionChange override that gets called for login/logout/unlock/lock events. I was originally thinking this was for all such events (from physical machine or Terminal Server).
It looks like this only applies to Terminal Server sessions(?) So, apparently, the sessionId that I get back is a TerminalServer-specific thing. As #RbMm points out below, this override probably wouldn't get called in the first place on Windows Home edition. It's a moot point, though, because it was the local (physical) logon events I was interested in, and that's completely different from Terminal Service sessions.
It seems odd to me that the service base class would have a useful event like this, but have it tied to Terminal Services, rather than work for all cases. Maybe someone has some insight into this?
(EDIT #2) REALIZATION:
#RbMm's comments have cleared up some misconceptions that I started with. Here's a update:
The OnSessionChange event is only for Terminal Services, and has nothing to do with local (physical) logon sessions (I was conflating the two).
I'm only interested in the local logon sessions, so I'll be looking for a way to get notified about them inside my service. If no such notification is available, I'll have to set up a timer and poll.
I'll need to derive a user account SID from whatever piece of information I receive along with such a notification (or periodic call to LsaGetLogonSessionData)
I think you can retrieve this information via WMI
Win32_LogonSession
class Win32_LogonSession : Win32_Session
{
string Caption;
string Description;
datetime InstallDate;
string Name;
string Status;
datetime StartTime;
string AuthenticationPackage;
string LogonId;
uint32 LogonType;
};
Furthermore:
LogonId
Data type: string
Access type: Read-only
Qualifiers: key
ID assigned to the logon session.
Example
var scope = new ManagementScope(ManagementPath.DefaultPath);
var query = new SelectQuery($"Select * from Win32_LogonSession where LogonId = {SessionId}");
var searcher = new ManagementObjectSearcher(scope, query);
var results = searcher.Get();
foreach (ManagementObject mo in results)
{
}
Note this is fully untested
You can resolve this info via processes instead of tokens.
I dont have a c# sample here, but here is PS-snippet to demo the concept:
# https://learn.microsoft.com/en-us/windows/win32/devnotes/getting-the-active-console-session-id
$sessionId = [System.Runtime.InteropServices.Marshal]::ReadByte(0x7ffe02d8)
$pList = (Get-CimInstance Win32_Process -Filter "name = 'explorer.exe' and SessionId=$sessionId")
$winEx = $pList | where {$_.Path -eq 'C:\WINDOWS\explorer.exe'} | sort CreationDate | select -First 1
$sid = (Invoke-CimMethod -InputObject $winEx -MethodName GetOwnerSid).sid
I created a small WPF application that does some operations. I would like to distribute this application to some people, but I want it to be accessible only by the authorized people. I don't really need a registering mechanism.
Because the application is quite small and will be delivered as an EXE file, I don't think that having a database would be an efficient idea.
I was thinking of having a file within the application that contain the credentials of the authorized people, but as far as I know, WPF applications can be easily reversed engineered. I turned my thinking into having the application contact a server to authorize the person or something, but wasn't sure whether it is a good choice or not.
Can you please suggest or throw at me some readings or best practices to study, because whenever I search about this topic I get an example of implementing the UI (which is something i know how to do) and not the login mechanism.
Design Guidelines for Rich Client Applications by MSDN
https://msdn.microsoft.com/en-in/library/ee658087.aspx
Read Security Considerations, Data Handling Considerations and Data
Access
It is very easy to reverse any .Net app , So the point of having an authentication system is for dealing with Noobs and people who do not know about reverse programming , you can use authentication system using Cpu Id for example witch i use , but any way like i said any .Net is reversible .
I will shier my authentication logic with you:
public static string GetId( )
{
string cpuInfo = string.Empty;
ManagementClass mc = new ManagementClass("win32_processor");
ManagementObjectCollection moc = mc.GetInstances( );
foreach (ManagementObject mo in moc)
{
if (cpuInfo == "")
{
//Get only the first CPU's ID
cpuInfo = mo.Properties["processorID"].Value.ToString( );
break;
}
}
return cpuInfo;
}
After you have cpu id do some encryption
Public static string Encrypt(string CpuId)
{ // do some encryption
retuen encryptionCpuId;
}
after that in your UI create a dialog window show the user his cpuID and he will send it to you, after that you will encrypt user's cpuID and give him his activation Key , to do that you must create an other project for generate encryption , And in your App (That you want to publish) check :
if(Key== Encrypt(GetId()) {// Welcome }
else {Environment.Exit(0); }
So every user have his own Key.
After all this you must know that any one can reflect your code and crack this mechanism.
I am using LDAP for querying the list of domains available. My logic works fine when i run this on a machine where one nic card is available and it is successfully querying the list of domains, However when i run this on a machine which is having multiple nic cards i.e. One for Domain A and other one for Domain B, I am getting exception reason is simple i.e. DirectoryEntry() binding is failing.
I need to use LDAP provider only for this.
Below is the code snippet :
using (DirectoryEntry RootDSE = new DirectoryEntry("LDAP://rootDSE"))
{
// Retrieve the Configuration Naming Context from RootDSE
string configNC = RootDSE.Properties["configurationNamingContext"].Value.ToString();
// Connect to the Configuration Naming Context
using (DirectoryEntry configSearchRoot = new DirectoryEntry("LDAP://" + configNC))
{
// Search for all partitions where the NetBIOSName is set.
using (DirectorySearcher configSearch = new DirectorySearcher(configSearchRoot))
{
configSearch.Filter = ("(NETBIOSName=*)");
// Configure search to return dnsroot and ncname attributes
configSearch.PropertiesToLoad.Add("dnsroot");
configSearch.PropertiesToLoad.Add("ncname");
using (SearchResultCollection forestPartitionList = configSearch.FindAll())
{
When using an LDAP binding string without a server or domain, like LDAP://rootDSE, the default domain will be the domain that the computer is joined to. If the computer isn't joined to a domain, then the binding will fail (you would also need to provide a username and password). The user running the app, or the thread for web sites/services, or the user if one was specified during binding, must be able to read on the target domain, otherwise the binding will fail.
If none of this fixes your problem, then you need to give more information about the two domains. For example, are they in the same forest?
I would like to be able to retrieve the SID of a local Machine like the PSGetSID
utility from Sysinternals but using C#.
Is this possible?
Edit:
I am looking for a solution that will work for computers that may or may not be members of a Domain.
This has good helper class to use lookupaccountname win32 api call.
get machine SID (including primary domain controller)
I did not find a way to do this with native C#
You could do it via pinvoke and just get it from the Win32 API.
http://www.pinvoke.net/default.aspx/advapi32.lookupaccountname
There may also be a way to get it in "pure" .NET.
Another SO post from user ewall has a pure .NET solution with examples in both C# and PowerShell. It uses the DirectoryEntry and SecurityDescriptor objects. See:
How can I retrieve a Windows Computer's SID using WMI?
SIDs documentation:
https://learn.microsoft.com/en-us/windows/security/identity-protection/access-control/security-identifiers
Local user account SID form: S-1-5-21-xxxxxxxxx-xxxxxxxxx-xxxxxxxxxx-yyyy
System specific part: xxx...xxx
User account specific part: yyyy
So you just need to remove the last group of digits from a user account SID to get the system SID.
If your code is a Windows application, you can get the system SID this way:
using System.Security.Principal;
string systemSid;
using (WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent())
{
systemSid = windowsIdentity.User.Value.Substring(0, windowsIdentity.User.Value.LastIndexOf('-'));
}
If your code is a web application it usually runs in the context of an application pool identity and if your code is a Windows service it usually runs in the context of a system account (system, local service, network service). None of these identities are user accounts. So you cannot use the code above. You need to either know a user account name, or list user accounts.
If you know a user account name, you can get the system SID this way:
using System.Security.Principal;
NTAccount ntAccount = new NTAccount("MACHINE_NAME\\UserAccountName");
SecurityIdentifier sid = (SecurityIdentifier)ntAccount.Translate(typeof(SecurityIdentifier));
string systemSid = sid.Value.Substring(0, sid.Value.LastIndexOf('-'));
But you cannot assume the name of a standard user like "guest" because standard local user accounts names are localized, because this standard user account may have been deleted, and because a standard user account may be suppressed in future Windows releases.
So most of the time, from a web application or from a Windows service, you need to list user accounts by means of WMI:
// your project must reference System.Management.dll
using System.Management;
SelectQuery selectQuery = new SelectQuery("SELECT * FROM Win32_UserAccount");
ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(selectQuery);
string systemSid;
foreach (ManagementObject managementObject in managementObjectSearcher.Get())
{
if (1 == (byte)managementObject["SIDType"])
{
systemSid = managementObject["SID"] as string;
break;
}
}
systemSid = systemSid.Substring(0, systemSid.Value.LastIndexOf('-'));
Win32_UserAccount class documentation:
https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
UPDATE
About 100 times faster:
using Microsoft.Win32;
RegistryKey key = null;
string sid = null;
try
{
foreach (string subKeyName in Registry.Users.GetSubKeyNames())
{
if(subKeyName.StartsWith("S-1-5-21-"))
{
sid = subKeyName.Substring(0, subKeyName.LastIndexOf('-'));
break;
}
}
}
catch (Exception ex)
{
// ...
}
finally
{
if (key != null)
key.Close();
}
Within c#, I need to be able to
Connect to a remote system, specifying username/password as appropriate
List the members of a localgroup on that system
Fetch the results back to the executing computer
So for example I would connect to \SOMESYSTEM with appropriate creds, and fetch back a list of local administrators including SOMESYSTEM\Administrator, SOMESYSTEM\Bob, DOMAIN\AlanH, "DOMAIN\Domain Administrators".
I've tried this with system.directoryservices.accountmanagement but am running into problems with authentication. Sometimes I get:
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. (Exception from HRESULT: 0x800704C3)
The above is trying because there will be situations where I simply cannot unmap existing drives or UNC connections.
Other times my program gets UNKNOWN ERROR and the security log on the remote system reports an error 675, code 0x19 which is KDC_ERR_PREAUTH_REQUIRED.
I need a simpler and less error prone way to do this!
davidg was on the right track, and I am crediting him with the answer.
But the WMI query necessary was a little less than straightfoward, since I needed not just a list of users for the whole machine, but the subset of users and groups, whether local or domain, that were members of the local Administrators group. For the record, that WMI query was:
SELECT PartComponent FROM Win32_GroupUser WHERE GroupComponent = "Win32_Group.Domain='thehostname',Name='thegroupname'"
Here's the full code snippet:
public string GroupMembers(string targethost, string groupname, string targetusername, string targetpassword)
{
StringBuilder result = new StringBuilder();
try
{
ConnectionOptions Conn = new ConnectionOptions();
if (targethost != Environment.MachineName) //WMI errors if creds given for localhost
{
Conn.Username = targetusername; //can be null
Conn.Password = targetpassword; //can be null
}
Conn.Timeout = TimeSpan.FromSeconds(2);
ManagementScope scope = new ManagementScope("\\\\" + targethost + "\\root\\cimv2", Conn);
scope.Connect();
StringBuilder qs = new StringBuilder();
qs.Append("SELECT PartComponent FROM Win32_GroupUser WHERE GroupComponent = \"Win32_Group.Domain='");
qs.Append(targethost);
qs.Append("',Name='");
qs.Append(groupname);
qs.AppendLine("'\"");
ObjectQuery query = new ObjectQuery(qs.ToString());
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection queryCollection = searcher.Get();
foreach (ManagementObject m in queryCollection)
{
ManagementPath path = new ManagementPath(m["PartComponent"].ToString());
{
String[] names = path.RelativePath.Split(',');
result.Append(names[0].Substring(names[0].IndexOf("=") + 1).Replace("\"", " ").Trim() + "\\");
result.AppendLine(names[1].Substring(names[1].IndexOf("=") + 1).Replace("\"", " ").Trim());
}
}
return result.ToString();
}
catch (Exception e)
{
Console.WriteLine("Error. Message: " + e.Message);
return "fail";
}
}
So, if I invoke Groupmembers("Server1", "Administrators", "myusername", "mypassword"); I get a single string returned with:
SERVER1\Administrator
MYDOMAIN\Domain Admins
The actual WMI return is more like this:
\\SERVER1\root\cimv2:Win32_UserAccount.Domain="SERVER1",Name="Administrator"
... so as you can see, I had to do a little string manipulation to pretty it up.
This should be easy to do using WMI. Here you have a pointer to some docs:
WMI Documentation for Win32_UserAccount
Even if you have no previous experience with WMI, it should be quite easy to turn that VB Script code at the bottom of the page into some .NET code.
Hope this helped!
I would recommend using the Win32 API function NetLocalGroupGetMembers. It is much more straight forward than trying to figure out the crazy LDAP syntax, which is necessary for some of the other solutions recommended here. As long as you impersonate the user you want to run the check as by calling "LoginUser", you should not run into any security issues.
You can find sample code for doing the impersonation here.
If you need help figuring out how to call "NetLocalGroupGetMembers" from C#, I reccomend that you checkout Jared Parson's PInvoke assistant, which you can download from codeplex.
If you are running the code in an ASP.NET app running in IIS, and want to impersonate the user accessing the website in order to make the call, then you may need to grant "Trusted for Delegation" permission to the production web server.
If you are running on the desktop, then using the active user's security credentials should not be a problem.
It is possible that you network admin could have revoked access to the "Securable Object" for the particular machine you are trying to access. Unfortunately that access is necessary for all of the network management api functions to work. If that is the case, then you will need to grant access to the "Securable Object" for whatever users you want to execute as. With the default windows security settings all authenticated users should have access, however.
I hope this helps.
-Scott
You should be able to do this with System.DirectoryServices.DirectoryEntry. If you are having trouble running it remotely, maybe you could install something on the remote machines to give you your data via some sort of RPC, like remoting or a web service. But I think what you're trying should be possible remotely without getting too fancy.
If Windows won't let you connect through it's login mechanism, I think your only option is to run something on the remote machine with an open port (either directly or through remoting or a web service, as mentioned).