Как защитить сервис от gzip бомбы? - PullRequest
1 голос
/ 17 июня 2019

У меня есть test.gzip файл с json

{"events": [
{"uuid":"56c1718c-8eb3-11e9-8157-e4b97a2c93d3",
"timestamp":"2019-06-14 14:47:31 +0000",
"number":732,
"user": {"full_name":"0"*1024*1024*1024}}]}

полное имя_файла содержит 1 ГБ 0, размер файла в архиве ~ 1 МБ

как я могу защитить свой сервис во время распаковки, чтобы мойпамять не закончилась?

func ReadGzFile(filename string) ([]byte, error) {
    fi, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer fi.Close()

    fz, err := gzip.NewReader(fi)
    if err != nil {
        return nil, err
    }
    defer fz.Close()

    s, err := ioutil.ReadAll(fz)
    if err != nil {
        return nil, err
    }
    return s, nil
}

func main() {
    b, err := ReadGzFile("test.gzip")
    if err != nil {
        log.Println(err)
    }
    var dat map[string]interface{}
    if err := json.Unmarshal(b, &dat); err != nil {
        panic(err)
    }
    fmt.Println(dat)
}

В этом случае вывод может убить мой сервис OOMKiller

Ответы [ 2 ]

4 голосов
/ 17 июня 2019

Что может быть обманчиво, так это то, что сжатый размер может быть значительно меньше допустимого (размер, который вы можете или хотите обработать). В вашем примере ввод составляет около 1 МБ, а несжатый размер - около 1 ГБ.

При чтении несжатых данных следует остановиться после достижения разумного предела. Чтобы легко сделать это, вы можете использовать io.LimitReader(), где вы можете указать максимальное количество байтов, которые вы хотите прочитать. Да, вам нужно обернуть поток в разархивированном виде , а не исходный сжатый поток.

Это пример того, как это будет выглядеть:

limited := io.LimitReader(fz, 2http.MaxBytesReader()1024)

s, err := ioutil.ReadAll(limited)

Приведенный выше пример ограничивает читаемые данные до 2 МБ. Что происходит, когда разархивированные данные больше, чем это? io.Reader, возвращаемый io.LimitReader() (что, кстати, io.LimitedReader) сообщит io.EOF. Это защищает ваш сервер от атаки, но, возможно, не лучший способ справиться с этим.

Поскольку вы упомянули, что это API для отдыха, лучшим решением будет аналогичное *1024*. Это оборачивает переданный читатель для чтения до заданного предела, и, если он достигнут, он возвращает ошибку, а также отправляет ошибку обратно клиенту HTTP, а также закрывает базовое устройство чтения. Если поведение по умолчанию http.MaxBytesReader() вам не подходит, проверьте его источники, скопируйте и измените его, это относительно просто. Настройте его под свои нужды.

Также обратите внимание, что вы не должны читать все (несжатые данные) в память. Вы можете передать «ограниченный читатель» на json.NewDecoder(), который будет считывать данные с данного читателя при декодировании входного JSON. Конечно, если переданный ограниченный читатель сообщает об ошибке, декодирование завершится неудачей.

2 голосов
/ 17 июня 2019

Не читайте все в память. Работайте в потоке, если это возможно. Это 100% возможно в вашем примере:

func ReadGzFile(filename string) (io.ReadCloser, error) {
    fi, err := os.Open(filename)
    if err != nil {
        return nil, err
    }

    return gzip.NewReader(fi)
}

func main() {
    b, err := ReadGzFile("test.gzip")
    if err != nil {
        log.Println(err)
    }
    defer b.Close()
    var dat map[string]interface{}
    if err := json.NewDecoder(b).Decode(&dat); err != nil {
        panic(err)
    }
    fmt.Println(dat)
}

Этот Decode подход имеет побочный эффект (который может или не может быть желательным) игнорирования любого мусора в потоке после первого допустимого объекта JSON. В вашем случае это похоже на выгоду. В некоторых случаях это может быть не так.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...