Уже не школьник & Bсё ещё блогер
Прозрачный прокси

Введение

Эту заметку я по большей части для себя пишу, т.к. сейчас всё делается костылями, чтобы собрать все растяжки. Именно описание всех этих растяжек на пути к рабочему решению и есть цель этой заметки.

Проблема

Всё больше ресурсов блокируют в последнее время. У меня уже довольно давно имеется входная точка Tor внутри моего VPN. Однако, для того, чтобы ходить через Tor в Интернет необходимо настроить клиент соответствующим образом, чтобы он использовался socks-proxy. Если только, не проксировать трафик прозрачно!

Однако, надо иметь в виду, что я не хочу гнать весь трафик через свой VPN.

Что есть?

Перед тем, как садиться за настройку прозрачного прокси, у меня уже был поднят свой DNS-сервер. В том числе через VPN я хожу на ресурсы по работе, которые доступны только внутри рабочей локальной сети. В связи с этим у меня и поднят DNS-сервер.

Входная точка Tor у меня тоже есть — об этом уже сказал.

Остальное, думаю, не имеет значения.

Что же получилось в итоге

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

Для того, чтобы сходить в Интернет через Tor, нужен клиент socks-proxy. Но я хочу прозрачный прокси, ещё и с перехватом. Прозрачно перехватывать HTTP трафик умеет privoxy. Чтобы не возиться с редиректами и маршрутами можно просто подменить DNS запись. Таким образом, клиентское устройство будет само отправлять запрос моему прокси серверу.

Но есть нюанс: privoxy не умеет HTTPS. Но, есть squid — в нём, конечно, полно мин, но его можно заставить атаковать мои устройства по принципу MITM.

Приведу диаграмму в качестве примера. На ней gosuslugi.ru — ресурс, к которому я хочу ходить напрямую, а example.com — ресурс, к которому я хочу ходить анонимно через Tor. Буковы рядом с доменами — условные IP адреса.

Итак, два дня мучений

2022.03.05

Начинаю новый раунд: ставлю squid. Дело в том, что sslsplit, как оказалось, не умеет работать с прокси. Но squid должен уметь. Правда, не из коробки, но можно собрать с поддержкой MITM-атаки.

Для сборки нужно сначала установить зависимости:

apt build-dep squid
apt install libssl-dev

Внезапно, (я не знал) можно скачать сорцы через apt:

apt source squid

Далее, в файл debian/rules в переменную DEB_CONFIGURE_EXTRA_FLAGS нужно добавить ключи конфигурации для включения SSL:

--enable-ssl \
--enable-ssl-crtd \
--with-openssl

По идее, теперь нужно только собрать да установить пакет в систему:

dpkg-buildpackage -d -uc -us
dpkg -i ../squid*.deb

Наткнулся на следующую ошибку:

In file included from ../../src/anyp/PortCfg.h:18:0,  
                from PortCfg.cc:10:  
../../src/ssl/gadgets.h:83:45: error: ‘CRYPTO_LOCK_X509’ was not declared in this scope  
typedef LockingPointer<X509, X509_free_cpp, CRYPTO_LOCK_X509> X509_Pointer;  
                                            ^~~~~~~~~~~~~~~~  
../../src/ssl/gadgets.h:83:61: error: template argument 3 is invalid  
typedef LockingPointer<X509, X509_free_cpp, CRYPTO_LOCK_X509> X509_Pointer;  
                                                            ^  
../../src/ssl/gadgets.h:89:53: error: ‘CRYPTO_LOCK_EVP_PKEY’ was not declared in this scope  
typedef LockingPointer<EVP_PKEY, EVP_PKEY_free_cpp, CRYPTO_LOCK_EVP_PKEY> EVP_PKEY_Pointer;  
                                                    ^~~~~~~~~~~~~~~~~~~~  
../../src/ssl/gadgets.h:89:73: error: template argument 3 is invalid  
typedef LockingPointer<EVP_PKEY, EVP_PKEY_free_cpp, CRYPTO_LOCK_EVP_PKEY> EVP_PKEY_Pointer;  
                                                                        ^  
../../src/ssl/gadgets.h:116:43: error: ‘CRYPTO_LOCK_SSL’ was not declared in this scope  
typedef LockingPointer<SSL, SSL_free_cpp, CRYPTO_LOCK_SSL> SSL_Pointer;  
                                          ^~~~~~~~~~~~~~~  
../../src/ssl/gadgets.h:116:58: error: template argument 3 is invalid  
typedef LockingPointer<SSL, SSL_free_cpp, CRYPTO_LOCK_SSL> SSL_Pointer;

Если ты из ESR — ставь лайк! Да, надо поставить другую версию SSL библиотеки: libssl1.0-dev.

Так, у меня ещё одна ошибка:

ErrorDetailManager.cc: In member function ‘virtual bool Ssl::ErrorDetailFile::parse(const char*, int, boo  
l)’:  
ErrorDetailManager.cc:215:35: error: invalid conversion from ‘const char*’ to ‘size_t {aka long unsigned  
int}’ [-fpermissive]  
            if (!parser.parse(s, e)) {  
                                  ^  
In file included from ../../src/HttpMsg.h:16:0,  
                from ../../src/HttpRequest.h:16,  
                from ErrorDetailManager.h:13,  
                from ErrorDetail.h:13,  
                from ErrorDetailManager.cc:10:  
../../src/HttpHeader.h:223:9: note:   initializing argument 2 of ‘int HttpHeader::parse(const char*, size  
_t)’  
    int parse(const char *header_start, size_t len);  
        ^~~~~

Попробую использовать этот патч:

From f42a67c56f8b16ce1b09df6caef1543d30ebc1c7 Mon Sep 17 00:00:00 2001
From: Christos Tsantilas <chtsanti@users.sourceforge.net>
Date: Sun, 9 Nov 2014 14:45:46 +0200
Subject: [PATCH] Parser-NG: fixes to allow ecap and ssl subsystems build

---
 src/adaptation/ecap/MessageRep.cc | 3 +--
 src/ssl/ErrorDetailManager.cc     | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/adaptation/ecap/MessageRep.cc b/src/adaptation/ecap/MessageRep.cc
index 4002856b042..8bbfeb1ae93 100644
--- a/src/adaptation/ecap/MessageRep.cc
+++ b/src/adaptation/ecap/MessageRep.cc
@@ -235,8 +235,7 @@ Adaptation::Ecap::RequestLineRep::method(const Name &aMethod)
         theMessage.method = HttpRequestMethod(static_cast<Http::MethodType>(id));
     } else {
         const std::string &image = aMethod.image();
-        theMessage.method = HttpRequestMethod(image.data(),
-                                              image.data() + image.size());
+        theMessage.method.HttpRequestMethodXXX(image.c_str());
     }
 }

diff --git a/src/ssl/ErrorDetailManager.cc b/src/ssl/ErrorDetailManager.cc
index b798d4b2762..43fc4b00529 100644
--- a/src/ssl/ErrorDetailManager.cc
+++ b/src/ssl/ErrorDetailManager.cc
@@ -212,7 +212,7 @@ Ssl::ErrorDetailFile::parse(const char *buffer, int len, bool eof)

         if ( s != e) {
             DetailEntryParser parser;
-            if (!parser.parse(s, e)) {
+            if (!parser.parse(s, e - s)) {
                 debugs(83, DBG_IMPORTANT, HERE <<
                        "WARNING! parse error on:" << s);
                 return false;

В bash это будет выглядеть так:

wget https://github.com/squid-cache/squid/commit/f42a67c56f8b16ce1b09df6caef1543d30ebc1c7.patch -O debian/patches/9990-fix-ssl.patch
echo 9990-fix-ssl.patch >> debian/patches/series

Да, надо засовывать его в иерархию dpkg, иначе dpkg-source ругаться будет.[^1]

[^1]: Вывод: надо собирать всё из свежих сорцов своими руками — убивать на это больше времени, зато потом сидеть-пердеть и в ус не дуть, и оно рано или поздно точно соберётся (можно же самому всё починить!).

И ещё одна ошибка:

MessageRep.cc: In member functionvirtual void Adaptation::Ecap::RequestLineRep::method(const Name&)’:  
MessageRep.cc:236:27: error: ‘class HttpRequestMethod’ has no member named ‘HttpRequestMethodXXX’; did yo  
u mean ‘HttpRequestMethod’?  
        theMessage.method.HttpRequestMethodXXX(image.c_str());  
                          ^~~~~~~~~~~~~~~~~~~~

Откатываю правку в этом файле из патча, который мы скачали. Надеюсь, сработает.

In file included from client_side.cc:132:0:  
ssl/certificate_db.h:56:0: error: "Here" redefined [-Werror]  
#define Here __FILE__, __LINE__  

In file included from ../src/base/TextException.h:15:0,  
                from ../src/SBufExceptions.h:12,  
                from ../src/SBuf.h:14,  
                from ../src/http/MethodType.h:12,  
                from ../src/HttpRequestMethod.h:12,  
                from ../src/AccessLogEntry.h:18,  
                from acl/FilledChecklist.h:12,  
                from client_side.cc:61:  
../src/base/Here.h:15:0: note: this is the location of the previous definition  
#define Here() SourceLocation(__FUNCTION__, __FILE__, __LINE__)

Пошёл по пути наименьшего сопротивления, решил её, отредактировав патч debian/patches/CVE-2019-12526.patch, чтобы макрос назывался в нём по-другому: Here1.

А ещё я словил virtual memory exhausted: Cannot allocate memory. Полагаю, дело лишь в количестве оперативы на моём VPS (2 ГиБ). Попробовал дать ключик, чтоб он собирал одним потоком, но не помогло. Придётся локально собирать. Это уже завтра. Могу попробовать запустить образ из reg.ru в виртуалке, но, КМК, достаточно репу такую же собрать и каталог squid_biuld стянуть.

2022.03.06

Пришлось поставить локально машину на Debian. К сожалению, нашёл только 9.13 (на моём хосте 9.11). Кажется, не должно ничего сломать. Собралось нормально.

Так, по крайней мере теперь он понимает директивы для настройки перехвата HTTPS. Однако, надо теперь с ключами разобраться:

[root@GRayCloud squid_deb]# /usr/sbin/squid -YCN -d 9 -f /etc/squid/squid_privoxy.conf  
FATAL: No valid signing SSL certificate configured for HTTPS_port 10.XXX.0.1:443  
Squid Cache (Version 3.5.23): Terminated abnormally.  
CPU Usage: 0.016 seconds = 0.012 user + 0.004 sys  
Maximum Resident Size: 60176 KB  
Page faults with physical i/o: 0

Собственно, ключи этими командами генерировал:

openssl genrsa -out /etc/squid/ssl/squid.key
openssl req -new -key /etc/squid/ssl/squid.key -out /etc/squid/ssl/squid.csr
openssl x509 -req -days 3650 -in /etc/squid/ssl/squid.csr -signkey /etc/squid/ssl/squid.key -out /etc/squid/ssl/squid.pem
openssl x509 -in /etc/squid/ssl/squid.pem -outform DER -out squid.der

По идее, squid.der надо установить на клиентское устройство. Однако, например, на моём телефоне браузер не жалуется. Может быть, это как-то связано с моим VPN, который также по сертификатам работает.

Блять... Оказывается, squid, когда перехватывает HTTPS, не ходит в DNS разрешать доменное имя! Он берёт IP из оригинального запроса! Что сделал squid, когда я попытался сходить на myip.ru? Залупился сам в себя! 10/10.

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

Изучаю логи с отладкой. Кажется, я обосрался. Он прям печатает запрос:

2022/03/06 16:49:50.347| 11,2| client_side.cc(2352) parseHttpRequest: HTTP Client local=10.XXX.0.1:443 remote=10.YYY.0.30:58290 FD 13 flags=33
2022/03/06 16:49:50.347| 11,2| client_side.cc(2353) parseHttpRequest: HTTP Client REQUEST:
---------
CONNECT 10.XXX.0.1:443 HTTP/1.1
Host: 10.XXX.0.1:443

То есть, запрос изначально отправляется с IP, а не с DNS именем.

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

Обратил внимание, что автор использует некий "Peek and splice". Толком не читал: что это, но могу предположить, что имеется в виду. Думаю, речь о том, чтобы установить соединение с клиентом, принять от него условный GET и только потом пойти к настоящему серверу по адресу, в который разрешится домен из запроса.

В общем, я добавил следующие опции в конфигу:

acl step1 at_step SslBump1  
ssl_bump peek step1 all  
ssl_bump splice all  
ssl_bump server-first all  
ssl_bump none all  
never_direct allow all

— и всё заработало как надо. Дальше уже была возня с DNS, чтоб в твиттер пускало. И оно пускает!


2022-03-07 14:20