Abou Chleih

{the magic lies between the brackets}

Menü Schließen

Kategorie: Sicherheit (Seite 1 von 2)

Check_MK Überwachungs-Skript für EMC VNXe Maschinen (Unisphere CLI)

Zur Überwachung von VNXe Storage Systemen (NAS) innerhalb von Check_MK gab es keine ordentlichen Funktionen.
Im Netz findet man auch nicht viel zu diesem Thema.
Allerdings bieten die VNXe Systeme eine Remote Möglichkeit zur Konfiguration via Shell, die sog. Unisphere CLI; kurz: UEMCLI.
Diese wird für die verschiedensten Systeme angeboten, allerdings für Linux Systeme nur in Form ein .rpm Datei (Red Hat Package).
Auf Debian Systemen steht man jetzt etwas blöd da, also konvertierte ich das Paket mit alien in das .deb Format.

faviconunispherecli-linux-64-x86-en-us_3.0.0.1.16-2_amd64.deb

Nach der Installation finden sich die Binaries der uemcli im Pfad /opt/emc/uemcli/bin.

vnxe_check

Das Skript, welches wir in Check_MK aufrufen werden, müssen wir nun in einen für den Check_MK user verfügbaren Pfad packen (hier: /opt/omd/sites/monitor/local/share/check_mk) und via chmod -x ausführbar machen. Die Rechte müssen evtl. auch angepasst werden.
Im Anschluss können wir einen Legacy oder Classical Check in Check_MK anlegen, welcher das Programm ausführt.
Davor allerdings noch die Security Level der UEMCLI via /opt/emc/uemcli/bin/setlevel.sh -l auf low setzen, damit die Zertifikate nicht mehr manuell akzeptiert werden müssen.

Nun können wir das Programm mit folgendem Befehl ausführen: /opt/omd/sites/monitor/local/share/check_mk/vnxe_check <HOST> <USER> <PW> <PATH> <COL> <OPTIONAL REGEX>
Es gibt die entsprechenden Codes zurück. Die Fehlercodes von Degraded/Minor failure/Major failure werden als CRITICAL angezeigt (Returnvalue 1), bei einem Programmfehler oder leerer Response (timeout) wird UNKNOWN zurückgegeben (Returnvalue 2), bei einem Fehlercode OK_BUT wird WARNING angezeigt (Returnvalue 2)

First parameter: IP/Domainname
Second parameter: username and domain in the following structure: <DOMAIN>/<USER>
Third parameter: password
Fourth parameter: directory or file which is queried (see manual inside the bash file)
Fifth parameter: column index of health status (see manual inside the bash file)
Sixth parameter is optional: grep filter which looks for the beginning of a line and returns the whole line  | grep \“.*$6.*\“

Der letzte Parameter wird von mir verwendet, um einzelne Geräte innerhalb eines Arrays anzusprechen, bspw. eine einzelne Platte im Disk Array

Erste Schritte: Installation und Erstkonfiguration von Nagios3/Check_MK auf Debian 7.0

Um meine Windows Server 2008 R2 Server, sowie meine Virtualisierungsinfrastruktur (auf VMWare ESXI) zu monitoren, suchte ich nach einer einfachen, aber auch umfangreichen Lösung.
So stieß ich auf das sehr verbreitete Nagios Softwarepaket, welches allerdings erst mit vielen Plugins ausgestattet werden muss, um vernünftig zu funktionieren.
Daraufhin habe ich meine Suche fortgeführt und bin auf die Open Monitoring Distribution (kurz: OMD oder OMDistro) gestoßen, welche die wichtigsten Plugins und Softwarepakete in einem Paket zusammenfasst, die da wären:

  • Check_MK (umfangreiche Erweiterung auf Nagios)
  • Nagios3
  • NagVis (Visualisierungsplugin)
  • Thruk (WebInterface)
  • Icinga (WebInterface)

Ich habe mich jetzt hier aber für Check_MK und Nagios3 (logischerweise) entschieden.
Die Installation auf einem Grundsystem (hier: Debian 7.0) ist sehr einfach.

Eine statische IP Adresse muss in der network configuration gesetzt sein!

Zu aller erst lädt man sich die Distribution als .deb-Paket auf der offiziellen Website herunter und verbindet sich per SSH auf den Server.
Ich nutze im folgenden für den Datenverkehr auf den Server winSCP, welches auf SSH basiert.
Via winSCP kann man die Datei in das Verzeichnis „/usr/local/“ kopieren – natürlich sind auch andere Pfade möglich – und führt den Befehl zur Installation via gdebi aus:
gdebi /usr/local/omd-0.43_0.43lucid1_amd64.deb
Anschließend folgt man den Anweisungen, setzt die Kennwörter und konfiguriert das System.
Nach der Installation muss man noch überprüfen, ob das Modul „HTTP Redirects/Proxy“ aktiviert ist.
a2enmod proxy_http
Danach aber noch schauen, ob die Einstellungen in /etc/apache2/mods-enabled/proxy.conf richtig und sicher sind (Zugriff von außen blockieren).
Nun können wir die OMD starten, indem wir zuerst den Pfad noch festlegen, hier http://<IP>/Monitoring/:
omd create Monitoring
omd start

Nun navigiert man zu folgender Seite http://<IP>/Monitoring und sieht alle o.g. Funktionen.
Man navigiert nun zu Check_MK und geben die Admin Daten (Standarduser: omdadmin, PW: omd) ein.
Hier kann nun über die Schaltfläche Hosts, neue Hosts anlegen:

wato_hostscheckmk_newhost

 

Anschließend gibt man die Daten des Servers ein und wählt den Agent Type SNMP, wenn man SNMP nutzen möchte. Anschließend definiert man noch die Community und klickt dann auf „Safe & Go To Services“.
Hier bekommt man dann eine Fehlermeldung, dass keine Rückmeldung via SNMP stattfand

snmp_error

 

Man muss nun also noch SNMP auf dem Windows Server aktivieren.
Dazu verbindet man sich via RDP auf den Server, öffnet den Server Manager und navigiert zu Features und Feature hinzufügen.
Anschließend wählt man den SNMP-Dienst aus und lässt diesen installieren.
Nach der Installation öffnen wir die services.msc und navigieren zum Dienst „SNMP-Dienst“. Hier klicken öffnet man nun die Eigenschaften und definiert den Agent, sowie die die Community und akzeptierte Hosts:
snmp_1 snmp_2
Community ist hier: Check_MK (Rechte: NUR LESEN), die IP-Adresse des Nagios3-Servers ist 192.168.178.128, Kontakt und Standort kann man frei wählen.
Anschließend quittieren wir alles mit Übernehmen und OK.

In Check_MK können wir nun nochmals den Service-Scan ausführen und bekommen einige Infos über SNMP zugeschickt
checkmk_services

Jetzt speicher man alles und lässt die Änderungen aktiv werden:

activate_changes

Fertig! Der Server wird nun monitored und über das Dashboard lassen sich nun die Hosts überwachen.

[PHP] Nutzung eines CAPTCHA Bildes

Wir erstellten ein CAPTCHA Bild im vorigen Beitrag.
Nun wollen wir dieses natürlich auch nutzen.
Zuerst benötigen wir die PHP-Datei (im folgenden Beitrag generatecaptcha.php).

Diese binden wir nun ein.
Wir erstellen eine simple HTML-Datei (hier jetzt: index.html).
Da die php-Datei eine Bilddatei ausgibt, können wir das Bild einfach per HTML einbinden.
Dazu benutzen wir den HTML Image-tag:

<img id="captcha" title="Captcha-Code" alt="" src="generatecaptcha.php" /> 

Dem Image weisen wir nun noch eine ID zu. Wieso? Dazu kommen wir noch.
Ein Feld, in welches wir das CAPTCHA eintragen können und einen Button, welcher die Daten absendet, benötigen wir nun noch:


CAPTCHA Abfrage

 <img id="captcha" title="Captcha-Code" alt="" src="generatecaptcha.php" />

<form action="checkcaptcha.php" method="POST"><input type="text" name="captcha_solve" />
 <input type="submit" /></form>

Nun gehen wir noch einmal in die generatecaptcha.php:

Denn wie soll der Server wissen, welches CAPTCHA wir generiert haben bzw. welcher Text der richtige ist?
Wir benötigen sogenannten Session-Variablen.
Diese sehen wie folgt aus:

 $_SESSION['mySessionVar'] 

Es ist zu beachten, dass diese Session-Variablen immer global zugänglich sind. Sie müssen also nicht per Schlüsselwort global definiert werden.
Bevor man solche Variablen jedoch nutzt, muss man eine Session auf dem Server starten, dazu folgende Funktion aufrufen:

session_start();
unset($_SESSION['captcha_spam']);

Hier resette ich gleich die Session-Variable captcha_spam, um sicher zu gehen, dass diese nicht mehr gefüllt ist (unnötig, aber sicher ist sicher.)

Nun erstelle ich das Bild in der generatecaptcha.php und schreibe den Text in die Sessionvar:

 $_SESSION['captcha_spam'] = $text; 

Die generatecaptcha.php ist nun fertig, zurück zur index.html.
Hier verweisen wir im form-Tag auf die checkcaptcha.php.
Diese erstellen wir nun:
Da wir in der HTML-Datei die Daten per POST übergeben und der Name des Feldes captcha_solve lautet, holen wir die wie folgt aus dem HTTP-Paket:

 $_POST['captcha_solve'] 

Die Sessionvar holen wir, wie oben beschrieben, per

 $_SESSION['captcha_spam'] 

Sollten der Text in der $_POST-Var und der in der $_SESSION-Var identisch sein, so hat der User den richtigen CAPTCHA-Text eingegeben:

session_start();
if($_SESSION['captcha_spam'] == $_POST['captcha_solve']){
	echo 'RIGHT';
        //TODO HERE
}else{
	echo 'WRONG';
       //FALSCHER CAPTCHA EINGEBEN
}

Jetzt nochmal zu der vorhin genannten ID des Image-Tags.
Will man jetzt noch, dass das CAPTCHA erneuerbar ist, falls es nicht lesbar sein sollte, so sollte dieser Code genügen. Er erneuert das Bild und gibt es aus (ein simpler refresh):

 <img style="cursor: pointer;" onclick="document.getElementById(&quot;captcha&quot;).src = &quot;generatecaptcha.php?r=&quot;+Math.random();" alt="" src="refresh.png" />

Das Math.Random() war nötig, da der Internet Explorer nur Links aufruft/aktualisert, welche sich ändern.

Den vollständigen Sourcecode findet ihr auf GitHub

[PHP] Erstellen eines CAPTCHA Bildes

Um Spam zu vermeiden, gibt es viele Möglichkeiten.
Eine der bekanntesten ist das Captcha Bild, in welchem ein Text zu finden ist. Diesen muss man dann einfach in ein Textfeld eintragen.
Aber wie erstellt man ein einfaches Captcha Bild? PHP bietet dafür einige Funktionen, die sogenannten Image-Funktionen.

Als erstes braucht man für ein Bild natürlich eine Größe, bestehend aus der vertikalen Höhe und horizontalen Breite des Bilds:

$width = 150;
$height = 60; 

Da ein Captcha eher in die Breite als in die Höhe geht, wird das Bild natürlich breiter als hoch.

Nun müssen wir erstmal ein (leeres) Bild erstellen, bzw. den Speicher dafür bereitstellen.
Dazu bietet PHP die Funktion:

 $image = imagecreatetruecolor ($width, $height);
$colorbg = imagecolorallocate ( $image, 243, 243, 243 );

Diese nimmt die Höhe und Breite des Bildes als Parameter.
Danach setzen wir die Hintergrundfarbe des Bildes (hier ein Grauton).

Nun zeichnen wir einfach mal einen Text in das bestehende Bild:

$color = imagecolorallocate ( $image, 10, 36, 106 );
imagestring ($image, 5,30, 70, 'This is my text', $color);
//$image = Referenz auf das Bild
//5 (zweiter Parameter) = Der Font
//30 (dritter Parameter) = horizontaler Abstand
//70 (vierter Parameter) = vertikaler Abstand 

Das Ergebnis sieht in etwa so aus:
captcha_mytext_easy

Nun müssen wir es den Bots etwas schwerer machen:
Einen einfach String zu zeichnen und diesen auszugeben ist zwar einfach, aber jedes halbwegs gute AntiCaptcha Programm würde das sofort auslesen können, zumal ständig der gleiche dastehen würde.

Deshalb fügen wir dem Bild noch einige Verzerrungen hinzu.
Lasst uns zuerst mit dem Hintergrund anfangen. Diesen zeichne ich jetzt völlig random Pixel für Pixel.
Dazu durchlaufen wir das Bild einfach Pixel für Pixel (ein Bild ist ja nichts weiter als ein zweidimensionales Pixelarray).
Setzen einen Pixel in einer random ausgewählten Farbe und laufen zum nächstenPixel

 for($y = 1; $y <= $height; $y++) {
    for($x= 1; $x <= $width; $x++) {
        $r = mt_rand(0,255);
        $g = mt_rand(0,255);
        $b = mt_rand(0,255);
        $color = imagecolorallocate ($image, $r , $g , $b);
        imagesetpixel($image,$x - 1 , $y - 1, $color);
    }
}

Das Ergebnis könnte dann so aussehen:
captcha_randombg

Nun machen wir es nochmals schwerer:
Wir zeichnen einfach noch ein paar Striche durch’s Bild.

 for($ctr = 1; $ctr <= 4; $ctr++){
	imagesetthickness($Image, rand(0,10));
	imageline($image, rand(0,$width),rand(0,$height),rand(0,$width), rand(0,$height),imagecolorallocate ( $image, rand(0,255), rand(0,255), rand(0,255) ));
}

Hier werden 4 Striche gezeichnet, welche irgendwo durch das Bild verlaufen und eine Dicke von 0 – 10 haben.
Die Farbe ist auch wieder random.
Die Funktion dafür lautet:

imageline($image, $startposx, $startposy, $endposx, $endposy , $farbe);

Ergebnis:
captcha_bgstripes

Nun zum wohl wichtigsten Part des Bildes, dem Text:

 function generateRandomString($length) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $randomString = "";
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, strlen($characters) - 1)];
    }
    return $randomString;
}

Hier generieren wir einen String mit der Länge x (als Parameter).
Dazu erstellen wir solange einen String aus den oben gelisteten chars, bis wir die Länge erreicht haben.
Auch ist es wichtig zu wissen, dass wir einfach auf den einzelnen char im string zugreifen können, da ein string ein Array von chars ist (char[]).
Nun weisen wir diesen Text einer Variable (für die Übersichtlichkeit) und erstellen die Farbe des Strings auf dem Bild:

$text = generateRandomString(5); //Random String mit der Länge 5 erstellen
$color = ImageColorAllocate($image, rand(64,255),  rand(128,255) , rand(128,255)); //Farbe des Strings 

Nun nutzen wir, anders als vorhin, eine andere Funktion, um den Text auf das Bild zu schreiben:

 imagettftext($im, $ttfsize, $angle, $x, $y, $color, $ttf, $text); 

Diese bietet einige Vorteile gegenüber

imagestring ($image, $font, $x , $y , $color);

Zum einen kann man einen Winkel definieren, den Text als schräg stellen, zum anderen kann man einen eigenen Font einbinden.
Der Text wird also noch etwas verzerrter dargstellt, bspw.:
captcha_imagestringttf

Fügt man nun all die Funktionen zusammen, die wir in diesem Post behandelt haben, bekommt man ca. folgenden Code (+ einige Anpassungen).
Das Ergebnis sieht wie folgt aus:
captcha_fullcaptcha

Vollständiger Code

 

Wie man dieses CAPTCHA Bild nun nutzt, steht im nächsten Beitrag.

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #4 – ActiveDirectory

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #1 – Plain
[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #2 – Verschlüsselung und SecureString
[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #3 – MySQL und Hash

ActiveDir_auth
Bei Active Directory (ab Windows Server 2008 Active Directory Domain Services) handelt es sich um einen Verzeichnisdienst (englisch directory service) von Microsoft. Sie speichert Benutzerinformationen(Benutzernamen, Kennwort sowie Gruppen) zentral auf einer Datenbank und stellt diese den Clients im Netzwerk zur Verfügung. Dadurch ist es, im Gegensatz zu einer Arbeitsgruppe, möglich die Benutzerdaten auf jeden Rechner in der Domäne zu verteilen. Somit ist es nicht mehr nötig jedes Profil auf jedem Rechner einzurichten, was eine enorme Zeitersparnis ergibt.

In vielen Firmen wird Active Directory genutzt und eine Authentifizierung ist  mit C# in Verbindung mit dem .NET-Framework schnell geschrieben.
Ich werden hier beschreiben, wie sich ein Benutzer durch Eingabe seines Benutzernamens und Kennworts authentifizieren kann, sowie die Überprüfung, ob sich der Benutzer in einer Gruppe befindet.

Authentifizierung über Benutzernamen und Kennwort:

Zuerst widmen wir uns der Authentifizierung an einer Domäne mittels Benutzername und Passwort.
Mit folgendem Code beziehen wir die aktuell verwendete Domäne aus dem System.

System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName;

Die einfachste Variante ergibt sich mit der Verwendung von PrincipalContext-Klasse, welche sich im Namespace System.DirectoryServices.AccountManagement befindet

public static bool? AuthenticateUserWithAD(string Domain, string Username, string Password)
{
	try
	{
		using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain))
		{
			//Überprüft die übergebenen Anmeldedaten mit dem angegeben ContextType (ApplicationDirectory, Domain oder Machine)
			return pc.ValidateCredentials(Username, Password); //Gibt true oder false zurück
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, ex.Source);
		return null;
	}
}

Wenn man nicht auf PrincipalContext zurückgreifen möchte, so kann man den Benutzer auch mittels DirectoryEntry aus dem Namespace System.DirectoryServices authentifizieren.

Dazu verwendet man folgenden Methode:

private static readonly int ERROR_DS_NO_SUCH_OBJECT = -2147016656; //Fehlercode für LDAP_NO_SUCH_OBJECT 0x80072030 | http://msdn.microsoft.com/en-us/library/aa746528(v=vs.85).aspx
public static DirectoryEntry GetDirectoryEntry(string Domain, string username, string password)
{
	DirectoryEntry dirEntry = new DirectoryEntry();
	dirEntry.Path = "LDAP://" + Domain;
	dirEntry.Username = username;
	dirEntry.Password = password;

	try
	{
		if (dirEntry.NativeObject == null)
		{
			// Kein ActiveDir-Objekt gefunden
			dirEntry = null;
		}
	}
	catch (COMException ex)
	{
		if (ex.ErrorCode == ERROR_DS_NO_SUCH_OBJECT)
		{
			MessageBox.Show("Es wurde kein Objekt, welches mit den Eingaben übereinstimmt gefunden", "Fehler");
		}
		else
		{
			MessageBox.Show(ex.ErrorCode.ToString() + "\r\n" + ex.Message);
		}
		dirEntry.Close();
		dirEntry = null;
	}
	return dirEntry;
}

Um nun einen Wahrheitswert (Boolean) zurückzugeben, muss man prüfen, ob das DirectoryEntry-Objekt gefüllt oder null ist.
Dazu verwendet man folgende Methode, welche wir später dann auch über unsere Klasse aufrufen.

public static bool AuthenticateUser(string Domain, string username, string password)
{
	return GetDirectoryEntry(Domain, username, password) != null;
}

Falls man nur wissen möchte, ob der Benutzer existiert, so kann man ebenfalls PrincipalContext verwenden, jedoch diesmal in Verbindung mit der statischen Methode FindByIdentity der UserPrincipal-Klasse

public static bool CheckIfUserExists(string Domain, string Username)
{
	using (var pc = new PrincipalContext(ContextType.Domain, Domain))
	{
		using (var User = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, Username))
		{
			return User != null;
		}
	}
}

Überprüfung, ob sich Benutzer in Gruppe befindet:

Nun möchte man natürlich nicht immer den Benutzer authentifizieren oder prüfen, ob dieser existiert, sondern spezielle Funktionen einem gewissen Kreis von Personen bereitstellen.

Dazu bietet ActiveDirectory s.g. Gruppen

Eine Gruppe besteht aus Benutzer- und Computerkonten, Kontakten und anderen Gruppen, die als eine Einheit verwaltet werden können. Benutzer und Computer, die zu einer bestimmten Gruppe gehören, werden als Gruppenmitglieder bezeichnet.
Die Verwendung von Gruppen vereinfacht die Verwaltung, indem vielen Konten in einem Schritt einheitliche Berechtigungen und Rechte zugewiesen werden können, anstatt jedem Konto einzeln Berechtigungen und Rechte zuweisen zu müssen.

Für die Prüfung, ob der Benutzer Mitglied der Gruppe ist, gibt es zwei Möglichkeiten.

  1. Unter Verwendung von DirectoryEntry
  2. Benutzung WindowsIdentity

Zuerst unter Verwendung von DirectoryEntry

public static bool? CheckIfUserIsInGroupWithDirectoryEntry(string Domain, string Username, string Password, string Group)
{
	if (Username == "" || Password == "")
	{
		return false;
	}

	try
	{
		DirectoryEntry entry = new DirectoryEntry("LDAP://" + Domain, Username, Password);
		DirectorySearcher mySearcher = new DirectorySearcher(entry);
		mySearcher.Filter = "(&(objectClass=user)(|(cn=" + Username + ")(sAMAccountName=" + Username + ")))";
		SearchResult result = mySearcher.FindOne();

		foreach (string GroupPath in result.Properties["memberOf"])
		{
			if (GroupPath.Contains(Group))
			{
				return true;
			}
		}
	}
	catch (DirectoryServicesCOMException DirSvrComEx)
	{
		MessageBox.Show(DirSvrComEx.Message);
		return null;
	}
	return false;
}

Und die Alternative mit WindowsIdentity:

public static bool? CheckIfUserIsInGroup(string Username, string Groupname)
{
	try
	{
		using (var identity = new WindowsIdentity(Username))
		{
			var principal = new WindowsPrincipal(identity);
			return principal.IsInRole(Groupname);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
		return null;
	}
}

Möchte man den aktuellen Benutzer verwenden (und somit keine Eingabe der Login-Informationen erfordern), so kann man die Methode wie folgt anpassen

public static bool? CheckIfCurrentUserIsInGroup(string Groupname)
{
	try
	{
		IntPtr accountToken = WindowsIdentity.GetCurrent().Token;
		using (var identity = new WindowsIdentity(accountToken))
		{
			var principal = new WindowsPrincipal(identity);
			return principal.IsInRole(Groupname);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
		return null;
	}
}

Zu guter Letzt das Demo-Projekt:

20130811_Verweis-Manager - OleDBTest_000034

ActiveDirectory_Auth.zip

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #3 – MySQL und Hash

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #1 – Plain
[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #2 – Verschlüsselung und SecureString

Eine Authentifizierung mittels MySQL ist ziemlich einfach und schnell geschrieben.

Wir müssen lediglich den .NET Connector für MySQL herunterladen, den Verweis auf die DLL hinzufügen und folgenden Namespace mittels

using MySql.Data.MySqlClient;

einbinden.

Nun benötigen wir ein Objekt der MySQLConnection, welches den ConnectionString beinhaltete und eine Verbindung zum MySQL-Server bereitstellt.

Ich erstelle ein Field vom Typ MySQLConnection (also eine Variable, auf welche alle Methoden innerhalb der Klasse zugreifen können), welches jedoch noch nicht initialisiert wurde.

MySqlConnection connection = null;

In der öffentlichen Methode setupConnection() erstelle ich den ConnectionString (mit den Parametern) und initialisiere den MySQLConnector mit dem ConnectionString.

public void setupConnection(string server, string database, string user, string password)
{
     string myConnectionString = "SERVER=" + server + ";" +
     "DATABASE=" + database + ";" +
     "UID=" + user + ";" +
     "PASSWORD=" + password + ";";
     connection = new MySqlConnection(myConnectionString);
}

Nun erstelle ich eine weitere öffentliche Methode, welche zur Authentifizierung dient.

public bool? Authenticate(string username, string password, string table) //Das Fragezeichen (?) nach einem Typ gibt an, dass der <a href="http://msdn.microsoft.com/de-de/library/1t3y8s4s(v=vs.90).aspx" target="_blank">Typ NULL-Werte zulässt (nullable)</a>
{
	try
	{
		MySqlCommand command = connection.CreateCommand();
		command.CommandText = "SELECT * FROM "+ table +" WHERE Username=?Username"; //Erstellt die parametrisierte Abfrage (Tabellen können nicht parameterisiert werden)
		command.Parameters.AddWithValue("?Username", username); //Weißt dem Parameter einen Wert zu
		MySqlDataReader Reader;
		connection.Open(); //Öffnet die Verbindung zum Server
		Reader = command.ExecuteReader(); Führt die Abfrage auf dem Server aus
		while (Reader.Read()) //Solange noch Daten verfügbar sind
		{
			if (Reader[1].ToString() == username) //Prüfe ob der Wert der ZWEITEN(!) Spalte mit dem übergebenen Usernamen übereinstimmt
			{ //Falls dies der Fall ist,
				if (Reader[2].ToString() == password)//prüfe ob der Wert der dritten Spalte mit dem Passwort übereinstimmt
				{//Sollte dem so sein,
					connection.Close(); //so schließe die Verbindung
					return true; //und geben den Wahrheitswert "wahr" zurück
				}
			}
		}
                //Sollten die Parameter nicht mit den Daten aus der DB übereingestimmt haben
		connection.Close(); //So schließe die Verbindung
		return false; //Und geben "false" zurück
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
		connection.Close();
		return null;
	}
}

Hashing

Nun ist es natürlich äußerst fahrlässig Passwörter klar in der Datenbank zu hinterlegen. Im besten Fall soll es unmöglich sein, das Passwort wieder im Klartext aus der Datenbank zu beziehen und genau hier wird Hashing genutzt. Im Gegensatz zur Verschlüsselung ist es dabei nicht mehr möglich das gehashte Passwort in das ursprüngliche zurückzuwandeln.
Es gibt ziemlich viele Hashfunktionen, zwei der Bekanntesten sind MD5 und SHA.

MD5
SHA-2
Vorteile
Nachteile
Vorteile
Nachteile
Schnell
Nicht kollisionsresistent
Bisher nicht geknackt
Langsamer als MD5
Viele Regenbogentabellen
Gilt als kollisionsresistent

Die Schnelligkeit beider Hashfunktionen ist aber auch eine der größten Schwächen in Bezug auf die Sicherheit des Passworts. So ist heutzutage möglich ein 8-stelliges Passwort, bestehend aus Zahlen und Buchstaben, gehasht mit SHA-2 GPU-gestützt innerhalb eines Tages zu knacken.

MD5

Nun aber zur Implementierung einer MD5-Hashfunktion mit C# und dem .NET-Framework. Dazu verwenden wir die MD5CryptoSerivceProvider-Klasse

public string CreateMD5Hash(string unhashedString)
{
	if (String.IsNullOrEmpty(unhashedString))
		return string.Empty;

	MD5 md5 = new MD5CryptoServiceProvider(); //Alternativ kann man auch MD5 md5 = MD5.Create(); verwenden
        //Create() initalisiert MD5CryptoServiceProvider, da MD5CryptoServiceProvider MD5 implementiert. MD5 ist eine abstrakte Klasse
	byte[] unhashedByteArray = Encoding.Default.GetBytes(unhashedString);
	byte[] result = md5.ComputeHash(unhashedByteArray);

	return System.BitConverter.ToString(result).ToLower().Replace("-", "");
}

SHA-2

Beinahe den selben Code kann man für SHA-2 verwenden. Statt dem MD5CryptoServiceProvider verwenden wir die SHA256CryptoServiceProvider-Klasse

public string CreateSHA2Hash(string unhashedString)
{
	if (String.IsNullOrEmpty(unhashedString))
		return string.Empty;

	SHA256 sha2 = new SHA256CryptoServiceProvider();
	byte[] unhashedByteArray = Encoding.Default.GetBytes(unhashedString);
	byte[] result = sha2.ComputeHash(unhashedByteArray);

	return System.BitConverter.ToString(result).ToLower().Replace("-", "");
}

Und jetzt haben wir eine Todsünde begangen. Das Hashes sind nicht gesalzen und können somit einfach per Rainbow Tables geknackt werden.

Salted MD5

Genau für diesen Zweck wurde HMAC entwickelt. Ein Implementierung in das .NET-Framework erfolgte mit der HMACMD5-Klasse

public string CreateSaltedMD5Hash(string unhashedString, byte[] key)
{
	HMACMD5 saltedMD5 = new HMACMD5();

	saltedMD5.Key = key;

	byte[] unhashedByteArray = Encoding.Default.GetBytes(unhashedString);
	byte[] result = saltedMD5.ComputeHash(unhashedByteArray);

	return System.BitConverter.ToString(result).ToLower().Replace("-", "");
}

Salted SHA-2

Für SHA2 gibt es die HMACSHA256-Klasse

public string CreateSaltedSHA2Hash(string unhashedString, byte[] key)
{
	HMACSHA256 saltedSHA2 = new HMACSHA256();

	saltedSHA2.Key = key;
	byte[] unhashedByteArray = Encoding.Default.GetBytes(unhashedString);
	byte[] result = saltedSHA2.ComputeHash(unhashedByteArray);

	return System.BitConverter.ToString(result).ToLower().Replace("-", "");
}

Wie ich oben bereits erwähnt habe, ist die Geschwindigkeit die größte Schwäche dieser Algorithmen. Darum sollte man MD5 und SHA auch mit Salt nicht zum Hashen von Passwörtern verwenden (MD5 und SHA dienen zur Berechnung einer eindeutigen Prüfsumme, nicht zum Hashen von Passwörtern). Für diesen Zweck wurden langsamere Algorithmen entwickelt, welche generell für das Verschlüsseln von Passwörtern verwendet werden sollten.  Die Bekanntesten sind PBKDF2 und bcrypt.

Zu guter Letzt das Demo-Projekt:

20130811_Verweis-Manager - OleDBTest_000034

MySQL_Hash.zip

[C#/.NET] AES-Ver- und Entschlüsselung mit Rijndael

Bei AES (Advanced Encryption Standard) bzw Rijndael (ausgesprochen: Rain-Dahl [rɛindaːl]) handelt es sich um einen Verschlüsselungsalgorithmus, welcher im Oktober 2000 als Standard festgelegt wurde und den Vorgänger DES (Data Encryption Standard) ablöst.
AES setzt auf eine Blockverschlüsselung, d.h. der Schlüssel wird in mehrer Rundenschlüssel eingeteilt, mit welchem pro Runde in jedem Block die Bytes ersetzt (SubBytes), verschoben (ShiftRows) und vermischt (MixColumns) werden. Es gibt je nach Schlüssellänge 10 (128 bit), 12 (192 bit) oder 14 (256 bit) Runden (R) und eine zusätzliche Endrunde, so werden R+1 Rundenschlüssel benötigt.

Für weitere Informationen empfehle ich:

Nun zur Verschlüsselung eines Strings mit AES in C# zum einen nach AES-Spezifikationen (FIPS-197), zum anderen nach Rijndael (welcher ja zum AES Algorithmus gewählt wurde).
Zwischen AES und Rijndael gibt es, trotz der Wahl von Rijndael zum Algorithmus, Unterschiede, welche eine Kompatibilität untereinander unmöglich machen.

  1. Rijndael erlaubt variable Block- und Schlüsselgrößen (128, 160, 192, 224,  256), wohingegen bei AES eine Blockgröße von 128 bit und eine Schlüsselgröße von 128, 192 oder 256 bit vorgeschrieben sind.
  2. Rijndael erlaubt zusätzlich zu Cipher Block Chaining Mode (CBC) die Verwendung von Cipher Feedback Mode (CFB) als Betriebsmodus, wohingegen AES nur Ersteres unterstützt (bzw. CFB nur mit 128 bit Feedback)

Nun aber zum Code:

AES (FIPS-197)Rijndael
Ich habe folgenden statische Klasse erstellt, mit der wir sämtliche Vorgänge (also Ver- und Entschlüsseln) durchführen können.
Statische AES Klasse

Und so nutzt man die oben erstellte Klasse, in vollem Umfang:

Aufruf/Nutzung der AES-Klasse
Folgende Klasse (Form) wird zur Erstellung des Schlüssels verwendet und über die Methode AES_CreateOwnSaltedKey aufgerufen.
Form zum Erstellen des Schlüssels

Folgende Klasse (Form) bietet die Möglichkeit der Eingabe des Schlüssels, welcher zu Entschlüsselung benötigt wird. Ein Objekt der Klasse wird in der Methode AES_DecryptWithOwnKey erstellt und angezeigt.

Klasse zur Schlüssel-Eingabe zur Entschlüsselung
Inhalt folgt noch. Der Code gleicht jenem der Aes-Klasse jedoch zu 90%

Zu guter Letzt das Demo-Projekt:

20130811_Verweis-Manager - OleDBTest_000034

AES_Rijndael.zip

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #2 – Verschlüsselung und SecureString

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #1 – Plain

Im ersten Teil der Serie beschriebe ich, wie man eine einfach Authentifizierung mit hart-codierten oder im Klartext (Plain) gespeicherten Passwörtern erstellt. Nun möchte man das Passwort natürlich eher selten so offen und unsicher speichern. Im Folgenden geht es um das Verschlüsseln und Entschlüsseln, sowie das Sichern von Texten (Strings)

Verschlüsseln:

Als Paradebeispielt für eine unsichere Verschlüsselung gilt die Caesar-Verschlüsselung (hier ROT13), dabei wird ein Buchstabe einfach durch den 13 Stellen vor- oder hinter liegenden Buchstaben ersetzt.

    static class Rot13
    {
        ///
/// Verschiebe Buchstaben um 13 Stellen
        ///
        public static string Enrypt(string pString)
        {
            string rot13String = "";
            foreach (char c in pOrgString)
            {
                if (c >= 'a' && c <= 'm' || c >= 'A' && c <= 'M')//falls der Buchstabe zwischen a/A und m/M liegt (m/M da es sich um den 13. Buchstaben handelt)
                {
                    rot13String += ((char)((int)c + 13)); //dann füge dem verschlüsselten String den Buchstaben, welcher 13 Stellen hinter dem   originalen Buchstaben steht hinzu
                }
                else if (c >= 'n' && c <= 'z' || c >= 'N' && c <= 'Z') //falls der Buchstabe zwischen n/N und z/Z liegt (n/N da es sich um den 14. Buchstaben also den ersten rotierten handelt)
                {
                    rot13String += ((char)((int)c - 13)); // dann ziehe 13 Stellen ab und füge den neune Char hinzu
                }
                else //Sollte es sich um keinen Buchstaben im lateinischen Alphabet handeln,
                {
                    rot13String += c; //So füge ihn unverändert hinzu
                }
            }
            return rot13String;
        }
    }

Entschlüsselt wird es indem man selben Code nochmals mit dem ROT13-verschlüsselten String als Parameter ausführt

Für die Verschlüsselung eines Strings mit AES (Rijndael-Algorithmus) verweise ich auf: [C#] AES-Ver und Entschlüsselung mit Rijndael

SecureString

Die Idee hinter dem SecureString ist es, die Zeit in der sich das Passwort (bzw. der zu sichernde Text) als Typ String im System befindet auf ein Minimum zu reduzieren. Dazu wird der String in ein Binärobjekt gewandelt und im unmanaged Memory (nicht verwalteter Speicher) gespeichert. Das führt dazu, dass das Objekt nicht durch den Garbage Collector berührt wird. Der SecureString wird fest im RAM verankert, sodass er nicht Teil der virtuellen Speichers ist und so „geswappt“ wird (Swap kann Daten auf der Festplatte hinterlassen, welche auch nach Beenden der Applikation noch vorhanden sind).
Früher oder später wird es nötig sein die Daten als String zwischenzuspeichern (beispielsweise, wenn der User das Passwort eingeben muss oder es wieder angezeigt werden soll).

Nun aber zum Code:
1. Erstellen eines SecureStrings:
Das Erstellen eines SecureString geschieht in der Regel mit der Methode AppendChar(char c). Zusätzlich gibt es noch die Methoden InsertAt(int index, char c), RemoveAt(int index) und SetAt(int index, char c).

        public void ConvertCharArrayToSecureString(char[] CharArray)
        {
            foreach (char c in CharArray) //IEnumerable
            {
                secureStr.AppendChar(c); //Für jeden Buchstaben (char) in dem String(Text in der Textbox), fügen wir den aktuellen Buchstaben dem SecureString an
            }
        }

Sobald das Passwort in den SecureString gewandelt wurde, solltet ihr den String/die TextBox leeren und den SecureString mittels der Methode MakeReadOnly() als schreibgeschützt festlegen.

Rufen wir nun die ToString() des SecureStrings auf und geben den Rückgabewert per MessageBox aus, so wird uns lediglich der Typ des Objekts angezeigt
2013-09-16 13_39_42-

2. Wandeln eines SecureStrings in einen String

Nun möchte man möglicherweise den SecureString doch auslesen. Dies lässt sich mit folgender Methode bewerkstelligen:

        public string ConvertSecureStringToString()
        {
            IntPtr unmanagedString = IntPtr.Zero; //Erstellt einen Zeiger (zeigt bisher auf nichts)
            try
            {
                unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(secureStr); //kopiert den SecureString in den nicht verwalteten Speicher (dient als Puffer)
                return Marshal.PtrToStringUni(unmanagedString); //Reserviert einen verwalteten String (unseren Rückgabetyp) und kopiert eine nicht verwaltete Zeichenfolge (unser SecureString) hinein.
            }
            finally //Der im Finally-Block befindliche Code wirk auf jeden Fall ausgeführt (auch bei einer Exception)!
            {
                Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString); //Gibt den Zeiger, welcher mit SecureStringToGlobalAllocUnicode reserviert wurde, frei (Puffer wird befreit)
            }
        }

Bitte vergesst den Code im finally-Block nicht, denn ohne diesen entsteht eine Möglichkeit das Passwort auszulesen.

In unserem oberen Beispiel muss ich einen kompletten String bzw. ein komplettes CharArray übergeben, sodass es hier noch die Möglichkeit gibt, das komplette Passwort an einen Stück zu auszulesen, um dies noch weiter zu erschweren, kann man wie folgt vorgehen.

Man erstelle eine normale TextBox (in meinem Beispiel SecureString_SecurerTextBox_textBox genannt) und registriere einen Eventhandler für das KeyPress-Event. In der Methode des Events verwendet man dann folgenden Code:

private void SecureString_SecurerTextBox_textBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\b') //Falls die Rücktaste gedrückt wurde,
            {
                DeleteLastCharFromSecureString(); //soll der letzte Buchstabe des SecureStrings gelöscht werden
                if(SecureString_SecurerTextBox_textBox.Text.Length > 0) //Sowie der letzte Buchstabe des Textes in der TextBox, wenn dort noch mindestens ein Buchstabe vorhanden ist
                    SecureString_SecurerTextBox_textBox.Text = SecureString_SecurerTextBox_textBox.Text.Remove(SecureString_SecurerTextBox_textBox.Text.Length - 1, 1);
            }
            else //Sollte die Taste eines anderen Buchstaben gedrückt worden sein,
            {
                AddCharToSecureString(e.KeyChar); //rufe die Methode zum Anfügen des Buchstabens auf
                SecureString_SecurerTextBox_textBox.Text += "*"; //und füge dem Text der TextBox ein Sternchen an
            }
            e.Handled = true; //Bricht das KeyPress-Ereignis ab (es wird kein Buchstabe hinzugefügt)
        }

Folgende Methoden sind zusätzlich erforderlich:

        private void AddCharToSecureString(char c)
        {
            secureStr.AppendChar(c); //Fügt einen Buchstaben an letzter Stelle des SecureStrings an
        }

        private void DeleteCharFromSecureString()
        {
            if(secureStr.Length > 0)
                secureStr.RemoveAt(secureStr.Length - 1); //Löscht den letzten Buchstaben im SecureString
        }

Durch die Vorgehensweise wird der Buchstabe nur als Parameter übergeben und in der TextBox lediglich ein Sternchen (*) hinterlegt. Die Zeit in der sich das Passwort offen/komplett im Speicher liegt, ist stark reduziert. Leider leidet die Benutzerfreundlichkeit und die Übersichtlichkeit des Codes darunter.

Zu guter Letzt das Demo Projekt:

20130811_Verweis-Manager - OleDBTest_000034

ROT13 und SecureString

[C#/.NET] Verschiedene Authentifizierungsmöglichkeiten #1 – Plain

Im Klartext hinterlegte Passwörter sind natürlich in keinster Weise zu empfehlen, da sie leicht ausgelesen werden können. Dennoch sind die für kleinere Programme, welche unkritisch sind manchmal sinnvoll, da solch ein Passwortschutz schnell geschrieben ist und seinen Zweck allemal erfüllt.
Ein mir bekanntes Beispiel wäre ein Textticker, in welchem man den Pfad nur mittels Passworteingabe ändern kann (solch ein Programm ist sehr unkritisch und bedarf keines extra Schutzes. Zudem verhindert solch ein Passwort, dass unbedarfte Benutzer die Einstellungen (un)absichtlich verändern).

Hartcodiert

Ist wohl die einfachste Möglichkeit ein Passwortschutz zu integrieren.
Dazu benötigt man lediglich zwei Strings(Username/Passwort) und legt diese wie gewünscht fest. Bspw. Username:Root, Passwort:tooR
In der Credentialsabfrage vergleichen wir dann einfach die hinterlegten mit jenen, welche eingegeben wurden.
Folgender Code wird dafür benötigt:

        string login_user = "Root"; //Hartcodierter Eintrag des Users
        string login_pw = "tooR"; //Hartcodierter Eintrag für das Passwort

        public Form1()
        {
            InitializeComponent();
            auth_button.Click += new EventHandler(auth_button_Click); //Eventhandler für den Click auf den Authentifizierungbutton
        }

        private void auth_button_Click(object sender, EventArgs e)
        {
            if (authenticate() == true) //Aufruf der Methode zum Vergleich der eingetragenen Werte mit den hinterlegten
                MessageBox.Show("Erfolgreich authentifiziert");
            else
                MessageBox.Show("Anmeldung war nicht erfolgreich");
        }

        private bool authenticate() //Rückgabetyp boolean
        {
            if (user_textBox.Text == login_user && pw_textBox.Text == login_pw) //Überprüfung der Gleichheit der Werte
                return true; //Gibt true zurück, falls die Werte gleich sind
            else //Ansonsten false
                return false;
        }

In Datei klar gespeichert

Ist eigentlich das selbe Spiel, jedoch weisen wir den Strings nicht „sofort“ einen Werte zu, sondern lesen sie zuerst aus einer Datei aus und dann weisen wir sie zu.
Da sich dieser Variante nur durch das Auslesen unterscheidet, werde ich nur eben jenes beschreiben.

Zuerst fügen wir den Namespace System.IO; mittels:

using System.IO;

hinzu und rufen folgende Methode beim Starten des Programms(im Konstruktor der Main-Klasse) auf

        private void GetLoginValues()
        {
            int lineCount = 1; //Wird benötigt, um die aktuelle Zeile zu ermitteln
            StreamReader credValueReader = new StreamReader("credValue.txt"); //Hier wird das Objekt angelegt und der Pfad festgelegt
            //in diesem Fall muss die Datei im Verzeichnis des Programmes liegen
            while (!credValueReader.EndOfStream) //Solange nicht das Ende der Datei erreicht wurde
            {
                switch (lineCount)
                {
                    case 1:
                        login_user = credValueReader.ReadLine(); //Weißt dem Userstring den Wert der ersten Zeile zu
                        break;
                    case 2:
                        login_pw = credValueReader.ReadLine(); //Weißt dem Passwortstring den Wert aus der nächsten Zeile(2) zu
                        break;
                }
                lineCount++; //erhöht den Zeilenzähler
            }
            credValueReader.Close(); //Löst den Zugriff auf die Datei(ist sehr wichtig!)
        }

Cross Site Scripting #2 – Bekannte Beispiele

Um sich die Wirkung und das Ausmaß eines XSS-Angriffs auszumalen, möchte ich einige Beispiele aus den letzten Jahren einbringen:

Im Jahre 2010 verschafften sich Cracker (im Sinne von destruktiven Hackern) Zugang zu verschiedenen Servern der Apache Foundation [1].
Dieser Angriff wurde durch ein Ticket mit manipulierte Webadresse durchgeführt.
Durch dieses Ticket sollte die Cookie-Session des angemeldeten Users gestohlen werden (Session stealing).

Durch diesen und einen zweiten, parallelen Bruteforce-Angriff erlangten die Angreifer dann die Kontrolle über den JIRA-Server, wo sie ungehindert Dateien durchstöbern und herunterladen konnten. Zusätzlich wurden Passworteingaben abgefangen.

Die Folgen waren, dass die Server für einige Stunden offline waren, die Passwörter geändert, die Sicherheitslücken gestopft und der Hauptserver komplett ausgetauscht werden musste.


Im Oktober 2005 wurde ein XSS-Wurm in MySpace entdeckt, welcher innerhalb von 20 Stunden über eine Millionen Profile befiel.

Der Entwickler des Wurms Samy Kamkar, dessen Vorname auf der Bezeichner des Wurms ist, zielte darauf ab, die Freundesliste des Entwicklers zu füllen.
Sobald jemand auf ein „infiziertes“ Profil klickte, lud das JavaScript-Script und addete Samy Kamkars Account zur Friendlist hinzu.
Zudem erschien ein „Status“-Post mit dem Inhalt: „but most of all, Samy is my hero“.

Nachdem MySpace den Wurm entdeckte, schlossen sie die Lücke und verklagten Kamkar.
Der Entwickler wurde zu drei Jahren Computerabstinenz, 90 Tagen gemeinnütziger Arbeit und einer Entschädigung in unbekannter Höhe verurteilt.
Der Wurm ist und bleibt trotzdem der sich am schnellsten verbreitende Wurm in der Computergeschichte.


© 2018 Abou Chleih. Alle Rechte vorbehalten.

Thema von Anders Norén.