Исправлено две уязвимости:

* Веб-сервер не отклонял новый пароль длинной 0. chpasswd.c предполагает, что длина вывода генератора хеша пароля хотя бы 1,
   но при таком пароле его длина тоже 0. Таким образом генерировался неправильный новый файл creds.
 * Даже неправильные запросы заставляли chpasswd.c делать резервные копии файла creds. Поэтому, любой человек мог совершить DoS
   атаку, отправив очень много запросов и заставив chpasswd.c сделать очень много резервных копий. Теперь, creds копируется только
   после правильных запросов.

Немного отформатирован файл chpasswd.c, а файл webserver.go был перетабулирован.
This commit is contained in:
Nikita Osokin 2025-01-14 23:46:12 +05:00
parent c3ad534959
commit 3b9ea77879
3 changed files with 130 additions and 106 deletions

View file

@ -36,6 +36,7 @@
#define USER_NOT_FOUND -3 #define USER_NOT_FOUND -3
#define INCORRECT_OLD_PASSWORD -4 #define INCORRECT_OLD_PASSWORD -4
#define PASSWORD_MISMATCH -5 #define PASSWORD_MISMATCH -5
#define INVALID_INPUT -6
#define OLD_PATH_BASE 20 #define OLD_PATH_BASE 20
#define BUF_LEN 64 * 1024 #define BUF_LEN 64 * 1024
@ -61,42 +62,41 @@ int open_creds_old(size_t i) {
return -1; return -1;
} }
void copy_to(FILE * restrict src, FILE * restrict dest, const char * destPath) { int copy_to(FILE * restrict src, FILE * restrict dest) {
size_t readen /* неправильные глаголы... */, written; size_t readen /* неправильные глаголы... */, written;
while(!feof(src)) { while(!feof(src)) {
readen = fread(buf, 1, BUF_LEN, src); readen = fread(buf, 1, BUF_LEN, src);
written = fwrite(buf, 1, readen, dest); written = fwrite(buf, 1, readen, dest);
if(written != readen) { if(written != readen) {
fclose(src); return 0;
fclose(dest); }
remove(destPath); }
exit(INTERNAL_ERROR); return 1;
}
}
} }
void backup(FILE * restrict creds) { int backup(FILE * restrict creds) {
int creds_old_fd = open_creds_old(OLD_PATH_BASE); int creds_old_fd = open_creds_old(OLD_PATH_BASE);
if(creds_old_fd == -1) { if(creds_old_fd == -1) {
fclose(creds); return 0;
exit(INTERNAL_ERROR);
} }
FILE * creds_old = fdopen(creds_old_fd, "w"); FILE * creds_old = fdopen(creds_old_fd, "w");
if(creds_old == NULL) { if(creds_old == NULL) {
fclose(creds);
close(creds_old_fd); close(creds_old_fd);
remove(creds_old_path); remove(creds_old_path);
exit(INTERNAL_ERROR); return 0;
} }
copy_to(creds, creds_old, creds_old_path); int ret = copy_to(creds, creds_old);
fclose(creds_old); fclose(creds_old);
rewind(creds); if(!ret) {
remove(creds_old_path);
}
return ret;
} }
char * found_user_line( char * found_user_line(
@ -176,8 +176,6 @@ int main(int argc, char ** argv) {
return INTERNAL_ERROR; return INTERNAL_ERROR;
} }
backup(creds);
// Формирование нового файла creds // Формирование нового файла creds
FILE * creds_new = fopen("/etc/mail/creds.new", "w"); FILE * creds_new = fopen("/etc/mail/creds.new", "w");
@ -189,16 +187,18 @@ int main(int argc, char ** argv) {
char * line = NULL; char * line = NULL;
size_t n; size_t n;
char * hash_begin, * hash_iter; char * hash_begin, * hash_iter, * enc;
// Ищем и изменяем строку пользователя
while((hash_begin = found_user_line(&line, &n, creds, username, creds_new)) == NULL); while((hash_begin = found_user_line(&line, &n, creds, username, creds_new)) == NULL);
fputs(username, creds_new); fputs(username, creds_new);
fputc(':', creds_new); fputc(':', creds_new);
for(hash_iter = hash_begin; *hash_iter != ':'; hash_iter++); for(hash_iter = hash_begin; *hash_iter != ':'; hash_iter++);
*hash_iter = '\0'; // Теперь мы ограничили часть строки, которая содержит хеш старого пароля *hash_iter = '\0'; // Теперь мы ограничили часть строки, которая содержит хеш старого пароля
// и можем сравнить его с хешем данного пароля
char * enc = crypt(old_password, hash_begin); enc = crypt(old_password, hash_begin);
if(strcmp(enc, hash_begin)) { if(strcmp(enc, hash_begin)) {
fclose(creds); fclose(creds);
fclose(creds_new); fclose(creds_new);
@ -208,6 +208,7 @@ int main(int argc, char ** argv) {
} }
// Получаем хеш нового пароля
int inpipe[2], outpipe[2], status; int inpipe[2], outpipe[2], status;
size_t new_hash_len; size_t new_hash_len;
pipe(inpipe); pipe(inpipe);
@ -236,7 +237,6 @@ int main(int argc, char ** argv) {
write(inpipe[1], password, password_len); write(inpipe[1], password, password_len);
close(inpipe[1]); close(inpipe[1]);
wait(&status); wait(&status);
if(status) { if(status) {
fclose(creds); fclose(creds);
@ -248,14 +248,38 @@ int main(int argc, char ** argv) {
new_hash_len = read(outpipe[0], buf, 64 * 1024); new_hash_len = read(outpipe[0], buf, 64 * 1024);
close(outpipe[0]); close(outpipe[0]);
// Основывается на том, что последний символ вывода smtpctl encrypt - '\n' if(new_hash_len == 0) {
buf[new_hash_len - 1] = '\0'; fclose(creds);
fclose(creds_new);
remove("/etc/mail/creds.new");
return INVALID_INPUT; // Скорее всего, в этом виноват пользователь, либо серверу плохо
}
buf[new_hash_len - 1] = '\0'; // Последний символ вывода smtpctl encrypt должен быть '\n'
fputs(buf, creds_new); fputs(buf, creds_new);
// Копируем строку пользователя и весь оставшийся файл до конца
fputc(':', creds_new); fputc(':', creds_new);
fputs(hash_iter + 1, creds_new); fputs(hash_iter + 1, creds_new);
copy_to(creds, creds_new, "/etc/mail/creds.new"); if(!copy_to(creds, creds_new)) {
fclose(creds);
fclose(creds_new);
remove("/etc/mail/creds.new");
return INTERNAL_ERROR;
}
rewind(creds);
if(!backup(creds)) {
fclose(creds);
fclose(creds_new);
remove("/etc/mail/creds.new");
return INTERNAL_ERROR;
}
fclose(creds_new); fclose(creds_new);
fclose(creds); fclose(creds);

View file

@ -60,7 +60,7 @@
return "Новые пароли не совпадают."; return "Новые пароли не совпадают.";
} }
if(response_body == "250") { if(response_body == "250") {
return "Неправильно заполнены поля, или слишком длинные данные (макс. 2048 байтов на поле)."; return "Пустые, неправильно заполненные или слишком длинные поля (макс. 2048 байтов на поле).";
} }
return response_body; return response_body;
} }

View file

@ -52,7 +52,7 @@ func validate_chpasswd_input(reader io.Reader) ([]byte, bool) {
line_start := 0 line_start := 0
for i < l { for i < l {
if buf[i] == '\n' { if buf[i] == '\n' {
if(i + 1 - line_start > 2049 || i + 1 - line_start == 0) { if(i + 1 - line_start > 2049 || i + 1 - line_start == 1) {
return nil, false return nil, false
} }