PI Services

Le blog des collaborateurs de PI Services

[Powershell] - Importer un certificat dans le store user d'un gMSA

J'ai récemment eu besoin d'importer un certificat dans le magasin personnel d'un gMSA pour m'en resservir dans un script plus tard.

Contrairement à un compte standard, il n'est en effet pas logique / "possible" d'ouvrir une session et d'importer le certificat directement depuis la session du gMSA, par conséquent il me fallait une autre solution.

Pour cela j'ai onc utilisé une tache planifié et un script Powershell.

Le code

Ce que je vous invite à faire, c'est de faire vos modifications et signer votre script afin de ne pas avoir à bypass ou modifier les policies.

$params = @{
    FilePath = 'C:\temp\CertForgMSA.pfx'
    CertStoreLocation = 'Cert:\CurrentUser\My'
    Password = ConvertTo-SecureString -AsPlainText "MyUltraS3curePassword" -Force
}
Import-PfxCertificate @params

 

Donc ici dans les paramètres j'ai :

  • FilePath : Représente le chemin de mon script 
  • CertStoreLocation : Représente le store du gMSA
  • Password : Le mot de passe du PFX (essayez d'en utiliser un plus secure :D )

Créer la tâche

J'ai déjà présenté dans l'article ICI comment faire, nous allons donc le faire un Powershell maintenant.

$TaskAction = New-ScheduledTaskAction -Execute C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe  -Argument "-File C:\Temp\ImportPfx.ps1"
 
$TaskTrigger = New-ScheduledTaskTrigger -At 00:00 -Once # vous pouvez utiliser d'autre valeurs selon vos besoin Par exemple Daily
 
$TaskPrincipal = New-ScheduledTaskPrincipal -UserID Lab\gMSA01$ -LogonType Password
 
Register-ScheduledTask MygMSATask –Action $TaskAction –Trigger $TaskTrigger –Principal $TaskPrincipal

 

Et voilà, il n'y a plus qu'a attendre que la tâche s'exécute; on peut également vérifier via une commande supplémentaire que le script est bien présent (Get-Item sur le store user avec un export dans un fichier texte par exemple).

[Windows Server] - Utiliser un gMSA pour ses tâches planifiées

Si il y a bien une chose que j'essaie de pousser dans un environnement Active Directory, c'est de supprimer les comptes avec un mot de passe qui n'expire jamais, utilisés pour exécuter des tâches planifiées; pour les remplacer par des gMSA.

Souvent mal appréhendé, je vais ici vous montrer qu'il n'y a quasiment pas de différence dans l'utilisation.

Créer une tâche

Via GUI

Pour créer une tâche en mode graphique, on ne change pas les habitudes.

Dans un premier temps on lance le Task Scheduler

Puis on créé un tâche que l'ion va nommer

On lui définit ses paramètres

Bon jusque la rien de différent de ce que vous avez l'habitude de faire.

Passons donc à la suite, éditez la tâche.

Et à partir d'ici remplaçons notre compte par le gMSA.

Donc on sélectionne "Change User or Group..." et on s'assure de rechercher sur le domaine donc on sélectionne "Location"

On sélectionne "Entire Directory" et on valide

à partir de la on va sélectionner "Object Types..."

Et tout désélectionner  sauf "Service Accounts" puis on valide

Enfin on recherche son gMSA et on valide

Et voilà, comme quoi il n'y a rien de plus simple car, on s'évite même de devoir gérer les identifiants du compte de service.

 

Via Powershell

 

$TaskAction = New-ScheduledTaskAction -Execute C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe  -Argument "-File C:\Temp\ImportPfx.ps1"
 
$TaskTrigger = New-ScheduledTaskTrigger -At 00:00 -Once # vous pouvez utiliser d'autre valeurs selon vos besoin Par exemple Daily
 
$TaskPrincipal = New-ScheduledTaskPrincipal -UserID Lab\gMSA01$ -LogonType Password
 
Register-ScheduledTask MygMSATask –Action $TaskAction –Trigger $TaskTrigger –Principal $TaskPrincipal

 

 

Modifier un tâche

Via GUI

Souvent, la première fois qu'on y fait face c'est un peu déroutant, car lors de la modification de la tâche le popup demandant les identifiants arrive.

Pour éviter cela rien de plus simple, ouvrez votre tâche planifié.

Modifiez vos paramètres, par exemple ici nouds allons modifier l'heure d'exécution.

Une fois modifié, nous validons avec "Ok", puis nous retournerons sur le menu "General"

Puis comme nous l'avons fais lors de la  création de la tâche planifiée, donc on sélectionne "Change User or Group..." et on s'assure de rechercher sur le domaine donc on sélectionne "Location"

On sélectionne "Entire Directory" et on valide

à partir de la on va sélectionner "Object Types..."

Et tout désélectionner  sauf "Service Accounts" puis on valide

Enfin on recherche son gMSA et on valide

Et enfin on sélectionne "Ok"

 

Via Powershell

$Task = Get-ScheduledTask -TaskName MygMSATask
$Time = New-ScheduledTaskTrigger -At 21:00 -Once
$TaskPrincipal = New-ScheduledTaskPrincipal -UserID Lab\gMSA01$ -LogonType Password

Set-ScheduledTask -TaskName $Task.TaskName -Trigger $Time -Principal $TaskPrincipal

 

[Microsoft Graph] - Restreindre la portée de l'Application Permission Site.Selected

Dans cet article, nous allons voir comment restreindre la portée de délégation à un site Sharepoint pour une App via la permission Site.Selected.

Délégation des droits pour l'App

Dans Entra, sélectionnez votre App et rendez vous sur "Api Premissions"

 

Puis sélectionnez "Microsoft Graph"

Ensuite "Application permissions" et recherchez "Sites.Selected"

Ensuite validez en faisant "Add Permission" et enfin "Grant admin consent for XXXX"

Maintenant qu'on a donné un accès Sharepoint Online à notre App, nous allons utiliser Powershell pour restreindre ses droits.

 

Connexion

$Connection = Connect-PnPOnline -Url https://xxxxxxx-admin.sharepoint.com -Interactive -ReturnConnection -ForceAuthentication

Restriction

Grant-PnPAzureADAppSitePermission -AppId 52c15f64-938e-4ad3-9454-c02f754b06c2 -DisplayName Demo-AppSharepoint -Site "https://MONADMIN.sharepoint.com/sites/benefits" -Permissions Read -Verbose -Connection $Connection

 

Check

Get-PnPAzureADAppSitePermission -AppIdentity 52c15f64-938e-4ad3-9454-c02f754b06c2 -Site "https://MONADMIN.sharepoint.com/sites/benefits"

 

[Microsoft Graph] - Restreindre la portée de l'Application Permission Mail.Send

Lorsque l'on délègue la permission Mail.Send avec le type Application Permission, cela permet à l'application d'envoyer des mails avec n'importe quelle boite aux lettres du Tenant.

Nous allons ici voir comment restreindre cette portée à une ou plusieurs boites aux lettres.

Creation d'un mail enabled security group

 Nous allons dans un premier temps créer un 'Mail Enabled Security Group' depuis le portail Exchange Online ou via Powershell.

Donnons lui un nom et une description.

Assignons lui un propriétaire.

Ce dernier aura pour membre la ou les boites aux lettres depuis lesquelles nous autoriserons l'envoie de Mail.

Donnons lui une adresse de messagerie

Et enfin on valide.

La création de la Policy via une petite ligne Powershell

Via Powershell nous allons créer un Policy afin de restreindre l'envoie de mail à notre App pour le groupe créé précédement.

 

# Connect to Exchange Online
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline

# Create App Policy
New-ApplicationAccessPolicy -AccessRight RestrictAccess -AppId "0bb96e35-dced-4569-aaec-1531a17157ad" -PolicyScopeGroupId RestrictToSalesOnly@monadressesmail.com -Description "Restrict this app's access to members of security group RestrictToSalesOnly."

 

Vérification

Une petite ligne Powershell pour vérifier que la policy fonctionne.

Test-ApplicationAccessPolicy -Identity RandomUser@monadressmail.com -AppId "0bb96e35-dced-4569-aaec-1531a17157ad"

Donc ici on peut vérifier que cela ne fonctionne pas pour l'utilisateur random, mais si on essaie avec l'adresse autorisée

Test-ApplicationAccessPolicy -Identity SalesTeam@monadressmail.com -AppId "0bb96e35-dced-4569-aaec-1531a17157ad"

 

[Microsoft Graph] - Delegated permissions vs Application permissions

 

Les permissions Microsoft Graph.

Les permissions attribuées au sein d'un Tenant Microsoft 365 (types et étendues) peuvent avoir un impact sur ce dernier, il est donc important de bien identifier votre besoin pour les attribuer.

Lorsque l'on délègue des permissions Microsoft Graph à une App dans un Tenant, celle-ci peut se voir attribuer deux types "Delegated" ou "Application", nous allons tenter de voir la différence entre les deux.

Pour illustrer ces deux types nous allons nous référer à la présentation de Microsoft ci dessous.

Delegated Permissions

Permet à une application, d'agir en tant que l'utilisateur pour une action spécifique.

Ces permissions sont orientées pour des applications qui accèdent en tant que l'utilisateur identifié et, ne pourront excéder la porté de l'utilisateur.

Prenons par exemple l'App "Demo-DelegatedPerm" cette dernière possède le droit "Mail.Send".

Ce qui implique que lorsque l'utilisateur va vouloir utiliser cette application un consentement va lui être demandé.

A partir du moment ou l'utilisateur aura donné son consentement l'application sera autorisé a envoyer des mails en son nom.

Attention : Dans ce type de délégation l'approbation peut être donnée soit :

  • Par l'utilisateur lorsqu'il utilise l'application.
  • Par l'Administrateur pour tous les utilisateurs du Tenant lorsqu'il donne les permissions. 

 

Application Permissions

Permet à une application d'agir en tant qu'elle même, au lieu d'un utilisateur spécifique.

Ces permissions sont orientées pour des applications qui accèdent en tant qu'application elles mêmes et, auront une porté d'action sur l'ensemble du Tenant.

Prenons ici l'App "Demo-ApplicationPerm" cette dernière possède le droit "Mail.Send".

Ce qui implique que lorsque l'Administrateur va approuver les droits, cette application sera en mesure d'envoyer des mails depuis n'importe quelle boite aux lettres du tenant.

 

 

Qu'est ce que ça implique ?

En terme d'accès nous pouvons constater que l'un est beaucoup plus permissif que l'autre (encore que cela soit discutable si c'est l'administrateur qui a consenti) et, qu'il faut à minima restreindre la porté de l'accès.

En effet dans un contexte de moindre privilèges, l'attribution des droits via "Application Permissions" doit être contenu / restreint afin d'en limiter sa portée; ainsi nous pourrons nous prémunir d'une application avec une trop grande portée d'action sur l'environnement.

 

[Powershell] - Fonction pour obtenir les membres des groupes Active Directory supérieur à 5000 objets.

Problème :

Il existe une limitation à la commande "Get-ADGroupMember" et cette dernière est de 5000, ce qui veut dire que si le groupe détient plus de 5000 membres vous obtiendrez un joli message d'erreur du type :

Get-ADGroupMember : The size limit for this resquest was exceeded

Solution :

Voici une fonction Powershell qui vous permettra de récupérer l'intégralité des membres d'un groupe même s'il y en a 10 000 dedans.

Vous pourrez choisir de retourner :

  • Les utilisateurs
  • Les groupes
  • Le tout
Function Get-AllMembers {
     <#
    .SYNOPSIS
    Return a list of members for a group.

    .DESCRIPTION
    Get-AllMembers is a function that returns a list of members for a specific group.
    
    .PARAMETER Name
    The name of the group you want to get the member list.

    .EXAMPLE
    Get-AllMembers "Domain Admins", "DNS Admins"

    .INPUTS
    String

    .OUTPUTS
        PSCustomObject

    .NOTES
        Author:  ADELAIDE Mathieu
    #>
    PARAM (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [STRING]$Name,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)]
        [ValidateSet("UsersOnly","GroupsOnly","All")]
        [STRING]$Return
        )
    Process {
        $Name | Foreach {
            $GroupName = $_
            $ArrayUsers = @()
            $ArrayGroups = @()
            $ArrayAll = @()
            Try {
                $DistinguishedName = Get-ADGroup -Identity $GroupName -ErrorAction Stop | select -ExpandProperty DistinguishedName
                # Searching all Users who's member of current Group
                Try {
                    $AllUsersMembers = Get-ADUser -LDAPFilter "(&(objectCategory=user)(memberOf=$DistinguishedName))" -ErrorAction Stop
                    $AllUsersMembers | foreach {
                        $ArrayUsers += New-Object psobject -Property @{
                            GroupName = $GroupName
                            DistinguishedName = $_.DistinguishedName
                            Enabled = $_.Enabled
                            GivenName = $_.GivenName
                            Name = $_.Name
                            ObjectClass = $_.ObjectClass
                            ObjectGUID = $_.ObjectGUID
                            SamAccountName = $_.SamAccountName
                            SID = $_.SID
                            Surname = $_.Surname
                            UserPrincipalName = $_.UserPrincipalName
                            }

                        # Collect All
                        $ArrayAll += New-Object psobject -Property @{
                            GroupName = $GroupName
                            DistinguishedName = $_.DistinguishedName
                            Enabled = $_.Enabled
                            GivenName = $_.GivenName
                            Name = $_.Name
                            ObjectClass = $_.ObjectClass
                            ObjectGUID = $_.ObjectGUID
                            SamAccountName = $_.SamAccountName
                            SID = $_.SID
                            Surname = $_.Surname
                            UserPrincipalName = $_.UserPrincipalName
                            }
                        }
                    }
                Catch {
                    Write-Warning -Message "Unable to find all users member of $Name"
                    }
                # Searching all Groups who's member of current Group
                Try {
                    $AllGroupsMembers = Get-ADGroup -LDAPFilter "(&(objectCategory=group)(memberOf=$DistinguishedName))" -ErrorAction Stop
                    $AllGroupsMembers | foreach {
                        $ArrayGroups += New-Object psobject -Property @{
                            GroupName = $GroupName
                            DistinguishedName = $_.DistinguishedName
                            GroupCategory = $_.GroupCategory
                            GroupScope = $_.GroupScope
                            Name = $_.Name
                            ObjectClass = $_.ObjectClass
                            ObjectGUID = $_.ObjectGUID
                            SamAccountName = $_.SamAccountName
                            SID = $_.SID
                            }

                        # Collect All
                        $ArrayAll += New-Object psobject -Property @{
                            GroupName = $GroupName
                            DistinguishedName = $_.DistinguishedName
                            Enabled = $_.Enabled
                            GivenName = $_.GivenName
                            Name = $_.Name
                            ObjectClass = $_.ObjectClass
                            ObjectGUID = $_.ObjectGUID
                            SamAccountName = $_.SamAccountName
                            SID = $_.SID
                            Surname = $_.Surname
                            UserPrincipalName = $_.UserPrincipalName
                            }
                        }
                    }
                Catch {
                    # Return an error message if member not found.
                    Write-Warning -Message "Unable to find all groups member of $Name"
                    }
                }
            Catch {
                # Return an error message if Group was not found.
                Write-Warning -Message "Unable to find $Name"
                }
            
            Switch ($Return) {
                "UsersOnly" {Return $ArrayUsers}
                "GroupsOnly" {Return $ArrayGroups}
                "All" {Return $ArrayAll}
                }

            # Release
            $GroupName = $null
            $DistinguishedName = $null
            $AllUsersMembers = $null
            $AllGroupsMembers = $null
            }
        }
    }

Powershell : Lister les règles dans une boite aux lettre Exchange Online

De temps en temps, des équipes de sécurité détectent des règles de transfert de courrier électronique en dehors de l'entreprise et souhaitent les neutraliser.

Dans certain cas la règle de transfert peut être neutralisée directement depuis le portail d'administration mais, dans d'autres cas ce n'est pas possible (règle créer directement depuis le client outlook par exemple); c'est a ce moment qu'on me demande de trouver et neutraliser la règle.

Pour ce faire rien de plus simple, il faut :

  1. Se connecter à Exchange online avec les bonnes permissions.
  2. Identifier la boite en question.
  3. Rechercher les règles sur la boite.
  4. Identifier celle qui dérange.
  5. La supprimer.

A l'aide de Powershell connectez vous à Exchange Online

Connect-ExchangeOnline

Identifier la boite aux lettres que vous souhaitez cibler, j'utilise de préférence la "PirmarySMTPAddress", puis faite une recherche des règles sur la boite via :

Get-InboxRule -Mailbox xxxxxxx@xxxxxxxx.xxx

Il se peut que la commande retourne plusieurs règles, dans ce cas prenez le temps de parcourir le champs "Description" de chacune pour comprendre ce que chaque règle fait; il est également possible de vérifier directement les champs "ForwardAsAttachmentTo" et "ForwardTo" pour voir vers quelle adresse est renvoyé le mail.

Une fois cette dernière identifiée, récupérez son attribut "RuleIdentity" et lancez la commande suivante en prenant soin de remplacer les "0000000000000" par la valeur RuleIdentity que vous avez récupéré.

 Remove-InboxRule -Mailbox xxxxxxxxxxx@xxxxxxx.xxx -Identity 0000000000000 -Confirm:$false



O365 : Réaliser un hard match (Update)

Dans un précédent article j'expliquais comment réaliser un hard match (https://blog.piservices.fr/post/2021/04/01/o365-realiser-un-hard-match).

J'ai récemment eu à me servir de nouveau des cmdlets et ces dernières ne fonctionnaient pas correctement, il est possible de le faire via API Graph, mais les commande Azure AD permettent encore de le faire si besoin.

 

# Get GUID for User
$User = Get-ADUser jdupont | select ObjectGUID,UserPrincipalName
$Upn = $User.UserPrincipalName
$Guid = $User.ObjectGUID.Guid
 
# Convert GUID to ImmutableID
$ImmutableId = [System.Convert]::ToBase64String(([GUID]($User.ObjectGUID)).tobytearray())
 
# Connect Azure AD
Connect-AzureAD

# Retrieve my user
$User = Get-AzureADUser -SearchString "jdupont @mydomain.com"

# Set ImmutableID to user
Set-AzureADUser -ObjectId $User.ObjectID -ImmutableId $ImmutableId

 

Active Directory : Lister tous les comptes dont le mot ne passe n'expire jamais

Dans toute annuaire Active Directory il existe une mauvaise pratique, celle dont je voudrais vous parler aujourd'hui est la non expiration des mots de passe pour un / des comptes du domaine. 

Pourquoi ?

Il existe plusieurs arguments au fait qu'un mot de passe qui n'expire jamais est une mauvaise pratique, je mettrais en avant ici les deux qui me posent le plus problème:

  • Tout d'abord un mot de passe qui n'expire jamais a plus de chance d'être "découvert" dans des attaques de type brute force; si on se concentre sur ces comptes en particulier, le fait qu'il n'y ai pas de rotation de mot de passe, laisse une plus grande période de temps à l'attaquant pour le découvrir.
  • Si le compte est compromis, l'attaquant a par définition un accès "constant" au SI, puisque tant que la rotation du mot de passe n'aura pas lieu, ce dernier conservera son accès.

Que faire ?

Bien qu'il ne soit pas une bonne pratique d'autoriser la non expiration des mots de passe, je les croise tous le jours dans tout Active Directory, avec toujours "une bonne raison" de l'avoir fait.

En revanche, même s'il n'est pas possible de s'en séparer, il est tout de même bon de mettre en oeuvre quelques bonne pratiques lorsque l'on est dans cette situation :

  • Lister les comptes dont le mot de passe n'expire jamais.
  • Documenter la cause de cette configuration.
  • Documenter leur emploi (raison d'utilisation, application dans lesquelles ils sont utilisés, machines sur lesquelles ils sont utilisés).
  • Documenter la date du dernier changement de mot de passe (même si elle peut être retrouvée dans l'AD).
  • Documenter une procédure de changement de mot de passe (documentation applicative, processus de dépendance...).
  • Indiquer une personne / équipe en mesure de pouvoir réaliser le changement de mot de passe (il se peut qu'il y ai des développeurs, prestataires externes, éditeurs qui se servent de ce mot de passe). 
  • Réaliser une rotation du mot de passe manuellement.

Lister les comptes avec Powershell.

# Variables
$RootFolder = "C:\Temp"
$Workefolder = "$RootFolder\NeverExpires"
$LogFolder = "$RootFolder\Logs"
$LogFile = "$LogFolder\NeverExpires.txt"
$AllFolders = $RootFolder, $Workefolder, $LogFolder

# Check and create if needed
foreach ($Folder in $AllFolders) {
    If (!(Test-Path $Folder)) {
        Try {
            New-Item $Folder -ItemType Directory -ErrorAction Stop
            }
        Catch {
            Write-Warning $($_)
            }
        }
    }

# Import module
Try {
    Import-Module ActiveDirectory -ErrorAction Stop
    }
Catch {
    Write-Output "Failed to import module ActiveDirectory" | Add-Content $LogFile
    Exit
    }

# Get Active Directory users that have a password that never expires
Try {
    $AllEnabledUsers = Get-ADUser -Filter {Enabled -eq $true} -Properties PasswordNeverExpires -ErrorAction Stop
    $PasswordNeverExpires = $AllEnabledUsers.Where({$_.PasswordNeverExpires -ne $false})
    }
Catch {
    Write-Output "Failed to collect users" | Add-Content $LogFile
    }

# Export result
$PasswordNeverExpires | Export-Csv "$Workefolder\PasswordNeverExpires.csv" -Delimiter ";" -Encoding UTF8

 

Powershell : Change the owner of my GPO

Pour faire suite au précédent articles Powershell : Who's the owner of my AD objects et Powershell : Change the owner of my AD objects voici comment trouver les GPO dont le propriétaire n'est pas "Domain Admins" et les modifier.

# Get Domain
$Domain = Get-ADDomain | select -ExpandProperty NetBIOSName

# Get all GPO
$AllGPO = Get-GPO -All
$AllADGPO = Get-ADObject -Filter {(ObjectClass -eq "groupPolicyContainer")} -Properties displayName

# Filter GPO that are not owned by Domain Admins
$NoGood = $AllGPO.Where({$_.owner -ne "$Domain\Domain Admins"})

# Change owner of all invalid GPO
$NoGood | select -First 1 | foreach {
    $DisplayName = $_.DisplayName
    $Id = $_.ID
    $Guid = $Id.Guid

    $CurrentGpo = $AllADGPO.Where({$_.DisplayName -eq $DisplayName})
    Write-Host $CurrentGpo
    Write-Host $DisplayName -ForegroundColor Magenta
    
    $adsiTarget = [adsi]"LDAP://$($CurrentGpo.DistinguishedName)"
    $idRef = New-Object System.Security.Principal.NTAccount("$Domain", "Domain Admins")
    $adsiTarget.PSBase.ObjectSecurity.SetOwner($idRef)
    $adsiTarget.PSBase.CommitChanges()
    
    
    $DisplayName = $null
    $Id = $null
    }