nfc-tools/libfreefare

mifare_desfire_{change_key,set_default_key} fall back to plaintext without active authentication

malexmave opened this issue · 3 comments

When using mifare_desfire_change_key without authenticating first, parts of the new key is (according to the debug log) transmitted in plaintext and then rejected by the card with an "invalid length" response. I don't know if this is intended behaviour, but it seems potentially unsafe.

Here is a log when attempting to set the key to the AES key {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}

*** mifare_desfire_get_version ***
===> 0000   90 60 00 00 00                                   |.`...           |
<=== 0000   04 01 01 01 00 18 05 91 af                       |.........       |
*** mifare_desfire_get_version ***
===> 0000   90 af 00 00 00                                   |.....           |
<=== 0000   04 01 01 01 04 18 05 91 af                       |.........       |
*** mifare_desfire_get_version ***
===> 0000   90 af 00 00 00                                   |.....           |
<=== 0000   04 5c 22 1a 12 4e 80 b9 0c 16 4d 40 34 16 91 00  |.\"..N....M@4...|
Found Mifare DESFire with UID 045c221a124e80. 
*** mifare_desfire_change_key ***
===> 0000   90 c4 00 00 14 80 00 10 23 33 44 55 66 76 ff ff  |........#3DUfv..|
===> 0010   fe fe ff fe ff ff 00 ad 98 00                    |..........      |
<=== 0000   91 7e                                            |.~              |
mifare_desfire_set_default_key: LENGTH_ERROR

Same when using mifare_desfire_set_default_key instead of mifare_desfire_change_key:

*** mifare_desfire_get_version ***
===> 0000   90 60 00 00 00                                   |.`...           |
<=== 0000   04 01 01 01 00 18 05 91 af                       |.........       |
*** mifare_desfire_get_version ***
===> 0000   90 af 00 00 00                                   |.....           |
<=== 0000   04 01 01 01 04 18 05 91 af                       |.........       |
*** mifare_desfire_get_version ***
===> 0000   90 af 00 00 00                                   |.....           |
<=== 0000   04 5c 22 1a 12 4e 80 b9 0c 16 4d 40 34 16 91 00  |.\"..N....M@4...|
Found Mifare DESFire with UID 045c221a124e80. 
*** mifare_desfire_set_default_key ***
===> 0000   90 5c 00 00 1a 01 00 10 23 33 44 55 66 76 ff ff  |.\......#3DUfv..|
===> 0010   fe fe ff fe ff ff 00 00 00 00 00 00 00 00 34 00  |..............4.|
<=== 0000   91 7e                                            |.~              |
mifare_desfire_set_default_key: LENGTH_ERROR

Proposed fix (unless this is intended behaviour): Check if authentication session exists before attempting to change the key, and abort locally if no authentication is active.

Well, debug information are not intended to be displayed nor used in a production context 😄 The real problem is that a part of the key can be intercepted by some spy near the card reader…

I don't have the DESFire spec at hand, but if calling mifare_desfire_change_key() unauthenticated is not supposed to happen, then it may make sense to check this before sending part of the new key on the air.

Can you please check this and tell us if this scenario is nonsense or if some cases, mifare_desfire_change_key() can be called without authentication and allow the new key to be intercepted?

Well, as I understand the debug log, the card is actually sending a reply (the line <=== 0000 91 7e), which would indicate that the message is actually being sent. I did not explicitly verify that is is physically sent - are there scenarios in which the debug log would show this without actual communication taking place, especially with a fairly esoteric "LENGTH_ERROR"? When calling the function with active authentication, the debug log shows encrypted data, so it's not an issue with the debug log (I think).

I am one of the developers of the NFC MitM interception app NFCGate, so I could also check it physically, but it would be some additional work, hence I am asking first if it is actually needed. (This is also why I filed the issue without explicitly stating that the attack this would be vulnerable to is a MitM - it slipped my mind as I am just in the MitM mindset all the time)

As the above debug logs result from actual code I wrote, yes, the set_default_key and change_key can be called without active authentication, at least when doing things in the following order:

  1. Connect to card
  2. Authenticate
  3. Reset card (thereby also resetting authentication status according to the standard)
  4. Call change_key / set_default_key

I had removed the intermediate steps from the log as I did not think they were important, but upon second thought, they may actually be important. I have created a gist with my (ugly - sorry, was not intended for release) PoC code. Compile it against a debug build of libfreefare to see the results.

So, I have glared at the docs, and as you say, some commands may be used without authentication when the card is in it's factory defaults configuration (which makes sense).

As far as I can tell, there is not much we can do from libfreefare itself.: it's the end-user responsibility to not issue such commands in an untrusted environment, because this could be the normal process of initial cards personalization.

Feel free to re-open this issue if you think about some improvement we can do.