2018年5月21日 星期一

ESP8266 Advanced Login Security

ESP8266 Advanced Login Security

More advanced login security for your esp project, including username, password, brute force and inactivity protection.
AdvancedShowcase (no instructions)1 hour2,681
ESP8266 Advanced Login Security

Things used in this project

Hardware components

NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Code

Arduino code

C/C++
Full arduino code, parts that are not explained are basic NodeMCU setup stuff.
#include <ESP8266WebServer.h>

//This is our login html page, contains user and pass fields that are posted to /login
const String loginPage = "<!DOCTYPE html><html><head><title>Login</title></head><body> <div id=\"login\"> <form action='/login' method='POST'> <center> <h1>Login </h1><p><input type='text' name='user' placeholder='User name'></p><p><input type='password' name='pass' placeholder='Password'></p><br><button type='submit' name='submit'>login</button></center> </form></body></html>";
//Our page if user succesfully loged in, includes timeout timer refresh and logout href
const String loginok = "<!DOCTYPE html><html><head><title>Login</title></head><body> <div> <form action='/' method='POST'> <center> <a href=\"/refresh\">Refresh</a><br><a href=\"/logoff\">Logoff</a></center> </form></body></html>";

const char* ssid     = "";
const char* password = "";

bool lock = false; //This bool is used to control device lockout 

String anchars = "abcdefghijklmnopqrstuvwxyz0123456789", username = "admin", loginPassword = "admin"; //anchars will be explained below. username will be compared with 'user' and loginPassword with 'pass' when login is done

unsigned long logincld = millis(), reqmillis = millis(), tempign = millis(); //First 2 timers are for lockout and last one is inactivity timer

uint8_t i, trycount = 0; // i is used for for index, trycount will be our buffer for remembering how many false entries there were

ESP8266WebServer server(80);

String sessioncookie; //this is cookie buffer

void setup(void){
  Serial.begin(115200);
  delay(10);

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  
  gencookie(); //generate new cookie on device start

  server.on("/", handleRoot);
  
  server.onNotFound(handleNotFound);
  
  server.on("/login", handleLogin);
  server.on("/refresh", refresh);
  server.on("/logoff", logoff);

  const char * headerkeys[] = {"User-Agent","Cookie"} ;
  size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*);
  server.collectHeaders(headerkeys, headerkeyssize ); //These 3 lines tell esp to collect User-Agent and Cookie in http header when request is made
  
  server.begin();
}


bool is_authentified(){ //This function checks for Cookie header in request, it compares variable c to sessioncookie and returns true if they are the same
  if (server.hasHeader("Cookie")){
    String cookie = server.header("Cookie"), authk = "c=" + sessioncookie;
    if (cookie.indexOf(authk) != -1) return true;
  }
  return false;
}

void handleRoot(){
  
  String header;
  if (!is_authentified()){ //This here checks if your cookie is valid in header and it redirects you to /login if not, if ok send loginok html file
    String header = "HTTP/1.1 301 OK\r\nLocation: /login\r\nCache-Control: no-cache\r\n\r\n";
    server.sendContent(header);
    return;
  }
  tempign = millis(); //reset the inactivity timer if someone logs in
  server.send(200, "text/html", loginok);
  
}

void handleLogin(){
  
  if (server.hasHeader("Cookie")){   
    String cookie = server.header("Cookie"); //Copy the Cookie header to this buffer
  }

  if (server.hasArg("user") && server.hasArg("pass")){ //if user posted with these arguments
    if (server.arg("user") == username &&  server.arg("pass") == loginPassword && !lock){ //check if login details are good and dont allow it if device is in lockdown
      String header = "HTTP/1.1 301 OK\r\nSet-Cookie: c=" + sessioncookie + "\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n"; //if above values are good, send 'Cookie' header with variable c, with format 'c=sessioncookie'
      server.sendContent(header);
      trycount = 0; //With good headers in mind, reset the trycount buffer
      return;
    }
  
  String msg; //this is our buffer that we will add to the login html page when headers are wrong or device is locked
  msg = "<center><br>";
  if (trycount != 10 && !lock)trycount++; //If system is not locked up the trycount buffer
  if (trycount < 10 && !lock){ //We go here if systems isn't locked out, we give user 10 times to make a mistake after we lock down the system, thus making brute force attack almost imposible
    msg += "Wrong username/password<p></p>";
    msg += "You have ";
    msg += (10 - trycount);
    msg += " tries before system temporarily locks out.";
    logincld = millis(); //Reset the logincld timer, since we still have available tries
  }
  
  if (trycount == 10){ //If too much bad tries
    if(lock){
      msg += "Too much invalid login requests, you can use this device in ";
      msg += 5 - ((millis() - logincld) / 60000); //Display lock time remaining in minutes
      msg += " minutes.";
    }
    else{
       logincld = millis();
       lock = true;  
       msg += "Too much invalid login requests, you can use this device in 5 minutes."; //This happens when your device first locks down
    }
     
    
  }
  }
  String content = loginPage;
  content +=  msg + "</center>";
  server.send(200, "text/html", content); //merge loginPage and msg and send it
}

void handleNotFound(){
  server.send(404, "text/plain", "Not found");
}

void gencookie(){
  sessioncookie = "";
  for( i = 0; i < 32; i++) sessioncookie += anchars[random(0, anchars.length())]; //Using randomchar from anchars string generate 32-bit cookie
}

void loop(void){
  server.handleClient();

    if(lock && abs(millis() - logincld) > 300000){
      lock = false;
      trycount = 0;
      logincld = millis(); //After 5 minutes is passed unlock the system
    }
    
    if(!lock && abs(millis() - logincld) > 60000){
      trycount = 0;
      logincld = millis();
      //After minute is passed without bad entries, reset trycount
    }
    
   
   if(abs(millis() - tempign) > 120000){
     gencookie();
     tempign = millis();
     //if there is no activity from loged on user, change the generate a new cookie. This is more secure than adding expiry to the cookie header
  } 
}

void logoff(){
  String header = "HTTP/1.1 301 OK\r\nSet-Cookie: c=0\r\nLocation: /login\r\nCache-Control: no-cache\r\n\r\n"; //Set 'c=0', it users header, effectively deleting it's header
  server.sendContent(header);
}

void refresh(){
  if(is_authentified()){ //this is for reseting the inactivity timer, it covers everything explained above
    tempign = millis();
    String header = "HTTP/1.1 301 OK\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n";
    server.sendContent(header); 
  }
  else{
    String header = "HTTP/1.1 301 OK\r\nLocation: /login\r\nCache-Control: no-cache\r\n\r\n";
    server.sendContent(header);
  }
}

沒有留言:

張貼留言

Messaging API作為替代方案

  LINE超好用功能要沒了!LINE Notify明年3月底終止服務,有什麼替代方案? LINE Notify將於2025年3月31日結束服務,官方建議改用Messaging API作為替代方案。 //CHANNEL_ACCESS_TOKEN = 'Messaging ...