--- orig/samba-4.11.6/auth/ntlmssp/ntlmssp_server.c 2020-01-08 10:24:52.000000000 +0000 +++ samba-4.11.6+dfsg/auth/ntlmssp/ntlmssp_server.c 2020-05-16 20:56:33.723451300 +0000 @@ -35,11 +35,17 @@ #include "param/param.h" #include "param/loadparm.h" #include "libcli/security/session.h" +#include "lib/tsocket/tsocket.h" + #include "lib/crypto/gnutls_helpers.h" #include #include +#include +#include +#include + #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH @@ -405,6 +411,115 @@ return tevent_req_post(req, ev); } +int getippart(char *begin, char *end) +{ + + char *p=begin; + int i=0; + if (end-begin>3)return -1; + if (end==begin) return -1; + DEBUG(1, ("IP part validator: %s %s\n",begin,end)); + while (p!=end) + { + if (isdigit(*p)) + { + i=i*10+(*p-'0'); + } + else return -1; + p++; + } + DEBUG(1, ("IP part validator: %i\n",i)); + if (i>255)return -1; + else return i; +} + +bool isencodedprivateip(char *str, const char delimiter) +{ + bool invalidip=true; + char *begin,*end; + int part1=0,part2=0,part3=0,part4=0; + begin = str; + end = strchr(str,delimiter); + DEBUG(1, ("IP string is %s and delimiter is %c\n",str,delimiter)); + if (end!=NULL) + { + if((part1=getippart(begin,end))==-1) + { + DEBUG(1, ("First part is not a valid IPv4 address\n")); + } + else + { + begin=end+1; + end = strchr(begin,delimiter); + if (end!=NULL) + { + if((part2=getippart(begin,end))==-1) + { + DEBUG(1, ("Second part is not a valid IPv4 address\n")); + } + else + { + begin=end+1; + end = strchr(begin,delimiter); + if (end!=NULL) + { + if((part3=getippart(begin,end))==-1) + { + DEBUG(1, ("Third part is not a valid IPv4 address\n")); + } + else + { + begin=end+1; + end = strchr(begin,delimiter); + if (end==NULL) + { + char *realend=begin; + //while (*realend!='\0')realend++; + + //things like 10_20_30_40L should be allowed + while (isdigit(*realend))realend++; + + if((part4=getippart(begin,realend))==-1) + { + DEBUG(1, ("Forth part is not a valid IPv4 address\n")); + } + else + { + DEBUG(1, ("IP parts are %i %i %i %i\n",part1, part2, part3, part4)); + + //private IPv4 ranges are - RFC1928 + //10.x.x.x + //172.16.x.x-172.31.x.x + //192.168.x.x + if (part1==10){ + DEBUG(1, ("10.x.x.x range\n")); + invalidip=false; + } else + if ((part1==172)&&(part2>=16)&&(part2<=31)) + { + DEBUG(1, ("172.16.x.x-172.31.x.x range\n")); + invalidip=false; + } else + if ((part1==192)&&(part2==168)) + { + DEBUG(1, ("192.168.x.x\n")); + invalidip=false; + } + //MAYBE 127.x.x.x should be added? + } + } + else DEBUG(1,("Too much delimiters found\n")); + } + } + else DEBUG(1,("Third delimiter not found\n")); + } + } + else DEBUG(1,("Second delimiter not found\n")); + } + }else DEBUG(1,("First delimiter not found\n")); + return !invalidip; +} + /** * Next state function for the Authenticate packet * @@ -520,6 +635,274 @@ } } + //This could would have to be included/copied to all other authentication types + + //AD and Samba know a userWorkstations attribute + //This attribute would be expected by admins to prevent users from logging in from untrusted workstations + //or anyone/anything else knowing a username/password combination from logging in + //without having access to a specific workstation. + + //It could be seen as being like a 2FA being the workstation the 2nd factor + + //In the case of a file server it has to pass on the workstation name of the calling station + //to grant users access (adding the file server to the list would allow access from anywhere) + //Thus AD seems to depend on the file server to do verification whether the caller + //is trustworthy and to do some kind of identity verification. + + //Having untrusted people knowing username/password combinations isn't good, + //but there are special cases where you control access to a set of specific workstations + //and want to restrict access to file shares to come from those controlled computers. + + //This could be done by some smb.conf include magic depending on the caller's IP and the username + //but when there is an userWorkstations attribute admins depend on, it should be doing the same + //out of the box. + + //The real solution would be having the workstation to authenticate itself in a secure way. + //For domain joined computers there should be some way as there is a computer workstation + //object in AD - but this might need changes in the underlying protocol. + + + //Solution 1: + //Special case workstation name verification + //Imagine the admin used the workstation name to represent the IPv4 address + //replacing dots by underscores to prevent confusion with DNS names (as the userWorkstations + //attribute could also store DNS names (at least when accessed by scripts and not the GUI)) + //which would cause workstation name verification to match only the first part of the address. + + //While userWorkstation attribute matching works to prevent workstations with other names to access + //we must prevent other IPv4 addresses to claim that they would be the allowed workstation. + + //Better way would be to look up the IPv4 address somewhere in the AD attributes of the workstation + //object and to compare it (which wouldn't help if it could be claimed by any other fake client). + + //Some kind of snake oil anyway, but possible safer than the previous checks + //as long as the admin can prevent others from claiming the IPv4 address (i.e. by implementing + //VLAN segmentation) and the same snake oil like smb.conf include magic + + //This implementation always enforces the workstation name to match the IPv4 address + //if the workstation name could be an encoded IP address + //For dual boot support it allows configurable strings to be added to the workstation name. + + + struct tsocket_address *remoteaddr=gensec_get_remote_address(gensec_security); + if (tsocket_address_is_inet(remoteaddr,"ipv4")) + { + bool isspecialip = false; + bool specialipisvalid = false; + if (lpcfg_workstation_name_ip_encoding(gensec_security->settings->lp_ctx)){ + /* max IPv4 is 255.255.255.255 thus 15 chars + 1 null byte */ + DEBUG(1, ("lpcfg_workstation_name_ip_encoding is enabled, do checks\n")); + TALLOC_CTX *mytmp_ctx=talloc_new(NULL); + char *myip; + char *delimiter; + const char *olddelimiter; + const char **allowedsuffixes = lpcfg_workstation_name_ip_encoding_suffix(gensec_security->settings->lp_ctx); + myip = tsocket_address_inet_addr_string(remoteaddr,mytmp_ctx); + for (int k=0; k<3; k++) + { + delimiter = strchr(myip,'.'); + *delimiter = '_'; + } + DEBUG(1, ("ntlmssp_server_auth comparision: %s %s\n",ntlmssp_state->client.netbios_name,myip)); + if (isencodedprivateip(ntlmssp_state->client.netbios_name,'_')) + { + DEBUG(1, ("We have a specially encoded private IPv4 address as workstation name\n")); + isspecialip = true; + if (strcmp(ntlmssp_state->client.netbios_name,myip)==0) + specialipisvalid=true; + else + { + int len=strlen(myip); + if(strncmp(myip,ntlmssp_state->client.netbios_name,len)==0) + { + if (allowedsuffixes != NULL) + { + const char **asp = allowedsuffixes; + char *nbname=ntlmssp_state->client.netbios_name; + nbname+=len; + DEBUG(1, ("Allowed suffixes exist\n")); + while (*asp != NULL) + { + DEBUG(1, ("Compare suffixes\n")); + DEBUG(1, ("Compare suffix %s with %s\n",*asp,nbname)); + if (strcasecmp(*asp,nbname)==0) + { + specialipisvalid = true; + } + asp++; + } + } + } + } + } + talloc_free(mytmp_ctx); + } + else + { + DEBUG(1, ("lpcfg_workstation_name_ip_encoding is not enabled\n")); + } + + if (!isspecialip) + { + //solution 2 + //We are not using specially encoded IPs but a workstation name + //so expect the admin to have configured reverse lookups properly + //WINS or anything else easily spoofable should be not used as we + //do a reverse lookup and verify whether it matches the claimed + //workstation name + + //As this also depends on the IP it is as insecure as the other way + //but it is providing an easy method if reverse lookups are configured properly + //and thus might need no changes on the client side + + //As this implementation has no access to the userWorkstations attribute + //and it should not break BYOD solutions for non-restricted user accounts + //we allow the admin to configure the names of the user accounts + //which always need this verification. + //As expanding groups currently doesn't work there is a second option + //to define an user prefix to add many users at once if they + //have the same prefix + + //To handle dual boot situations we allow a configurable list of suffixes + //which the client might add to its reverse lookup DNS name + //to allow both OS to be joined independently to the domain + + + const char **restrictedusers = lpcfg_workstation_restricted_users(gensec_security->settings->lp_ctx); + const char **restrictedusersprefix = lpcfg_workstation_restricted_users_prefix(gensec_security->settings->lp_ctx); + bool isrestricted = false, foundbyprefix=false; + + if (restrictedusers != NULL){ + DEBUG(1, ("Restricted users list exists\n")); + + while (*restrictedusers != NULL) + { + DEBUG(1, ("Compare restricted user\n")); + + DEBUG(1, ("Compare %s with %s\n",*restrictedusers,ntlmssp_state->user)); + //TODO: expand groups, etc?!!! + if (strcasecmp(*restrictedusers,ntlmssp_state->user)==0) //make usernames case insensitive + { + isrestricted = true; + } + restrictedusers++; + }} + else DEBUG(1, ("Restricted users list doesn't exist\n")); + if (isrestricted) DEBUG(1, ("Restricted user found\n")); + else DEBUG(1, ("User is not in restricted list\n")); + + if (restrictedusersprefix != NULL){ + DEBUG(1, ("Restricted users prefix list exists\n")); + + while (*restrictedusersprefix != NULL) + { + int len=strlen(*restrictedusersprefix); + DEBUG(1, ("Compare restricted user with prefix list\n")); + DEBUG(1, ("Compare prefix %s with %s\n",*restrictedusersprefix,ntlmssp_state->user)); + //TODO: expand groups, etc?!!! + if (strncasecmp(*restrictedusersprefix,ntlmssp_state->user,len)==0) //make usernames case insensitive + { + isrestricted = true; + foundbyprefix = true; + } + restrictedusersprefix++; + }} + else DEBUG(1, ("Restricted users prefix list doesn't exist\n")); + if (foundbyprefix) DEBUG(1, ("Restricted user found by prefix\n")); + else DEBUG(1, ("User is not in restricted prefix list\n")); + + + if (isrestricted) + { + + TALLOC_CTX *mytmp_ctx = talloc_new(NULL); + char *origip; + struct sockaddr_in addr; + char hbuf[NI_MAXHOST]; + origip = tsocket_address_inet_addr_string(remoteaddr,mytmp_ctx); + addr.sin_family = AF_INET; + addr.sin_port = htons(tsocket_address_inet_port(remoteaddr));//simply useless + inet_pton(AF_INET,origip,&addr.sin_addr); + + //Reuse function to validate whether caller ip is private + //If it is not private we probably can't control reverse lookup and + //so we alway fail as servers shouldn't be accessible from the internet anyway + if (!isencodedprivateip(origip,'.'))return NT_STATUS_INVALID_WORKSTATION; + + DEBUG(1, ("NO SPECIAL IP_1\n")); + if (getnameinfo((struct sockaddr *)&addr,sizeof(addr),hbuf,sizeof(hbuf), + NULL, 0, NI_NAMEREQD|NI_NOFQDN)) //as userWorkstation is probably without FQDN + { + DEBUG(1, ("REVERSE LOOKUP FAILED\n")); + return NT_STATUS_INVALID_WORKSTATION; + } + else + { + const char **allowednamesuffixes = lpcfg_workstation_name_suffix_restricted_users(gensec_security->settings->lp_ctx); + bool wsfound=false; + if (allowednamesuffixes != NULL){ + DEBUG(1, ("Workstation name suffix list exists\n")); + + while ((*allowednamesuffixes != NULL)&&(!wsfound)) + { + int suffixlen=strlen(*allowednamesuffixes); + int lookuplen=strlen(hbuf); + int nblen=strlen(ntlmssp_state->client.netbios_name); + + DEBUG(1, ("Compare hbuf %s and suffix %s with %s\n",hbuf,*allowednamesuffixes,ntlmssp_state->client.netbios_name)); + if ((lookuplen+suffixlen)==nblen) + if (strncasecmp(hbuf,ntlmssp_state->client.netbios_name,lookuplen)==0) + { + const char *p=ntlmssp_state->client.netbios_name; + p+=lookuplen; + if (strcasecmp(p,*allowednamesuffixes)==0){ + wsfound = true; + DEBUG(1, ("Workstation matched with suffix\n")); + + } + } + allowednamesuffixes++; + } + //We only temporarily store fail or success + //In case of fail nothing to return as the next verification will do that + } + if (!wsfound) + if (strlen(hbuf)!=strlen(ntlmssp_state->client.netbios_name)) + { + DEBUG(1, ("%s can't match %s\n",hbuf,ntlmssp_state->client.netbios_name)); + //Should be replaced with a description to prevent printing externally controlled characters + return NT_STATUS_INVALID_WORKSTATION; //NAME CAN't MATCH + } + else + { + for (int i=0; iclient.netbios_name[i])) + { + //Should be replaced with a description to prevent printing externally controlled characters + DEBUG(1, ("%s can't match %s\n",hbuf,ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_WORKSTATION; //NAME DOESN'T MATCH + } + } + } + } + talloc_free(mytmp_ctx); + } + } + else if (!specialipisvalid){ + DEBUG(1, ("SPECIAL_IP_WORKSTATIONNAME didn't match IP")); + return NT_STATUS_INVALID_WORKSTATION; + } + } + else + { + DEBUG(1, ("FIXME: Currently we can only handle IPv4, this breaks IPv6\n")); + + //This should be moved somewhere else + //to break IPv6 only for users with userWorkstation verification + return NT_STATUS_INVALID_WORKSTATION; + } + talloc_steal(state, state->encrypted_session_key.data); if (auth_flags != 0) {