Мама, я киберповстанец

от автора

История о том, как сисадмин борется с конторой с бюджетом в 60 млрд.
В продолжение комментария.

Ето я

Ето я

Введение

Есть грандиозный проект, и для его существования нужны 3 вещи:
— сервера
— люди
— vpn

Компания зарубежная, и мы с удовольствием используем AWS.
Требования регуляторов привели нас к OpenVPN.
А компания любит дешёвую рабочую силу, поэтому часть сотрудников живёт в РФ.

Получилась простая схема, на которой хотелось бы жить вечно.

client -> openvpn://bastion.example.com:1194

client -> openvpn://bastion.example.com:1194

Казалось бы, что может пойти не так? Именно тут появляется наш великолепный цензор в виде РКН и ставит палки в колёса.

КВН

КВН

Глава 1. Первые блокировки, obfs4proxy

В какой-то момент, клиенты в РФ начали отваливаться. Анализ быстро привёл нас к пониманию, что происходит обрыв tcp соединения на этапе подключения. Но только для OpenVPN, остальной траффик ходил как положено.
Значит нам нужно замаскировать траффик, и сделать это:
— максимально удобно для сотрудников, далеко не все кулхацкеры
— безопасно для инфры, лишных дырок появиться не должно

Так у нас появился obfs4proxy c связке с Viscosity

client -> obfs://127.0.0.1:1080 -> obfs://bastion.example.com:1195 -> openvpn://bastion.example.com:1194

client -> obfs://127.0.0.1:1080 -> obfs://bastion.example.com:1195 -> openvpn://bastion.example.com:1194

Конечно же никто не мог тогда предвидеть, на какую интересную дорожку мы встали.

Первые опьяняющие победы

Первые опьяняющие победы

Глава 2. Вынос части инфры из AWS.

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

client -> obfs://127.0.0.1:1080 -> obfs://tunnel.example.com:1195 -> openvpn://bastion.example.com:1194

client -> obfs://127.0.0.1:1080 -> obfs://tunnel.example.com:1195 -> openvpn://bastion.example.com:1194

Логика была банальна:
— поставщики виртуалок не обязаны ставить ТСПУ
— у хороших вендоров есть свой канал
— серваки светятся как отечественные в россии, но имеют зарубежный IP

Такое должно прожить долго и лавочку не прикроют, верно ведь?

Всё ещё просто и весело

Всё ещё просто и весело

Глава 3. Попытка попасть в белые списки.

Играть в кошки-мышки с государством интересно, но хотелось иногда просто поработать.
Поэтому следующим этапом была попытка попасть в белые списки. Мы легальные, аудионаркотики не продаём, неужели не мы можем работать как белые люди?
Спустя несколько месяцев ответ был получен, но результата это не дало.

Успешное добавление в белые списки не гарантирует работоспособность

Успешное добавление в белые списки не гарантирует работоспособность

Глава 4. Появление cloak/trojan/hysteria/vless

В какой-то момент часть сотрудников начала жаловаться на недоступность. Снова.
Ещё одна пачка анализов и приходим к пониманию, что траффик режется. Симпомы идентичны обычному использованию OpenVPN, всё намекает на то, что протокол не столь надёжен, как хотелось бы.
Начинаем искать альтернативы. Так в течении пологода у нас заводятся shadowsocks/amnezia/ipsec/cloak/trojan/hysteria/vless/vkturnproxy.

client -> ck-client://127.0.0.1:1080 -> ck-server://tunnel.example.com:443 -> openvpn://bastion.example.com:1194

client -> ck-client://127.0.0.1:1080 -> ck-server://tunnel.example.com:443 -> openvpn://bastion.example.com:1194

Решения которые прекрасно работали для меня одного, начинают отваливаться в разных регионах страны, на разных провайдерах, в разных случаях. И в этом чёрном ящике абсолютно не понятно:
— то ли накатывают белые списки
— то ли РКН сломал часть интернета
— то ли их DPI научился детектить протокол
— то ли мы провинились
— то ли кривые руки сотрудников

Всё приходит к тому, что у нас поднята пачка методов обхода блокировок (уже 10 в документации), а на сотрудниках я тестирую.

Безмерная любовь к нашим органам

Безмерная любовь к нашим органам

Глава 5. Белые списки, поднятие серверов в РФ

Научились обходить блокировки? Вот вам задачка со звёздочкой.
Видимо так решило моё любимое министерство цифрового отрицательного развития.
Появилась нужда резко найти IP из белых списков и использовать их. Решение пришло также быстро — обратный король мидас отечественного IT. cloud.vk.com позвляет быстро поднять VPS, а на их серваках внезапно доступны instagram/youtube/etc.

client -> ck-client://127.0.0.1:1080 -> ck-server://subway.example.com:443 -> openvpn://bastion.example.com:1194

client -> ck-client://127.0.0.1:1080 -> ck-server://subway.example.com:443 -> openvpn://bastion.example.com:1194

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

Любовь проявляется в разных формах

Любовь проявляется в разных формах

Глава 6. Отрыв IP, резервирование

В какой-то момент работа остановилась, и пришлось вспоминать основы high availability — наличие запасного сервера.

Рака яичек им пожелал, но это почему-то не решило проблему

Рака яичек им пожелал, но это почему-то не решило проблему

Сложно сказать, что послужило причиной блокровки:
— кто-то из сотрудников сидел в max с рабочего VPN’a
— DPI действительно научился палить vless
— мои кривые руки в конфигурации сервера
— активные пробы прошлись по всем портам и нашли лишнее

Причины были уже не важны, главное стало понимание, что нужно что-то делать. И самым простым оказалось поднять ещё чуть-чуть виртуалок.

client -> ck-client://127.0.0.1:1080 -> ck-server://{subway|underground}.example.com:443 -> openvpn://bastion.example.com:1194

client -> ck-client://127.0.0.1:1080 -> ck-server://{subway|underground}.example.com:443 -> openvpn://bastion.example.com:1194

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

Желание возлюбить

Желание возлюбить

Глава 7. Multi-hop

Появление вестей о шпионском ПО, которое будет сливать данные IP выходного сервера дало ясно понять, что входной и выходной адреса должны различаться. Так в арсенале появился gost.

client -> ck-client://127.0.0.1:1080 -> gost://subway.example.com:443 -> ck-server://tunnel.example.com:443 -> openvpn://bastion.example.com:1194

client -> ck-client://127.0.0.1:1080 -> gost://subway.example.com:443 -> ck-server://tunnel.example.com:443 -> openvpn://bastion.example.com:1194

Latency слегка подрос, зато наши сотрудники живут в стабильных условиях (надолго ли?).

Техносовпротивление

Техносовпротивление

Конфиги

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

Скрытый текст

gost.yaml

services:  - name: cloak-tcp    addr: :443    handler:      type: tcp    listener:      type: tcp    forwarder:      nodes:        - name: cloak-tcp          addr: {{ gost_target }}:443

obfs4proxy.conf

TOR_PT_MANAGED_TRANSPORT_VER=1TOR_PT_STATE_LOCATION=/var/lib/obfs4TOR_PT_SERVER_TRANSPORTS=obfs4TOR_PT_SERVER_BINDADDR=obfs4-0.0.0.0:{{ obfs4_port }}TOR_PT_ORPORT={{ obfs4_target }}

ck-server.json

{  "ProxyBook": {    "openvpn": [      "tcp",      "{{ cloak_remote }}"    ]  },  "BindAddr": [    ":443"  ],  "BypassUID": [    {{ cloak_uid }}  ],  "RedirAddr": "www.example.com",  "PrivateKey": "{{ cloak_private }}"}

ck-client.json

{  "RemoteHost": "subway.example.com",  "Transport": "direct",  "ProxyMethod": "openvpn",  "EncryptionMethod": "plain",  "UID": "{{ cloak_uid }}",  "PublicKey": "{{ cloak_public }}",  "ServerName": "www.example.com",  "NumConn": 4,  "BrowserSig": "chrome",  "StreamTimeout": 300}

xray-server.json

{  "log": {    "loglevel": "info"  },  "routing": {    "rules": [],    "domainStrategy": "AsIs"  },  "inbounds": [    {      "listen": "0.0.0.0",      "port": {{ xray_port }},      "protocol": "vless",      "tag": "vless_tls",      "settings": {        "clients": [          {            "id": "{{ xray_client }}",            "email": "{{ xray_email }}",            "flow": "xtls-rprx-vision"          }        ],        "decryption": "none"      },      "streamSettings": {        "network": "tcp",        "security": "reality",        "realitySettings": {          "show": false,          "dest": "www.example.com:443",          "xver": 0,          "serverNames": [            "www.example.com",            "example.com"          ],          "mldsa65Seed": "",          "privateKey": "{{ xray_private }}",          "minClientVer": "",          "maxClientVer": "",          "maxTimeDiff": 0,          "shortIds": [            {{ xray_short }}          ]        }      },      "sniffing": {        "enabled": true,        "destOverride": [          "http",          "tls"        ]      }    }  ],  "outbounds": [    {      "protocol": "freedom",      "tag": "direct"    },    {      "protocol": "blackhole",      "tag": "block"    }  ]}

xray-client.json

{    "log": {        "level": "info"      },          "inbounds": [                {               "listen": "127.0.0.1",              "port": 1080,            "protocol": "socks",                "settings": {                "udp": true                     },                       "sniffing": {                "enabled": true,                    "destOverride": [                       "http",                             "tls"                ]                    }                    }       ],          "outbounds": [               {               "domain_strategy": "ipv4_only",                                               "flow": "xtls-rprx-vision",                                                   "packet_encoding": "xudp",                                                    "server": "subway.example.com",                                               "server_port": 8443,                "tag": "proxy",                     "tls": {                     "alpn": [                    "h2"                 ],                       "enabled": true,                    "insecure": true,                   "reality": {                            "enabled": true,                    "public_key": "{{ xray_public }}",                     "short_id": "{{ xray_short }}"                                            },                       "server_name": "www.example.com",                                             "utls": {                    "enabled": true,                    "fingerprint": "chrome"                }            },            "type": "vless",            "uuid": "{{ xray_uuid }}"        },        {            "tag": "direct",            "protocol": "freedom"        },        {            "tag": "block",            "protocol": "blackhole"        }    ]}

ss-server.json

{    "server": "0.0.0.0",    "server_port": {{ ss_port }},    "password": "{{ ss_pass }}",    "method": "aes-256-gcm",    "mode":"tcp_and_udp",    "fast_open":false}

ss-client.json

{    "server": "tunnel.example.com",    "server_port": {{ ss_port }},    "password": "{{ ss_pass }}",    "method": "aes-256-gcm",    "local_address": "127.0.0.1",    "local_port": 1080}

hysteria-server.yaml

listen: :{{ hysteria_port }}tls:  cert: /etc/ssl/wildcard.crt  key: /etc/ssl/wildcard.keyauth:  type: userpass  userpass:     {{ hysteria_client }}: {{ hysteria_pass }}masquerade:  type: proxy  proxy:    url: https://www.example.com    rewriteHost: trueobfs:  type: salamander  salamander:    password: {{ hysteria_obfs }}resolver:  type: udp  tcp:    addr: 8.8.8.8:53    timeout: 4s  udp:    addr: 8.8.4.4:53    timeout: 4s  tls:    addr: 1.1.1.1:853    timeout: 10s    sni: cloudflare-dns.com    insecure: false  https:    addr: 1.1.1.1:443    timeout: 10s    sni: cloudflare-dns.com    insecure: false

hysteria-client.yaml

server: tunnel.example.com:1984auth: {{ hysteria_client }}: {{ hysteria_pass }}obfs:  type: salamander  salamander:password: {{ hysteria_obfs }}http:  listen: 127.0.0.1:8080socks5:  listen: 127.0.0.1:1080

trojan-server.json

{  "run_type": "server",  "local_addr": "0.0.0.0",  "local_port": {{ trojan_port }},  "remote_addr": "{{ trojan_remote }}",  "remote_port": 443,  "password": ["{{ trojan_password }}"],  "ssl": {    "cert": "/etc/ssl/wildcard.crt",    "key": "/etc/ssl/wildcard.key",    "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384",    "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384",    "prefer_server_cipher": true,    "alpn": [      "http/1.1"    ],    "alpn_port_override": {      "h2": 81    },    "reuse_session": true,    "session_ticket": false,    "session_timeout": 600,    "plain_http_response": "",    "curves": "",    "dhparam": ""  },  "tcp": {      "prefer_ipv4": false,      "no_delay": true,      "keep_alive": true,      "reuse_port": false,      "fast_open": false,      "fast_open_qlen": 20  },  "mysql": {      "enabled": false,      "server_addr": "127.0.0.1",      "server_port": 3306,      "database": "trojan",      "username": "trojan",      "password": "",      "key": "",      "cert": "",      "ca": ""  }}

trojan-client.json

{  "run_type": "client",  "local_addr": "127.0.0.1",  "local_port": 1080,  "remote_addr": "{{ trojan_remote }}",  "remote_port": {{ trojan_port }},  "password": [      "{{ trojan_password }}"  ],  "log_level": 1,  "ssl": {      "verify": true,      "verify_hostname": true,      "cert": "",      "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA",      "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384",      "sni": "www.example.com",      "alpn": [          "h2",          "http/1.1"      ],      "reuse_session": true,      "session_ticket": false,      "curves": ""  },  "tcp": {      "no_delay": true,      "keep_alive": true,      "reuse_port": false,      "fast_open": false,      "fast_open_qlen": 20  }}

Вывод

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

Особое внимание:
— ТСПУ начали поставлять на сети межрегиональных провайдеров, желательно настраивать multihop, чтобы траффик из РФ выходил и шифрованным, и обфусцированным
client -> vps rf -> vps eu -> vpn
— желательно не использовать обход блокировок на мобилках одновременно с небезопасными приложениями (макс/яндекс/банки), активно сливают, можно поймать бан на следующий день
— не забываем настраивать split-tunnel, чтобы траффик для отечественных сервисов шёл внутри страны
— РКН чувствителен к портам, для белых списков только 80/443, для обхода блокировок не использовать 1337/1984/3128/8080
— РКН быстро добавляет IP в блэклист сервера за рубежом, и рубит машину в РФ, если спалит

На данный момент самым живым является схема, когда:
— есть защита от активных проб
— есть валидные tls сертификаты и настоящий веб-сервер, который отвечает
— траффик шифруется лишь 1 раз
— траффик обфусцируется и очень похож на https
— входной и выходной адреса отличаются
— есть резервирование на несколько машин внутри страны

ссылка на оригинал статьи https://habr.com/ru/articles/1027990/