2014-12-08 05:07:59 +03:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This tests the restrictions on userAccountControl that apply even if write access is permitted
#
# Copyright Samuel Cabrero 2014 <samuelcabrero@kernevil.me>
# Copyright Andrew Bartlett 2014 <abartlet@samba.org>
#
# Licenced under the GPLv3
#
import optparse
import sys
import unittest
import samba
import samba . getopt as options
import samba . tests
import ldb
import base64
sys . path . insert ( 0 , " bin/python " )
from samba . tests . subunitrun import TestProgram , SubunitOptions
2015-03-16 19:22:01 +03:00
from samba . subunit . run import SubunitTestRunner
2014-12-08 05:07:59 +03:00
from samba . auth import system_session
from samba . samdb import SamDB
from samba . dcerpc import samr , security , lsa
from samba . credentials import Credentials
from samba . ndr import ndr_unpack , ndr_pack
from samba . tests import delete_force
from samba import gensec , sd_utils
from samba . credentials import DONT_USE_KERBEROS
from ldb import SCOPE_SUBTREE , SCOPE_BASE , LdbError
from ldb import Message , MessageElement , Dn
from ldb import FLAG_MOD_ADD , FLAG_MOD_REPLACE , FLAG_MOD_DELETE
from samba . dsdb import UF_SCRIPT , UF_ACCOUNTDISABLE , UF_00000004 , UF_HOMEDIR_REQUIRED , \
UF_LOCKOUT , UF_PASSWD_NOTREQD , UF_PASSWD_CANT_CHANGE , UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED , \
UF_TEMP_DUPLICATE_ACCOUNT , UF_NORMAL_ACCOUNT , UF_00000400 , UF_INTERDOMAIN_TRUST_ACCOUNT , \
UF_WORKSTATION_TRUST_ACCOUNT , UF_SERVER_TRUST_ACCOUNT , UF_00004000 , \
UF_00008000 , UF_DONT_EXPIRE_PASSWD , UF_MNS_LOGON_ACCOUNT , UF_SMARTCARD_REQUIRED , \
UF_TRUSTED_FOR_DELEGATION , UF_NOT_DELEGATED , UF_USE_DES_KEY_ONLY , UF_DONT_REQUIRE_PREAUTH , \
UF_PASSWORD_EXPIRED , UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION , UF_NO_AUTH_DATA_REQUIRED , \
UF_PARTIAL_SECRETS_ACCOUNT , UF_USE_AES_KEYS
2016-03-11 05:17:43 +03:00
parser = optparse . OptionParser ( " user_account_control.py [options] <host> " )
2014-12-08 05:07:59 +03:00
sambaopts = options . SambaOptions ( parser )
parser . add_option_group ( sambaopts )
parser . add_option_group ( options . VersionOptions ( parser ) )
# use command line creds if available
credopts = options . CredentialsOptions ( parser )
parser . add_option_group ( credopts )
opts , args = parser . parse_args ( )
if len ( args ) < 1 :
parser . print_usage ( )
sys . exit ( 1 )
host = args [ 0 ]
if not " :// " in host :
ldaphost = " ldap:// %s " % host
else :
ldaphost = host
start = host . rindex ( " :// " )
host = host . lstrip ( start + 3 )
lp = sambaopts . get_loadparm ( )
creds = credopts . get_credentials ( lp )
creds . set_gensec_features ( creds . get_gensec_features ( ) | gensec . FEATURE_SEAL )
bits = [ UF_SCRIPT , UF_ACCOUNTDISABLE , UF_00000004 , UF_HOMEDIR_REQUIRED ,
UF_LOCKOUT , UF_PASSWD_NOTREQD , UF_PASSWD_CANT_CHANGE ,
UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED ,
UF_TEMP_DUPLICATE_ACCOUNT , UF_NORMAL_ACCOUNT , UF_00000400 ,
UF_INTERDOMAIN_TRUST_ACCOUNT ,
UF_WORKSTATION_TRUST_ACCOUNT , UF_SERVER_TRUST_ACCOUNT , UF_00004000 ,
UF_00008000 , UF_DONT_EXPIRE_PASSWD , UF_MNS_LOGON_ACCOUNT , UF_SMARTCARD_REQUIRED ,
UF_TRUSTED_FOR_DELEGATION , UF_NOT_DELEGATED , UF_USE_DES_KEY_ONLY ,
UF_DONT_REQUIRE_PREAUTH ,
UF_PASSWORD_EXPIRED , UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION ,
UF_NO_AUTH_DATA_REQUIRED ,
UF_PARTIAL_SECRETS_ACCOUNT , UF_USE_AES_KEYS ,
int ( " 0x10000000 " , 16 ) , int ( " 0x20000000 " , 16 ) , int ( " 0x40000000 " , 16 ) , int ( " 0x80000000 " , 16 ) ]
2015-01-06 06:49:14 +03:00
account_types = set ( [ UF_NORMAL_ACCOUNT , UF_WORKSTATION_TRUST_ACCOUNT , UF_SERVER_TRUST_ACCOUNT ] )
2014-12-08 05:07:59 +03:00
class UserAccountControlTests ( samba . tests . TestCase ) :
def add_computer_ldap ( self , computername , others = None , samdb = None ) :
if samdb is None :
samdb = self . samdb
dn = " CN= %s ,OU=test_computer_ou1, %s " % ( computername , self . base_dn )
domainname = ldb . Dn ( self . samdb , self . samdb . domain_dn ( ) ) . canonical_str ( ) . replace ( " / " , " " )
samaccountname = " %s $ " % computername
dnshostname = " %s . %s " % ( computername , domainname )
msg_dict = {
" dn " : dn ,
" objectclass " : " computer " }
if others is not None :
msg_dict = dict ( msg_dict . items ( ) + others . items ( ) )
msg = ldb . Message . from_dict ( self . samdb , msg_dict )
msg [ " sAMAccountName " ] = samaccountname
print " Adding computer account %s " % computername
samdb . add ( msg )
def get_creds ( self , target_username , target_password ) :
creds_tmp = Credentials ( )
creds_tmp . set_username ( target_username )
creds_tmp . set_password ( target_password )
creds_tmp . set_domain ( creds . get_domain ( ) )
creds_tmp . set_realm ( creds . get_realm ( ) )
creds_tmp . set_workstation ( creds . get_workstation ( ) )
creds_tmp . set_gensec_features ( creds_tmp . get_gensec_features ( )
| gensec . FEATURE_SEAL )
creds_tmp . set_kerberos_state ( DONT_USE_KERBEROS ) # kinit is too expensive to use in a tight loop
return creds_tmp
def setUp ( self ) :
super ( UserAccountControlTests , self ) . setUp ( )
self . admin_creds = creds
self . admin_samdb = SamDB ( url = ldaphost ,
session_info = system_session ( ) ,
credentials = self . admin_creds , lp = lp )
self . unpriv_user = " testuser1 "
self . unpriv_user_pw = " samba123@ "
self . unpriv_creds = self . get_creds ( self . unpriv_user , self . unpriv_user_pw )
self . admin_samdb . newuser ( self . unpriv_user , self . unpriv_user_pw )
res = self . admin_samdb . search ( " CN= %s ,CN=Users, %s " % ( self . unpriv_user , self . admin_samdb . domain_dn ( ) ) ,
scope = SCOPE_BASE ,
attrs = [ " objectSid " ] )
self . assertEqual ( 1 , len ( res ) )
self . unpriv_user_sid = ndr_unpack ( security . dom_sid , res [ 0 ] [ " objectSid " ] [ 0 ] )
self . unpriv_user_dn = res [ 0 ] . dn
self . samdb = SamDB ( url = ldaphost , credentials = self . unpriv_creds , lp = lp )
self . domain_sid = security . dom_sid ( self . samdb . get_domain_sid ( ) )
self . base_dn = self . samdb . domain_dn ( )
self . samr = samr . samr ( " ncacn_ip_tcp: %s [sign] " % host , lp , self . unpriv_creds )
self . samr_handle = self . samr . Connect2 ( None , security . SEC_FLAG_MAXIMUM_ALLOWED )
self . samr_domain = self . samr . OpenDomain ( self . samr_handle , security . SEC_FLAG_MAXIMUM_ALLOWED , self . domain_sid )
self . sd_utils = sd_utils . SDUtils ( self . admin_samdb )
self . admin_samdb . create_ou ( " OU=test_computer_ou1, " + self . base_dn )
self . unpriv_user_sid = self . sd_utils . get_object_sid ( self . unpriv_user_dn )
mod = " (OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;; %s ) " % str ( self . unpriv_user_sid )
old_sd = self . sd_utils . read_sd_on_dn ( " OU=test_computer_ou1, " + self . base_dn )
self . sd_utils . dacl_add_ace ( " OU=test_computer_ou1, " + self . base_dn , mod )
self . add_computer_ldap ( " testcomputer-t " )
self . sd_utils . modify_sd_on_dn ( " OU=test_computer_ou1, " + self . base_dn , old_sd )
self . computernames = [ " testcomputer-0 " ]
# Get the SD of the template account, then force it to match
# what we expect for SeMachineAccountPrivilege accounts, so we
# can confirm we created the accounts correctly
self . sd_reference_cc = self . sd_utils . read_sd_on_dn ( " CN=testcomputer-t,OU=test_computer_ou1, %s " % ( self . base_dn ) )
self . sd_reference_modify = self . sd_utils . read_sd_on_dn ( " CN=testcomputer-t,OU=test_computer_ou1, %s " % ( self . base_dn ) )
for ace in self . sd_reference_modify . dacl . aces :
if ace . type == security . SEC_ACE_TYPE_ACCESS_ALLOWED and ace . trustee == self . unpriv_user_sid :
ace . access_mask = ace . access_mask | security . SEC_ADS_SELF_WRITE | security . SEC_ADS_WRITE_PROP
# Now reconnect without domain admin rights
self . samdb = SamDB ( url = ldaphost , credentials = self . unpriv_creds , lp = lp )
def tearDown ( self ) :
super ( UserAccountControlTests , self ) . tearDown ( )
for computername in self . computernames :
delete_force ( self . admin_samdb , " CN= %s ,OU=test_computer_ou1, %s " % ( computername , self . base_dn ) )
delete_force ( self . admin_samdb , " CN=testcomputer-t,OU=test_computer_ou1, %s " % ( self . base_dn ) )
delete_force ( self . admin_samdb , " OU=test_computer_ou1, %s " % ( self . base_dn ) )
delete_force ( self . admin_samdb , " CN= %s ,CN=Users, %s " % ( self . unpriv_user , self . base_dn ) )
def test_add_computer_sd_cc ( self ) :
user_sid = self . sd_utils . get_object_sid ( self . unpriv_user_dn )
mod = " (OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;; %s ) " % str ( user_sid )
old_sd = self . sd_utils . read_sd_on_dn ( " OU=test_computer_ou1, " + self . base_dn )
self . sd_utils . dacl_add_ace ( " OU=test_computer_ou1, " + self . base_dn , mod )
computername = self . computernames [ 0 ]
sd = ldb . MessageElement ( ( ndr_pack ( self . sd_reference_modify ) ) ,
ldb . FLAG_MOD_ADD ,
" nTSecurityDescriptor " )
self . add_computer_ldap ( computername ,
others = { " nTSecurityDescriptor " : sd } )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " ntSecurityDescriptor " ] )
desc = res [ 0 ] [ " nTSecurityDescriptor " ] [ 0 ]
desc = ndr_unpack ( security . descriptor , desc , allow_remaining = True )
sddl = desc . as_sddl ( self . domain_sid )
self . assertEqual ( self . sd_reference_modify . as_sddl ( self . domain_sid ) , sddl )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " description " ] = ldb . MessageElement (
( " A description " ) , ldb . FLAG_MOD_REPLACE ,
" description " )
self . samdb . modify ( m )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( samba . dsdb . UF_SERVER_TRUST_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
try :
self . samdb . modify ( m )
self . fail ( " Unexpectedly able to set userAccountControl to be a DC on %s " % m . dn )
except LdbError , ( enum , estr ) :
self . assertEqual ( ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS , enum )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( samba . dsdb . UF_WORKSTATION_TRUST_ACCOUNT | samba . dsdb . UF_PARTIAL_SECRETS_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
try :
self . samdb . modify ( m )
self . fail ( " Unexpectedly able to set userAccountControl to be an RODC on %s " % m . dn )
except LdbError , ( enum , estr ) :
self . assertEqual ( ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS , enum )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( samba . dsdb . UF_WORKSTATION_TRUST_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
2015-11-18 07:36:21 +03:00
try :
self . samdb . modify ( m )
self . fail ( " Unexpectedly able to set userAccountControl to be an Workstation on %s " % m . dn )
except LdbError , ( enum , estr ) :
self . assertEqual ( ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS , enum )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( samba . dsdb . UF_NORMAL_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
2014-12-08 05:07:59 +03:00
self . samdb . modify ( m )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " primaryGroupID " ] = ldb . MessageElement ( str ( security . DOMAIN_RID_ADMINS ) ,
ldb . FLAG_MOD_REPLACE , " primaryGroupID " )
try :
self . samdb . modify ( m )
except LdbError , ( enum , estr ) :
self . assertEqual ( ldb . ERR_UNWILLING_TO_PERFORM , enum )
return
self . fail ( )
def test_mod_computer_cc ( self ) :
user_sid = self . sd_utils . get_object_sid ( self . unpriv_user_dn )
mod = " (OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;; %s ) " % str ( user_sid )
old_sd = self . sd_utils . read_sd_on_dn ( " OU=test_computer_ou1, " + self . base_dn )
self . sd_utils . dacl_add_ace ( " OU=test_computer_ou1, " + self . base_dn , mod )
computername = self . computernames [ 0 ]
self . add_computer_ldap ( computername )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ ] )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " description " ] = ldb . MessageElement (
( " A description " ) , ldb . FLAG_MOD_REPLACE ,
" description " )
self . samdb . modify ( m )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( samba . dsdb . UF_WORKSTATION_TRUST_ACCOUNT | samba . dsdb . UF_PARTIAL_SECRETS_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
try :
self . samdb . modify ( m )
self . fail ( " Unexpectedly able to set userAccountControl on %s " % m . dn )
except LdbError , ( enum , estr ) :
self . assertEqual ( ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS , enum )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( samba . dsdb . UF_SERVER_TRUST_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
try :
self . samdb . modify ( m )
self . fail ( )
except LdbError , ( enum , estr ) :
self . assertEqual ( ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS , enum )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( samba . dsdb . UF_NORMAL_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
self . samdb . modify ( m )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( samba . dsdb . UF_WORKSTATION_TRUST_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
2015-11-18 07:36:21 +03:00
try :
self . samdb . modify ( m )
self . fail ( " Unexpectedly able to set userAccountControl to be an Workstation on %s " % m . dn )
except LdbError , ( enum , estr ) :
self . assertEqual ( ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS , enum )
2014-12-08 05:07:59 +03:00
def test_admin_mod_uac ( self ) :
computername = self . computernames [ 0 ]
self . add_computer_ldap ( computername , samdb = self . admin_samdb )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " userAccountControl " ] )
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) , UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD )
2014-12-10 04:15:54 +03:00
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT | UF_TRUSTED_FOR_DELEGATION ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
try :
self . admin_samdb . modify ( m )
self . fail ( " Unexpectedly able to set userAccountControl to UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT|UF_TRUSTED_FOR_DELEGATION on %s " % m . dn )
except LdbError , ( enum , estr ) :
self . assertEqual ( ldb . ERR_OTHER , enum )
2014-12-08 05:07:59 +03:00
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
self . admin_samdb . modify ( m )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " userAccountControl " ] )
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) , UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( UF_ACCOUNTDISABLE ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
self . admin_samdb . modify ( m )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " userAccountControl " ] )
2014-12-10 04:15:54 +03:00
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) , UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE )
2014-12-08 05:07:59 +03:00
def test_uac_bits_set ( self ) :
user_sid = self . sd_utils . get_object_sid ( self . unpriv_user_dn )
mod = " (OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;; %s ) " % str ( user_sid )
old_sd = self . sd_utils . read_sd_on_dn ( " OU=test_computer_ou1, " + self . base_dn )
self . sd_utils . dacl_add_ace ( " OU=test_computer_ou1, " + self . base_dn , mod )
computername = self . computernames [ 0 ]
self . add_computer_ldap ( computername )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ ] )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " description " ] = ldb . MessageElement (
( " A description " ) , ldb . FLAG_MOD_REPLACE ,
" description " )
self . samdb . modify ( m )
# These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
priv_to_auth_users_bits = set ( [ UF_PASSWD_NOTREQD , UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED ,
UF_DONT_EXPIRE_PASSWD ] )
2015-11-18 07:36:21 +03:00
# These bits really are privileged, or can't be changed from UF_NORMAL as a non-admin
2014-12-08 05:07:59 +03:00
priv_bits = set ( [ UF_INTERDOMAIN_TRUST_ACCOUNT , UF_SERVER_TRUST_ACCOUNT ,
2015-11-18 07:36:21 +03:00
UF_TRUSTED_FOR_DELEGATION , UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION ,
UF_WORKSTATION_TRUST_ACCOUNT ] )
2014-12-08 05:07:59 +03:00
2014-12-10 04:15:54 +03:00
invalid_bits = set ( [ UF_TEMP_DUPLICATE_ACCOUNT , UF_PARTIAL_SECRETS_ACCOUNT ] )
2014-12-08 05:07:59 +03:00
for bit in bits :
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( bit | UF_PASSWD_NOTREQD ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
try :
self . samdb . modify ( m )
if ( bit in priv_bits ) :
self . fail ( " Unexpectedly able to set userAccountControl bit 0x %08X on %s " % ( bit , m . dn ) )
except LdbError , ( enum , estr ) :
if bit in invalid_bits :
self . assertEqual ( enum , ldb . ERR_OTHER , " was not able to set 0x %08X on %s " % ( bit , m . dn ) )
# No point going on, try the next bit
continue
elif ( bit in priv_bits ) :
self . assertEqual ( ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS , enum )
else :
self . fail ( " Unable to set userAccountControl bit 0x %08X on %s : %s " % ( bit , m . dn , estr ) )
2015-01-06 06:49:14 +03:00
def uac_bits_unrelated_modify_helper ( self , account_type ) :
2014-12-08 05:07:59 +03:00
user_sid = self . sd_utils . get_object_sid ( self . unpriv_user_dn )
mod = " (OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;; %s ) " % str ( user_sid )
old_sd = self . sd_utils . read_sd_on_dn ( " OU=test_computer_ou1, " + self . base_dn )
self . sd_utils . dacl_add_ace ( " OU=test_computer_ou1, " + self . base_dn , mod )
computername = self . computernames [ 0 ]
2015-01-06 06:49:14 +03:00
self . add_computer_ldap ( computername , others = { " userAccountControl " : [ str ( account_type ) ] } )
2014-12-08 05:07:59 +03:00
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
2015-01-06 06:49:14 +03:00
attrs = [ " userAccountControl " ] )
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) , account_type )
2014-12-08 05:07:59 +03:00
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " description " ] = ldb . MessageElement (
( " A description " ) , ldb . FLAG_MOD_REPLACE ,
" description " )
self . samdb . modify ( m )
2014-12-10 04:15:54 +03:00
invalid_bits = set ( [ UF_TEMP_DUPLICATE_ACCOUNT , UF_PARTIAL_SECRETS_ACCOUNT ] )
2014-12-08 05:07:59 +03:00
2015-01-06 06:49:14 +03:00
# UF_LOCKOUT isn't actually ignored, it changes other
# attributes but does not stick here. See MS-SAMR 2.2.1.13
# UF_FLAG Codes clarification that UF_SCRIPT and
# UF_PASSWD_CANT_CHANGE are simply ignored by both clients and
# servers. Other bits are ignored as they are undefined, or
# are not set into the attribute (instead triggering other
# events).
ignored_bits = set ( [ UF_SCRIPT , UF_00000004 , UF_LOCKOUT , UF_PASSWD_CANT_CHANGE ,
UF_00000400 , UF_00004000 , UF_00008000 , UF_PASSWORD_EXPIRED ,
int ( " 0x10000000 " , 16 ) , int ( " 0x20000000 " , 16 ) , int ( " 0x40000000 " , 16 ) , int ( " 0x80000000 " , 16 ) ] )
2014-12-08 05:07:59 +03:00
super_priv_bits = set ( [ UF_INTERDOMAIN_TRUST_ACCOUNT ] )
2015-11-18 07:36:21 +03:00
priv_to_remove_bits = set ( [ UF_TRUSTED_FOR_DELEGATION , UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION , UF_WORKSTATION_TRUST_ACCOUNT ] )
2014-12-08 05:07:59 +03:00
for bit in bits :
2015-01-06 06:49:14 +03:00
# Reset this to the initial position, just to be sure
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( account_type ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
self . admin_samdb . modify ( m )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " userAccountControl " ] )
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) , account_type )
2014-12-08 05:07:59 +03:00
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( bit | UF_PASSWD_NOTREQD ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
try :
self . admin_samdb . modify ( m )
if bit in invalid_bits :
self . fail ( " Should have been unable to set userAccountControl bit 0x %08X on %s " % ( bit , m . dn ) )
except LdbError , ( enum , estr ) :
if bit in invalid_bits :
self . assertEqual ( enum , ldb . ERR_OTHER )
# No point going on, try the next bit
continue
elif bit in super_priv_bits :
self . assertEqual ( enum , ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS )
# No point going on, try the next bit
continue
else :
self . fail ( " Unable to set userAccountControl bit 0x %08X on %s : %s " % ( bit , m . dn , estr ) )
2015-01-06 06:49:14 +03:00
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " userAccountControl " ] )
if bit in ignored_bits :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) , UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD , " Bit 0x %08x shouldn ' t stick " % bit )
else :
if bit in account_types :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) , bit | UF_PASSWD_NOTREQD , " Bit 0x %08x didn ' t stick " % bit )
else :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) , bit | UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD , " Bit 0x %08x didn ' t stick " % bit )
2014-12-08 05:07:59 +03:00
try :
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( bit | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
self . samdb . modify ( m )
except LdbError , ( enum , estr ) :
self . fail ( " Unable to set userAccountControl bit 0x %08X on %s : %s " % ( bit , m . dn , estr ) )
2015-11-18 07:36:21 +03:00
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " userAccountControl " ] )
if bit in account_types :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ,
" bit 0X %08x should have been added (0X %08x vs 0X %08x ) "
% ( bit , int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ) )
elif bit in ignored_bits :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ,
" bit 0X %08x should have been added (0X %08x vs 0X %08x ) "
% ( bit , int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ) )
else :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ,
" bit 0X %08x should have been added (0X %08x vs 0X %08x ) "
% ( bit , int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ) )
2014-12-08 05:07:59 +03:00
try :
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " userAccountControl " ] = ldb . MessageElement ( str ( UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE ) ,
ldb . FLAG_MOD_REPLACE , " userAccountControl " )
self . samdb . modify ( m )
if bit in priv_to_remove_bits :
self . fail ( " Should have been unable to remove userAccountControl bit 0x %08X on %s " % ( bit , m . dn ) )
except LdbError , ( enum , estr ) :
if bit in priv_to_remove_bits :
self . assertEqual ( enum , ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS )
else :
2015-11-18 07:36:21 +03:00
self . fail ( " Unexpectedly unable to remove userAccountControl bit 0x %08X on %s : %s " % ( bit , m . dn , estr ) )
2014-12-08 05:07:59 +03:00
2015-01-06 06:49:14 +03:00
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " userAccountControl " ] )
if bit in priv_to_remove_bits :
2015-11-18 07:36:21 +03:00
if bit in account_types :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ,
" bit 0X %08x should not have been removed " % bit )
else :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ,
" bit 0X %08x should not have been removed " % bit )
2015-01-06 06:49:14 +03:00
else :
self . assertEqual ( int ( res [ 0 ] [ " userAccountControl " ] [ 0 ] ) ,
UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD ,
" bit 0X %08x should have been removed " % bit )
def test_uac_bits_unrelated_modify_normal ( self ) :
self . uac_bits_unrelated_modify_helper ( UF_NORMAL_ACCOUNT )
def test_uac_bits_unrelated_modify_workstation ( self ) :
self . uac_bits_unrelated_modify_helper ( UF_WORKSTATION_TRUST_ACCOUNT )
2014-12-08 05:07:59 +03:00
def test_uac_bits_add ( self ) :
computername = self . computernames [ 0 ]
user_sid = self . sd_utils . get_object_sid ( self . unpriv_user_dn )
mod = " (OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;; %s ) " % str ( user_sid )
old_sd = self . sd_utils . read_sd_on_dn ( " OU=test_computer_ou1, " + self . base_dn )
self . sd_utils . dacl_add_ace ( " OU=test_computer_ou1, " + self . base_dn , mod )
2014-12-10 04:15:54 +03:00
invalid_bits = set ( [ UF_TEMP_DUPLICATE_ACCOUNT , UF_PARTIAL_SECRETS_ACCOUNT ] )
2014-12-08 05:07:59 +03:00
# These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
priv_to_auth_users_bits = set ( [ UF_PASSWD_NOTREQD , UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED ,
UF_DONT_EXPIRE_PASSWD ] )
# These bits really are privileged
priv_bits = set ( [ UF_INTERDOMAIN_TRUST_ACCOUNT , UF_SERVER_TRUST_ACCOUNT ,
2014-12-10 04:15:54 +03:00
UF_TRUSTED_FOR_DELEGATION , UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION ] )
2014-12-08 05:07:59 +03:00
for bit in bits :
try :
self . add_computer_ldap ( computername , others = { " userAccountControl " : [ str ( bit ) ] } )
delete_force ( self . admin_samdb , " CN= %s ,OU=test_computer_ou1, %s " % ( computername , self . base_dn ) )
if bit in priv_bits :
self . fail ( " Unexpectdly able to set userAccountControl bit 0x %08X on %s " % ( bit , computername ) )
except LdbError , ( enum , estr ) :
if bit in invalid_bits :
self . assertEqual ( enum , ldb . ERR_OTHER , " Invalid bit 0x %08X was able to be set on %s " % ( bit , computername ) )
# No point going on, try the next bit
continue
elif bit in priv_bits :
self . assertEqual ( enum , ldb . ERR_INSUFFICIENT_ACCESS_RIGHTS )
continue
else :
self . fail ( " Unable to set userAccountControl bit 0x %08X on %s : %s " % ( bit , computername , estr ) )
2014-12-10 05:54:11 +03:00
def test_primarygroupID_cc_add ( self ) :
computername = self . computernames [ 0 ]
user_sid = self . sd_utils . get_object_sid ( self . unpriv_user_dn )
mod = " (OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;; %s ) " % str ( user_sid )
old_sd = self . sd_utils . read_sd_on_dn ( " OU=test_computer_ou1, " + self . base_dn )
self . sd_utils . dacl_add_ace ( " OU=test_computer_ou1, " + self . base_dn , mod )
try :
# When creating a new object, you can not ever set the primaryGroupID
self . add_computer_ldap ( computername , others = { " primaryGroupID " : [ str ( security . DOMAIN_RID_ADMINS ) ] } )
self . fail ( " Unexpectedly able to set primaryGruopID to be an admin on %s " % computername )
except LdbError , ( enum , estr ) :
self . assertEqual ( enum , ldb . ERR_UNWILLING_TO_PERFORM )
def test_primarygroupID_priv_DC_modify ( self ) :
computername = self . computernames [ 0 ]
self . add_computer_ldap ( computername ,
others = { " userAccountControl " : [ str ( UF_SERVER_TRUST_ACCOUNT ) ] } ,
samdb = self . admin_samdb )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " " ] )
m = ldb . Message ( )
m . dn = ldb . Dn ( self . admin_samdb , " <SID= %s - %d > " % ( str ( self . domain_sid ) ,
security . DOMAIN_RID_USERS ) )
m [ " member " ] = ldb . MessageElement (
[ str ( res [ 0 ] . dn ) ] , ldb . FLAG_MOD_ADD ,
" member " )
self . admin_samdb . modify ( m )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " primaryGroupID " ] = ldb . MessageElement (
[ str ( security . DOMAIN_RID_USERS ) ] , ldb . FLAG_MOD_REPLACE ,
" primaryGroupID " )
try :
self . admin_samdb . modify ( m )
# When creating a new object, you can not ever set the primaryGroupID
self . fail ( " Unexpectedly able to set primaryGroupID to be other than DCS on %s " % computername )
except LdbError , ( enum , estr ) :
self . assertEqual ( enum , ldb . ERR_UNWILLING_TO_PERFORM )
def test_primarygroupID_priv_member_modify ( self ) :
computername = self . computernames [ 0 ]
self . add_computer_ldap ( computername ,
others = { " userAccountControl " : [ str ( UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT ) ] } ,
samdb = self . admin_samdb )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " " ] )
m = ldb . Message ( )
m . dn = ldb . Dn ( self . admin_samdb , " <SID= %s - %d > " % ( str ( self . domain_sid ) ,
security . DOMAIN_RID_USERS ) )
m [ " member " ] = ldb . MessageElement (
[ str ( res [ 0 ] . dn ) ] , ldb . FLAG_MOD_ADD ,
" member " )
self . admin_samdb . modify ( m )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " primaryGroupID " ] = ldb . MessageElement (
[ str ( security . DOMAIN_RID_USERS ) ] , ldb . FLAG_MOD_REPLACE ,
" primaryGroupID " )
try :
self . admin_samdb . modify ( m )
# When creating a new object, you can not ever set the primaryGroupID
self . fail ( " Unexpectedly able to set primaryGroupID to be other than DCS on %s " % computername )
except LdbError , ( enum , estr ) :
self . assertEqual ( enum , ldb . ERR_UNWILLING_TO_PERFORM )
def test_primarygroupID_priv_user_modify ( self ) :
computername = self . computernames [ 0 ]
self . add_computer_ldap ( computername ,
others = { " userAccountControl " : [ str ( UF_WORKSTATION_TRUST_ACCOUNT ) ] } ,
samdb = self . admin_samdb )
res = self . admin_samdb . search ( " %s " % self . base_dn ,
expression = " (&(objectClass=computer)(samAccountName= %s $)) " % computername ,
scope = SCOPE_SUBTREE ,
attrs = [ " " ] )
m = ldb . Message ( )
m . dn = ldb . Dn ( self . admin_samdb , " <SID= %s - %d > " % ( str ( self . domain_sid ) ,
security . DOMAIN_RID_ADMINS ) )
m [ " member " ] = ldb . MessageElement (
[ str ( res [ 0 ] . dn ) ] , ldb . FLAG_MOD_ADD ,
" member " )
self . admin_samdb . modify ( m )
m = ldb . Message ( )
m . dn = res [ 0 ] . dn
m [ " primaryGroupID " ] = ldb . MessageElement (
[ str ( security . DOMAIN_RID_ADMINS ) ] , ldb . FLAG_MOD_REPLACE ,
" primaryGroupID " )
self . admin_samdb . modify ( m )
2014-12-08 05:07:59 +03:00
runner = SubunitTestRunner ( )
rc = 0
if not runner . run ( unittest . makeSuite ( UserAccountControlTests ) ) . wasSuccessful ( ) :
rc = 1
sys . exit ( rc )