Arduino Webserver mit Passwort schützen

Hallo,
Mein Arduino Webserver läuft nun, nur wie mache ich einen Login Dialog. (mit User und Pass)

Ich habe mich natürlich vorher informiert, vor allem da ich in Sachen HTML,Java… alles andere als ein Profi bin.

So richtig Sinn macht sowas wohl nur mit PHP.
Aber PHP auf dem Atmel ? :stuck_out_tongue:

Brauche da einen anderen Ansatz, nur wie ?

HT-Access... HTML Basic Authentication z.B. http://en.wikipedia.org/wiki/Basic_access_authentication http://de.wikipedia.org/wiki/HTTP-Authentifizierung

Sorry für mein Unverständnis, aber das läuft doch auch wieder auf PhP hinaus.

[u]Ich verstehe das so, wenn wenn was falsch ist bitte korrigieren:[/u] Php Code wird im Gegensatz zu Java auf dem Server ausgeführt, und das Ergebnis wird zum Client geschickt. Server ist dem Fall der Atmel, somit müsste dort ein Php Interpreter laufen.

Aber es muss doch irgendwie zu machen sein, siehe den Link aus dem englischen Bereich http://forum.arduino.cc/index.php?topic=69457.15

rudirabbit: Sorry für mein Unverständnis, aber das läuft doch auch wieder auf PhP hinaus.

Oh no, mit PHP hat das gar nichts zu tun. HTTP mit Basic Authentification gab es schon, da war PHP noch gar nicht erfunden.

Für eine Basic-Authentifikation muss auf dem Server nur der Username und das verschlüsselte Passwort gespeichert sein.

Der Client muss den Usernamen und das verschlüsselte Passwort senden.

Und wenn eine Anfrage an den Server kommt, gibt der Server(Arduino) die Daten nur heraus, wenn das verschlüsselte Passwort korrekt ist.

Leider ist die Sicherheit in dem Fall nicht besonders hoch, denn der Client muß alles per HTTP senden und darf das sichere Protokoll HTTPS nicht verwenden. Der Speicher reicht auf der Arduino-Plattform nicht aus, um einen verschlüsselten HTTPS-Server zu betreiben. Deshalb kann jeder Angreifer, der in der Lage ist, bei einer unverschlüsselten HTTP-Verbindung den Datenverkehr zwischen Server und Client mitzuschneiden, sowohl den Usernamen als auch das verschlüsselte Passwort aus dem Datenstrom abgreifen, und das ist ja bereits ausreichend, um vom Server Anfragen beantwortet zu bekommen.

Schlimmer wird es nur, wenn ein "normales Wort aus dem Lexikon" oder ein Standardpasswort als Passwort verwendet wird: In dem Fall könnte ein Hacker mit dem verschlüsselten Passwort eine Wörterbuchattacke starten, und wenn er darüber das Passwort bekommt, dann kann er bei vielen Usern anschließend auf alle mögliche Zugreifen: E-Mail, Facebook, Skype, PayPal, das Bankkonto und was weiß ich: Viele User neigen nämlich dazu, überall dasselbe Passwort zu verwenden, so dass wenn ein Hacker ein Passwort eines Users entschlüsselt bekommt, hat er danach nicht nur Zugriff auf das ausgespähte und gehackte System, sondern oft auch auf alle möglichen anderen Dienste des Users.

Wenn also unverschlüsseltes HTTP mit Basic-Authentification genutzt wird (und das hat mit PHP null die Bohne zu tun): Dafür auf jeden Fall ein EINMALIGES Passwort verwenden und nichts recyceltes.

rudirabbit: Sorry für mein Unverständnis, aber das läuft doch auch wieder auf PhP hinaus.

Was du als PhP-Code auf dem Webserver schreiben würdest, musst du in C/C++ auf dem Arduino schreiben.

Durch die stark begrenzten Resourcen auf dem Atmega, ist der Einsatz als Webserver ebenfals stark limitiert und eher als "einfach mal ausprobieren und sehen, dass es geht" gedacht. Produktiv würde ich ihn dafür nicht einsetzen. Nimm lieber mindestens einen RaspberryPie und schreibe in deinem gewünschten PHP :)

Nighti:
Was du als PhP-Code auf dem Webserver schreiben würdest, musst du in C/C++ auf dem Arduino schreiben.

OMG!

Nochmal: Authentifikation hat mit PHP rein gar nichts zu tun, nur mit HTTP und HTML!

Entweder der Browser sendet die Authentifikation zu einem Server mit, weil er dort bereits eingeloggt ist.

Oder der Browser bekommt vom Server eine Error-401 Seite (“Authentification required”) mit ganz normalem HTML-Code.

Wenn der Browser statt der angeforderten Seite die Error-401 Antwort bekommt, dann öffnet der Browser von ganz alleine (Standardverhalten jedes Browsers) ein Eingabefenster zur Eingabe von Benutzername und Passwort. Dort kann der Benutzer dann Benutzername und Passwort eingeben, und der Browser sendet die Werte in verschlüsselter Form nochmal als Anforderung an den Server. Entweder ist die “Anforderung mit Authentifikation” dann korrekt und der Browser bekommt die angeforderte Seite. Oder er bekommt wieder die Eingabeseite zur Eingabe von Benutzername und Passwort.

Bricht der Benutzer die Eingabe von Benutzername und Passwort ab, bekommt er die Error-401 Seite direkt zu sehen.

Und wenn das Login korrekt war, dann fordert der Browser danach bis zum Ende der Browser-Session dieselbe und auch alle anderen Seiten des Servers immer wieder mit derselben Authentifikation an. Um das Login-Fenster wieder zu sehen, muß man den Browser beenden und neu starten, dann wird wieder die Authentifikation angefordert. Manche Browser erlauben auch die Option “Passwort speichern”, dann funktioniert von dem Browser aus danach das Login komplett vollautomatisch, bis man das gespeicherte Passwort im Browser löscht oder auf dem Server ein neues Passwort setzt.

Beispiel-Sketch anbei. Es handelt sich um das modifizierte “Webserver”-Beispiel zur Ethernet-Library, das ich mal eben um Authentifikation erweitert habe.

Wollt ihr Username und Passwort raten oder hacken?

Oder soll ich Euch mitteilen, welchen Usernamen und welches Passwort ihr eingeben müßt, damit der Arduino alle 5 Sekunden aktualisiert eine Seite mit den analogRead-Werten der Analog-Pins ausgibt?

/*
  Web Server
 
 A simple web server that shows the value of the analog input pins.
 using an Arduino Wiznet Ethernet shield. 
 
 Circuit:
 * Ethernet shield attached to pins 10, 11, 12, 13
 * Analog inputs attached to pins A0 through A5 (optional)
 
 created 18 Dec 2009
 by David A. Mellis
 modified 9 Apr 2012
 by Tom Igoe
 
 */

#include <SPI.h>
#include <Ethernet.h>

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,2, 111);

// Initialize the Ethernet server library
// with the IP address and port you want to use 
// (port 80 is default for HTTP):
EthernetServer server(80);

void setup() {
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());
}


void SendOKpage(EthernetClient &client)
{
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connnection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
                    // add a meta refresh tag, so the browser pulls again every 5 seconds:
          client.println("<meta http-equiv=\"refresh\" content=\"5\">");
          // output the value of each analog input pin
          for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
            int sensorReading = analogRead(analogChannel);
            client.print("analog input ");
            client.print(analogChannel);
            client.print(" is ");
            client.print(sensorReading);
            client.println("
");       
          }
          client.println("</html>");
}


void SendAuthentificationpage(EthernetClient &client)
{
          client.println("HTTP/1.1 401 Authorization Required");
          client.println("WWW-Authenticate: Basic realm=\"Secure Area\"");
          client.println("Content-Type: text/html");
          client.println("Connnection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<HTML>  <HEAD>   <TITLE>Error</TITLE>");
          client.println(" </HEAD> <BODY><H1>401 Unauthorized.</H1></BODY> </HTML>");
}

char linebuf[80];
int charcount=0;
boolean authentificated=false;

void loop() {
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    memset(linebuf,0,sizeof(linebuf));
    charcount=0;
    authentificated=false;
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        linebuf[charcount]=c;
        if (charcount<sizeof(linebuf)-1) charcount++;
        Serial.write(c);
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          if (authentificated)
            SendOKpage(client);
          else
            SendAuthentificationpage(client);  
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
          if (strstr(linebuf,"Authorization: Basic")>0 && strstr(linebuf,"cGhwOm5laW4=")>0)
            authentificated=true;
          memset(linebuf,0,sizeof(linebuf));
          charcount=0;
        } 
        else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disonnected");
  }
}

Anpassung einer geeigneten IP-Adresse im Sketch für Euer Netzwerk nicht vergessen!

Danke jurs,

Es handelt sich um das modifizierte "Webserver"-Beispiel zur Ethernet-Library, das ich mal eben um Authentifikation erweitert habe

Mann wie bist du drauf, so eben mal weil gerade langeweile 8)

ich habe bewusst nicht nach einem Code gefragt, weil ich mich in das Thema einlesen wollte. Und wenn man das verstanden hat, muss das das ganze noch auf dem Atmel umgesetzt werden.

Dein Code hilft natürlich enorm :-) Wenn ich deinen Code ansehe, glaube ich den Vorgang fast verstanden zu haben.

Wollt ihr Username und Passwort raten oder hacken?

Nicht raten, ich denke das heißt:

php:nein

base64 codiert.

mfg Rudi

rudirabbit: Mann wie bist du drauf, so eben mal weil gerade langeweile 8)

Ist ja im wesentlichen nur das vorhandene Demoprogramm zum Ethernet-Shield. Einmal alles kopieren und ein paar Zeilen ändern. ;)

rudirabbit: Wenn ich deinen Code ansehe, glaube ich den Vorgang fast verstanden zu haben.

Wenn Du Fragen hast, einfach fragen!

Im Endeffekt geht es nur um Anforderungen vom Client und Antworten vom Server.

Wenn der Server eine Datei nur mit vorheriger Authentifizierung ausliefern soll, dann wartet der Server auf eine Zeile im Header, die anfängt mit: Authorization: Basic ... Wenn diese Zeile in Header nicht vorkommt oder die Kombination Username/Passwort falsch ist, dann sendet der Server anstelle der angeforderten Daten einen Statuscode Error-401 und fordert damit Username/Passwort an. Der Server sendet dazu eine Zeile, die anfängt mit: WWW-Authenticate: Basic ... Als "..." kann noch ein kurzer erläuternder Hinweis für den User mitgesendet werden, im Beispiel einfach nur "Secure Area", aber da könnte auch ein ganzer langer Satz mitgesendet werden.

Sobald ein Web-Browser auf eine Anforderung einen Error-401 Statuscode und die Aufforderung "WWW-Authenticate: Basic" vom Server zurückbekommt, wird jeder Browser jedes Browserherstellers automatisch ein Eingabefenster für Username und Passwort öffen und vom User abfragen. Wenn der User diese Daten eingibt, werden sie mit einer "Authorization: Basic" im Header wieder an den Server zurückgesendet und dieselben Daten nochmal angefordert.

Dabei handelt es sich übrigens um eine Single-Sign-on Authentifikation, d.h. der Browser merkt sich, dass eine Autentifikation angefordert wurde und er sendet bei nachfolgenden Anfragen, auch wenn andere Daten angefordert werden, während derselben "Session" immer dieselbe Authentifikation mit, so dass nicht bei jeder neuen Seite auch neu autentifiziert werden muss, sondern nur einmal pro "Session", also im Allgemeinen so lange das Browserprogramm nicht beendet wird.

rudirabbit: Nicht raten, ich denke das heißt:

php:nein

base64 codiert.

Bravo, genau so ist es!

Username und Passwort werden einfach BASE64 codiert gesendet. Und da die Sendungen bei HTTP unverschlüsselt sind, kann die Daten leicht rückwärts dekodieren, wenn der codierte String bekannt ist. Hacker, die den Datenverkehr belauschen können, könnten das Passwort abfischen durch eine "Man-in-the-middle-Attacke", wenn sie sich irgendwo in den Datenverkehr einklinken können.

Diese Angriffsmöglichkeit ließe sich nur durch (verschlüsseltes) HTTPS statt (unverschlüsseltes) HTTP ausschalten, aber Arduino hat nicht genug Speicher und Rechenpower, um verschlüsselte HTTPS-Verbindungen aufzubauen.

Wie ich gezeigt habe, muß der Server nicht einmal BASE64-Verschlüsselung selbst beherrschen, wenn man dem Server die verschlüsselte Username/Passwort-Kombination zum Vergleichen gibt. Nur der Browser muss in der Lage sein, aus dem eingegebenen Usernamen und Passwort den verschlüsselten String zu bilden.

Das oben gepostete Demoprogramm kann auch gleich zum Ermitteln der verschlüsselten Strings verwendet werden, die man ins Programm einbauen kann. Da der Browser jeden beliebigen Usernamen und jedes beliebige Passwort verschlüsselt und an den Server sendet, und mein Demoprogramm die empfangenen Header im seriellen Monitor anzeigt, erscheinen die verschlüsselten Strings im seriellen Monitor, man braucht nur im seriellen Monitor nach der Zeile "Authorization: Basic" Ausschau halten und kann den verschlüsselten String, der vom Browser empfangen wurde, dort direkt ablesen.

Wie gesagt, Basic-Authentifikation per HTTP ist kein Hexenwerk. Das ist eines der wenigen Dinge, die im Internet heute noch genau so wie vor 20 Jahren funktionieren, das beherrscht jeder handelsübliche Webbrowser, und zwar komplett ohne Browser-Kompatibilitätsprobleme. Und man kann diese Authentifizierung mit geringem Aufwand sogar in einem Arduino-Webserver einbauen, weil serverseitig dafür weder viel Speicher noch viel Rechenpower benötigt wird (und schon gar nicht PHP).

Auf meinem Arduino läuft auch ein “Webserver”.
Da ich nicht will dass jedermann auf meinem Arduino herumspielt habe ich einen ganz einfachen Schutz eingebaut.

Durch ein Aufruf der Arduino-IP-Adresse auf dem Standardport 80 gibt der Arduino keine Antwort.
Lediglich wenn der richtige Steuerbefehl mit der GET-Methode mitkommt reagiert der Arduino und gibt Antwort:

z.B.:
boolean sendMyPage(char* URL) {
if (URL[0] == ‘/’ && URL[1] == ‘1’ && URL[2] == ‘?’) {
Mach 1. Aktion
return true;
}
if (URL[0] == ‘/’ && URL[1] == ‘2’ && URL[2] == ‘?’) {
Mach 2. Aktion
return true;

Also würde ein Aufruf http://ipadresse/ nicht ergeben und ins leere laufen.
Ein Aufruf http://ipadresse/1? würde Aktion 1 auslösen, ein Aufruf http://ipadresse/2? würde dann Aktion zwei auslösen.

Komplexer umgesetzt könnten diese “Zugriffcodes” auf mehrere Stellen erweitert und dynamisch gestaltet werden.
Man könnte z.B. die Programmierung so machen, dass ich mit jedem Aufruf des Webserver nebst dem richtigen “Zugriffscode” gleichzeitig der nächste “Zugriffscode” übermittelt wird. Arduino würde dann das nächste Mal wiederum den neuen Code erwarten. Im Zusammenspiel mit eine Datenbank, PHP und Arduino könnte das mit wenig Aufwand umgesetzt werden.

Damit wäre schon ein relativ guter Schutz umgesetzt, da diese Variante Eigenbau ist und keinem bekannten Protokoll entspricht…bei herkömmlichen Methoden haben es die Hacker einfacher weil sie wissen wo sie ansetzen müssen…

jurs:
OMG!

Nochmal: Authentifikation hat mit PHP rein gar nichts zu tun, nur mit HTTP und HTML!

Das ist richtig. Habe auch nichts anderes behauptet. Mit der Skriptsprache PHP erzeugst du HTML-Code
nur eben je nach Lib/Framework mit abstrahierten Funktionen.
Das muss man beim Atmega alles selber in C/C++ schreiben. Entweder die Funktionen nachbilden
und diese benutzen oder direkt HTML-Code erzeugen.
Dieser Gedankengang fehlt bei vielen, die ein wenig mit PHP und static HTML auf den Webservern rumgespielt haben
und ist ganz wichtig für das Verständnis.

PS: … und bitte nicht aufregen. Alles wird gut :wink:

Wie gesagt, Basic-Authentifikation per HTTP ist kein Hexenwerk.

Wenn man vorher noch nie was damit zu tun gehabt hat, und sich erst mal Wikimässig einlesen muß dann ist es auf den ersten Blick schon ein Hexenwerk. Aber mit deiner Starthilfe und Erklärungen ist es ein logischer Vorgang :)

So einen ähnlichen Ansatz wie schwitzer beschreibt hatte vorher auch im Kopf.

Ich wollte dann aber schon die klassische Variante mit User und Passwort Dialog. Und ich dachte vorher auch immer diese Loginseite kommt vom Server, so kann man sich irren.

Also wieder was gelernt.

Nighti: Mit der Skriptsprache PHP erzeugst du HTML-Code nur eben je nach Lib/Framework mit abstrahierten Funktionen. Das muss man beim Atmega alles selber in C/C++ schreiben. Entweder die Funktionen nachbilden und diese benutzen oder direkt HTML-Code erzeugen.

Sorry verstehe ich nicht wirklich. Auf "richtigen" Servern wird der Php Code dort verabeitet und als HTML zum Client geschickt. Der Client kann den eigentichen PHP Quellcode nicht sehen.

Man könnte aber das gleiche via Java machen nur mit dem Unterschied das der Java Code auf den Client sichtbar ist. Und so läuft es auf dem Atmega, wo muss man selber C/C++ Code schreiben. OK man muss auf dem ATmega C/C++ Code schreiben um den Java Code zum Client zu schicken.

Den Java Code müsste man immer schreiben, auch auf einen richtigem Server genauso wie dort einen PHP Code.

Was meinst du mit Funktionen nachbilden ?

@jurs: Deinen Vorschlag habe ich in meinem Projekt eingebaut, klappt prima.

Da ich dort noch mit der String Class arbeite, lasse ich mir den SRam Verbrauch auf der Website anzeigen. (Wird später noch geändert auf Char array) Dieser steigt auch nach längerer Laufzeit nicht an, also soweit kein memory leak.

Was mir Sorgen macht ist der SRam Verbrauch bis jetzt: nur noch 3324 Bytes frei auf einem Arduino Mega Board.

Die Website ist liegt noch komplett im Source Code, hatte Probleme mit dem Laden von der SD Karte. (Anderer Thread hier, wurde auch erklärt was falsch läuft)

Wenn ich die Website von der SD lade, geht dann mein SRam Verbrauch dramatisch zurück ? Denn Flash habe ich genug frei.

rudirabbit:
Was mir Sorgen macht ist der SRam Verbrauch bis jetzt: nur noch 3324 Bytes frei auf einem Arduino Mega Board.

Inwiefern macht Dir das Sorgen?
Ein UNO hat insgesamt nur 2048 Bytes RAM und Du hast noch mehr RAM frei als ein UNO überhaupt hat.

Meine Faustformel ist: “Normale” Arduino Programme laufen mit ausreichend RAM, wenn am Ende der Setup-Routine mindestens 250 Bytes freier RAM-Speicher angezeigt werden. Allerdings verwende ich in Funktionen keine String-Objekte, deklariere in Funktionen keine riesigen char-Arrays, und übergebe char-Arrays in Funktionen nur als Pointer.

rudirabbit:
Wenn ich die Website von der SD lade, geht dann mein SRam Verbrauch dramatisch zurück ?

Je nachdem, das kommt auf Dein Programm an.

Der RAM-Verbrauch kann sogar zunehmen, denn sobald Du die erste SD-Kartenfunktion verwendest, werden über 512 Bytes RAM von der SD-Library reserviert und verwendet: 512 Bytes ist die Sektorengröße einer SD-Karte mit FAT-Formatierung, und das ist der Mindestspeicher, um einen Sektor der SD-Karte ins RAM zu laden. Mit weniger RAM kann keine SD-Library arbeiten. Also sinkt der freie RAM-Speicher erstmal um mehr als 512 Bytes, sobald die SD-Library verwendet wird.

Nur wenn Du jetzt die konstanten Strings in Deinem Sketch, die als Webseiten ausgegeben werden sollen, ohne F-Makros definiert hast, nimmt der RAM-Verbrauch ab, wenn diese Strings aus dem Programm verschwinden.

Solche print-Zeilen belegen kräftig RAM-Speicher:

          client.println("<HTML>  <HEAD>   <TITLE>Error</TITLE>");
          client.println(" </HEAD> <BODY><H1>401 Unauthorized.</H1></BODY> </HTML>");

Wenn diese Zeilen aus dem Programm verschwinden, sinkt der RAM-Bedarf des Programms.

Der RAM-Bedarf des Programms ist aber auch schon geringer, wenn die konstanten Strings aus dem Flash geholt werden:

          client.println(F("<HTML>  <HEAD>   <TITLE>Error</TITLE>"));
          client.println(F(" </HEAD> <BODY><H1>401 Unauthorized.</H1></BODY> </HTML>"));

Und wenn solche Zeilen aus dem Programm verschwinden, wo der Text sowieso nur im Flash-Speicher gespeichert ist, sinkt der RAM-Verbrauch nicht.

Wenn Du reichlich Flash-Speicher frei hast und es Dir nur darum geht, RAM-Speicher zu sparen, könntest Du einfach alle konstanten Strings, die mit print oder println ausgegeben werden, mit dem F-Makro aus dem Flash-Speicher ausgeben. Dann sinkt der RAM-Verbrauch auf jeden Fall und Du hast mehr RAM-Speicher frei.