Security

Unlocking OpenVPN Access Server: Removing Connection Limits for Unlimited VPN Access

Lately, more and more friends have been asking me to help them regain access to Facebook, Instagram, Telegram, YouTube, and other blocked sites that may still hold important information. To help them, I’ve always used OpenVPN Access Server. Its free version comes with several licensing constraints. In this article, I’ll show how to deal with them.

Access Server is a great tool that you can install on a server with just a couple of commands. It makes client management easy—edit subnets, profiles, passwords, and more. Doing all of that manually would be much more cumbersome.

Here’s the catch: the free version limits the number of connections—only two clients can be active at the same time. The paid license is pricey, and you can’t pay for it with a Russian credit card right now anyway. Unfortunately, the same goes for many apps and useful hacker resources. Just yesterday, for example, I couldn’t pay for Hack The Box, which really bummed me out. So instead of tackling HTB boxes, we’ll practice the old Russian pastime—cracking!

warning

As of this writing, the laws prohibiting the use of unlicensed software remain in force. The idea of allowing unlicensed use of software from companies exiting Russia is only being discussed. We are merely demonstrating a vulnerability that enables bypassing license verification. If you choose to apply this knowledge, you do so at your own risk. Keep track of current legislation, especially if you’re operating commercially.

Rather than reinvent the wheel, let’s see what the search engines return for the query “OpenVPN Access Server license unlimited.” Interestingly, there’s a repository that was removed from GitHub for copyright infringement. We find a mirror and check the description: it requires CentOS 7. In the installation script, after installing the openvpn-as package itself, it also replaces the file pyovpn-2.0-py2.7.egg at /usr/local/openvpn/python/sites-enabled.

I wouldn’t replace such a critical system component—especially one that handles the web frontend—sight unseen, and I don’t recommend you do either. All the more so with privacy-focused software. So before we use it, let’s inspect what’s inside. We’ll compare the hacked builds against the original files.

We’ll unpack the original RPM to find pyovpn-2.0-py2.7.egg. You can extract a .rpm with plain tar:

$ tar xf openvs.rpm

A .egg file is just a standard ZIP archive. You’ll find it at pyovpn-2.0-py2.7/usr/local/openvpn_as/lib/python/.

Next, unpack the hacked pyovpn-2.0-py2.7.egg from the bundle:

$ unzip pyovpn-2.0-py2.7.egg

Let’s find the files that have been modified.

$ 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

Now let’s compare the files themselves, but first we need to decompile them, since .pyc is bytecode. We’ll use the decompile6 utility, which works great with Python 2.7, 3.7, and 3.8. I’ll be doing everything on macOS, but the commands are unlikely to differ on Linux.

$ 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

Let’s look at the contents of the decompiled file:

$ 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)

Interesting! The for loop iterates over all attributes of uprop2, skipping any whose names start with two underscores. The old_figure function is turned into a reference to the figure method of the UsageProperties class, and the function in the UsageFigure class is redirected to new_figure. It’s hard to say why this was done. My guess is that UsageProperties is used elsewhere, and to avoid changing it everywhere, they pulled this not-so-obvious trick.

We decompile uprop2 and realize it’s actually the original uprop, where the license check is performed.

...
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

The crack author used object substitution without modifying the main executable. The approach is clear.

Now let’s try the same thing with the current version of OpenVPN Access Server. I’ll be testing on a VPS running Debian 11 (Bullseye).

Download the latest version from the developer’s site. Install it or extract the .deb archive, then check which Python version pyovpn targets:

$ ls /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.9.egg

Right, Python 3.9, which isn’t ideal, since decompilation for that version isn’t supported yet.

info

You can support the decompiler’s developer and help move the project forward. Learn more in his sponsorship message: sponsorship message.

I have a hunch the Debian 10 build is the right choice, because from the wiki we learn that Debian 11 (Bullseye) ships Python 3.9, while Debian 10 (Buster) has 3.7.

Installing on Debian 10

Since the VPS is new, I’ll just switch the OS to version 10 and see which OpenVPS-AS versions are available:

$ 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

Great—the version is the latest, same as in v11, so it’s up to date. Install it and check which Python version pyovpn uses. It should be 3.7.

$ 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

Exactly: this is the latest version (2.10.1) and it uses Python 3.7. That lines up. Let’s check how much has changed compared to version 2.0.5, which was initially found to be hacked. To avoid shuffling files back and forth, I’m installing python-decompile3 on the server:

$ git clone https://github.com/rocky/python-decompile3
$ cd python-decompile3
$ pip3 install -e .

Once again, extract the .egg:

$ 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

Next, decompile it:

$ cd ./pyovpn/lic
$ decompyle3 uprop.pyc > uprop.py
$ cat uprop.py

Looking at the differences, everything is the same except for minor syntax discrepancies. But what was the author of the hack aiming for by using two files? Why not just explicitly specify the number of connections at the end of the figure function, bypassing all the checks?

Let’s try a simpler approach. Add ret['concurrent_connections'] = 1337 right before returning (ret):

$ 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):
...

Save the file and compile:

$ python3 -m compileall uprop.py
$ rm uprop.pyc uprop.py
$ cp __pycache__/uprop.cpython-37.pyc ./uprop.pyc
$ rm -Rf __pycache__

Archive and replace the .egg package:

$ cd /opt/ovpn
$ zip -r *
$ sudo rm /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.7.egg
$ sudo cp common.zip /usr/local/openvpn_as/lib/python/pyovpn-2.0-py3.7.egg

And restart OpenVPN-AS. I cleared the logs because I was encountering errors while experimenting. That step is optional.

$ sudo service openvpnas stop
$ sudo rm /var/log/openvpnas.log
$ sudo touch /var/log/openvpnas.log

Start the openvpnas service:

$ service openvpnas start

Verify that everything is OK and there are no errors:

cat /var/log/openvpnas.log

Head to the admin panel at :943/admin and you’ll see that 1,337 connections are available.

5 active connections out of 1337 available
5 active connections out of 1337 available

info

If you’ve lost your password and can’t access the admin panel, run passwd openvpn.

Testing showed excellent performance with two or more devices.

Conclusions

With many VPN services currently blocked or unable to accept payments, Access Server is a solid way to quickly spin up your own alternative with user profile management. Whether you use a pirated activation method is up to you—but as it turns out, it’s not hard at all.

it? Share: