- GET и POST аутентификация на сервере Arduino
GET и POST аутентификация на web-сервере Arduino
Практика для студентов. Мясищев А.А.
С появлением технологий удаленного управления оборудованием через сеть Интернет актуальной стала задача авторизации на управляющих серверах. Часто такими серверами являются web-сервера, построенные на основе микроконтроллеров среди которых наиболее распространены в составе контроллеров Arduino. Контроллеры Arduino наиболее доступны, дешевые, имеют большое количество плат расширения (шилдов), бесплатную программную поддержку с большим количеством библиотек. Однако в отличие от современных компьютеров Arduino имеют скромные вычислительные ресурсы в связи с чем не в состоянии поддерживать полноценные сетевые протоколы, не говоря уже о программном обеспечении обеспечивающих шифрование данных в пакетах. Поэтому команды по управлению оборудованием посылаются с браузера клиента на web-сервер Arduino без пароля идентификации. Таким образом удаленно может управлять устройствами любой пользователь, которому известен адрес сервера. Это может нанести вред правильному функционированию систем, которые могут располагаться в офисе, предприятии, учреждении, жилом доме.
Для надежной авторизации на серверах с популярными сетевыми операционными системами (Linux, FreeBSD, OpenBSD, Windows Server, ...) используется протокол https, данные которого «упаковываются» в криптографический протокол SSL или TLS, обеспечивая защиту этих данных. Работа этих протоколов выполняется с помощью специального программного обеспечения, которое не может быть установлено на сервера Arduino вследствие, в частности, малой памяти микроконтроллеров. Также и стек коммуникационных протоколов Ethernet Shield Arduino использует сокращенные, урезанные версии протоколов TCP/IP. Несмотря на указанные проблемы рассмотрим возможность упрощенной удаленной авторизации на сервере Arduino. Для этого воспользуемся технологией GET запросов POST.
Рассмотрим web-сервер на Arduino, который управляет удаленно тремя светодиодами и получает данные с температурного датчика. На рисунке 1 показан сервер на Arduino mega с контроллером сети на ENC28J60. Подключение этих двух контроллеров для библиотеки UIPEthernet показано здесь.
Рис.1. Макет исследуемого web-сервера на Arduino mega
Сервер на браузере клиента формирует форму, которая не должна отображать вводимые символы. Включение и выключение светодиодов выполняется вводом следующих кодов:
Включить красный - 11361
Выключить красный - 11360
Включить зеленый - 11381
Выключить зеленый - 11380
Включить синий - 11401
Выключить синий - 11400
Задача заключается в том, чтобы не удалось подсмотреть другому пользователю вводимые в форму коды управления. Шифрование данных по сети не рассматривается.
Рассмотрим GET запрос.
Заголовок, посылаемый от браузера клиента серверу имеет вид:
GET / HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Accept-Language: ru,en-US;q=0.8,en;q=0.5,uk;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; MALNJS; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 192.168.1.18:81
Connection: Keep-Alive
В конце каждой строки заголовка следуют символы \r\n. В случае передачи параметров для включения красного и синего светодиодов заголовок будет следующим:
GET /?ron=11361&roff=&gon=&goff=&bon=11401&boff= HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Referer: http://192.168.1.18:81/
Accept-Language: ru,en-US;q=0.8,en;q=0.5,uk;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; MALNJS; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 192.168.1.18:81
Connection: Keep-Alive
Таким образом при GET запросе передача параметров на управление передается в первой строке заголовка браузера. На рисунке 2 представлен интерфейс управления. Здесь видно, что в адресной строке в открытом виде передаются все кодовые параметры на управление устройствами. Поэтому коды на управление можно легко подсмотреть с адресной строки. Программа сервера представлена ниже.
Рис.2. Интерфейс управления для GET запроса
Программа сервера Arduino для GET аутентификации
#include <UIPEthernet.h>
#include <OneWire.h>
#include <stdio.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// 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, 0x11, 0xE1
};
IPAddress ip(192, 168, 1, 18);
char buf[30];
// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(81);
void setup() {
sensors.begin();
// Open serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
pinMode(3, OUTPUT); // 3-й вывод определен как выход для светодиода
pinMode(5, OUTPUT); // 5-й вывод определен как выход
pinMode(7, OUTPUT); // 7-й вывод определен как выход
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
Serial.print("server is at ");
Serial.println(Ethernet.localIP());
}
void loop() {
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
Serial.println("----- \n new client");
// an http request ends with a blank line
boolean currentLineIsBlank = true;
byte k=0;
String bufget = String("");
while (client.connected()) {
if (client.available()) {
char c = client.read();
if(k==0) bufget += c;
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(bufget.indexOf("ron=11361") >= 0) {digitalWrite(3, HIGH);}
if(bufget.indexOf("roff=11360") >= 0) {digitalWrite(3, LOW);}
if(bufget.indexOf("gon=11381") >= 0) {digitalWrite(5, HIGH);}
if(bufget.indexOf("goff=11380") >= 0) {digitalWrite(5, LOW);}
if(bufget.indexOf("bon=11401") >= 0) {digitalWrite(7, HIGH);}
if(bufget.indexOf("boff=11400") >= 0) {digitalWrite(7, LOW);}
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.print("Connection: close\r\n\r\n"); // the connection will be closed after completion of the response
client.println("<!DOCTYPE HTML>");
client.println("<html lang=\"ru\">");
client.println("<head>");
client.println("<meta charset='Windows-1251'>");
client.println("<title>Arduino mega</title>");
client.println("</head>");
client.println("<body>");
client.println("<font size=5 color=blue> Web-server на Arduino MEGA 1280 <br>и контроллере enc28j60 <br>с аутентификацией без защиты</font>");
client.println("<br>");
client.println("<hr>");
client.println("<FORM action=\"\" method=\"GET\">");
client.println("<label for='pro'><b>Включить красный светодиод:</b></label><br>");
client.println("<input type='password' name='ron' id='pro'><br>");
client.println("<label for='prf'><b>Выключить красный светодиод:</b></label><br>");
client.println("<input type='password' name='roff' id='prf'><br><br>");
client.println("<label for='pgo'><b>Включить зеленый светодиод:</b></label><br>");
client.println("<input type='password' name='gon' id='pgo'><br>");
client.println("<label for='pgf'><b>Выключить зеленый светодиод:</b></label><br>");
client.println("<input type='password' name='goff' id='pgf'><br><br>");
client.println("<label for='pbo'><b>Включить синий светодиод:</b></label><br>");
client.println("<input type='password' name='bon' id='pbo'><br>");
client.println("<label for='pbf'><b>Выключить синий светодиод:</b></label><br>");
client.println("<input type='password' name='boff' id='pbf'><br><br>");
client.println("<INPUT type=\"submit\" value=\"Ввести\">");
client.println("</FORM>");
client.println("<br>");
sensors.requestTemperatures();
dtostrf(sensors.getTempCByIndex(0),7,2,buf);
client.print("<font size=4 color=broun>Температура ");
client.print(buf);
client.println(" градусов C</font><br><br>");
if (digitalRead(3)){ client.println("<font size=4 color=red>Красный светодиод ВКЛЮЧЕН</font><br>"); }
else{ client.println("<font size=4 color=red>Красный светодиод ВЫКЛЮЧЕН</font><br>"); }
if (digitalRead(5)){ client.println("<font size=4 color=green>Зеленый светодиод ВКЛЮЧЕН</font><br>");}
else{ client.println("<font size=4 color=green>Зеленый светодиод ВЫКЛЮЧЕН</font><br>"); }
if (digitalRead(7)){client.println("<font size=4 color=blue>Синий светодиод ВКЛЮЧЕН</font><br>"); }
else{ client.println("<font size=4 color=blue>Синий светодиод ВЫКЛЮЧЕН</font><br>"); }
client.println("<hr>");
client.print("Свободно памяти: ");
client.println(freeRam());
Serial.println("bufget:");
Serial.println(bufget); // Распечатка GET запроса
client.println("</body>");
client.println("</html>");
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
if(bufget.indexOf("GET /")>=0) k=1; //Если получена 1-я строка заголовка,
// то дальше в буфер символы не записываем
} 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 disconnected");
}
}
int freeRam () {
extern int __heap_start, *__brkval; int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); }
Рассмотрим POST запрос. В этом случае параметры управления(коды) передаются не через адресную строку. Заголовок, который посылает браузер при POST запросе, имеет следующий вид:
POST / HTTP/1.1
Accept: text/html, application/xhtml+xml, image/jxr, */*
Referer: http://192.168.1.18:81/
Accept-Language: ru,en-US;q=0.8,en;q=0.5,uk;q=0.3
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; MALNJS; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 192.168.1.18:81
Content-Length: 42
Connection: Keep-Alive
Cache-Control: no-cache
<---здесь пустая строка--->
ron=11361&roff=&gon=&goff=&bon=11401&boff=
Параметры для включения красного и синего светодиодов передаются после основного заголовка через пустую строку. Первая строка заголовка указывает на передачу POST запроса.
На рисунке 3 представлен интерфейс управления для POST запроса. Видно, что в адресной строке не передаются кодовые параметры на управление устройствами, например, при включении красного и синего светодиодов.
Рис.3. Интерфейс управления для POST запроса
Программа сервера для POST запроса представлена здесь. Для считывания кодового параметра используются операторы
while(client.available()) { //Обработка запроса POST
post = client.read();
if (buffer.length() <= bufferMax) { buffer += post;
}
}
которые считывают часть заголовка, находящегося после пустой строки основной части заголовка. В массив buffer записываются управляющие параметры (ron=11361&roff=&gon=&goff=&bon=11401&boff=). В зависимости от их значения операторами
if(buffer.indexOf("ron=11361") >= 0) { digitalWrite(3, HIGH); }
if(buffer.indexOf("roff=11360") >= 0) { digitalWrite(3, LOW); }
if(buffer.indexOf("gon=11381") >= 0) { digitalWrite(5, HIGH); }
if(buffer.indexOf("goff=11380") >= 0) { digitalWrite(5, LOW); }
if(buffer.indexOf("bon=11401") >= 0) { digitalWrite(7, HIGH); }
if(buffer.indexOf("boff=11400") >= 0) { digitalWrite(7, LOW); }
выполняются соответствующие действия(включение или выключение).
Из изложенного выше видно, что при POST запросе при вводе в форму кодов управления их значения подсмотреть невозможно(как и при вводе пароля при идентификации в системе через консоль). Однако данные передаются по сети в открытой форме, без кодирования. Поэтому используя программы - sniffer можно всегда перехватить переданные данные и выявить управляющие коды.
Выводы
1. Авторизация на сервере Arduino с использованием метода запроса GET является нецелесообразной, так как при использовании форм ввода с полями для пароля коды открыто высвечиваются в адресной строке. Поэтому рядом находящийся пользователь их легко может подсмотреть.
2. Авторизация с использованием метода POST скрывает передаваемые коды, однако коды передаются по сети в открытой, в не кодированной форме, поэтому их можно перехватить с помощью программ снифферов.
22.02.2018 г.