Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

security audit fixes #23

Merged
merged 13 commits into from
Dec 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ COUNT ?= 5
TIMEOUT ?= 10
LOCAL_TARGET := $(shell ip -4 addr show docker0 | grep 'inet ' | awk '{print $$2}' | cut -f 1 -d /)
COVERAGE_THRESHOLD := 88
FLAGS=-ldflags="-w -s -buildid=none -linkmode=external" -buildmode=pie -buildvcs=false

build:
@go build
@go build ${FLAGS}

build-rel:
@go build -ldflags="-w -s" -buildvcs=false -o minivpn
@go build ${FLAGS} -o minivpn
@upx --brute minivpn
@GOOS=darwin go build -ldflags="-w -s" -buildvcs=false -o minivpn-osx
@GOOS=windows go build -ldflags="-w -s" -buildvcs=false -o minivpn.exe
@GOOS=darwin go build -d ${FLAGS} -o minivpn-osx
@GOOS=windows go build ${FLAGS} -o minivpn.exe

build-race:
@go build -race
Expand Down
37 changes: 35 additions & 2 deletions proxy.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package main

import (
"errors"
"fmt"
"net"
"os"
"runtime"
"syscall"

socks5 "github.com/armon/go-socks5"
"github.com/ooni/minivpn/vpn"
Expand Down Expand Up @@ -40,8 +43,38 @@ func ListenAndServeSocks(opts *vpn.Options) {
}

addr := net.JoinHostPort(ip, port)
fmt.Printf("[+] Started socks5 proxy at %s\n", addr)
fmt.Printf("[+] Starting socks5 proxy at %s\n", addr)
if err := server.ListenAndServe("tcp", addr); err != nil {
panic(err)
if isErrorAddressAlreadyInUse(err) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking you could perhaps just call server.ListendAndServe with addr = {ip}:0 so as to make the OS pick the next available port for you.

I then saw, though, that the sock5 library you are using doesn't return the net.listener (https://github.com/armon/go-socks5/blob/master/socks5.go#L100) so if you actually need to know the picked port you have no way of extracting it :(

I guess we can leave this as-is for the time being, but it might be worth opening an issue as future work to open PR on https://github.com/armon/go-socks5 to return the listener (or set it as an attribute to the server struct).

  • MIV-01-007 Possible DoS via Predictable Port Usage

fmt.Printf("[!] Address %s already in use\n", addr)
for i := 1; i < 1e4; i++ {
addr := net.JoinHostPort(ip, fmt.Sprintf("%d", i+1024))
fmt.Println("[+] Trying to listen on", addr)
if err := server.ListenAndServe("tcp", addr); err != nil {
continue
}
}
} else {
panic(err)
}
}
}

func isErrorAddressAlreadyInUse(err error) bool {
var eOsSyscall *os.SyscallError
if !errors.As(err, &eOsSyscall) {
return false
}
var errErrno syscall.Errno // doesn't need a "*" (ptr) because it's already a ptr (uintptr)
if !errors.As(eOsSyscall, &errErrno) {
return false
}
if errErrno == syscall.EADDRINUSE {
return true
}
const WSAEADDRINUSE = 10048
if runtime.GOOS == "windows" && errErrno == WSAEADDRINUSE {
return true
}
return false
}
8 changes: 6 additions & 2 deletions scripts/bootstrap-provider
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import os
import sys
import urllib.request

PROVIDERS=("calyx", "riseup")

APIS = {
"calyx": "https://api.vpn.calyx.dev/",
"riseup": "https://api.float.hexacab.org/"
Expand Down Expand Up @@ -31,8 +33,10 @@ key cert.pem
def check_args():
if len(sys.argv) != 2:
print("Usage: bootstrap-provider <provider>")
os.exit(1)

sys.exit(1)
if sys.argv[1] not in PROVIDERS:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • MIV-01-011 Missing Verification on VPN bootstrap-provider Utility Script

print("Invalid provider")
sys.exit(1)

def getPath(provider):
return os.path.join(os.getcwd(), "data", provider)
Expand Down
13 changes: 13 additions & 0 deletions vpn/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ func bytesUnpadPKCS7(b []byte, blockSize int) ([]byte, error) {

// bytesPadPKCS7 returns the PKCS#7 padding of a byte array.
func bytesPadPKCS7(b []byte, blockSize int) ([]byte, error) {
if blockSize == 0 {
return nil, fmt.Errorf("%w: %s", errBadInput, "blocksize cannot be zero")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • MIV-01-002 Possible DoS via Integer Division by Zero

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we are at it, I think we should also protect against negative blockSize values. The assumption in the report is that the attacker may be able to control the blockSize value, hence if that is the case, this snippet will also lead to a crash:

    b := []byte("a")
    blockSize := -1
    psiz := blockSize - len(b) % blockSize
    padding := bytes.Repeat([]byte{byte(psiz)}, psiz)

via

panic: bytes: negative Repeat count

goroutine 1 [running]:
bytes.Repeat(0xc0000a2f27, 0x1, 0x1, 0xffffffffffffffff, 0x46, 0x4db500, 0xc000010080)
    /usr/local/go/src/bytes/bytes.go:499 +0x1aa
main.main()
    /home/runner/3bh1srnac6d/main.go:11 +0x62

}
// If lth mod blockSize == 0, then the input gets appended a whole block size
// See https://datatracker.ietf.org/doc/html/rfc5652#section-6.3
if blockSize > math.MaxUint8 {
Expand Down Expand Up @@ -142,3 +145,13 @@ func bufWriteUint32(buf *bytes.Buffer, val uint32) {
binary.BigEndian.PutUint32(numBuf[:], val)
buf.Write(numBuf[:])
}

// bufWriteUint24 is a convenience function that appends to the given buffer
// 3 bytes containing the big-endian representation of the given uint32 value.
// Caller is responsible to ensure the passed value does not overflow the
// maximal capacity of 3 bytes.
func bufWriteUint24(buf *bytes.Buffer, val uint32) {
b := &bytes.Buffer{}
bufWriteUint32(b, val)
buf.Write(b.Bytes()[1:])
}
5 changes: 5 additions & 0 deletions vpn/bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,8 @@ func Test_bytesPadPKCS7(t *testing.T) {
})
}
}

// Regression test for MIV-01-002
func Test_Crash_bytesPadPCKS7(t *testing.T) {
bytesPadPKCS7(nil, 0)
}
7 changes: 4 additions & 3 deletions vpn/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ var (
// - during the handshake (mtu).
// - after server pushes config options(ip, gw).
type tunnelInfo struct {
mtu int
ip string
gw string
mtu int
ip string
gw string
peerID int
}

// vpnClient is a net.Conn that uses the VPN tunnel. It is a net.Conn with an
Expand Down
15 changes: 1 addition & 14 deletions vpn/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ func (a *dataCipherAES) encrypt(key []byte, data *plaintextData) ([]byte, error)
return []byte{}, fmt.Errorf("%w: wrong padding", errCannotEncrypt)
}
mode := cipher.NewCBCEncrypter(block, data.iv)

ciphertext := make([]byte, len(data.plaintext))
mode.CryptBlocks(ciphertext, data.plaintext)
return ciphertext, nil
Expand Down Expand Up @@ -370,17 +371,3 @@ func pHash(result, secret, seed []byte, hash func() hash.Hash) {
a = h.Sum(nil)
}
}

// TODO(ainghazal): this function is not needed if we use Hash.Size()
// TODO: refactor, use hash.Reset() and create hash function in the constructor.
func getHashLength(s string) uint8 {
switch s {
case "sha1":
return 20
case "sha256":
return 32
case "sha512":
return 64
}
return 0
}
Loading