Le blog technique

Toutes les astuces #tech des collaborateurs de PI Services.

#openblogPI

Retrouvez les articles à la une

[Le saviez vous ?] – Network Level Authentication (NLA)

NLA qu’est ce que c’est ?

NLA, pour Network Level Authentication est un mécanisme de sécurité implémenté par Microsoft dans la version 6.0 du RDP; rendu disponible pour la première fois avec Windows Vista / Server 2008, ce dernier a pour but de valider les identifiants avant de charger la connexion RDP.

Pour ce faire il utilise le Credential Security Support Provider (CredSSP), mais nous reviendrons sur cet élément dans un autre article.

Mais pourquoi ?

Et bien simplement pour permettre la réduction de surface d’attaque car, lorsque le  NLA n’est pas activé, il est possible de charger la session RDP avant l’authentification par n’importe qui.

Et alors me direz vous, Eh bien de ce fait il est possible pour un attaquant de tenter une attaque de type « brute force » ou bien tenter de lister les utilisateurs déjà authentifiés sur le serveur.

Et c’est tout ?

Bien que l’objectif premier soit de réduire la surface d’attaque, il y a un autre bénéfice avec le NLA, cela permet aussi de réduire la consommation de ressources du serveur.

Comment ? Simplement, prenons l’exemple d’un serveur sur lequel le NLA n’est pas activé, lorsqu’un un utilisateur tente une connexion RDP, le serveur doit charger la session qui amène l’utilisateur a la mire de connexion, les ressources nécessaires à la session sont donc consommées par le serveur.

Alors que sur un serveur où le NLA est activé, une validation des accès est faîte en amont et, si l’utilisateur n’a pas accès au serveur il reçoit un refus de connexion avant que le serveur n’ai eu besoin de charger la session.

Par conséquent lorsque le NLA est activé, le serveur économise les ressources liés aux sessions non autorisées.

[Le saviez vous ?] – La résolution de nom sous Windows

La résolution de nom dans un environnement Windows est relativement simple mais parfois certaines étapes sont pourtant oubliées et créées une confusion dans les diagnostics, réalisons donc un petit tour d’horizon.

Dans un environnement Windows, lorsque qu’un ordinateur (Client_1) veut communiquer avec une autre ressource (Serveur_A) il doit en premier lieu trouver l’adresse associée au nom de Serveur_A.

Pour cela  le Client_1 va pratiquer les étapes suivantes :

  • 1- Vérifier le nom local (son nom à lui); en effet cela pourrait être lui-même.
  • 2- Vérifier dans son cache de résolution DNS.
  • 3- Vérifier dans son fichier Hosts (C: Windows\System32\drivers\etc\hosts)
  • 4- Demander au serveur DNS.
  • 5- Faire appel à un vieux protocole Windows, Link-Local Multicast Name Résolution (LLMNR).
  • 6- Faire appel à un protocole encore plus vieux NetBios Name Service (NBTNS).

En ce qui concerne les deux derniers, pour des raisons de sécurité que nous détaillerons dans un autre article ils devraient être désactivés sur un domaine Active Directory.

Python – Executer des commandes sur une machine Windows distante.

Le script ci-dessous propose d’utiliser le module pypsrp (https://pypi.org/project/pypsrp/) pour executer n’importe quelle commande (cmd/powershell) sur une machine windows distante.

Dans cet exemple l’authentification se fait via kerberos, mais le parametre auth de wsman peut utiliser basiccertificatenegotiatentlmkerberoscredssp

wsman = WSMan(target, ssl=False, auth='kerberos')

Par defaut le mode winrm utilisé (–winrm-mode) est runspacepool qui est plus compatible avec des commandes ou des blocs de code powershell.

Un test de connectivité winrm (–test-winrm-port, par defaut activé) permet de tester la connexion avant d’executer la commande.

Le paramètre –execute-remote-action etant par defaut desactivé (False), il est necessaire de l’activer (–execute-remote-action True) pour executer effectivement la commande.

help = """
SCRIPT D'EXECUTION DE COMMANDES WINRM SUR UNE MACHINE WINDOWS CIBLE DEFINIE EN ARGUMENT.

Description :
Ce script a été développé à l'occasion d'une demande de reboots récurrents de nombreuses machines Windows. Il a été implémenté de manière à pouvoir être réutilisé pour d'autre commandes `winrm` que le reboot.


ARGUMENTS:
- --get-help True: Affiche l'aide.
- --win-computer: Nom de la machine cible Windows sur laquelle exécuter la commande. (Requis)
- --execute-remote-action True: Execute les actions sur les machines distantes. (Valeur par défaut: False)
- --remote-command: Commande à exécuter sur les machines distantes. (Valeur par défaut: whoami)
- --test-winrm-port True: Test de la connexion au port WinRM des machines distantes avec le module socket. (Valeur par défaut: True)
- --winrm-mode: methode d'execution de la commande sur la machine distante. (Valeur par défaut: runspacepool)




Exemple d'utilisation:
  - Executer la commande "whoami" sur la machine "mycomputer" avec le test de connexion au port WinRM:
  python run_winrm_cmd_on_host.py --win-computer mycomputer --execute-remote-action True --test-winrm-port True --remote-command "get-process"

  

  """


import sys
import os
import argparse
from pypsrp.shell import Process, WinRS
from pypsrp.powershell import PowerShell, RunspacePool
from pypsrp.wsman import WSMan
import socket


if __name__ == '__main__':


        ###########################################
        # Arguments du script
        ###########################################

        
        parser = argparse.ArgumentParser()

        parser.add_argument(
        '--get-help',
        type=str,
        choices=['True', 'False'],
        default='False',
        help='Affiche l\'aide.'
        )

        parser.add_argument(
        '--win-computer',
        type=str,
        help='machine cible Windows'
        )

        parser.add_argument(
        '--execute-remote-action',
        type=str,
        choices=['True', 'False'],
        default='False',
        help='Execute effectivement les actions sur les machines distantes.'
        )

        parser.add_argument(
        '--remote-command',
        type=str,
        default="whoami",
        help='Commande à exécuter sur les machines distantes.'
        )

        parser.add_argument(
        '--test-winrm-port',
        type=str,
        choices=['True', 'False'],
        default='True',
        help='Test de la connexion au port WinRM des machines distantes avec le module socket.'
        )

        parser.add_argument(
        '--winrm-mode',
        type=str,
        choices=['winrs', 'runspacepool'],
        default='runspacepool',
        help='methode d\'execution de la commande sur la machine distante.'
        )


        args = parser.parse_args()
        

        if args.get_help == 'True':
                print(
                help        
                )
                sys.exit(0)


           

        ############################################################################
        ### VERIFICATION DE LA CONNEXION A LA MACHINE CIBLE AVEC LE MODULE SOCKET
        ############################################################################
        
        if args.test_winrm_port == 'True':
                print("---------------------------------------------------------------------------------------------------------------------------------------------")
                print(f"TEST DE RESOLUTION DE NOM ET CONNEXION AU PORT 5985 (WINRM) pour la machine {args.win_computer} avec le module socket")
                print("---------------------------------------------------------------------------------------------------------------------------------------------")


                try:
                                
                                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                                sock.settimeout(5)
                                sockresult = sock.connect_ex((args.win_computer,5985))

                                if not socket.gethostbyname(args.win_computer):
                                        print("!!! resolution KO pour " + args.win_computer +"!!!")
                                        wincomputer_resolved = False
                                        exit(1)
                                        
                                        

                                if not (sockresult == 0):
                                        print("!!! WinRM TCP KO pour  " + args.win_computer +"!!!")
                                        wincomputer_resolved = True
                                        winrm_port_open = False
                                        exit(1)
                                        
                                        
                                
                                else:
                                        print("resolution OK et WinRM OK pour " + args.win_computer )
                                        
                                sock.close()
                               
                except Exception as error:
                                print(f"ERREUR: {error}")
                                exit(1)
                                

                                

                print("---------------------------------------------------------------------------------------------------------------------------------------------")


        
        ########################################
        ### ACTION SUR LA MACHINE AVEC WINRM
        ########################################
        


# Action executee si argument execute_remote_action = True et si la machine est joignable
        if args.execute_remote_action == 'True':
        

                

                        ### METHODE 1: WINRS
                        if args.winrm_mode == 'winrs':

                                try:
                                        target = args.win_computer
                                        print(f"EXECUTION DE LA COMMANDE SUR LA MACHINE \"{target}\"")
                                        print("------------------------------------------------------------------------------------------------------------------------------")
                                        #creates a http connection with no encryption and kerberos auth
                                        wsman = WSMan(target, ssl=False, auth='kerberos')
                                        
                                        # # create a shell with wsman
                                        with wsman, WinRS(wsman) as shell:
                                                # execute a process with arguments in the background
                                                process = Process(shell, args.remote_command)
                                                process.begin_invoke()  # start the invocation and return immediately
                                                process.poll_invoke()  # update the output stream
                                                process.end_invoke()  # finally wait until the process is finished
                                                output = str(process.stdout, errors='ignore')
                                                print(output)
                                                print("------------------------------------------------------------------------------------------------------------------------------\r")



                                except Exception as error:
                                        print(f"ERREUR: {error}")
                                        pass

                                

                        ### METHODE 2: RUNSPACEPOOL
                        if args.winrm_mode == 'runspacepool':

                                try:
                                        target = args.win_computer
                                        print(f"EXECUTION DE LA COMMANDE SUR LA MACHINE \"{target}\"")
                                        print("------------------------------------------------------------------------------------------------------------------------------")
                                        # creates a https connection with explicit kerberos auth and implicit credentials
                                        wsman = WSMan(target, ssl=False, auth="kerberos", cert_validation=False)

                                        
                                        with wsman, RunspacePool(wsman) as pool:

                                                # POUR EXECUTER UNE COMMANDE POWERSHELL SPECIFIQUE (IL FAUT FINIR AVEC OUT-STRING POUR AVOIR UN RETOUR DE TYPE STRING)
                                                # EX: execute 'Get-Process | Select-Object Name | out-string'
                                                ps = PowerShell(pool)
                                                #ps.add_cmdlet("get-process").add_cmdlet("Select-Object").add_argument("Name").add_cmdlet("Out-String")
                                                ps.add_script(args.remote_command + " | out-string")
                                                ps.invoke()
                                                for out in ps.output:
                                                        print(out)
                                                
                                                        
                                except Exception as error:
                                        print(f"ERREUR: {error}")
                                        pass