Перехват сообщения об ошибке от nginx
Как разработчики программного обеспечения, мы всегда заботимся о безопасности наших приложений и никогда не можем быть слишком осторожными. Всякий раз, когда одно из наших приложений становится общедоступным, мы крайне осторожны, чтобы не раскрыть личную информацию, которая может поставить под угрозу его работу или даже бизнес в целом.
Важнейшим моментом, на который мы все должны обратить внимание при публичном доступе к приложению, является то, как оно ведет себя при возникновении ошибки и что сообщается пользователю в этих сценариях. Плохо обработанный ответ на ошибку может не иметь большого значения для большинства пользователей, но будьте уверены, что для кого-то это будет иметь большое значение.
Чтобы помочь смягчить эти проблемы, в этой статье рассматривается использование функции proxy_intercept_errors
nginx.
Что такое nginx
Nginx — это широко используемый веб-сервер, который может иметь множество применений, наиболее распространенными из которых являются балансировщик нагрузки и обратный прокси. В этих случаях мы можем провести несколько обработок деталей запросов до и после обработки нашими приложениями.
Изображение ниже представляет собой базовое представление того, как можно использовать nginx:
Замена сообщений об ошибках
Чтобы увидеть перехват ошибок в действии, давайте создадим простой проект, используя Go, nginx и docker-compose.
Репозиторий примеров вы можете найти здесь.
Приложение
Наш пример — простое приложение Go, содержащее простой http-сервер со следующими маршрутами и соответствующими кодами возврата:
/success
,200
;/bad_request
,400
;/not_found
,404
;/internal_server_error
,500
.
package main
import (
"net/http"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Get("/success", func(c *fiber.Ctx) error {
c.Set("Content-Type", "application/json")
return c.SendString(`{"message": "success"}`)
})
app.Get("/bad_request", func(c *fiber.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Status(http.StatusBadRequest).SendString(`{"error": "The new password cannot be the same as the previous one"}`)
})
app.Get("/not_found", func(c *fiber.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Status(http.StatusNotFound).SendString(`{"error":"This resource is not found at database 'vuln'"`)
})
app.Get("/internal_server_error", func(c *fiber.Ctx) error {
c.Set("Content-Type", "application/json")
return c.Status(http.StatusInternalServerError).SendString(`{"error":"Error log with private information"`)
})
app.Listen(":8000")
}
Идея состоит именно в том, чтобы иметь возможность моделировать несколько кодов ошибок, чтобы проверить нашу способность перехвата ошибок. Представьте, что в реальном сценарии любой из этих возвратов может содержать информацию, к которой клиент не имеет доступа.
Конфигурация nginx
Очень просто, наш файл конфигурации nginx выглядел примерно так:
events{}
http {
server {
listen 80;
error_page 404 500 /custom_err.html;
error_page 400 @error400;
location / {
# proxy_intercept_errors on;
proxy_pass http://app:8000;
}
location = /custom_err.html {
root /usr/share/nginx/html;
internal;
}
location @error400 {
default_type application/json;
internal;
return 400 '{"error": {"status_code": 400,"status": "Bad Request"}}';
}
}
}
Да, закомментированная строка сделана намеренно.
Тестирование приложения
Прежде чем мы лучше поймем, как все это работает, давайте посмотрим на наш пример в действии. Выполните следующую команду, чтобы запустить наше приложение:
$ make run
Как только все заработает, давайте проверим одну из конечных точек ошибки:
$ curl -i localhost/not_found
Результат должен быть таким:
HTTP/1.1 404 Not Found
Server: nginx/1.25.3
Date: Sun, 12 Nov 2023 02:42:58 GMT
Content-Type: application/json
Content-Length: 56
Connection: keep-alive
{"error":"This resource is not found at database 'vuln'"
И вуаля! Нам поступил ответ с компрометирующей информацией.
Перехват ошибок
Если убрать комментарий, то наше местоположение /
выглядит так:
#...
location / {
proxy_intercept_errors on;
proxy_pass http://app:8000;
}
#...
Вызвав ту же конечную точку, что и раньше, мы получим новый возврат:
HTTP/1.1 404 Not Found
Server: nginx/1.25.3
Date: Sun, 12 Nov 2023 02:43:50 GMT
Content-Type: text/html
Content-Length: 250
Connection: keep-alive
ETag: "654cd606-fa"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error page</title>
</head>
<body>
<h1>Ops... This is a custom error page</h1>
</body>
</html>
Это означает, что Nginx перехватил наш возврат ошибки и заменил его возвратом с нашей специальной страницы ошибок. И согласно документации, эта функция по умолчанию отключена.
Также возможно вернуть некоторые другие форматы ответа, например, когда мы выполняем команду:
$ curl -i localhost/bad_request
Что возвращает следующий ответ:
HTTP/1.1 400 Bad Request
Server: nginx/1.25.3
Date: Sun, 12 Nov 2023 02:57:55 GMT
Content-Type: application/json
Content-Length: 55
Connection: close
{"error": {"status_code": 400,"status": "Bad Request"}}
Как вы уже можете видеть, возвращаемый ответ определяется конфигурацией error_page.
Заключение
Как бы просто это ни было, перехват ошибок с помощью nginx может помочь решить несколько проблем. В этом посте продемонстрирован один из многих способов использования этой функции.
Не забудьте протестировать представленный здесь пример на себе и изучить самые разнообразные способы использования этой и других возможностей nginx.