IPv4kupit-proxy-ipv4.ru
ГлавнаяПроверка и диагностика → Проверка анонимности

Проверка анонимности прокси: какие заголовки уходят на сервер

Проверка анонимности прокси: какие заголовки уходят на сервер, раздел «Проверка и диагностика» справочника по прокси IPv4

Проверка анонимности сводится к одному действию: снять сырой запрос на стороне приёмника и посмотреть, появились ли в нём служебные поля, которых мы не отправляли. Для этого поднимается своя страница-эхо на обычном порту, через посредник уходит запрос по HTTP, и полученный набор заголовков сверяется с набором прямого запроса построчно.

Разбор страницы «Проверка анонимности прокси: какие заголовки уходят на сервер» по разделам

Ниже разобрана именно методика замера. Как поднять приёмник за несколько минут, как снять сырой дамп через nc, какие поля искать, чем отличается сверка по составу от сверки по порядку, что делать с отпечатком рукопожатия TLS, как проверяться при работе по SOCKS5 и как загнать всю процедуру в скрипт, который гоняет список сам. Теория трёх уровней вынесена в отдельный разбор про уровни анонимности прокси, общая проверка доступности живёт в материале как проверить, что прокси работает, здесь только замер.

Что мы проверяем и чем это отличается от проверки доступности

Проверка доступности отвечает на вопрос «запрос проходит». Проверка анонимности отвечает на другой: «что из наших сведений дошло до сервера сверх адреса выходного узла». Это разные замеры, у них разный инструмент и разный признак успеха. Первый закрывается кодом ответа. Второй закрывается дампом запроса.

Замер держится на трёх вопросах. Первый: какой адрес виден в самом соединении, то есть в поле remote_addr на приёмнике. Второй: какие служебные поля дописал посредник в заголовки. Третий: совпадает ли отпечаток клиента с тем, за кого клиент себя выдаёт по строке User-Agent. Первые два вопроса закрываются приёмником на своём сервере, третий требует отдельной пробы по рукопожатию.

Важная оговорка про протокол. Дописать что-либо в заголовки посредник может только там, где он эти заголовки видит, то есть на обычном HTTP. При работе по HTTPS открывается туннель методом CONNECT, шифрованный поток проходит насквозь, и внутрь него узел не заглядывает. Отсюда прямое следствие для методики: замер заголовков всегда делается по HTTP, иначе любой узел покажет ровную картину просто потому, что вмешаться ему нечем. Устройство самого туннеля разобрано в материале про туннель CONNECT.

Мы делаем оба захода подряд. Сначала HTTP на свой приёмник и разбор полей, затем HTTPS на тот же домен и сверка адреса соединения. Пара замеров занимает минуту и даёт полную картину по узлу.

Заголовки-маркеры: полный список и что выдаёт каждый

Служебных полей, по которым читается работа через посредника, немного, и все они известны наперёд. Часть добавляют прокси-серверы вроде Squid по своим настройкам, часть дописывают балансировщики и обратные прокси на стороне площадки. Смотреть надо на всё сразу, потому что одно поле без адреса клиента ещё терпимо, а связка из двух даёт площадке готовый признак.

ЗаголовокЧто в нём лежитЧто выдаёт
X-Forwarded-ForАдрес клиента, иногда цепочка через запятуюПрямая выдача исходного адреса, худший случай
ForwardedПары for, by, proto, host одной строкойТо же самое в стандартизованном виде, читается так же
X-Real-IPОдин адрес клиента без цепочкиИсходный адрес, ставится обратными прокси
Client-IP, X-Client-IPАдрес клиентаСтарые варианты того же поля, встречаются реже
ViaВерсия протокола и имя узла, часто с версией софтаФакт посредника и его тип, адрес клиента не раскрывает
Proxy-ConnectionЗначение keep-alive или closeПоле от клиента к прокси, наружу уходить не должно
X-Proxy-ID, X-Cache, X-Cache-LookupИдентификаторы кеша узлаРабота через кеширующий прокси
X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-PortИсходная схема, домен и портПризнак разворота запроса на промежуточном узле
Proxy-AuthorizationЛогин и пароль доступа к посредникуПоле для узла, до площадки доходить не должно вовсе
Warning, AgeСлужебные пометки кешаОтвет отдан кешем узла, не самой площадкой

Отдельно про Via. Само по себе это поле сообщает только факт посредника и не раскрывает исходный адрес, поэтому оно относится к среднему уровню и для многих задач терпимо. Ровная картина без единого служебного поля держится там, где узел ничего не дописывает по своей конфигурации: так работают анонимные прокси без служебных заголовков, и проверить это на своём приёмнике можно за одну команду.

Значения полей читаются так же внимательно, как их имена. X-Forwarded-For умеет содержать цепочку через запятую, и тогда первым в ней стоит исходный клиент, дальше идут промежуточные узлы по порядку прохождения. Дубль одного и того же имени двумя строками означает, что запрос прошёл через два узла подряд и каждый дописал своё. Значение unknown вместо адреса ставят узлы, которые сообщают факт пересылки без выдачи клиента: такая строка к провалу не относится.

Ещё один признак живёт не в самих полях. Кеширующий узел иногда переписывает Accept-Encoding, срезает Accept до звёздочки и добавляет собственный User-Agent при разворотах. Такие правки заметны только при сверке с прямым запросом, потому что по одному дампу отличить свой заголовок от переписанного нельзя.

Своя страница-эхо: почему приёмник на своём сервере точнее готового чекера

Готовый чекер отвечает быстро, и в этом его вся польза. Дальше начинаются неудобства, которые прямо портят замер.

Первое: почти любой публичный сервис стоит за собственным обратным прокси либо за сетью доставки. Этот слой сам дописывает X-Forwarded-For и X-Real-IP, и в ответе они лежат рядом с полями вашего узла. Разобрать, кто именно их поставил, по такому ответу невозможно. Второе: сервис отдаёт разобранный JSON, где заголовки уже приведены к единому виду, склеены дубли и потерян исходный порядок строк. Третье: сервис видит весь ваш трафик проверки и держит свои ограничения по частоте, поэтому прогон списка из тысячи строк упирается в отказы.

Приёмник на своём сервере снимает все три неудобства сразу. Он стоит на голом порту без промежуточных слоёв, печатает байты ровно в том виде, в котором они пришли, и выдерживает столько запросов, сколько мы через него пропустим. Требования к нему простые: обычный HTTP без сети доставки перед ним, никаких редиректов, никаких cookie, никакой сторонней логики. Любая из этих вещей меняет набор полей в следующем запросе и путает картину.

Порт под приёмник берём нестандартный, например 9000: он не пересекается с рабочими службами и сразу виден в логах. Часть узлов пропускает наружу ограниченный список портов, и тогда приёмник переносится на 80 либо на 8080, где проходит любой посредник. Проверяется это одним прямым запросом до начала замера: ответ пришёл, значит порт открыт по всему маршруту.

Держим такой приёмник на отдельном домене на серверной машине, где кроме него ничего нет. Годится любая площадка с внешним адресом: под задачу хватает самой скромной конфигурации, потому что нагрузка на приёмник исчерпывается разбором пары килобайт текста. Как устроены сами узлы, через которые пойдёт проверка, описано на странице про серверные адреса на собственном оборудовании.

Сырой дамп запроса: nc и приёмник на Python

Самый короткий путь снять сырые байты занимает одну строку. На сервере запускается nc в режиме прослушивания, с клиентской машины через посредник уходит запрос, и в терминале появляется всё, что дошло.

# на приёмнике: слушаем порт и печатаем то, что придёт
nc -l -p 9000

# на клиенте: гоним обычный GET через посредник
curl -x http://45.132.19.7:8000 http://echo.example.net:9000/probe

nc ничего не отвечает, поэтому curl подождёт и оборвётся по таймауту. Для замера это неважно: запрос уже напечатан целиком. Когда нужен короткий ответ и приём нескольких проб подряд, порт переоткрывается циклом, а заготовленный ответ подаётся на вход nc.

# после каждого запроса порт открывается заново, пришедшие байты идут в консоль
while true; do
  printf 'HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok' \
    | nc -l -p 9000 -q 1
  echo '--- запрос принят ---'
done

Под регулярную работу удобнее приёмник на Python: он печатает запрос в консоль, возвращает его же в теле ответа и не требует внешних пакетов.

# echo9000.py: печатает сырой запрос и возвращает его обратно клиенту
import socketserver

class Dump(socketserver.StreamRequestHandler):
    def handle(self):
        raw = b""
        while b"\r\n\r\n" not in raw:
            line = self.rfile.readline()
            if not line:
                break
            raw += line
        text = raw.decode("latin-1")
        print("--- from %s ---" % self.client_address[0])
        print(text, end="", flush=True)
        payload = ("remote_addr: %s\n%s" % (self.client_address[0], text)).encode("latin-1")
        head = (
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/plain; charset=utf-8\r\n"
            "Content-Length: %d\r\n"
            "Connection: close\r\n\r\n" % len(payload)
        )
        self.wfile.write(head.encode("latin-1") + payload)

class Server(socketserver.ThreadingTCPServer):
    allow_reuse_address = True

Server(("0.0.0.0", 9000), Dump).serve_forever()

Запускается одной командой, работает на любой версии Python третьей ветки:

python3 echo9000.py
curl -s -x http://45.132.19.7:8000 http://echo.example.net:9000/probe

Ответ приходит текстом, первая строка отдаёт адрес соединения, дальше идёт запрос как он пришёл. Строка remote_addr должна показывать выходной узел пула. Если там оказался адрес рабочей машины, запрос ушёл мимо посредника, и разбирать надо конфиг клиента вместе с переменными окружения http_proxy и HTTPS_PROXY.

Проверку по логину и паролю снимаем той же командой, меняется только строка подключения. Список выдаётся в форматах IP:PORT и IP:PORT:LOGIN:PASS, и оба варианта проверяются одинаково.

curl -s -x http://user5521:[email protected]:8000 http://echo.example.net:9000/probe

Отдельно смотрим, чтобы в дампе не оказалось поля Proxy-Authorization. Оно предназначено узлу и до площадки доходить не должно ни при каких настройках.

Сравнение прямого и проксированного запроса построчно

Один дамп отвечает на половину вопроса. Вторую половину закрывает сверка: те же самые заголовки уходят напрямую, оба ответа кладутся в файлы, и разница читается штатным diff. Тогда видно и добавленные поля, и переписанные значения, и потерянные строки.

# прямой запрос
curl -s http://echo.example.net:9000/probe > direct.txt

# тот же запрос через посредник
curl -s -x http://45.132.19.7:8000 http://echo.example.net:9000/probe > proxied.txt

# разница построчно
diff -u direct.txt proxied.txt

Чтобы сверка была честной, оба запроса должны выйти с одинаковым набором заголовков. curl по умолчанию ставит свой User-Agent и Accept, поэтому проще задать их руками и убрать всё лишнее.

curl -s --http1.1 \
  -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
  -H 'Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8' \
  -H 'Accept-Encoding: gzip, deflate' \
  -x http://45.132.19.7:8000 http://echo.example.net:9000/probe

Читается вывод по одному правилу. Строки со знаком плюс, которых не было в прямом запросе, дописал посредник. Строки со знаком минус посредник срезал. Совпавшие строки прошли насквозь. Пустая разница по служебным полям и различие только в поле remote_addr означают ровный проход: до площадки дошёл наш запрос и адрес выходного узла.

Одна тонкость про стартовую строку. При работе через HTTP-прокси клиент отправляет в ней полный адрес вида GET http://echo.example.net:9000/probe HTTP/1.1, при прямом запросе там стоит только путь. Это штатное различие протокола, к анонимности оно отношения не имеет, и в разнице его надо просто пропустить. Заголовок Host при этом остаётся на месте в обоих случаях, поэтому по нему сверка проходит без оговорок.

Полезно прогнать сверку дважды с разным набором исходных полей. Первый заход с минимальным набором из четырёх заголовков показывает, что узел дописывает от себя. Второй заход с полным браузерным набором показывает, что он переписывает в присланном. Два прохода занимают полминуты и разделяют два разных типа вмешательства, которые при одном заходе сливаются в общую разницу. Обе схемы доступа, по обычному HTTP и через шифрованный туннель, входят в один пакет: как они устроены, описано на странице, где оформляется доступ по протоколам HTTP и HTTPS.

Порядок заголовков и отпечаток рукопожатия TLS

Состав полей это первый слой картины. Второй слой это их порядок. Каждый клиент выставляет заголовки в устойчивой последовательности, и площадки давно читают её как подпись: браузер Chrome даёт один порядок, Firefox другой, curl третий, библиотека requests четвёртый. Посредник, который переписывает запрос через свой парсер, порядок иногда меняет, и запрос перестаёт походить на браузерный при полностью ровном составе полей.

Снимается порядок из того же дампа одной командой.

# только имена полей, в том порядке, в котором они пришли
grep -o '^[A-Za-z0-9-]\+:' proxied.txt | nl
grep -o '^[A-Za-z0-9-]\+:' direct.txt  | nl

# сверка последовательностей
diff <(grep -o '^[A-Za-z0-9-]\+:' direct.txt) <(grep -o '^[A-Za-z0-9-]\+:' proxied.txt)

Пустой вывод последней команды означает, что последовательность прошла без правок. Любая переставленная строка это повод присмотреться к узлу.

Третий слой лежит ещё ниже, до первого байта HTTP. При рукопожатии TLS клиент отправляет сообщение ClientHello, где перечислены версия протокола, наборы шифров, поддерживаемые группы и расширения. Порядок этих списков складывается в устойчивый отпечаток, который считают многие площадки. Через CONNECT рукопожатие идёт насквозь, поэтому отпечаток формирует ваш клиент, и посредник на него не влияет. Проверяется он сравнением двух проб.

# рукопожатие напрямую
openssl s_client -connect tls.example.net:443 -servername tls.example.net -tls1_2 </dev/null

# то же рукопожатие через туннель CONNECT
openssl s_client -proxy 45.132.19.7:8000 -connect tls.example.net:443 -servername tls.example.net -tls1_2 </dev/null

Совпадение согласованного набора шифров и версии протокола в обоих выводах означает, что туннель прозрачный и подмены по пути нет. Расхождение говорит о разборе шифрованного потока где-то на маршруте: чаще всего это локальный антивирус либо фильтр в корпоративной сети, и проверяются они на клиентской машине.

Практический вывод из третьего слоя простой. Отпечаток curl заметно отличается от браузерного, поэтому для сценариев, где на той стороне сидит браузерная проверка, замер повторяют настоящим браузером с профилем из антидетекта. Как это устроено, разобрано на странице про прокси для антидетект-браузеров.

Проверка при работе по SOCKS5

У SOCKS другой слой работы. Протокол занимается установкой соединения: клиент передаёт узлу адрес и порт назначения, узел открывает канал и дальше перекладывает байты в обе стороны, содержимого потока он не разбирает. Дописать Via или X-Forwarded-For через SOCKS5 попросту некому.

Методика от этого не отменяется, у неё меняется предмет. По SOCKS5 мы проверяем три вещи: адрес соединения на приёмнике, полное совпадение состава заголовков с прямым запросом и сторону, на которой разрешается доменное имя.

# имя разбирает клиент, наружу уходит уже адрес
curl -s -x socks5://45.132.19.7:1080 http://echo.example.net:9000/probe

# имя разбирает узел, запрос к службе имён уходит с его стороны
curl -s -x socks5h://45.132.19.7:1080 http://echo.example.net:9000/probe

Разница в одной букве меняет картину целиком. В схеме socks5 домен разрешает локальная машина, и на стороне службы имён остаётся след вашей сети при том, что дамп на приёмнике выглядит безупречно. Поэтому проверку анонимности по SOCKS5 всегда дополняем замером запросов к службе имён, он снимается отдельной пробой и разобран соседним материалом.

Сверка состава делается тем же diff по двум файлам, что и для HTTP. Ожидаемый результат жёстче: через SOCKS5 разница по заголовкам должна быть нулевой, включая стартовую строку, потому что запрос уходит байт в байт таким, каким его собрал клиент. Любое расхождение означает, что запрос прошёл не через тот узел, который указан в строке подключения. В пакете доступны SOCKS4 и SOCKS5 на выбор, и для проверок берём второй: он принимает доменные имена и умеет авторизацию по логину. Строки подключения и настройки собраны там, где оформляется покупка прокси SOCKS5 для скриптов.

Порядок проверки по шагам

Шаги идут в фиксированной последовательности: каждый следующий читается только при пройденном предыдущем. Смотреть порядок заголовков при промахе по адресу соединения бесполезно.

ШагЧто делаемПризнак прохожденияЧто означает промах
1Поднимаем приёмник на своём домене, порт 9000Прямой запрос вернул текст с полямиПорт закрыт фильтром, проверяем сеть на сервере
2Снимаем эталон прямым запросом в direct.txtФайл содержит стартовую строку и заголовкиЗапрос ушёл через системный прокси, чистим окружение
3Тот же запрос через посредник в proxied.txtОтвет пришёл, код 200Разбираем доступность отдельно, замер откладываем
4Читаем remote_addr в ответеАдрес выходного узла пулаСовпал с адресом машины: запрос идёт мимо посредника
5Сверяем состав полей через diffСлужебных полей из таблицы нетНайденное поле определяет уровень узла
6Сверяем порядок имён полейПоследовательности совпалиУзел переписывает запрос своим парсером
7Повторяем замер по HTTPSАдрес соединения тот жеТуннель уводит трафик другим маршрутом
8Сверяем рукопожатие через opensslВерсия и набор шифров совпалиПоток разбирается по пути, ищем фильтр на клиенте
9Для SOCKS5 проверяем сторону разбора имёнСхема socks5h, запрос к службе имён с узлаИмя разрешается локально, меняем схему
10Прогоняем серию по списку скриптомПровалов ноль на всей выборкеОтдельные строки отсеиваются до прогона

Первые шесть шагов занимают минуты три при готовом приёмнике. Полный круг с рукопожатием и серией укладывается в четверть часа. Бесплатный тест длительностью до 2 часов покрывает такую программу с большим запасом, и после него картина по узлам уже известна.

Автоматизация регулярной проверки и что считать провалом

Разовый замер отвечает за момент. Регулярный отвечает за рабочий режим, поэтому проверку удобно повесить на планировщик и получать короткий отчёт. Скрипт ниже берёт список в формате IP:PORT, гонит каждую строку через приёмник, ищет маркеры и возвращает ненулевой код завершения при первом же провале.

#!/bin/bash
# anon-check.sh: гоняем список через свою страницу-эхо и ищем служебные поля
ECHO="http://echo.example.net:9000/probe"
MARKERS='^(X-Forwarded-For|Forwarded|X-Real-IP|X-Client-IP|Client-IP|Via|Proxy-Connection|Proxy-Authorization|X-Proxy-ID):'
MY_ADDR=$(curl -s --max-time 10 http://echo.example.net:9000/probe | head -1 | awk '{print $2}')
fail=0; total=0

while read -r line; do
  [ -z "$line" ] && continue
  total=$((total + 1))
  out=$(curl -s --max-time 15 --http1.1 -x "http://$line" "$ECHO")
  if [ -z "$out" ]; then
    echo "NOANSWER $line"; fail=$((fail + 1)); continue
  fi
  seen=$(printf '%s\n' "$out" | head -1 | awk '{print $2}')
  hits=$(printf '%s\n' "$out" | grep -Ei "$MARKERS" | cut -d: -f1 | paste -sd, -)
  if [ "$seen" = "$MY_ADDR" ]; then
    echo "BYPASS   $line addr=$seen"; fail=$((fail + 1)); continue
  fi
  if [ -n "$hits" ]; then
    echo "MARKERS  $line -> $hits"; fail=$((fail + 1)); continue
  fi
  echo "OK       $line addr=$seen"
done < proxy.txt

echo "итого: $total, провалов: $fail"
[ "$fail" -eq 0 ]

Запускается он одной строкой и годится для планировщика: код завершения ноль означает, что вся выборка прошла.

bash anon-check.sh && echo "выборка ровная" || echo "есть строки на разбор"

Гонять весь список из примерно 12 000 адресов при каждом запуске незачем. Пул обновляется в реальном времени, ротация внутри него автоматическая, поэтому осмысленнее брать случайную выборку строк на полсотни и смотреть картину по ней.

shuf -n 50 full-list.txt > proxy.txt && bash anon-check.sh

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

Теперь про признак провала. Формулировка нужна жёсткая, потому что «вроде нормально» на выборке из полусотни строк ничего не означает.

Что видим в замереКак читаетсяПровал
remote_addr совпал с адресом рабочей машиныЗапрос ушёл мимо посредникаДа, замер недействителен целиком
X-Forwarded-For или Forwarded содержит адрес машиныИсходный адрес уходит открытым текстомДа, узел выводится из работы
X-Real-IP либо Client-IP с адресом машиныТо же самое другим полемДа
Proxy-Authorization дошёл до приёмникаДоступ к узлу утекает на площадкуДа
Via без адреса клиентаВиден факт посредникаНет, средний уровень, для многих задач рабочий
Proxy-Connection дошёл до приёмникаСлужебное поле не срезано узломНет, признак посредника без выдачи адреса
Порядок имён полей переставленЗапрос собран заново парсером узлаНет, повод присмотреться к узлу
Ответ пустой либо код отличается от 200Строка не отвечаетНет, случай проверки доступности
Отпечаток рукопожатия разошёлся с прямымПоток разбирается по путиДа, разбираем клиентскую машину
Разница по заголовкам при работе по SOCKS5Запрос прошёл не через указанный узелДа

Отчёт складываем в один файл с датами прогонов и долей провалов. Через месяц накапливается ряд, по которому видно поведение связки целиком поверх отдельных строк. Именно ряд отвечает на вопрос, держится ли картина ровной при переходе на боевой объём: разовый замер такого не показывает.

Частые вопросы

Нужно ли проверять анонимность на каждом адресе из списка?

Пул держится в районе 12 000 активных адресов, список обновляется в реальном времени, ротация внутри пула автоматическая. Проверять каждую строку перед каждым прогоном смысла мало: за время прогона состав выборки успевает смениться. Рабочий порядок такой: случайная выборка на полсотни строк через скрипт, и по ней видно поведение пула целиком.

Чем проверять прокси, если своего сервера под приёмник нет?

Для массовой проверки списка подойдёт чекер от Zennolab, у него есть демонстрационная версия: он проходит выборку пачкой и показывает отвечающие строки, время отклика и тип прокси. Для разбора заголовков приёмник всё же удобнее, и поднять его можно на любой машине с внешним адресом за несколько минут по коду из раздела выше.

Можно ли успеть проверить анонимность за бесплатный тест?

Бесплатный тест длится до 2 часов под ваш запрос, и полная программа замера укладывается в четверть часа. Порядок запуска короткий: выбрать тип прокси под свой софт, зарегистрироваться в кабинете, отправить запрос теста из меню, указать свой адрес в настройках, активировать доступ, затем написать оператору логин и тип прокси. Остаток окна уходит на боевой сценарий.

Что показывает замер при доступе по логину и паролю?

Ровно то же самое. Список выдаётся в двух форматах на выбор, IP:PORT и IP:PORT:LOGIN:PASS, забрать его можно ссылкой или файлом. Меняется только строка подключения в ключе -x, набор полей на приёмнике остаётся прежним. Отдельно смотрим, чтобы поле Proxy-Authorization до приёмника не дошло. Работу через привязанный адрес держат приватные серверные прокси с привязкой по адресу, и замер для них ничем не отличается.

Дальше по диагностике собраны соседние разборы: утечка запросов к DNS с настройками для браузеров и парсеров, проверка и отключение WebRTC для браузерных сценариев, замер скорости и времени отклика с методикой серии и подсчётом медианы. Полный перечень признаков, по которым площадка узнаёт посетителя, разобран в материале про то, что сайт видит о посетителе.

Материал сайта kupit-proxy-ipv4.ru. Рабочие адреса IPv4 и SOCKS5: iprazon.com.