Leider sind die Implementierungen für die 64Bit Integer Variable zur Generierung des IV und für die Sicherheitsvariable msgAuthoritativeEngineBoots
nicht mit den zugehörigen RFCs konform. Außerdem fehlt in der original Contiki SNMP Implementierung die Überprüfung des Benutzersicherheitslevels und die Überprüfung der Variablen msgAuthoritativeEngineTime
wurde nicht korrekt umgesetzt.
Laut RFC3826 muss die 64 Bit
Integer Variable beim Bootvorgang pseudo zufällig generiert werden.
In der momentanen Implementierung ist diese Variable durch zwei 32 Bit
Integer u32t privacyLow
und u32t privacyHigh
in der Datei
snmpd-conf.c
mit festen Werten definiert.
Um für diese Variablen nun zufällige Werte beim Bootvorgang zu generieren,
wird die Funktion random()
der AVR-GCC Library genutzt.
Diese wird zuerst mit einem Startwert (Seed) durch die Funktion srandom() gesetzt.
Zur Generierung eines Startwerts wird die Funktion u32t get_seed()
genutzt.
Die Funktion u32t get_seed()
, generiert aus dem Datenspeicher,
genauer gesagt aus dem SRAM Bereich des Datenspeichers in dem sich der Heap
und der Stack befindet, einen 32 Bit Anfangswert. Da dies beim Start geschieht,
ist der Inhalt des SRAMs zufällig.
Der Beginn des Heap Speichers wird über die Variable heap_start
, welche im
zugehörigen Linker Skript definiert ist, markiert.
Das Ende des SRAMS wird über das Makro RAMEND
(avr/io.h
) gekennzeichnet.
Es wird nun der gesamte Speicherbereich zwischen heap_start
und RAMEND
stückweise immer wieder mit einer XOR Verknüpfung durchwandert und der
Variable seed
zugewiesen. Die Funktion random()
wird nun nacheinander
den beiden Variablen u32t privacyLow
und u32t privacyHigh
zugewiesen,
wodurch diese nun pseudozufällig sind.
→Die Variablen sowie die dazugehörigen get-Funktionen wurden von der
Datei snmpd-conf.c
bzw. snmpd-conf.h
in die Dateien snmpd.c
bzw. snmpd.h
verschoben, da diese beim Programmstart generiert werden müssen.
Funktion: get_seed()
:
u32t get_seed ( ) { u32t seed = 0 ; u32t p = ( u32t ) (RAMEND+1) ; extern u32t __heap_start ; #if PDEBUG printf ( "RAMEND: %X\n__heap_start : %X" ,RAMEND, &__heap_start ); #endif while (p >= &__heap_start + 1) seed ^= *(--p); return seed; }
→ Der vollständige Quellcode mit allen Änderungen, kann am Ende dieser Seite heruntergeladen werden.
Die Variable msgAuthoritativeEngineBoots
muss laut RFC3414
innerhalb einer nicht flüchtigen Variable gespeichert werden und enthält
die Anzahl der Bootvorgänge seit der Installation des SNMP Agenten.
Sie wird innerhalb des USM msgAuthoritativeEngineBoots
zur Generierung des
Initialisierungsvektors sowie innerhalb des Timliness Moduls verwendet.
In der ursprünglichen Implementierung von Contiki SNMP wird diese Variable
einfach immer mit dem Wert Null verwendet.
Dies stellt natürlich ein erhebliches Sicherheitsrisko dar.
Es wird deshalb eine 32Bit unsigned Integer Variable innerhalb des EEPROM,
also im nicht flüchtigen Speicher, des ATmega1281 angelegt.
Um auf den EEPROM zuzugreifen, muss die Datei <avr/eeprom.h>
mit einbezogen werden.
Damit nun bei jedem Neustart des SNMP Agenten msgAuthoritativeEngineBoots
inkrementiert
wird, kommt die Funktion incMsgAuthoritativeEngineBoots()
, siehe folgenden Quellcode, zum Einsatz.
u8t incMsgAuthoritativeEngineBoots() { /*Increments the Value of MsgAuthoritativeEngineBoots when booting.*/ /*Checks if the maximum value of 2147483647 (RFC3414) is reached.*/ if((eeprom_read_dword(&MsgAuthoritativeEngineBoots))<2147483647) { eeprom_update_dword(&MsgAuthoritativeEngineBoots, (eeprom_read_dword(&MsgAuthoritativeEngineBoots)+1)); } else { #if PDEBUG printf("Maximum Number of MsgAuthoritativeEngineBoots reached, please reconfigure all secret values and reinstall SNMP Agent\n"); #endif } #if PDEBUG printf("MsgAuthoritativeEngineBoots = %lu\n",eeprom_read_dword(&MsgAuthoritativeEngineBoots)); #endif return 0; }
Sie wird beim Starten des snmpd_process in der Datei snmpd.c
aufgerufen
und erhöht die Variable um Eins.
Außerdem wurde die Ausgabe der Funktion getSysUpTime()
, welche die
Zeit für die Variable snmpEngineTime
liefert, wie in RFC3414
festgelegt, auf einen maximalen Wert von 2147482647
begrenzt.
Ist der Wert erreicht, so wird die Variable seconds
(Zeit in Sekunden seit Systemstart)
auf Null gesetzt und msgAuthoritativeEngineBoots
über die Funktion
incMsgAuthoritativeEngineBoots()
um Eins erhöht.
Des Weiteren fordert RFC3414 bei Erreichen
eines Wertes von 2147483647
für msgAuthoritativeEngineBoots
eine Sperrung des
Zugriffs auf den SNMP Agenten.
Bei einem Zugriff muss als Fehler eine notInTimeWindow Fehlermeldung zurückgesendet werden.
Dies wurde innerhalb der Datei usm.c
realisiert.
→ Der vollständige Quellcode mit allen Änderungen, kann am Ende dieser Seite heruntergeladen werden.
Laut RFC3414 muss vor dem Verarbeiten
einer eingehenden SNMP Nachricht überprüft werden ob das Sicherheitslevel der
eingehenden Nachricht mit dem Sicherheitslevel des dazugehörigen Benutzernamens
übereinstimmt.
Ist dies nicht der Fall, so soll die Nachricht verworfen werden und eine Meldung mit
dem Inhalt unsupportedSecurityLevel an den Sender zurückgesendet werden.
Die original Contiki SNMP Implementierung erfüllt diese Bedingung nicht.
Das USM Sicherheitsmodul antwortet immer mit dem Sicherheitslevel, das in der eingehenden Nachricht angegeben ist.
Lediglich der Benutzername wird überprüft.
Um dieses Problem zu lösen, wurden einige kleine Änderung innerhalb der Datei usm.c
vorgenommen.
Zuerst musste ein Objekt für die Fehlermeldung unsupportedSecurityLevel angelegt werden.
/** \brief The total number of packets received by the SNMP * engine which were dropped because they got an * unsupportedSecurityLevel for the user specified * in snmpd-conf.c. */ u8t usmStatsUnsupportedSecurityLevel_array[] = {0x2b, 0x06, 0x01, 0x06, 0x03, 0x0f, 0x01, 0x01, 0x01}; ptr_t usmStatsUnsupportedSecurityLevel = {usmStatsUnsupportedSecurityLevel_array, 9}; u32t usmStatsUnsupportedSecurityLevelCounter;
Nach diesem Schritt wird innerhalb der Funktion zur Bearbeitung eingehender Nachrichten vor dem Aufruf des Privacy bzw. des Authentication Moduls eine Überprüfung des Sicherheitslevels durchgeführt, sofern dieses innerhalb des Agenten aktiviert wurde.
#if ENABLE_AUTH if (!(request->msgFlags & FLAG_AUTH)) { #if PDEBUG printf("USM Modul: Error! User needs Authentication\n"); #endif TRY(report(request, &usmStatsUnsupportedSecurityLevel, &usmStatsUnsupportedSecurityLevelCounter)); return ERR_USM; } #endif
Entspricht das Sicherheitslevel der Nachricht nicht dem des dazugehörigen Benutzernamens, so wird mithilfe der Funktion report() eine Antwortnachricht mit dem Fehlercode unsupportedSecurityLevel an den Absender geschickt. Für das Privacy Modul wurde diese Funktion nach dem gleichen Prinzip umgesetzt.
→ Der vollständige Quellcode mit allen Änderungen, kann am Ende dieser Seite heruntergeladen werden.
Innerhalb des USM Moduls befindet sich das Timeliness Modul, dieses dient zur Überprüfung der Authentizität der Nachricht. Dazu muss laut RFC3414 die ankommende msgAuthoritativeEngineTime
mit der lokalen AuthoritativeEngineTime
des Agenten verglichen werden.
Der Unterschied zwischen beiden Zeiten darf 150 Sekunden nicht überschreiten.
Dies wurde im original Contiki SNMP mit der folgenden if-Abfrage umgesetzt.
if(request->msgAuthor i tat iveEngineBoots != getMsgAuthoritativeEngineBoots()|| abs(request->msgAuthoritativeEngineTime - getSysUpTime() ) < TIME_WINDOW) { TRY( report ( request, &usmStatsNotInTimeWindows, &usmStatsNotInTimeWindowsCounter ) ) ; return ERR_USM; }
Es wird also eine Absolutwertbildung der Subtraktion der msgAuthoritativeEngineTime
der ankommenden Nachricht und der Funktion getSysUpTime()
durchgeführt.
Der Wert der Variablen msgAuthoritativeEngineTime der eingehenden Nachricht ist in Sekunden angegeben.
Und die Funktion getSysUpTime()
liefert die Zeit seit dem letzten Systemstart in Millisekunden zurück.
Dies führt natürlich grundsätzlich zu einem Problem, sodass dieser Wert scheinbar immer größer als 150 Sekunden ist. Dies hat zur Folge, dass die Überprüfung innerhalb der if-Abfrage also immer null ist. Es wird also immer angenommen dass der Wert von msgAuthoritativeEngineTime
der eingehenden Nachricht innerhalb des Zeitfensters liegt. Dies wurde durch die folgende Quellcodeänderung korrigiert.
if (request->msgAuthoritativeEngineBoots != getMsgAuthoritativeEngineBoots() || abs(request->msgAuthoritativeEngineTime -(getSysUpTime()/100)) > TIME_WINDOW)
Da laut RFC3414 muss innerhalb des Discovery Vorgangs der zweite Report authentifiziert sein. Innerhalb der Funktion s8t report(message_v3_t* request, ptr_t* oid, u32t* counter)
muss hierzu eine Möglichkeit geschaffen werden.
In der originalen Implementierung ist die Versendung authentifizierter Reports nicht vorgesehen. Je nach verwendeten SNMP Manager kann dies jedoch dazu führen dass kein Zugriff auf den Agenten möglich ist. Die meisten Manager kürzen den Discovery Prozess ab und entnehmen die Werte für msgAuthoritativeEngineBoots
, sowie msgAuthoritativeEngineTime
dem ersten nicht authentifizierten Report, welcher jedoch nach RFC ausschließlich zur Synchronisierung der msgAuthoritativeEngineID
bestimmt ist. Hält sich eine SNMPv3 Manager Implementierung strikt an den vorgegebenen Ablauf aus RFC3414, so ist wie oben beschrieben kein Zugriff auf den Agenten möglich. Um dieses Problem zu beheben, genügt die folgende Änderung innerhalb der Datei usm.c
:
static s8t report(message_v3_t* request, ptr_t* oid, u32t* counter) { (*counter)++; if (!(request->msgFlags & FLAG_REPORTABLE)) { /* if the reportable flag is not set, then don't send a Report PDU */ return FAILURE; } // release variable bindings from the PDU free_varbinds(&request->pdu); printf("Sending Report....\n"); //request->msgFlags = 0; /*sz*/ /*Added this lines to perform authentication for reports which need it*/ /*Especially for the SNMP Discovery Process, see RFC3414, Sec.4*/ if(request->msgFlags & FLAG_AUTH) { request->msgFlags = FLAG_AUTH; printf("Report set to Auth\n"); } /*sz*/ request->pdu.response_type = BER_TYPE_SNMP_REPORT; request->pdu.varbind_first_ptr = varbind_list_append(0); if (!request->pdu.varbind_first_ptr) { return FAILURE; } request->pdu.varbind_first_ptr->varbind.oid_ptr = oid; request->pdu.varbind_first_ptr->varbind.value_type = BER_TYPE_COUNTER; request->pdu.varbind_first_ptr->varbind.value.u_value = *counter; return 0; }
Hier kann der komplette Quellcode des geänderten Contiki SNMP heruntergeladen werden: snmpd13.zip