Will man eine .NET Webapplikation mit einer neueren Version ersetzen, sollte man diese offline nehmen. Ansonsten werden die HTTP-Anfragen munter weiter bedient und dies führt dann oft zu unschönen Fehlermeldungen.
Eine elegante Methode ist die Datei „App_Offline.htm“. Wird ins Root-Verzeichnis der .NET Webapplikation eine Datei mit dem Namen „App_Offline.htm“ kopiert, werden sämtliche neuen HTTP-Anfragen mit dem Inhalt dieser Datei beantwortet und zugleich wird die Webapplikation sowie deren „Application Domain“ beendet.
Nach den Wartungsarbeiten oder dem Updaten wird die Datei „App_Offline.htm“ einfach wieder gelöscht. Ist die Datei nicht mehr vorhanden, werden sämtliche Anfragen wieder wie gewohnt beantwortet.
Schritte um eine .NET Webapplikation zu aktualisieren:
Das Open Web Application Security Project (OWASP) ist eine Non-Profit-Organisation mit dem Ziel, die Sicherheit von Anwendungen und Diensten im World Wide Web zu verbessern. [1]
Dazu veröffentlicht OWASP eine Rangliste mit den zehn schwerwiegendsten Sicherheitsschwachstellen von Webanwendungen. Auf Platz 1 ist „Injection“, das Einschleusen von fremdem Code.
Passend zum Thema Injection musste ich erst kürzlich feststellen, dass es immer noch Webanwendungen gibt bei denen SQL-Injection ohne grosse mühe funktioniert.
SQL-Injection
Das erste Mal aufmerksam auf das Thema SQL-Injection wurde ich im Jahr 2000. In dieser Zeit waren sehr viele Webanwendungen davon betroffen. Auch bekannte online Shops waren vor dieser Problematik nicht gefeit. Dies war auch die Zeit des „Quick-Login“, oft genügte die Eingabe des Prozentzeichens (%) bei Passwort und Benutzername und man wurde mit dem erst besten Login angemeldet.
Inzwischen sollte eigentlich jeder Programmierer, der mit Datenbanken arbeitet, wissen was SQL-Injection ist und was es für geeigneten Gegenmassnahmen gibt. Sei dies die Verwendung von Stored Procedures, Prepared Statements oder dem Maskieren von Metazeichen. Auch sollte die Webanwendung Benutzereingaben genau prüfen und nur korrekte Werte akzeptieren. Hat man keinen Einfluss auf die Webanwendung, kann auch ein WAF (Web Application Firewall) eingesetzt werden.
Aber leider musste ich schon des Öfteren feststellen, dass es auch im 2013 Programmierer gibt, die sich nicht mit diesem Thema beschäftigen. Im Glauben ein Projekt schneller fertigzustellen wird dies einfach ignoriert. Auch Begründungen wie: „Diese Anwendung ist nur für den internen Gebrauch…“ oder „Unsere Daten sind doch nicht interessant für einen Hacker…“.
Und was passiert bei einem „DROP TABLE;“ oder „WAITFOR DELAY ‚0:30:00‘–“ usw.?
Ich glaube diese Programmierer haben noch nicht bemerkt, das Programmieren viel mehr Spass macht, wenn man versucht „sicheren“ und „sauberen“ Code zu schreiben.
COM-Komponenten können sowohl in Form von Laufzeitmodulen (DLL’s) als auch als ausführbare Programme implementiert werden. Damit die Funktionen der Komponente für den COM-Client sichtbar sind, müssen folgende Punkte befolgt werden:
Die Klasse muss „public“ sein
Properties, Methoden und Events müssen „public“ sein
Properties und Methoden müssen im Klassen-Interface definiert sein
Events müssen im Event-Interface definiert sein
Erstellen:
Zuerst muss das COM-Interface definiert werden (Definition der Schnittstelle)
Als zweiter Schritt muss die Klasse implementiert werden. Die Klasse muss den Namespace „InteropServices“ importieren (using System.Runtime.InteropServices;)
In den Projekteinstellungen unter „Erstellen“ muss die Option „Für COM-Interop registrieren“ sowie unter „Anwendung->Assemblyinformationen“ die Option „Assembly COM-sichtbar machen“ aktiviert werden
Am Schluss muss das Assembly noch signiert werden. Dies kann in den Einstellungen unter „Signierung“ gemacht werden. Dazu wird eine neue Schlüsseldatei erstellt
COM-Komponente registrieren:
Um die COM-Komponente auf einem anderen Gerät zu verwenden muss diese zuerst registriert werden. Dazu wird das Programm „RegAsm.exe“ verwendet.
Mit Hilfe von sogenannten alternativen Datenströme (ADS), können im Dateisystem NTFS, zusätzliche Informationen / Daten zu den Hauptdaten gespeichert werden. Da Windows die alternativen Datenströme nicht standardmässig anzeigt, werden diese auch verwendet um Malware oder Daten zu verstecken.
Erstellen eines ADS
Ein ADS wird mit einem Doppelpunkt an eine Datei angehängt: „Dateiname:AD-Streamname“
Anhängen eines Textes:
echo "Ich bin nicht sofort Sichtbar" > normaler.txt:unsichtbar.txt
Schauen wir mit dem Windows-Explorer das Verzeichnis an, in dem die Datei liegt, können wir nur die Datei „normaler.txt“ sehen. Diese Datei scheint jedoch keine Daten zu enthalten, da sie 0 KB gross ist.
Anhängen einer Datei:
Es können beliebige Binär- Asciidateien gespeichert werden. In diesem Beispiel wird die „netcat.exe“ angehängt.
type nc.exe > normaler.txt:security.exe
Ausführen einer Datei im ADS
In den meisten Beispielen, im Internet, wird die angehängte Exe-Datei direkt mit dem Befehl start ausgeführt:
start c:\tmp\ads\normaler.txt:security.exe
Leider funktioniert dies bei mir unter Windows 7 nicht. Wird aber ein Systemlink auf diesen ADS gemacht, kann Netcat ausgeführt werden. Daher erstelle ich zuerst ein Systemlink auf diesen ADS:
Jetzt kann mit dem Befehl start Netcat ausgeführt werden:
start c:\tmp\ads\happy.exe
Anzeigen von AD-Streams
Windows 7 / Windows Vista:
Ab Windows Vista wurde der Befehl dir mit dem Parameter „/R“ erweitert. Dieser listet alle dazugehörigen AD-Streams einer Datei auf.
dir /R
Windows XP / Windows 2000:
Bei den früheren Windows Versionen musste ein zusätzliches Tool verwendet werden um die AD-Streams anzuzeigen. Dabei kann das Command-Line Tool Streams 1.56 verwendet werden.
Eine Ubuntu Server Installation kann mit dem „do-release-upgrade utility“ auf die nächsthöhere Version aktualisiert werden. Die Verwendung dieses Utility wird in der Ubuntu Dokumentation als die empfohlene Methode beschrieben.
Bevor man die Aktualisierung auf einen neueren Release beginnt, sollte man sicherstellen, dass das System auf dem neusten Stand ist.
Dazu holt man zuerst die neusten Paketinformationen:
>sudo apt-get update
Als Nächstes werden die Pakete aktualisiert: >sudo apt-get dist-upgrade
Nun kann mit dem Release-Upgrade begonnen werden:
>do-release-upgrade
Jetzt folgt man nur noch den Anweisungen auf dem Bildschirm. Werden Konfigurationdateien geändert sollte man die Änderungen gut prüfen oder eben die eigenen behalten.
04.02.2014: Es scheint das Load Balancing mit aktiviertem Squid bei vielen gar nicht mehr funktioniert. Die Failover-Konfiguration funktioniert jedoch ohne Probleme mit dem Squid Proxy. Siehe pfSense Forum
Um einen Standort mit redundanter Internetanbindung auszurüsten, eignet sich die Firewall-Distribution pfSense 2.x perfekt. Die Distribution unterstützt standardmässig Load Balancing und Failover mit mehreren WAN-Anschlüssen. Mehr dazu findet man im pfSense Wiki unter Multi-WAN.
Wird der Web Proxy Squid nicht auf dem gleichen Host betrieben, sondern hinter der Firewall, bietet auch diese Konfiguration keine grossen Probleme. Den das LoadBalancing und Failover funktioniert gut.
In diesem Beitrag werde ich auf die Konfiguration eingehen, bei der Squid auf dem selben Host läuft. Im Web findet man sehr viele Beiträge zu diesem Thema. Es scheint für diese Lösung keine Standardkonfiguration zu geben. Je nachdem ob man zusätzliche Pakete installiert hat, kann die Konfiguration abweichen. Was bei einer Installation läuft, muss nicht zwangsläufig bei einer anderen funktionieren. Ich werde hier einfach meine Erfahrungen und Ergänzungen vorstellen, die ich beim Sichten der verschiedenen Anleitungen gemacht habe, bis ich eine funktionierende Konfiguration hatte. Dieser Beitrag soll kein vollständiges „Howto“ sein, sondern nur die wichtigen Punkte hervorheben, die mir geholfen haben.
Schritt 1: Multi-WAN
Als erster Schritt muss die Multi-WAN Konfiguration, wie in der Anleitung beschrieben, erstellt und ohne Proxy getestet werden. Dabei sollten zusätzlich folgende wichtige Punkte beachtet werden:
Gateways Einstellungen
Anmerkung zur pfSense Version 2.0.3:
Unter „System->Routing->Gateways“ sollte kein Gateway als „Default-Gateway“ markiert sein. Es scheint als verwendet pfSense standardmässig den Gateway des WAN-Interfaces als „Default-Gateway“. (Beim Versuch das WAN2-Interface als „Default-Gateway“ zu definieren funktionierte die Abfrage über Squid nicht mehr.)
Anmerkung zur pfSense Version 2.1:
Mit der Version 2.1 scheint das Definieren eines „Default-Gateway“ auch bei mir wieder zu Funktionieren. Daher sollte unter „System->Routing->Gateways“ ein Gateway als „Default-Gateway“ markiert werden.
Unter „Diagnostics->Routes“ kann die Routingtabelle eingesehen werden und dort findet man auch den Eintrag des „Default-Gateway“.
Ein weiterer wichtiger Punkt ist die Einstellung „Allow default gateway switching“ welche aktiviert werden sollte. So wird beim Ausfall des WAN-Interfaces automatisch ein anderes Interface als „Default-Gateway“ eingesetzt. Dies kann unter „System->Advanced->Miscellaneous“ geändert werden.
DNS-Server Einstellungen
Bei den DNS-Server Einstellungen sollten pro Gateway mindestens ein DNS-Server eingetragen werden. Was auch nicht schaden kann, ist ein öffentlicher DNS-Server der über alle Interfaces erreichbar ist. Hier in diesem Beispiel wurde zusätzlich ein DNS-Server von Google angegeben. Eintragen kann man diese unter „System->General Setup“.
Firewall Rules
Zusätzlich zu den Firewall-Regeln die den Datenverkehr über den gewünschten Gateway oder die Gatewaygruppe leiten, muss noch eine eigene Regel für den DNS-Traffic des Squid Proxy erstellt werden.
Wurde die Multi-WAN Konfiguration erfolgreich getestet (Unterbruch simulieren der verschiedenen WAN’s, surfen funktioniert noch), kann mit der Konfiguration des Proxy-Server’s angefangen werden.
Dazu werden unter „Services->Proxy Server“ die Interfaces ausgewählt bei denen die HTTP-Abfrage über den Proxy Server laufen sollen. Anmerkung: In manchen Anleitungen steht, man soll das „Loopback“ Interface auch auswählen. Dies wird jedoch in dieser Konfiguration nicht verwendet, sondern führt nur zu Fehler bei den Proxy abfragen.
Als letzte Einstellung muss unter „Custom Options“ die Zeile „tcp_outgoing_address 127.0.0.1;“ eingetragen werden. Mit diesem Befehl schickt Squid sämtliche TCP-Anfragen wieder zurück an pfSense, wo dann die Pakete an den richtigen Gateway verschickt werden.
Zum Schluss
Nach diesen Einstellungen sollten nun alle HTTP-Anfragen die über den Squid Web Proxy gehen, auch vom Load Balancing und Failover Mechanismus von pfSense profitieren. Diese Konfiguration habe ich nun seit einiger Zeit im Einsatz und es scheint gut zu funktionieren.
Bei Webapplikationen welche die Anmeldeinformationen an eine IP knüpfen, kann das Load Balancing zu Probleme führen. Nach dem Anmelden wird man kurze Zeit später wieder abgemeldet. Das ist immer dann der Fall, wenn die Verbindung neu über einen anderen Gateway geht. Um diese Problematik zu minimieren, kann man eigene Floating-Rules definieren oder man verwendet die Option „Use sticky connections“. Diese Option sorgt dafür, dass eine bestehende Verbindung immer über denselben Gateway geleitet wird. Aktivieren kann man sie unter „System->Advanced->Miscellaneous“. (Siehe Screenshot „Load Balancing Einstellungen“)
Hat man den Verdacht das etwas mit dem Arbeitsspeicher nicht mehr in Ordnung ist, da der PC plötzlich langsamer ist oder verschiedene Programme einfach nicht mehr ohne Probleme laufen, bietet Windows 7 ein kleines Tool, um den Arbeitsspeicher zu testen.
Am einfachsten kann dieses Tool über die Suchmaske im Startmenü ausgeführt werden. Dazu sucht man nach „arbeitsspeich“.
Wurde das Tool gestartet, legt man fest, wann die Arbeitsspeicher überprüfung ausgeführt werden soll, sofort oder erst beim nächsten Starten des Computers. Wählt man sofort, wird der Computer für die Ausführung neu gestartet.
Alternative Memtest86+
Möchte man etwas ausführlichere Tests machen, kann dafür das gratis Programm Memtest86+ verwendet werden. Memtest86+ ist ein Fork des schon länger existierenden Memtest86. Auf der Downloadseite von Memtest86+ findet man vorgefertigte ISO-Images für Boot CD’s und ein Auto-Installer für USB-Sticks.
Beide Programme (Memtest86+ und Memtest86) und viele weitere nützliche Tools findet man auch auf der „Ultimate Boot CD“.
In den vorhergehenden Beiträgen Teil 1 & Teil 2 haben wir eine Adressklasse erstellt um B2B- und B2C-Adressen zu speichern. Nun werden wir diese Adressklasse erweitern, damit auch Kommunikationsdetails (Telefon, Email, usw.) gespeichert werden können.
Als Erstes erstellen wir die Klasse „CommunicationDetail“ um damit die Kommunikationsdetails zu speichern:
namespace XML_Serialisierung
{
public class CommunicationDetail
{
public enum CommunicationDetailType
{
PRIVATE_MOBILE,
BUSINESS_MOBILE,
PRIVATE_PHONE,
BUSINESS_PHONE,
PRIVATE_FAX,
BUSINESS_FAX,
PRIVATE_EMAIL,
BUSINESS_EMAIL,
SKYPE_MESSANGER
}
[XmlAttribute("Typ")]
public CommunicationDetailType DetailType { get; set; }
[XmlElement("Wert")]
public string Value { get; set; }
public CommunicationDetail()
{
}
public CommunicationDetail(CommunicationDetailType detailType, string value)
{
this.Value = value;
this.DetailType = detailType;
}
}
}
Nun werden wir die Address-Klasse mit einer Liste vom Typ „CommunicationDetail“ erweitern. Damit beim Serialisieren der Liste, die einzelnen Details auch richtig im XML-Abgebildet werden, verwenden wir das Attribut [XmlArray(„“)] um den Namen des Hauptelementes festzulegen und [XmlArrayItem(„“)] um damit den Namen der Unterknoten zu bestimmen.
namespace XML_Serialisierung
{
public class Address
{
[XmlAttribute("Adressenummer")]
public int AddressNumber { get; set; }
[XmlElement("Strasse")]
public string Street { get; set; }
[XmlElement("Hausnumer")]
public string HouseNumber { get; set; }
[XmlElement("Land")]
public string Country { get; set; }
[XmlElement("Postleitzahl")]
public string ZIP { get; set; }
[XmlElement("Stadt")]
public string City { get; set; }
[XmlArray("Kommunikation")]
[XmlArrayItem("Kommunikationsdetail")]
public List CommunicationNumbers;
public Address()
{
this.CommunicationNumbers = new List();
}
}
}
Klasse mit dem XmlSerializer serialisieren
Nun haben wir die Möglichkeit die Adresse mit Kommunikationsdetails zu erweitern:
/*b2c addresse*/
AddressB2C addressB2C_2 = new AddressB2C();
addressB2C_2.AddressNumber = 123456789;
addressB2C_2.Firstname = "Max";
addressB2C_2.Lastname = "Mustermann";
addressB2C_2.Street = "Musterstrasse";
addressB2C_2.HouseNumber = "77a";
addressB2C_2.City = "Musterhausen";
addressB2C_2.ZIP = "1234";
addressB2C_2.Country = "In einem fernen Land";
CommunicationDetail mobile = new CommunicationDetail(CommunicationDetail.CommunicationDetailType.PRIVATE_MOBILE, "079 123 45 67");
addressB2C_2.CommunicationNumbers.Add(mobile);
CommunicationDetail email = new CommunicationDetail(CommunicationDetail.CommunicationDetailType.PRIVATE_EMAIL, "meine@email.ch");
addressB2C_2.CommunicationNumbers.Add(email);
XmlSerializer xmls3 = new XmlSerializer(typeof(AddressB2C));
StreamWriter mwriter3 = new StreamWriter("b2cAddressWithNumbers.xml");
xmls3.Serialize(mwriter3, addressB2C_2);
mwriter3.Close();
Und als Ergebniss erhalten wir folgendes XML:
079 123 45 67meine@email.chMusterstrasse77aIn einem fernen Land1234MusterhausenMaxMustermann
Um bei allen Rechnern in einem Netzwerk die Zeit synchron zu halten, benötigt man einen Zeitserver. Bei der Firewall-Distribution pfSense ist standardmässig OpenNTPD installiert. OpenNTPD ist eine einfache Implementierung des „Network Time Protocol“, welche den einfachen Betrieb eines NTP-Client und NTP-Server ermöglicht.
In diesem Beitrag wird der OpenNTPD von pfSense als Netzwerkzeitserver verwendet. Die Firewall bezieht die Zeit bei einem offiziellen Zeitserver und beantwortet NTP-Anfragen von den verschiedenen lokalen Netzwerken. Diese Konfiguration vereinfacht die Verwaltung und minimiert die NTP-Abfragen gegen Aussen.
1. Konfigurieren des pfSense NTP-Client
Damit auf der Firewall die aktuelle Zeit eines offiziellen Zeitservers verwendet wird, muss dieser im Menü „System -> General Setup“ konfiguriert werden. Hier in diesem Beispiel wird der offizielle Zeitserver für die Schweiz „ntp.metas.ch“ verwendet. Wichtig: Die richtige Zeitzone muss eingestellt sein!
2. Konfigurieren des pfSense NTP-Server
Damit NTP-Anfragen auch beantwortet werden, muss der OpenNTP-Deamon unter „Services -> OpenNTPD“ aktiviert werden. Als Nächstes wird noch angegeben an welchen Interfaces (Netzwerke) eine NTP-Abfrage erlaubt wird.
Im oberen Beispiel sind NTP-Anfragen aus den Netzwerken LAN, DMZ und Gray1 erlaubt. NTP-Anfragen aus dem WAN-Interface werden ignoriert.
3. Konfigurieren der Server und Clients
Zum Schluss müssen die verschiedenen Server und Clients so konfiguriert werden, damit die Firewall als Zeitserver verwendet wird.
Im vorhergehenden Beitrag haben wir aus einer einfachen Adressklasse eine XML-Datei generiert. In diesem Beitrag werden wir eine Kundenklasse erstellen, die entweder eine B2B- oder B2C-Adresse enthält und dann in eine XML-Datei geschrieben wird. Dazu erstellen wir zuerst eine Kundenklasse:
using System.Xml.Serialization;
namespace XML_Serialisierung
{
[XmlRoot("Kunde")]
public class Customer
{
[XmlAttribute("Kundennummer")]
public int CustomerID { get; set; }
[XmlElement("Adresse")]
public Address CustomerAddress { get; set; }
public Customer(bool b2c)
{
if (b2c == true)
{
this.CustomerAddress = new AddressB2C();
}
else
{
this.CustomerAddress = new AddressB2B();
}
}
/*default constructor*/
public Customer() {
this.CustomerAddress = new AddressB2C();
}
}
}
Wird jetzt versucht diese Klasse zu serialiseren wird eine Exception geworfen:
System.InvalidOperationException wurde nicht behandelt.
Message="Beim Generieren des XML-Dokuments ist ein Fehler aufgetreten."
Source="System.Xml"
StackTrace:
bei System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
bei System.Xml.Serialization.XmlSerializer.Serialize(TextWriter textWriter, Object o)
bei XML_Serialisierung.Program.Main(String[] args) in D:\data\Projects\intern\blog\src\XML-Serialisierung\Program.cs:Zeile 59.
bei System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Threading.ThreadHelper.ThreadStart()
InnerException: System.InvalidOperationException
Message="Der Typ XML_Serialisierung.AddressB2B wurde nicht erwartet. Verwenden Sie das XmlInclude- oder das SoapInclude-Attribut, um Typen anzugeben, die nicht statisch sind."
Source="uwky4mcz"
StackTrace:
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCustomer.Write2_Address(String n, String ns, Address o, Boolean isNullable, Boolean needType)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCustomer.Write3_Customer(String n, String ns, Customer o, Boolean isNullable, Boolean needType)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterCustomer.Write4_Kunde(Object o)
Da das Property „CustomerAddress“ vom Typ Address ist, beim serialisieren jedoch als Typ AddressB2B oder AddressB2C erscheint, weiss der XmlSerializer nicht, wie er diesen Datentyp verarbeiten soll. Damit der XmlSerializer damit korrekt umgehen kann, müssen wir ihn mit den möglichen Datentypen bekannt machen. Dazu wird das Attribut [XmlInclude(„“)] verwendet.
using System.Xml.Serialization;
namespace XML_Serialisierung
{
[XmlInclude(typeof(AddressB2C))]
[XmlInclude(typeof(AddressB2B))]
[XmlRoot("Kunde")]
public class Customer
{
[XmlAttribute("Kundennummer")]
public int CustomerID { get; set; }
[XmlElement("Adresse")]
public Address CustomerAddress { get; set; }
public Customer(bool b2c)
{
if (b2c == true)
{
this.CustomerAddress = new AddressB2C();
}
else
{
this.CustomerAddress = new AddressB2B();
}
}
/*default constructor*/
public Customer() {
this.CustomerAddress = new AddressB2B();
}
}
}
Klasse mit dem XmlSerializer serialisieren
Starten wir nun nochmals einen Versuch, wird das Serialsieren funktionieren.
/*create a customer object*/
Customer customer = new Customer(false);
customer.CustomerID = 175;
AddressB2B b2bAddress = ((AddressB2B)(customer.CustomerAddress));
b2bAddress.AddressNumber = 123456789;
b2bAddress.CompanyName = "Muster AG";
b2bAddress.Division = "Musterkreation";
b2bAddress.Street = "Musterstrasse";
b2bAddress.HouseNumber = "77a";
b2bAddress.City = "Musterhausen";
b2bAddress.ZIP = "1234";
b2bAddress.Country = "In einem Land";
XmlSerializer xmls2 = new XmlSerializer(typeof(Customer));
StreamWriter mwriter2 = new StreamWriter("CustomerWithb2bAddress.xml");
xmls2.Serialize(mwriter2, customer);
mwriter.Close();
Und als Ergebnis erhalten wir:
Musterstrasse 77a In einem Land 1234 Musterhausen Muster AG Musterkreation