Skip to content

Commit f4fdecc

Browse files
fix(csrf): Fix SCRF vulnerability in OTA examples and libraries (#11530)
* fix(csrf): Fix SCRF vulnerability in WebUpdate.ino * fix(csrf): Prevent CSRF on other OTA examples * fix(csrf): Require auth user and pass to be changed * ci(pre-commit): Apply automatic fixes --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 8cf0818 commit f4fdecc

File tree

3 files changed

+90
-7
lines changed

3 files changed

+90
-7
lines changed

libraries/HTTPUpdateServer/src/HTTPUpdateServer.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static const char serverIndex[] PROGMEM =
2727
</body>
2828
</html>)";
2929
static const char successResponse[] PROGMEM = "<META http-equiv=\"refresh\" content=\"15;URL=/\">Update Success! Rebooting...";
30+
static const char *csrfHeaders[2] = {"Origin", "Host"};
3031

3132
class HTTPUpdateServer {
3233
public:
@@ -56,6 +57,9 @@ class HTTPUpdateServer {
5657
_username = username;
5758
_password = password;
5859

60+
// collect headers for CSRF verification
61+
_server->collectHeaders(csrfHeaders, 2);
62+
5963
// handler for the /update form page
6064
_server->on(path.c_str(), HTTP_GET, [&]() {
6165
if (_username != emptyString && _password != emptyString && !_server->authenticate(_username.c_str(), _password.c_str())) {
@@ -69,6 +73,10 @@ class HTTPUpdateServer {
6973
path.c_str(), HTTP_POST,
7074
[&]() {
7175
if (!_authenticated) {
76+
if (_username == emptyString || _password == emptyString) {
77+
_server->send(200, F("text/html"), String(F("Update error: Wrong origin received!")));
78+
return;
79+
}
7280
return _server->requestAuthentication();
7381
}
7482
if (Update.hasError()) {
@@ -100,6 +108,17 @@ class HTTPUpdateServer {
100108
return;
101109
}
102110

111+
String origin = _server->header(String(csrfHeaders[0]));
112+
String host = _server->header(String(csrfHeaders[1]));
113+
String expectedOrigin = String("http://") + host;
114+
if (origin != expectedOrigin) {
115+
if (_serial_output) {
116+
Serial.printf("Wrong origin received! Expected: %s, Received: %s\n", expectedOrigin.c_str(), origin.c_str());
117+
}
118+
_authenticated = false;
119+
return;
120+
}
121+
103122
if (_serial_output) {
104123
Serial.printf("Update: %s\n", upload.filename.c_str());
105124
}

libraries/Update/examples/OTAWebUpdater/OTAWebUpdater.ino

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@
88
#define SSID_FORMAT "ESP32-%06lX" // 12 chars total
99
//#define PASSWORD "test123456" // generate if remarked
1010

11+
// Set the username and password for firmware upload
12+
const char *authUser = "........";
13+
const char *authPass = "........";
14+
1115
WebServer server(80);
1216
Ticker tkSecond;
1317
uint8_t otaDone = 0;
1418

19+
const char *csrfHeaders[2] = {"Origin", "Host"};
20+
static bool authenticated = false;
21+
1522
const char *alphanum = "0123456789!@#$%^&*abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1623
String generatePass(uint8_t str_len) {
1724
String buff;
@@ -38,13 +45,17 @@ void apMode() {
3845
}
3946

4047
void handleUpdateEnd() {
48+
if (!authenticated) {
49+
return server.requestAuthentication();
50+
}
4151
server.sendHeader("Connection", "close");
4252
if (Update.hasError()) {
4353
server.send(502, "text/plain", Update.errorString());
4454
} else {
4555
server.sendHeader("Refresh", "10");
4656
server.sendHeader("Location", "/");
4757
server.send(307);
58+
delay(500);
4859
ESP.restart();
4960
}
5061
}
@@ -56,18 +67,34 @@ void handleUpdate() {
5667
}
5768
HTTPUpload &upload = server.upload();
5869
if (upload.status == UPLOAD_FILE_START) {
70+
authenticated = server.authenticate(authUser, authPass);
71+
if (!authenticated) {
72+
Serial.println("Authentication fail!");
73+
otaDone = 0;
74+
return;
75+
}
76+
String origin = server.header(String(csrfHeaders[0]));
77+
String host = server.header(String(csrfHeaders[1]));
78+
String expectedOrigin = String("http://") + host;
79+
if (origin != expectedOrigin) {
80+
Serial.printf("Wrong origin received! Expected: %s, Received: %s\n", expectedOrigin.c_str(), origin.c_str());
81+
authenticated = false;
82+
otaDone = 0;
83+
return;
84+
}
85+
5986
Serial.printf("Receiving Update: %s, Size: %d\n", upload.filename.c_str(), fsize);
6087
if (!Update.begin(fsize)) {
6188
otaDone = 0;
6289
Update.printError(Serial);
6390
}
64-
} else if (upload.status == UPLOAD_FILE_WRITE) {
91+
} else if (authenticated && upload.status == UPLOAD_FILE_WRITE) {
6592
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
6693
Update.printError(Serial);
6794
} else {
6895
otaDone = 100 * Update.progress() / Update.size();
6996
}
70-
} else if (upload.status == UPLOAD_FILE_END) {
97+
} else if (authenticated && upload.status == UPLOAD_FILE_END) {
7198
if (Update.end(true)) {
7299
Serial.printf("Update Success: %u bytes\nRebooting...\n", upload.totalSize);
73100
} else {
@@ -78,6 +105,7 @@ void handleUpdate() {
78105
}
79106

80107
void webServerInit() {
108+
server.collectHeaders(csrfHeaders, 2);
81109
server.on(
82110
"/update", HTTP_POST,
83111
[]() {
@@ -92,6 +120,9 @@ void webServerInit() {
92120
server.send_P(200, "image/x-icon", favicon_ico_gz, favicon_ico_gz_len);
93121
});
94122
server.onNotFound([]() {
123+
if (!server.authenticate(authUser, authPass)) {
124+
return server.requestAuthentication();
125+
}
95126
server.send(200, "text/html", indexHtml);
96127
});
97128
server.begin();

libraries/WebServer/examples/WebUpdate/WebUpdate.ino

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ const char *host = "esp32-webupdate";
1212
const char *ssid = "........";
1313
const char *password = "........";
1414

15+
// Set the username and password for firmware upload
16+
const char *authUser = "........";
17+
const char *authPass = "........";
18+
1519
WebServer server(80);
1620
const char *serverIndex =
1721
"<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
1822

23+
const char *csrfHeaders[2] = {"Origin", "Host"};
24+
static bool authenticated = false;
25+
1926
void setup(void) {
2027
Serial.begin(115200);
2128
Serial.println();
@@ -24,37 +31,63 @@ void setup(void) {
2431
WiFi.begin(ssid, password);
2532
if (WiFi.waitForConnectResult() == WL_CONNECTED) {
2633
MDNS.begin(host);
34+
server.collectHeaders(csrfHeaders, 2);
2735
server.on("/", HTTP_GET, []() {
36+
if (!server.authenticate(authUser, authPass)) {
37+
return server.requestAuthentication();
38+
}
2839
server.sendHeader("Connection", "close");
2940
server.send(200, "text/html", serverIndex);
3041
});
3142
server.on(
3243
"/update", HTTP_POST,
3344
[]() {
45+
if (!authenticated) {
46+
return server.requestAuthentication();
47+
}
3448
server.sendHeader("Connection", "close");
35-
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
36-
ESP.restart();
49+
if (Update.hasError()) {
50+
server.send(200, "text/plain", "FAIL");
51+
} else {
52+
server.send(200, "text/plain", "Success! Rebooting...");
53+
delay(500);
54+
ESP.restart();
55+
}
3756
},
3857
[]() {
3958
HTTPUpload &upload = server.upload();
4059
if (upload.status == UPLOAD_FILE_START) {
4160
Serial.setDebugOutput(true);
61+
authenticated = server.authenticate(authUser, authPass);
62+
if (!authenticated) {
63+
Serial.println("Authentication fail!");
64+
return;
65+
}
66+
String origin = server.header(String(csrfHeaders[0]));
67+
String host = server.header(String(csrfHeaders[1]));
68+
String expectedOrigin = String("http://") + host;
69+
if (origin != expectedOrigin) {
70+
Serial.printf("Wrong origin received! Expected: %s, Received: %s\n", expectedOrigin.c_str(), origin.c_str());
71+
authenticated = false;
72+
return;
73+
}
74+
4275
Serial.printf("Update: %s\n", upload.filename.c_str());
4376
if (!Update.begin()) { //start with max available size
4477
Update.printError(Serial);
4578
}
46-
} else if (upload.status == UPLOAD_FILE_WRITE) {
79+
} else if (authenticated && upload.status == UPLOAD_FILE_WRITE) {
4780
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
4881
Update.printError(Serial);
4982
}
50-
} else if (upload.status == UPLOAD_FILE_END) {
83+
} else if (authenticated && upload.status == UPLOAD_FILE_END) {
5184
if (Update.end(true)) { //true to set the size to the current progress
5285
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
5386
} else {
5487
Update.printError(Serial);
5588
}
5689
Serial.setDebugOutput(false);
57-
} else {
90+
} else if (authenticated) {
5891
Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status);
5992
}
6093
}

0 commit comments

Comments
 (0)