Как обойти ограничения OpenVPN Access Server

Добро пожаловать на наш форум!

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


Gibby

Автор
Команда проекта

Регистрация
Сообщений
2,013
Репутация
54
Сделок

Что такое OpenVPN Access Server​

OpenVPN Access Server — удобный инструмент, который устанавливается на сервере в пару команд и позволяет комфортно работать с клиентами: изменять подсети, профили, пароли и другие настройки. Делать все это вручную было бы намного тяжелее.

В бесплатной версии OpenVPN Access Server есть ограничение на число подключений: одновременно могут подключаться только два клиента. Платная лицензия, кроме того, что стоит дорого, ее теперь невозможно физически оплатить.

Как обойти ограничения OpenVPN Access Server​

Мы не будем изобретать велосипед, а посмотрим, какое решение предлагает поисковик. Гуглим запрос «OpenVPN Access Server license unlimited». Находим один удаленный с GitHub репозиторий. Находим зеркало и читаем описание: требуется версия CentOS 7. В скрипте установки после инсталляции самого пакета openvpn-as меняется файл pyovpn-2.0-py2.7.egg по пути:
1/usr/local/openvpn/python/sites-enabled

Я бы не стал менять вслепую этот важный элемент системы, который отвечает том числе за веб‑фронтенди. Тем более в ПО, связанным с конфиденциальностью. Поэтому для начала глянем, что там внутри.

Сравним модифицированные варианты файлов с исходными.

Вытащим оригинальный RPM в поисках pyovpn-2.0-py2.7.egg. Для распаковки .rpm сгодиться обычный tar:
1$ tar xf openvs.rpm

А .egg — обычный ZIP, который находится по адресу:
1pyovpn-2.0-py2.7/usr/local/openvpn_as/lib/python/

Далее распаковываем модифицированный pyovpn-2.0-py2.7.egg из комплекта:
1$ unzip pyovpn-2.0-py2.7.egg

Попробуем отыскать файлы, в которых что‑то менялось.
1
2
3
4
$ diff -rq ./pyovpn-2.0-py2.7_hacked ./pyovpn-2.0-py2.7_original
Files ./pyovpn-2.0-py2.7_hacked/pyovpn/lic/uprop.pyo and ./pyovpn-2.0-py2.7_original/pyovpn/lic/uprop.pyo differ
Only in ./pyovpn-2.0-py2.7_hacked/pyovpn/lic: uprop2.pyo
Files ./pyovpn-2.0-py2.7_hacked/pyovpn/production.pyo and ./pyovpn-2.0-py2.7_original/pyovpn/production.pyo differ

Теперь сравним сами файлы, но прежде их нужно декомпилировать, потому что .pyc — это байт‑код. Для этого будем использовать утилиту decompile6, которая прекрасно работает с версиями Python 2.7, 3.7, 3.8.

В моем случае используется macOS, но в ОС Linux команды вряд ли отличаются.
1
2
3
$ pip install decompyle6
$ uncompyle6 /Users/n0a/Work/openvpn_decompile/test_diff/pyovpn-2.0-py2.7_hacked/pyovpn/lic/uprop.pyo > uprop.py
$ cat uprop.py

Глянем содержимое декомпилированного файла:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat uprop.py
import uprop2
old_figure = None
def new_figure(self, licdict):
ret = old_figure(self, licdict)
ret['concurrent_connections'] = 1024
return ret
for x in dir(uprop2):
if x[:2] == '__':
continue
if x == 'UsageProperties':
exec 'old_figure = uprop2.UsageProperties.figure'
exec 'uprop2.UsageProperties.figure = new_figure'
exec '%s = uprop2.%s' % (x, x)

Интересно! Цикл for — это перебор всех атрибутов uprop2. Те атрибуты, названия которых начинаются с двух подчеркиваний, пропускаются. Функция old_figure становится ссылкой на метод figure класса UsageProperties, а функция в классе UsageFigure ссылается на new_figure.

Сложно сказать, зачем это сделано. Могу предположить, что класс UsageProperties используется где‑то еще, и, чтобы не менять везде, сделали такой неочевидный трюк.

Декомпилируем uprop2 и понимаем, что это оригинальный uprop, в котором и выполняется проверка лицензии.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
...
class UsageProperties(object):
def figure(self, licdict):
proplist = set(('concurrent_connections',))
good = set()
ret = None
if licdict:
for key, props in licdict.items():
if 'quota_properties' not in props:
print 'License Manager: key %s is missing usage properties' % key
continue
proplist.update(props['quota_properties'].split(','))
good.add(key)
for prop in proplist:
v_agg = 0
v_nonagg = 0
if licdict:
for key, props in licdict.items():
if key in good:
if prop in props:
try:
nonagg = int(props[prop])
except:
raise Passthru('license property %s (%s)' % (prop, props.get(prop).__repr__()))
v_nonagg = max(v_nonagg, nonagg)
prop_agg = '%s_aggregated' % prop
agg = 0
if prop_agg in props:
try:
agg = int(props[prop_agg])
except:
raise Passthru('aggregated license property %s (%s)' % (prop_agg, props.get(prop_agg).__repr__()))
v_agg += agg
if DEBUG:
print 'PROP=%s KEY=%s agg=%d(%d) nonagg=%d(%d)' % (prop,
key,
agg,
v_agg,
nonagg,
v_nonagg)
apc = self._apc()
v_agg += apc
if ret == None:
ret = {}
ret[prop] = max(v_agg + v_nonagg, bool('v_agg') + bool('v_nonagg'))
ret['apc'] = bool(apc)
if DEBUG:
print "ret['%s'] = v_agg(%d) + v_nonagg(%d)" % (prop, v_agg, v_nonagg)
return ret

Разработчик кряка использовал подмену объекта, не трогая основной файл. Принцип понятен.

Теперь попробуем провернуть то же самое, только для актуальной версии OpenVPN Aсcess Server. Экспериментировать буду на VPS с актуальной Debian 11 Bullseye.

Скачиваем последнюю версию с сайта разработчика. Устанавливаем или распаковываем архив .deb и смотрим версию Python, для которой написан pyovpn:
1$ ls /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.9.egg

Ага, 3.9, что не очень подходит, так как декомпиляция для этой версии Python пока не реализована.

Что‑то мне подсказывает, что версия для Debian 10 будет подходящей, так как, изучив вики, узнаем, что в 11-й версии (Bullseye) уже Python 3.9, а в 10-й (Buster) — 3.7.


Установка в Debian 10​

Поскольку VPS новая, просто поменяю ОС на десятую версию и посмотрю доступные версии OpenVPS-AS:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ apt update && apt -y install ca-certificates wget net-tools gnupg
wget -qO - https://as-repository.openvpn.net/as-repo-public.gpg | apt-key add -
echo "deb http://as-repository.openvpn.net/as/debian buster main">/etc/apt/sources.list.d/openvpn-as-repo.list
$ apt update
$ apt policy openvpn-as
openvpn-as:
Installed: (none)
Candidate: 2.10.1-d5bffc76-Debian10
Version table:
2.10.1-d5bffc76-Debian10 500
500 http://as-repository.openvpn.net/as/debian buster/main amd64 Packages
2.10.0-ca1e86b5-Debian10 500
500 http://as-repository.openvpn.net/as/debian buster/main amd64 Packages
2.9.6-1090f6b3-Debian10 500
500 http://as-repository.openvpn.net/as/debian buster/main amd64 Packages
2.9.5-82d54e5b-Debian10 500
500 http://as-repository.openvpn.net/as/debian buster/main amd64 Packages
2.9.4-8b3ce898-Debian10 500
...

Отлично, версия последняя, как в 11-й, а значит, актуальная. Устанавливаем и смотрим версию Python, которая используется в pyovpn. Должно быть 3.7.
1
2
3
$ apt -y install openvpn-as
$ ls /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.7.egg
/usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.7.egg

Так и есть: это последняя версия (2.10.1) и она использует Python 3.7. Все складывается. Давайте проверим, многое ли изменилось по сравнению с версией 2.0.5, которая была изначально найдена похеканной. Чтобы не таскать файлы туда‑сюда, ставлю на сервере python-decompile3:
1
2
3
$ git clone https://github.com/rocky/python-decompile3
$ cd python-decompile3
$ pip3 install -e

И снова распаковываем .egg:
1
2
3
4
5
6
$ mkdir /opt/ovpn && cd ovpn
$ cp /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.7.egg ./
$ cp /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.7.egg pyovpn-2.0-py3.7.zip
$ unzip pyovpn-2.0-py3.7.zip && rm pyovpn-2.0-py3.7.zip
$ ls
EGG-INFO pyovpn

Затем декомпилируем:
1
2
3
$ cd ./pyovpn/lic
$ decompyle3 uprop.pyc > uprop.py
$ cat uprop.py

Смотрим отличия и видим, что все то же самое, кроме небольших расхождений в синтаксисе. Но что имел в виду автор хака, когда использовал два файла? Почему просто не указать явно в конце функции figure количество соединений, минуя все проверки?


Давайте попробуем более простой вариант. Добавляем ret['concurrent_connections'] = 1337 перед возвратом (ret):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nano uprop.py
...
apc = self._apc()
v_agg += apc
if ret == None:
ret = {}
ret[prop] = max(v_agg + v_nonagg, bool('v_agg') + bool('v_nonagg'))
ret['apc'] = bool(apc)
if DEBUG:
print("ret['%s'] = v_agg(%d) + v_nonagg(%d)" % (prop, v_agg, v_nonagg))
ret['concurrent_connections'] = 1337
return ret
def _apc(self):
...

Сохраняем файл, компилируем:
1
2
3
4
$ python3 -m compileall uprop.py
$ rm uprop.pyc uprop.py
$ cp __pycache__/uprop.cpython-37.pyc ./uprop.pyc
$ rm -Rf __pycache__

Архивируем и заменяем пакет .egg:
1
2
3
4
$ cd /opt/ovpn
$ zip -r *
$ sudo rm /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.7.egg
$ sudo common.zip /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.7.egg

И перезагружаем openvpn-as. Я удалял логи, так как ловил ошибки во время экспериментов. Это делать необязательно.
1
2
3
$ sudo service openvpnas stop
$ sudo rm /var/log/openvpnas.log
$ sudo touch /var/log/openvpnas.log

Запускаем сервис openvpnas:
1$ service openvpnas start

Убеждаемся, что все в порядке и ошибок нет:
1cat /var/log/openvpnas.log

Идем в админку на порте 943/admin и видим, что нам доступно 1337 подключений.

0.jpg
Пять активных подключений из 1337 возможных

Если вы вдруг потеряли пароль и не можете попасть в админку, напишите passwd openvpn.

Тестирование показало отличную работу с двумя и более устройствами.


Заключение​

Многие VPN сейчас под блокировками или не могут принять оплату, поэтому Access Server — неплохой способ быстро развернуть собственный аналог с возможностью управлять профилями пользователей. Пользоваться ли таким методом активации — решать вам, но это, как оказалось, совсем не сложно.
 
Сверху