Исправлено две уязвимости:
* Веб-сервер не отклонял новый пароль длинной 0. chpasswd.c предполагает, что длина вывода генератора хеша пароля хотя бы 1, но при таком пароле его длина тоже 0. Таким образом генерировался неправильный новый файл creds. * Даже неправильные запросы заставляли chpasswd.c делать резервные копии файла creds. Поэтому, любой человек мог совершить DoS атаку, отправив очень много запросов и заставив chpasswd.c сделать очень много резервных копий. Теперь, creds копируется только после правильных запросов. Немного отформатирован файл chpasswd.c, а файл webserver.go был перетабулирован.
This commit is contained in:
parent
c3ad534959
commit
3b9ea77879
3 changed files with 130 additions and 106 deletions
70
chpasswd.c
70
chpasswd.c
|
@ -36,6 +36,7 @@
|
|||
#define USER_NOT_FOUND -3
|
||||
#define INCORRECT_OLD_PASSWORD -4
|
||||
#define PASSWORD_MISMATCH -5
|
||||
#define INVALID_INPUT -6
|
||||
#define OLD_PATH_BASE 20
|
||||
#define BUF_LEN 64 * 1024
|
||||
|
||||
|
@ -61,42 +62,41 @@ int open_creds_old(size_t i) {
|
|||
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;
|
||||
|
||||
while(!feof(src)) {
|
||||
readen = fread(buf, 1, BUF_LEN, src);
|
||||
written = fwrite(buf, 1, readen, dest);
|
||||
if(written != readen) {
|
||||
fclose(src);
|
||||
fclose(dest);
|
||||
remove(destPath);
|
||||
|
||||
exit(INTERNAL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void backup(FILE * restrict creds) {
|
||||
int backup(FILE * restrict creds) {
|
||||
int creds_old_fd = open_creds_old(OLD_PATH_BASE);
|
||||
if(creds_old_fd == -1) {
|
||||
fclose(creds);
|
||||
|
||||
exit(INTERNAL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
FILE * creds_old = fdopen(creds_old_fd, "w");
|
||||
if(creds_old == NULL) {
|
||||
fclose(creds);
|
||||
close(creds_old_fd);
|
||||
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);
|
||||
rewind(creds);
|
||||
if(!ret) {
|
||||
remove(creds_old_path);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
char * found_user_line(
|
||||
|
@ -176,8 +176,6 @@ int main(int argc, char ** argv) {
|
|||
return INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
backup(creds);
|
||||
|
||||
|
||||
// Формирование нового файла creds
|
||||
FILE * creds_new = fopen("/etc/mail/creds.new", "w");
|
||||
|
@ -189,16 +187,18 @@ int main(int argc, char ** argv) {
|
|||
|
||||
char * line = NULL;
|
||||
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);
|
||||
fputs(username, creds_new);
|
||||
fputc(':', creds_new);
|
||||
|
||||
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)) {
|
||||
fclose(creds);
|
||||
fclose(creds_new);
|
||||
|
@ -208,6 +208,7 @@ int main(int argc, char ** argv) {
|
|||
}
|
||||
|
||||
|
||||
// Получаем хеш нового пароля
|
||||
int inpipe[2], outpipe[2], status;
|
||||
size_t new_hash_len;
|
||||
pipe(inpipe);
|
||||
|
@ -236,7 +237,6 @@ int main(int argc, char ** argv) {
|
|||
|
||||
write(inpipe[1], password, password_len);
|
||||
close(inpipe[1]);
|
||||
|
||||
wait(&status);
|
||||
if(status) {
|
||||
fclose(creds);
|
||||
|
@ -248,14 +248,38 @@ int main(int argc, char ** argv) {
|
|||
|
||||
new_hash_len = read(outpipe[0], buf, 64 * 1024);
|
||||
close(outpipe[0]);
|
||||
// Основывается на том, что последний символ вывода smtpctl encrypt - '\n'
|
||||
buf[new_hash_len - 1] = '\0';
|
||||
if(new_hash_len == 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);
|
||||
|
||||
|
||||
// Копируем строку пользователя и весь оставшийся файл до конца
|
||||
fputc(':', 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);
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
return "Новые пароли не совпадают.";
|
||||
}
|
||||
if(response_body == "250") {
|
||||
return "Неправильно заполнены поля, или слишком длинные данные (макс. 2048 байтов на поле).";
|
||||
return "Пустые, неправильно заполненные или слишком длинные поля (макс. 2048 байтов на поле).";
|
||||
}
|
||||
return response_body;
|
||||
}
|
||||
|
|
160
webserver.go
160
webserver.go
|
@ -38,88 +38,88 @@ var index_text []byte
|
|||
var passwd_file_mutex sync.Mutex
|
||||
|
||||
func validate_chpasswd_input(reader io.Reader) ([]byte, bool) {
|
||||
buf := make([]byte, 2049 * 4)
|
||||
l, _ := reader.Read(buf)
|
||||
buf := make([]byte, 2049 * 4)
|
||||
l, _ := reader.Read(buf)
|
||||
|
||||
eof_check := make([]byte, 1)
|
||||
n, err := reader.Read(eof_check)
|
||||
if n != 0 || err != io.EOF {
|
||||
return nil, false
|
||||
}
|
||||
eof_check := make([]byte, 1)
|
||||
n, err := reader.Read(eof_check)
|
||||
if n != 0 || err != io.EOF {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
i := 0
|
||||
lines_found := 0
|
||||
line_start := 0
|
||||
for i < l {
|
||||
if buf[i] == '\n' {
|
||||
if(i + 1 - line_start > 2049 || i + 1 - line_start == 0) {
|
||||
return nil, false
|
||||
}
|
||||
i := 0
|
||||
lines_found := 0
|
||||
line_start := 0
|
||||
for i < l {
|
||||
if buf[i] == '\n' {
|
||||
if(i + 1 - line_start > 2049 || i + 1 - line_start == 1) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
line_start = i + 1
|
||||
lines_found++
|
||||
if(lines_found == 4) {
|
||||
break
|
||||
}
|
||||
line_start = i + 1
|
||||
lines_found++
|
||||
if(lines_found == 4) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if lines_found != 4 || i + 1 != l {
|
||||
return nil, false
|
||||
}
|
||||
return buf, true
|
||||
if lines_found != 4 || i + 1 != l {
|
||||
return nil, false
|
||||
}
|
||||
return buf, true
|
||||
}
|
||||
|
||||
func handle_request(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.URL.Path != "/" {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
writer.Write(status404_text)
|
||||
} else {
|
||||
if request.URL.Path != "/" {
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
writer.Write(status404_text)
|
||||
} else {
|
||||
if request.Method == http.MethodGet {
|
||||
writer.Write(index_text)
|
||||
} else if request.Method == http.MethodPost {
|
||||
passwd_file_mutex.Lock()
|
||||
writer.Write(index_text)
|
||||
} else if request.Method == http.MethodPost {
|
||||
passwd_file_mutex.Lock()
|
||||
|
||||
lines, could_read := validate_chpasswd_input(request.Body)
|
||||
if !could_read {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
lines, could_read := validate_chpasswd_input(request.Body)
|
||||
if !could_read {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
writer.Write([]byte("250")) // -6, INVALID_INPUT
|
||||
|
||||
passwd_file_mutex.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
chpasswd := exec.Command("./chpasswd", "./chpasswd")
|
||||
chpasswd.Stdin = strings.NewReader(string(lines))
|
||||
chpasswd_res := chpasswd.Run()
|
||||
if chpasswd_res != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
writer.Write([]byte(strconv.Itoa(chpasswd_res.(*exec.ExitError).ExitCode())))
|
||||
chpasswd := exec.Command("./chpasswd", "./chpasswd")
|
||||
chpasswd.Stdin = strings.NewReader(string(lines))
|
||||
chpasswd_res := chpasswd.Run()
|
||||
if chpasswd_res != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
writer.Write([]byte(strconv.Itoa(chpasswd_res.(*exec.ExitError).ExitCode())))
|
||||
|
||||
passwd_file_mutex.Unlock()
|
||||
passwd_file_mutex.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
update := exec.Command("/sbin/smtpctl", "update", "table", "creds")
|
||||
update_res := update.Run()
|
||||
if update_res != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
writer.Write([]byte("255")) // -1, INTERNAL_ERROR
|
||||
update := exec.Command("/sbin/smtpctl", "update", "table", "creds")
|
||||
update_res := update.Run()
|
||||
if update_res != nil {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
writer.Write([]byte("255")) // -1, INTERNAL_ERROR
|
||||
|
||||
passwd_file_mutex.Unlock()
|
||||
passwd_file_mutex.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write([]byte("0"))
|
||||
writer.Write([]byte("0"))
|
||||
|
||||
passwd_file_mutex.Unlock()
|
||||
return
|
||||
passwd_file_mutex.Unlock()
|
||||
return
|
||||
} else {
|
||||
writer.WriteHeader(http.StatusNotImplemented)
|
||||
writer.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handle_favicon(writer http.ResponseWriter, request *http.Request) {
|
||||
|
@ -127,36 +127,36 @@ func handle_favicon(writer http.ResponseWriter, request *http.Request) {
|
|||
}
|
||||
|
||||
func get_file_text(name string) []byte {
|
||||
var err error
|
||||
var err error
|
||||
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
file_stat, err := file.Stat()
|
||||
if err != nil {
|
||||
file.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
text := make([]byte, file_stat.Size())
|
||||
file_stat, err := file.Stat()
|
||||
if err != nil {
|
||||
file.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
text := make([]byte, file_stat.Size())
|
||||
|
||||
_, err = file.Read(text);
|
||||
if err != nil {
|
||||
file.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
_, err = file.Read(text);
|
||||
if err != nil {
|
||||
file.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
return text
|
||||
file.Close()
|
||||
return text
|
||||
}
|
||||
|
||||
func main() {
|
||||
index_text = get_file_text("index.html")
|
||||
status404_text = get_file_text("404.html")
|
||||
index_text = get_file_text("index.html")
|
||||
status404_text = get_file_text("404.html")
|
||||
|
||||
http.HandleFunc("/favicon.ico", handle_favicon)
|
||||
http.HandleFunc("/", handle_request)
|
||||
http.ListenAndServe(":62272", nil)
|
||||
http.HandleFunc("/favicon.ico", handle_favicon)
|
||||
http.HandleFunc("/", handle_request)
|
||||
http.ListenAndServe(":62272", nil)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue