company_banner

Gixy — open source от Яндекса, который сделает конфигурирование Nginx безопасным

    Nginx, однозначно, один из крутейших веб-серверов. Однако, будучи в меру простым, довольно расширяемым и производительным, он требует уважительного отношения к себе. Впрочем, это относится к почти любому ПО, от которого зависит безопасность и работоспособность сервиса. Признаюсь, нам нравится Nginx. В Яндексе он представлен огромным количеством инсталляций с разнообразной конфигурацией: от простых reverse proxy до полноценных приложений. Благодаря такому разнообразию у нас накопился некий опыт его [не]безопасного конфигурирования, которым мы хотим поделиться.



    Но обо всем по порядку. Нас давно терзал вопрос безопасного конфигурирования Nginx, ведь он — полноправный кубик веб-приложения, а значит, и его конфигурация требует не меньшего контроля с нашей стороны, чем код самого приложения. В прошлом году нам стало очевидно, что этот процесс требует серьезной автоматизации. Так начался in-house проект Gixy, требования к которому мы обозначили следующим образом:

    — быть простым;
    — но расширяемым;
    — с возможностью удобного встраивания в процессы тестирования;
    — неплохо бы уметь резолвить инклюды;
    — и работать с переменными;
    — и про регулярные выражения не забыть.

    Признаться, мы до последнего колебались с выбором языка (между Golang и Python). В итоге был выбран Python с надеждой на то, что он более распространен, а значит, будет чуть проще с развитием.

    О проблемах


    На этом покончим со вступлением и перейдем к примерам распространенных проблем :) Чтобы избежать путаницы в будущем, во всех примерах использовалась текущая mainline версия Nginx — 1.13.0.

    Server-Side-Request-Forgery

    Server Side Request Forgery — уязвимость, позволяющая выполнять различного рода запросы от имени веб-приложения (в нашем случае от имени Nginx). Возникает, когда злоумышленник может контролировать адрес проксируемого сервера — например, в случае некорректной настройки XSendfile.

    По своему опыту могу сказать, что зачастую уязвимость связана с несколькими ошибками:

    — отсутствие директивы internal. Ее смысл заключается в указании того, что определенный location может использоваться только для внутренних запросов;
    — небезопасное внутреннее перенаправление.

    Если с первым случаем все понятно, то с внутренним перенаправлением дела обстоят не так просто. Полагаю, многие из вас видели/писали подобную конфигурацию:

    location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
        internal;
    
        proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
        proxy_set_header Host $proxy_host;
    }

    К сожалению, в такой конфигурации вам необходимо проверить как минимум все директивы rewrite и try_files, так как согласно документации:

    Внутренними запросами являются:
    – запросы, перенаправленные директивами error_page, index, random_index и try_files;
    – запросы, перенаправленные с помощью поля “X-Accel-Redirect” заголовка ответа вышестоящего сервера;
    – подзапросы, формируемые командой “include virtual” модуля ngx_http_ssi_module и директивами модуля ngx_http_addition_module;
    – запросы, изменённые директивой rewrite.

    Получается, любой неосторожный реврайт позволит сделать запрос в internal location. В этом довольно легко убедиться:

    – конфигурация:

    location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
        internal;
    
        return 200 "proto: $proxy_proto\nhost: $proxy_host\npath: $proxy_path";
    }
    
    rewrite ^/(?!_api)(.*)/\.files/(.*)$ /$1/.download?file=$2 last;

    – эксплуатация:

    GET /internal-proxy/http/evil.com/.files/some HTTP/1.0
    Host: localhost
    
    
    
    HTTP/1.1 200 OK
    Content-Length: 42
    Content-Type: application/octet-stream
    Date: Fri, 28 Apr 2017 13:55:51 GMT
    Server: nginx/1.13.0
    
    proto: http
    host: evil.com
    path: .download
    

    В данной ситуации мы обычно рекомендуем несколько практик:

    — использовать только internal location для проксирования;
    — по возможности запретить передачу пользовательских данных;
    — обезопасить адрес проксируемого сервера:

    • если количество проксируемых хостов ограничено (например, у вас S3), то лучше их захардкодить и выбирать при помощи map или иным удобным для вас образом;
    • если по какой-то причине нет возможности перечислить все возможные хосты для проксирования, его стоит подписать.

    Плохие регулярные выражения для валидации реферера или ориджина

    У вас есть проблема. Вы решили использовать регулярные выражения, чтобы её решить.
    – Теперь у вас две проблемы.

    Нередко валидация заголовка запроса «Referer» или «Origin» делается при помощи регулярного выражения. Зачастую это необходимо для условного выставления заголовка X-Frame-Options (защита от ClickJacking) или реализации Cross-Origin Resource Sharing (CORS). И если с валидацией «Referer» все немного проще и, с некоторыми условиями, можно отказаться от регулярного выражения в пользу модуля ngx_http_referer_module, то с «Origin» не все так однозначно.

    Мы выделяем два основных класса проблем:

    — ошибки в составлении регулярного выражения;
    — разрешение недоверенных third-party доменов.

    Проблемная конфигурация выглядит следующим образом:

    if ($http_origin ~* ((^https://www\.yandex\.ru)|(^https://ya\.ru)/)) {
    	add_header 'Access-Control-Allow-Origin' "$http_origin";
    	add_header 'Access-Control-Allow-Credentials' 'true';
    }

    На самом деле я очень упростил регулярное выражение, но даже в этом примере увидеть проблему с первого раза не столь просто. Людям проще писать регулярные выражения, нежели читать их.

    К счастью, машине несвойственна эта проблема, поэтому Gixy умеет самостоятельно определять, что это регулярное выражение сматчит www.yandex.ru.evil.com как валидный origin и сообщит вам об этом:

    $ gixy --origins-domains yandex.ru,ya.ru /etc/nginx/nginx.conf
    
    ==================== Results ===================
    
    Problem: [origins] Validation regex for "origin" or "referrer" matches untrusted domain.
    Description: Improve the regular expression to match only trusted referrers.
    Additional info: https://github.com/yandex/gixy/blob/master/docs/ru/plugins/origins.md
    Reason: Regex matches "https://www.yandex.ru.evil.com" as a valid origin.
    Pseudo config:
    include /etc/nginx/sites/default.conf;
    
    	server {
    		server_name _;
    
    		if ($http_origin ~* ((^https://www\.yandex\.ru)|(^https://ya\.ru)/)) {
    		}
    	}

    Или, если считать ya.ru недостаточно доверенным, сообщит о ориджинах ya.ru и www.yandex.ru.evil.com:

    $ gixy --origins-domains yandex.ru /etc/nginx/nginx.conf
    
    ==================== Results ===================
    
    Problem: [origins] Validation regex for "origin" or "referrer" matches untrusted domain.
    Description: Improve the regular expression to match only trusted referrers.
    Additional info: https://github.com/yandex/gixy/blob/master/docs/ru/plugins/origins.md
    Reason: Regex matches "https://www.yandex.ru.evil.com", "https://ya.ru/" as a valid origin.
    Pseudo config:
    include /etc/nginx/sites/default.conf;
    
    	server {
    		server_name _;
    
    		if ($http_origin ~* ((^https://www\.yandex\.ru)|(^https://ya\.ru)/)) {
    		}
    	}

    HTTP Splitting

    HTTP Splitting используется для атак на приложение, стоящее за Nginx (HTTP Request Splitting), или на клиентов приложения (HTTP Response Splitting). Уязвимость возникает в случае, когда атакующий может внедрить символ перевода строки \n в запрос или ответ, формируемый Nginx.

    Безотказного совета (кроме как быть внимательными) у меня нет, но всегда следует обращать внимание на несколько вещей:

    — какие переменные используются в директивах, отвечающих за формирование запросов (могут ли они содержать CRLF), например: rewrite, return, add_header, proxy_set_header и proxy_pass;
    — используются ли переменные $uri и $document_uri, и если да, то в каких директивах, так как они гарантированно содержат урлдекодированное значение;
    — уделить особое внимание переменным, полученным из групп с исключающим диапазоном: (?P[^.]+).

    Пример с исключающим диапазоном:

    — конфигурация:

    server {
        listen 80 default;
    
        location ~ /v1/((?<action>[^.]*)\.json)?$ {
            add_header X-Action $action;
            return 200 "OK";
        }
    }

    — эксплуатация:

    GET /v1/see%20below%0d%0ax-crlf-header:injected.json HTTP/1.0
    Host: localhost
    
    
    
    HTTP/1.1 200 OK
    Content-Length: 2
    Content-Type: application/octet-stream
    Date: Fri, 28 Apr 2017 13:57:28 GMT
    Server: nginx/1.13.0
    X-Action: see below
    x-crlf-header: injected
    
    OK

    Как вы видите, мы смогли добавить заголовок ответа x-crlf-header: injected. Это случилось благодаря стечению нескольких обстоятельств:

    — add_header не кодирует/валидирует переданные ему значения, считая, что автор знает о последствиях;
    — значение пути нормализуется перед обработкой локейшена;
    — переменная $action была выделена из группы регулярного выражения с исключающим диапазоном: [^.]*;
    — таким образом, значение переменной $action стало равно see below\r\nx-crlf-header:injected и попало в HTTP-ответ.

    К счастью, Gixy с немалым успехом справляется с этой задачей:

    — он знает об «опасных» переменных — точнее, он знает о допустимом множестве символов в большинстве встроенных переменных. Таким образом, отличие $request_uri от $uri для него очевидно;
    — умеет выделять переменные из групп регулярного выражения;
    — умеет определять, может ли какой-либо символ (в нашем случае \n) сматчиться регулярным выражением (или отдельно взятой группой).

    Другой интересный пример — реврайт с помощью try_files:

    — конфигурация:

    server {
        listen 80 default;
    
        location / {
            try_files $uri $uri/ /index.php?q=$uri;
        }
    
        location ~ \.php {
            proxy_set_header X-Real-IP  $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header Host $host;
            proxy_pass http://127.0.0.1:9000;
        }
    }

    — эксплуатация (на 127.0.0.1:9000 слушает отладочный echo-сервер):

    GET /request%20HTTP/1.0%0aInjection: HTTP/1.0
    Host: localhost
    
    
    
    HTTP/1.1 200 Ok
    Content-Length: 244
    Content-Type: text/plain
    Date: Fri, 28 Apr 2017 13:59:18 GMT
    Server: nginx/1.13.0
    
    GET /index.php?q=/request HTTP/1.0\n
    Injection: HTTP/1.0\r\n
    X-Real-IP: 127.0.0.1\r\n
    X-Forwarded-For: 127.0.0.1\r\n
    Host: localhost\r\n
    Connection: close\r\n
    User-Agent: HTTPie/0.9.8\r\n
    Accept-Encoding: gzip, deflate\r\n
    Accept: */*\r\n
    \r\n

    Что делать?

    — Старайтесь использовать более безопасные переменные, например $request_uri вместо $uri.
    — Запретите перевод строки в исключающем диапазоне, например /some/(?[^/\s]+) вместо /some/(?[^/]+.
    — Возможно, хорошей идеей будет добавить валидацию $uri (только если вы знаете, что делаете).

    Переопределение «вышестоящих» заголовков ответа директивой add_header

    Это известная особенность Nginx, о которую спотыкались и будут продолжать спотыкаться многие из нас. Суть крайне проста — если у вас устанавливаются заголовки на одном уровне (например, в серверной секции), а уровнем ниже (например, в локейшене) устанавливаются какие-либо еще, то первый не будет применен.

    Наиболее простой пример выглядит следующим образом:

    server {
        listen 80 default;
        server_name _;
    
        add_header X-Content-Type-Options nosniff;
        location / {
            add_header X-Frame-Options DENY;
        }
    }

    В данном случае заголовок ответа X-Content-Type-Options не будет установлен при обработке локейшена /.

    Gixy с успехом расскажет вам об этом:

    $ gixy /etc/nginx/nginx.conf
    
    ==================== Results ===================
    
    Problem: [add_header_redefinition] Nested "add_header" drops parent headers.
    Description: "add_header" replaces ALL parent headers. See documentation: http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header
    Additional info: https://github.com/yandex/gixy/blob/master/docs/ru/plugins/addheaderredefinition.md
    Reason: Parent headers "x-content-type-options" was dropped in current level
    Pseudo config:
    include /etc/nginx/sites/default.conf;
    
    	server {
    		server_name _;
    		add_header X-Content-Type-Options nosniff;
    
    		location / {
    			add_header X-Frame-Options DENY;
    		}
    	}

    Мне известно несколько способов решить эту проблему:

    — продублировать важные заголовки;
    — устанавливать заголовки на одном уровне, например в серверной секции;
    — рассмотреть вариант с использованием модуля ngx_headers_more.

    Каждый из них имеет свои преимущества и недостатки. Какой предпочесть, зависит от вас.

    О Gixy


    Надеюсь, я вас убедил в том, что конфигурация Nginx требует более пристального внимания. Я также верю в то, что статический анализ конфигураций Nginx может работать (это также подтверждает опыт Nginx Amplify). К сожалению, не всегда есть возможность автоматически определить все пограничные случаи или специфичные особенности приложения, стоящего за Nginx. Так, к примеру, я не стал включать в стандартный набор проверку переопределения заголовков запроса X-Forwarded-*, так как реакция на них зависит от приложения, а в некоторых случаях к ним и вовсе нельзя притрагиваться (например, при множественном проксировании). Но у себя вы можете сделать нужные вам проверки, основываясь на более глубоком понимании работы приложения. Да, сейчас Gixy не умеет определять весь спектр известных нам проблем, но учится и, возможно, с вашей помощью начнет делать это лучше и полнее.

    Если же говорить о сценариях использования, то для себя мы выделили несколько типовых случаев:

    — запуск в тестовой среде, где установлен nginx;
    — веб-приложение для проверки отдельно взятого блока. Это бывает полезно, когда вам встретился подозрительный участок конфига;
    — HTTP API для интеграции с CI или тонкими клиентами.

    Нам кажется, что наиболее интересен вариант с использованием HTTP API для тонких клиентов. Ведь в таком случае мы можем централизованно управлять нужными нам проверками, обновлять их и так далее. К счастью, современные версии nginx обладают ключом -T для тестирования конфигурации и дампа оной, а Gixy умеет парсить этот формат.

    Сами посудите, насколько это удобно
    $ nginx -T | http -v https://gixy/api/check Content-Type:'application/nginx'
    POST /api/check HTTP/1.1
    Accept: application/json, */*
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Content-Length: 959
    Content-Type: application/nginx
    Host: gixy
    User-Agent: HTTPie/0.9.8

    # configuration file /etc/nginx/nginx.conf:
    user http;
    worker_processes 1;

    #daemon on;
    events {
    worker_connections 1024;
    }

    http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;
    gzip on;
    access_log /var/log/nginx/access.log combined;
    error_log /var/log/nginx/error.log debug;

    include sites/*.conf;
    }

    # configuration file /etc/nginx/mime.types:
    types {
    text/html html htm shtml;
    text/css css;
    text/xml xml;
    image/gif gif;
    image/jpeg jpeg jpg;
    application/javascript js;
    application/atom+xml atom;
    application/rss+xml rss;
    }

    # configuration file /etc/nginx/sites/default.conf:
    server {
    listen 80;
    return 301 https://some$uri;
    }

    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Type: application/json
    Date: Tue, 24 Apr 2017 19:45:57 GMT
    Keep-Alive: timeout=120
    Server: nginx
    Transfer-Encoding: chunked

    {
    "result": [
    {
    "auditor": "http_splitting",
    "config": "\ninclude /etc/nginx/sites/default.conf;\n\n\tserver {\n\t\treturn 301 https://some$uri;\n\t}",
    "description": "Текущая конфигурация позволяет злоумышленнику внедрить символ перевода строки (\"\\n\") в запрос или ответ формируемый nginx. В первую очередь это касается директив: rewrite, return и proxy_pass.",
    "help_url": "https://wiki/product-security/gixy/httpsplitting/",
    "reason": "At least variable \"$uri\" can contain \"\\n\"",
    "recommendation": "ограничьте допустимый набор символов, зачастую достаточно использовать более безопасное значение (e.g. \"$request_uri\" вместо \"$uri\").",
    "severity": "HIGH",
    "summary": "Обнаружена уязвимость типа HTTP Splitting"
    }
    ],
    "status": "ok",
    "warnings": []
    }


    Напоследок хотелось бы подчеркнуть тот факт, что это первая публичная alpha-версия Gixy, поэтому API может изменяться без сохранения обратной совместимости. В связи с этим, если у вас есть необходимость в реализации собственного плагина, лучше написать Issue или прислать Pull Request — тогда мы вместе что-то придумаем.

    Надеюсь, наш опыт был вам интересен и полезен, и, быть может, даже заставил пересмотреть свои конфигурации еще раз;)
    Метки:
    Яндекс 601,51
    Как мы делаем Яндекс
    Поделиться публикацией
    Комментарии 21
    • +2

      А зачем вы используете для валидации Origin директиву if если есть более "правильный" способ — map?

      • +5
        У меня нет ответа на вопрос, почему if используется чаще, чем map. Map, конечно же, тоже используется для валидации, но заметно реже:( Я это вижу как в Яндексе так и за его пределами.
        Именно поэтому я и начал с валидации регулярок в if. В будущем планирую добавить проверки для map (там будут примерно те же проблемы) и возможно добавить какую-то рекомендательную проверку по переписыванию if на map. Тут есть над чем подумать:)
        • 0

          Чаще всего проблема с map, в том, что он должен идти где-то в http локейшионе и получается, что map слегка оторван от конкретного виртуал хоста.


          if'ы же можно поместить внутрь server/location контекстов и поэтому с ними слегка проще работать. Особенно если виртуалхосты имеют тенденцию мигрировать между серверами.

      • –3
        За безопасные конфиги спасибо. А вот ставить ваши бинарники постерегусь.
        • +5
          Ваше право:)
          • –8
            А вдруг кто действительно установит?

          • 0
            Спасибо, интересный инструмент. Есть Roadmap? Планируете ли еще что-то добавить?
            • +3
              Точного roadmap пока нет, примерные планы выглядят так:
              — улучшить работу с переменными, добавить новых директив которые могут их предоставлять. Например, сейчас нет поддержки server_name :(
              — продолжить улучшать работу с регулярками, что мы можно было делать более хитрые проверки. Кстати, я его обернул в самостоятельное приложение (бывает полезно, когда нужно быстренько проверить регулярочку): Regex Ninja
              — разобраться с разными стьюпидами и написать хорошую документацию

              Ну и, конечно же, написать новых проверок:)
              Например, для Open Redirect'ов в реврайтах:
                  location /a {
                      rewrite (.*)$ https://example.com$1 permanent;
                  }
              
                  location /b {
                      rewrite ^/(.*)$ https://example.com$1 permanent;
                  }
              

              Экплуатация:
              $ http -h http://localhost/a%0a.evil.com
              HTTP/1.1 301 Moved Permanently
              Connection: keep-alive
              Content-Length: 185
              Content-Type: text/html
              Date: Sat, 29 Apr 2017 10:07:21 GMT
              Location: https://example.com.evil.com
              Server: nginx/1.13.0
              
              $ http -h http://localhost/b.evil.com
              HTTP/1.1 301 Moved Permanently
              Connection: keep-alive
              Content-Length: 185
              Content-Type: text/html
              Date: Sat, 29 Apr 2017 10:07:40 GMT
              Location: https://example.comb.evil.com
              Server: nginx/1.13.0
              
              
            • +1

              Спасибо за очень интересный инструмент, который поможет закрыть много дырок в конфигах.
              Хочу заметить, что часть дырок можно избежать заранее, если стараться по максимуму использовать map и избегать if (как завещал Игорь Сысоев).
              Например, это хорошо подходит для http_origin. Вариант с регуляркой в if


              if ($http_origin ~* ((^https://www\.yandex\.ru)|(^https://ya\.ru)/)) {
                  add_header 'Access-Control-Allow-Origin' "$http_origin";
                  add_header 'Access-Control-Allow-Credentials' 'true';
              }

              можно переделать на более безопасный вариант с map:


              map $http_origin $ok_origin {
                  "https://www.yandex.ru" "1";
                  "https://ya.ru" "1";
              }
              
              if ($ok_origin) {
                  add_header 'Access-Control-Allow-Origin' "$http_origin";
                  add_header 'Access-Control-Allow-Credentials' 'true';
              }
              

              Получается более безопасная, читабельная и масштабируемая конфигурация.
              Хотя, стоит признать, что в вашем примере была просто проблема с регуляркой — нужно было просто добавить $ в конец.


              Ещё хочу добавить — проблема с переопределением вышестоящих настроек встречается не только у add_header, но и у error_page.

              • 0
                А так же у proxy_set_header. Думаю, что список таких директив можно продолжить.
                • 0
                  Ещё хочу добавить — проблема с переопределением вышестоящих настроек встречается не только у add_header, но и у error_page.

                  А так же у proxy_set_header. Думаю, что список таких директив можно продолжить.

                  А давайте продолжим список! Главное, что бы это могло привести к проблемам безопасности.
                  К примеру, с proxy_set_header я могу придумать кейс в котором это может стать проблемой (хоть пока и не встречал). А вот не сознательное переопределение error_page не выглядит чем-то серьезным.

              • –1

                Скажу сразу, что создание тулзы, которая ищет косяки в конфигах, это крутое занятие и я уважаю эту работу.


                Ну и… висела ваша статья перез глазами у меня несколько дней. Вроде и придраться как-бы не к чему, так как привые конфиги отбраковываются, а утилитка показывает как все плохо.


                И вот сегодня ягодка созрела: зачем вообще писать такие кривые конфиги? Ваша тулза будет хороша начинающим юзерам, которые конфигурируют nginx. Если же человек руку набил на нем и набил руку на регулярках, то пользы как бы и нет.


                Вообще весь смысл статьи в том, что надо правильно писать регулярки, или не писать из вовсе, если не умеешь (например отличная альтернатива с map).


                location ~ /v1/((?<action>[^.]*)\.json)?$ { — вообще режет глаз.
                v1/((?<action>[^\\w]*)\.json)? — верный вариант. Если там встречаются {|_ и т.д., то надо добавить их в "словарик" или просто оторвать яйца тому, что апи разрабывает, так как он ничерта не понимает в том, как надо разрабатывать. Так что этот пример из неоткуда, и в нормальной жизни (и в компании яндекс, надеюсь), он не может случится.


                try_files $uri $uri/ /index.php?q=$uri; — не видел ни одного гайда для нубов, где бы использовали $uri, вместо $request_uri в этой связке.


                Ваши примеры реврайтов из comment_10197332
                location /a { return 302 https://example.com$request_uri; }
                правильный вариант, и рабочий пример https://0x10k.com/a%0A.evil.com, и оффиц. сайт об этом трындит.


                Я все к тому, что у вас очень уж простые случаи, ничего серьезного. Было бы больше ценности статьи (и меньше маркетинга пиара), если бы вы показали серьезные ляпы и их детект.

                • 0
                  Классная идея! Хотелось бы только узнать на сколько она применима в случае если у меня на серверах стоит какая-нибудь панель управления хостингом типа ИСП\Адженти\Весты\Плеска? Там ведь конфиги могут перезаписываться если кто-то изменения через панель внес. А так — спасибо авторам, буду пробовать там где панелек нет :-)
                  • 0
                    Хотелось бы только узнать на сколько она применима в случае если у меня на серверах стоит какая-нибудь панель управления хостингом типа ИСП\Адженти\Весты\Плеска?

                    Честно говоря, ничего не могу сказать по этому поводу. Я не большой сторонник панелей управления, поэтому опыта работы с ними крайне мало. Быть может, кто-то с большим опытом подскажет как правильно сделать интеграцию, какие существуют подводные камни и вот это все. А может и сделает это самостоятельно ;-)
                  • +1
                    ==================== Results ===================
                    No issues found.

                    ==================== Summary ===================
                    Total issues:
                    Unspecified: 0
                    Low: 0
                    Medium: 0
                    High: 0


                    что я делаю не так? :)
                    • 0
                      А почему не использовать Яндексовый внутренний http-сервер phantom(который, например, в Яндекс танке используется) и аналогичный тул для него?
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • 0
                          яндекс может

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое