Closed jcrapuchettes closed 7 months ago
I would be happy to contribute code or code up my own solution. I just need some direction.
Is there a way for me to add a calculation of the NTLM Hash to user attributes when the password is changed?
Yeah you can do so in the password change flow.
Other question though: why not use the Radius provider directly instead of using the LDAP one?
Can you help me unpack how I might do that in the password change flow? I didn't see any way to inject arbitrary code.
The RADIUS provider doesn't support EAP w/ PEAP-MSCHAPv2 as far as I know..
In the flow default-password-change
(to adapt if you have a custom one), you'd go to "Stage Bindings", expand the default-password-change-write
binding, click "Create and bind Policy", select "Expression Policy", give it a meaningful name, and then have an expression that looks like:
user.attributes["ipaNTHash"] = ntlm_hash(context['flow_plan'].context["prompt_data"]["password"])
I might be wrong about the code itself, I'm not 100% fluent in authentik policy yet, but it should be this. Also, you'll need to get an ntlm_hash function somehow.
@rissson your help got me to the point that my testing with eapol_test
works! The final policy code looks like this:
from Crypto.Hash import MD4
request.user.attributes["ipaNTHash"] = MD4.new(context["prompt_data"]["password"].encode('utf-16-le')).digest().hex()
return True
Perfect! Would you be willing to contribute to https://docs.goauthentik.io/integrations/?
how to set the policy in version 2024.2 ? i get the error
@ZUCCzwp you will have to translate that error for us. I haven't upgraded to 2024.2 yet.
@jcrapuchettes Could you share the freeradius config you used to get it to work with authentik ldap?
After upgrading to 2024.4.2, I found (like @ZUCCzwp did) that the Crypto package no longer is included in the shipped docker container. I will post when I find a solution.
Here is the solution: https://gist.github.com/jcrapuchettes/830c99982e391c858f5b7eb066c02749
@jcrapuchettes Thank you for your work, you save me a lot of time, i'm wondering if you can share with me the authentik configuration in order to setting up in the right way my server :). And if it is possible, can you share your virtual server configuration? for example i see in your eap config file, that you have two virtual server, inner-tunnel-peap and inner-tunnel-ttls. in any case thank you
Hi,
Is it possible to update ipaNTHash
when user's password is changed by an administrator via admin panel?
@jcrapuchettes I noticed that this policy didn't work if You're reseting password
I came up with version that works eather during password change from user level and recovery flow:
import hashlib
def generate_ntlm_hash(password):
"""Generate an NTLM hash from the given password."""
password_unicode = password.encode('utf-16le')
return hashlib.new('md4', password_unicode).hexdigest()
def store_ntlm_hash(user, password):
"""Store the NTLM hash in the user's attributes."""
if not user.attributes:
user.attributes = {}
user.attributes["ipaNTHash"] = generate_ntlm_hash(password)
return user.save()
# Determine if the user is authenticated or pending
user = request.user if request.user and not request.user.is_anonymous else context.get("pending_user")
if user:
# Extract the password from the context
password = context.get("prompt_data", {}).get("password") or context.get("password")
if password:
return store_ntlm_hash(user, password)
else:
ak_message("There is no new password value in context")
return False
else:
ak_message("There is no user context")
return False
@showier-drastic Admin's set password calls an endpoint that directly sets a new password. It's bypass every policy, so unfortunately not, it's not possible.
The best workaround so far is to generate recovery links or send recovery emails to the user.
I didn't want to ask every user to change their passwords in order to populate new hash fields, so I tried to incorporate this into a authentication flow. I've duplicated the default one and swapped the password stage by a prompt stage. The prompt stage should have a password field, and a custom validation policy. Then, I've created the policy as follows :
import os
import base64
import hashlib
import datetime
from authentik.stages.password.stage import authenticate
def extract_ldap_hash(ldap_hash, salt_size=4):
if not ldap_hash:
return None, os.urandom(salt_size)
try:
ssha_password = base64.b64decode(ldap_hash).decode('utf-8')
except Exception:
return None, os.urandom(salt_size)
if not ssha_password.startswith('{SSHA}'):
return None, os.urandom(salt_size)
cleaned_ssha_password = ssha_password[6:]
ssha_dec = base64.b64decode(cleaned_ssha_password)
payload = ssha_dec[:-salt_size]
salt = ssha_dec[-salt_size:]
return payload, salt
def generate_ldap_hash(user, password):
ldap_hash = user.attributes.get("userPassword")
payload, salt = extract_ldap_hash(ldap_hash)
check_hash = hashlib.sha1(password.encode('utf-8') + salt).digest()
return (b'{SSHA}' + base64.b64encode(check_hash + salt)).decode('utf-8')
def generate_ntlm_hash(user, password):
password_unicode = password.encode('utf-16le')
return hashlib.new('md4', password_unicode).hexdigest()
def store_user_hash(user, password):
now = datetime.datetime.now().timestamp()
if not user.attributes:
user.attributes = {}
previous_attributes = {**user.attributes}
user.attributes["userPassword"] = generate_ldap_hash(user, password)
user.attributes["ipaNTHash"] = generate_ntlm_hash(user, password)
user.attributes["sambaNTPassword"] = user.attributes["ipaNTHash"].upper()
return user.save()
user = request.user if request.user and not request.user.is_anonymous else context.get("pending_user")
if user:
# Extract the password from the context
password = context.get("prompt_data", {}).get("password") or context.get("password")
if password:
if authenticate(
request=request.http_request,
backends=[
'authentik.core.auth.InbuiltBackend',
'authentik.core.auth.TokenBackend',
'authentik.sources.ldap.auth.LDAPBackend',
],
username=user.username, password=password
):
store_user_hash(user, password)
return True
else:
ak_message("Invalid password")
return False
else:
ak_message("There is no new password value in context")
return False
else:
ak_message("There is no user context")
return False
As you can see in this, I use this policy to populate two fields, for LDAP and RADIUS logins (although I haven't tested it yet). I don't think doing this is a particularly good idea, as it relies on authentik internals and could break with an update. The flow works nonetheless.
EDIT: I've tweaked the code a bit, since I realized I could directly use the authenticate
function found in the password stage. It does not change my evaluation of the situation though. If authentik could allow the user to specify a validation policy during a password stage that would prevent this kind of workarounds.
That being said, it doesn't even work for my use case, since the authentik LDAP outpost does not support SAMBA LDAP schema.
Describe your question I’m going down the path of figuring out WPA Enterprise WiFi Security. As I have been reviewing posts online and github issues, I’ve been trying to figure out why I wouldn’t be able to use the following setup: Authentik -> LDAP Outpost -> FreeRADIUS -> UniFi. After setting it all up, the FreeRADIUS server reported "mschap: FAILED: No NT-Password." I noticed that the LDAP settings in FreeRADIUS included
control:NT-Password := 'ipaNTHash'
. I tried adding an attribute to my user in Authentik calledipaNTHash
and set its value to the NTLM hash of my password. I then tested my setup witheapol_test
and got a SUCCESS! I've also tested connecting through two different computers and it worked!My question: Is there a way for me to add a calculation of the NTLM Hash to user attributes when the password is changed? Could I make use of a property mapping?
Relevant info
Version and Deployment (please complete the following information):
Additional context WPA Enterprise through PEAP-MSCHAPv2 is pretty standard and having to setup FreeRADIUS and FreeIPA seems like a lot given the competition offers their own RADIUS server that supports PEAP-MSCHAPv2. I know that it is possible to setup FreeRADIUS to communicate to Authentik via OAuth2, but that required EAP-TTLS/PAP. I'm not opposed to that with the exception that we have a number of iOS users and from my research, it will be a pain to set them up.