GT-2658 - Active Directory via Kerberos authentication

This commit is contained in:
dev747368 2019-09-03 17:38:51 -04:00
parent a62730477e
commit 841e75ad8d
8 changed files with 215 additions and 38 deletions

View file

@ -42,7 +42,8 @@ import org.apache.logging.log4j.Logger;
import generic.jar.ResourceFile;
import generic.random.SecureRandomFactory;
import ghidra.framework.*;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.framework.remote.*;
import ghidra.net.ApplicationKeyManagerFactory;
import ghidra.net.SSLContextInitializer;
@ -85,7 +86,8 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
OS_PASSWORD_LOGIN("OS Password"),
PKI_LOGIN("PKI"),
ALT_OS_PASSWORD_LOGIN("OS Password & Password File"),
JAAS_LOGIN("JAAS");
JAAS_LOGIN("JAAS"),
KRB5_AD("Active Directory via Kerberos");
private String description;
@ -105,6 +107,7 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
case 2: return PKI_LOGIN;
case 3: return ALT_OS_PASSWORD_LOGIN;
case 4: return JAAS_LOGIN;
case 5: return KRB5_AD;
default: return null;
}
//@formatter:on
@ -165,22 +168,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
requireExplicitPasswordReset = false;
authModule = new PasswordFileAuthenticationModule(allowUserToSpecifyName);
break;
// case ALT_OS_PASSWORD_LOGIN:
// supportLocalPasswords = true;
// case OS_PASSWORD_LOGIN:
// OperatingSystem os = OperatingSystem.CURRENT_OPERATING_SYSTEM;
// if (os == OperatingSystem.WINDOWS) {
// authModule = new NTPasswordAuthenticationModule(loginDomain,
// nameCallbackAllowed, authMode == ALT_OS_PASSWORD_LOGIN);
// }
// else if (os == UNIX) {
// authModule = new UnixPasswordAuthenticationModule(nameCallbackAllowed);
// }
// else {
// throw new IllegalArgumentException(
// "OS Password Authentication only supported for Microsoft Windows");
// }
// break;
case PKI_LOGIN:
if (altSSHLoginAllowed) {
log.warn("SSH authentication option ignored when PKI authentication used");
@ -198,10 +185,22 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
case JAAS_LOGIN:
authModule = new JAASAuthenticationModule("auth", allowUserToSpecifyName);
break;
case KRB5_AD:
if (loginDomain == null || loginDomain.isBlank()) {
throw new IllegalArgumentException("Missing login domain value -d<domainname>");
}
authModule = new Krb5ActiveDirectoryAuthenticationModule(loginDomain,
allowUserToSpecifyName);
break;
default:
throw new IllegalArgumentException("Unsupported Authentication mode: " + authMode);
}
if (authModule != null) {
// allow the auth modules to verify their configuration state before continuing
authModule.ensureConfig();
}
if (altSSHLoginAllowed) {
SecureRandomFactory.getSecureRandom(); // incur initialization delay up-front
sshAuthModule = new SSHAuthenticationModule(allowUserToSpecifyName);
@ -356,11 +355,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
if (authModule instanceof PasswordFileAuthenticationModule) {
supportPasswordChange = true;
}
// else if (authModule instanceof NTPasswordAuthenticationModule) {
// supportPasswordChange =
// ((NTPasswordAuthenticationModule) authModule).usingLocalAuthentication(
// authCallbacks);
// }
}
else if (!mgr.getUserManager().isValidUser(username)) {
FailedLoginException e = new FailedLoginException("Unknown user: " + username);
@ -550,13 +544,6 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
displayUsage("Invalid authentication mode: " + s);
System.exit(-1);
}
if (authMode == OS_PASSWORD_LOGIN || authMode == ALT_OS_PASSWORD_LOGIN) {
if (OperatingSystem.CURRENT_OPERATING_SYSTEM != OperatingSystem.WINDOWS) {
displayUsage("Authentication mode (" + authMode +
") only supported under Microsoft Windows");
System.exit(-1);
}
}
}
else if (s.startsWith("-ip")) { // setting server remote access hostname
int nextArgIndex = i + 1;

View file

@ -1,5 +1,8 @@
Ghidra server startup parameters.
Command line parameters: [-ip <hostname>] [-i #.#.#.#] [-p#] [-a#] [-d<ntDomain>] [-e<days>] [-u] [-jaas <path_to_jaas_config_file>] [-autoProvision] [-n] <repository_path>
Command line parameters:
[-ip <hostname>] [-i #.#.#.#] [-p#] [-n]
[-a#] [-d<ntDomain>] [-e<days>] [-u] [-jaas <path_to_jaas_config_file>] [-autoProvision]
<repository_path>
-ip <hostname> : identifies the remote access IPv4 address or hostname (FQDN) which should be
used by remote clients to access the server.
@ -12,6 +15,7 @@ Command line parameters: [-ip <hostname>] [-i #.#.#.#] [-p#] [-a#] [-d<ntDomain>
0 - Private user password
2 - PKI Authentication
4 - JAAS Authentication controlled by config file pointed to by -jaas
5 - Active Directory via Kerberos. Requires -d<active_directory_domainname.tld>
-anonymous : enables anonymous repository access (see svrREADME.html for details)

View file

@ -26,6 +26,10 @@ public interface AuthenticationModule {
public static final String USERNAME_CALLBACK_PROMPT = "User ID";
public static final String PASSWORD_CALLBACK_PROMPT = "Password";
default void ensureConfig() {
// default nothing
}
/**
* Complete the authentication process.
* <p>

View file

@ -0,0 +1,181 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.server.security;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.naming.NamingException;
import javax.naming.directory.*;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import com.sun.security.auth.module.Krb5LoginModule;
import ghidra.framework.remote.GhidraPrincipal;
import ghidra.server.UserManager;
/**
* A Ghidra {@link AuthenticationModule} that authenticates against an Active Directory Kerberos system
* using JAAS's {@link Krb5LoginModule}.
* <p>
* This auth module needs to know the Active Directory domain name, and then from there it can bootstrap
* itself using DNS lookups to find the Kerberos server.
*/
public class Krb5ActiveDirectoryAuthenticationModule implements AuthenticationModule {
private boolean allowUserToSpecifyName;
private String domainName;
private boolean stripDomainFromUsername = true;
public Krb5ActiveDirectoryAuthenticationModule(String domainName, boolean allowUserToSpecifyName) {
this.domainName = domainName;
this.allowUserToSpecifyName = allowUserToSpecifyName;
}
@Override
public void ensureConfig() {
if (domainName == null || domainName.isBlank()) {
throw new IllegalArgumentException("Missing domain name");
}
String loginServer = null;
try {
InetSocketAddress dc = getFirstDomainController(domainName);
loginServer = dc.getHostName();
}
catch (NamingException e) {
// fall thru
}
if (loginServer == null) {
throw new IllegalArgumentException("No domain controller for " + domainName);
}
System.setProperty("java.security.krb5.realm", domainName.toUpperCase());
System.setProperty("java.security.krb5.kdc", loginServer);
}
@Override
public String authenticate(UserManager userMgr, Subject subject, Callback[] ghidra_callbacks)
throws LoginException {
GhidraPrincipal principal = GhidraPrincipal.getGhidraPrincipal(subject);
AtomicReference<String> userName = new AtomicReference<>();
LoginContext lc = new LoginContext("", null, (loginmodule_callbacks) -> {
// The Krb5LoginModule tends to call this callback handler multiple times.
// Once for name, and then again for password.
PasswordCallback srcPcb = AuthenticationModule.getFirstCallbackOfType(
PasswordCallback.class, ghidra_callbacks);
NameCallback srcNcb =
AuthenticationModule.getFirstCallbackOfType(NameCallback.class, ghidra_callbacks);
String tmpName =
(allowUserToSpecifyName && srcNcb != null) ? srcNcb.getName() : principal.getName();
if (stripDomainFromUsername && tmpName.contains("\\")) {
tmpName = tmpName.replaceFirst("^.*\\\\", "");
}
if (tmpName == null || srcPcb == null || tmpName.isBlank()) {
throw new IOException("Missing username or password values");
}
NameCallback destNcb = AuthenticationModule.getFirstCallbackOfType(NameCallback.class,
loginmodule_callbacks);
PasswordCallback destPcb = AuthenticationModule.getFirstCallbackOfType(
PasswordCallback.class, loginmodule_callbacks);
if (destNcb != null) {
destNcb.setName(tmpName);
}
if (destPcb != null) {
destPcb.setPassword(srcPcb.getPassword());
}
userName.set(tmpName);
}, new JAASConfiguration("com.sun.security.auth.module.Krb5LoginModule"));
lc.login();
return userName.get();
}
@Override
public Callback[] getAuthenticationCallbacks() {
return AuthenticationModule.createSimpleNamePasswordCallbacks(allowUserToSpecifyName);
}
@Override
public boolean anonymousCallbacksAllowed() {
return false;
}
@Override
public boolean isNameCallbackAllowed() {
return allowUserToSpecifyName;
}
//--------------------------------------------------------------------------------------------------
private static class JAASConfiguration extends Configuration {
private AppConfigurationEntry staticConfigEntry;
private Map<String, Object> options = new HashMap<>();
public JAASConfiguration(String loginModuleClassName) {
staticConfigEntry = new AppConfigurationEntry(loginModuleClassName,
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
return new AppConfigurationEntry[] { staticConfigEntry };
}
public void addOption(String name, Object value) {
options.put(name, value);
}
}
private static final String SRV_RECORD_TYPE = "SRV";
private static InetSocketAddress getFirstDomainController(String domainName)
throws NamingException {
DirContext ctx = new InitialDirContext();
Attributes attributes = ctx.getAttributes("dns:/_ldap._tcp.dc._msdcs." + domainName,
new String[] { SRV_RECORD_TYPE });
if (attributes.get(SRV_RECORD_TYPE) == null) {
return null;
}
String srvRec = attributes.get(SRV_RECORD_TYPE).get().toString();
String[] recParts = srvRec.split("\\s+", -1);
int port = Integer.parseInt(recParts[2]);
String host = recParts[3];
return new InetSocketAddress(host, port);
}
}

View file

@ -21,8 +21,7 @@ then
echo "Login failed: username has 'bad' in it: $NAME" 1>&2
exit 100
else
echo "OK"
echo "Login successful" 1>&2
fi
echo "Returning from script" 1>&2

View file

@ -6,7 +6,8 @@
auth {
net.sf.jpam.jaas.JpamLoginModule required
// the serviceName parameter controls which PAM service Ghidra will try to authenticate against
// The serviceName parameter controls which PAM service Ghidra will try to authenticate against.
// This corresponds to a file called /etc/pam.d/<serviceName>
serviceName="system-auth"
;
};

View file

@ -16,7 +16,7 @@
auth {
com.sun.security.auth.module.LdapLoginModule REQUIRED
userProvider="ldaps://<your_active_directory_ldap_server_hostname>:3269"
authIdentity="{USERNAME}@<your_active_directory_domain_name>"
authIdentity="{USERNAME}@<your_active_directory_domain_name.tld>"
userFilter="(sAMAccountName={USERNAME})"
debug=true;
};

View file

@ -115,6 +115,7 @@ ghidra.repositories.dir=./repositories
# 0 - Private user password
# 2 - PKI Authentication
# 4 - JAAS Authentication
# 5 - Active Directory via Kerberos. Requires -d<active_directory_domainname.tld>
# -anonymous : enables anonymous repository access (see svrREADME.html for details)
# -ssh : enables SSH authentication for headless clients
# -e<days> : specifies default password expiration time in days (-a0 mode only, default is 1-day)