Serialisierbare Klasse mit sensiblen Daten
Beschreibung
Serialisierbare Klasse mit sensiblen Daten ist eine Schwachstelle, bei der eine Klasse, die sensible Informationen enthält, nicht explizit die Serialisierung verhindert, was Angreifern ermöglicht, die Daten durch Serialisierung der Klasse über eine andere Klasse zu extrahieren. Serialisierbare Klassen sind effektiv offene Klassen, da Daten in ihnen nicht versteckt werden können - jeder Code mit Zugriff auf eine Instanz kann diese zu einem Byte-Stream serialisieren, übertragen oder speichern und die enthaltenen Daten extrahieren. Dies umgeht die normalen Zugriffskontrollen und die Kapselung, die sensible Informationen zur Laufzeit schützen.
Risiko
Die Serialisierung sensibler Daten schafft schwerwiegende Vertraulichkeits- und Integritätsrisiken. Angreifer können sensible Objekte in Byte-Streams schreiben, um Passwörter, kryptografische Schlüssel, persönliche Informationen oder Finanzdaten zu extrahieren. Serialisierte Daten können im Transit abgefangen, in Logs gespeichert oder aus Speicherabbildern extrahiert werden. Die serialisierte Form legt private Felder offen, die normalerweise durch Zugriffsmodifikatoren geschützt wären. Deserialisierung kann auch ausgenutzt werden, um modifizierte sensible Daten zurück in die Anwendung zu injizieren. In verteilten Systemen kann die Serialisierung versehentlich sensible Daten an nicht vertrauenswürdige Komponenten übertragen.
Lösung
Verhindern Sie die Serialisierung von Klassen mit sensiblen Daten, indem Sie eine finale writeObject()-Methode definieren, die NotSerializableException wirft. Alternativ implementieren Sie Externalizable und werfen Exceptions in den erforderlichen Methoden. Markieren Sie sensible Felder als transient, damit sie von der Serialisierung ausgeschlossen werden. Wenn Serialisierung erforderlich ist, verschlüsseln Sie sensible Felder vor der Serialisierung und entschlüsseln Sie nach der Deserialisierung. Verwenden Sie Serialisierungsfilter (Java 9+), um serialisierte Objekte zu validieren. Erwägen Sie sichere Alternativen zur Java-Serialisierung wie JSON mit explizitem Feld-Mapping, das sensible Daten ausschließt.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Ein Angreifer kann die Klasse in einen Byte-Stream schreiben und wichtige Daten daraus extrahieren, wodurch unbefugter Zugriff auf sensible Informationen wie Anmeldedaten, Schlüssel oder persönliche Daten erlangt wird. |
| Integrität | Bereich: Integrität Anwendungsdaten modifizieren - Angreifer können serialisierte Daten modifizieren und zurück in die Anwendung deserialisieren, was möglicherweise den sensiblen Zustand korrumpiert oder Sicherheitsüberprüfungen umgeht. |
Beispielcode
Verwundbarer Code
// Verwundbar: Serialisierbare Klasse mit sensiblen Daten
import java.io.*;
public class VulnerablePatientRecord implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String patientId;
private String socialSecurityNumber; // Hochsensibel!
private String medicalHistory; // Geschützte Gesundheitsinformation
private String diagnosis;
private byte[] geneticData; // Extrem sensibel
public VulnerablePatientRecord(String name, String ssn) {
this.name = name;
this.socialSecurityNumber = ssn;
}
// Kein Schutz gegen Serialisierung!
// Jeder Code kann serialisieren und alle Felder extrahieren
}
// Angreifer extrahiert sensible Daten
public class PatientDataExploit {
public void extractData(VulnerablePatientRecord patient)
throws Exception {
// Patientenakte serialisieren
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(patient);
oos.close();
// Angreifer hat jetzt Byte-Array mit allen sensiblen Daten
byte[] serializedData = baos.toByteArray();
// Kann in Datei geschrieben, über Netzwerk gesendet werden, etc.
Files.write(Paths.get("/tmp/gestohlene_patientendaten.ser"), serializedData);
// Oder Felder direkt extrahieren
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(serializedData)
);
VulnerablePatientRecord stolen =
(VulnerablePatientRecord) ois.readObject();
// Auf private Felder über Reflection oder Getter zugreifen
System.out.println("SSN: " + stolen.getSocialSecurityNumber());
}
}
// Verwundbar: Benutzeranmeldedaten serialisierbar
public class VulnerableUserSession implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String passwordHash;
private String authToken;
private byte[] sessionKey;
private Date loginTime;
private Set<String> permissions;
// Alle sensiblen Sitzungsdaten können serialisiert werden!
}
// Verwundbar: Serialisierbar durch Vererbung
public class VulnerableSecureData extends SerializableBase {
// Erbt Serializable von Elternklasse
private String apiKey;
private String secretToken;
private PrivateKey signingKey;
// Auch ohne explizites Serializable macht Elternklasse es serialisierbar
}
// Verwundbar: Standardmäßig serialisierbar in einigen Frameworks
@Entity // JPA-Entities sind oft serialisierbar
public class VulnerableUser implements Serializable {
@Id
private Long id;
private String username;
@Column(name = "password_hash")
private String passwordHash; // Sensibel!
@Column(name = "security_question")
private String securityQuestion;
@Column(name = "security_answer")
private String securityAnswer; // Sensibel!
}
// Verwundbar: Finanzdaten
public class VulnerableBankAccount implements Serializable {
private static final long serialVersionUID = 1L;
private String accountNumber;
private String routingNumber;
private double balance;
private String pin; // Sehr sensibel!
private List<Transaction> transactionHistory;
// Serialisierung legt alle Finanzdaten offen
}
// Angreifer kann abfangen und modifizieren
public class FinancialExploit {
public void manipulateAccount(byte[] serializedAccount) throws Exception {
// Deserialisieren
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(serializedAccount)
);
VulnerableBankAccount account =
(VulnerableBankAccount) ois.readObject();
// Reflection nutzen, um Kontostand zu modifizieren
Field balanceField = account.getClass().getDeclaredField("balance");
balanceField.setAccessible(true);
balanceField.setDouble(account, 1000000.0); // Kontostand aufblähen!
// Mit modifizierten Daten erneut serialisieren
// ... zurück an Anwendung senden
}
}
Lösungscode
// Behoben: Serialisierung sensibler Klasse verhindern
import java.io.*;
public final class SecurePatientRecord implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String patientId;
// Behoben: Transient-Felder werden nicht serialisiert
private transient String socialSecurityNumber;
private transient String medicalHistory;
private transient byte[] geneticData;
// Felder, die serialisiert werden können (nicht-sensibel)
private String diagnosis;
private Date visitDate;
public SecurePatientRecord(String name, String ssn) {
this.name = name;
this.socialSecurityNumber = ssn;
}
// Behoben: Exception bei Serialisierungsversuch werfen
private void writeObject(ObjectOutputStream out)
throws NotSerializableException {
throw new NotSerializableException(
"Patientenakten können nicht serialisiert werden"
);
}
// Behoben: Auch Deserialisierung verhindern
private void readObject(ObjectInputStream in)
throws NotSerializableException {
throw new NotSerializableException(
"Patientenakten können nicht deserialisiert werden"
);
}
}
// Behoben: Alternative - sensible Daten während Serialisierung verschlüsseln
public class SecureEncryptedRecord implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String patientId;
// Behoben: Verschlüsselt speichern, nicht im Klartext
private byte[] encryptedSSN;
private byte[] encryptedMedicalHistory;
// Behoben: Transient - niemals serialisieren
private transient String plaintextSSN;
private transient String plaintextMedicalHistory;
public SecureEncryptedRecord(String name, String ssn, Key encryptionKey) {
this.name = name;
this.plaintextSSN = ssn;
this.encryptedSSN = encrypt(ssn, encryptionKey);
}
// Behoben: Benutzerdefinierte Serialisierung mit Verschlüsselung
private void writeObject(ObjectOutputStream out) throws IOException {
// Sicherstellen, dass sensible Daten vor dem Schreiben verschlüsselt sind
if (plaintextSSN != null && encryptedSSN == null) {
throw new IOException("SSN muss vor Serialisierung verschlüsselt sein");
}
out.defaultWriteObject();
// Transient-Feld löschen
plaintextSSN = null;
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
// plaintextSSN bleibt null bis explizit entschlüsselt
}
public String getSSN(Key decryptionKey) {
if (plaintextSSN == null && encryptedSSN != null) {
plaintextSSN = decrypt(encryptedSSN, decryptionKey);
}
return plaintextSSN;
}
private byte[] encrypt(String data, Key key) {
// AES-GCM oder ähnliches verwenden
return EncryptionUtil.encrypt(data.getBytes(), key);
}
private String decrypt(byte[] data, Key key) {
return new String(EncryptionUtil.decrypt(data, key));
}
}
// Behoben: Externalizable für vollständige Kontrolle verwenden
public final class SecureUserSession implements Externalizable {
private String username;
private transient String passwordHash;
private transient String authToken;
private transient byte[] sessionKey;
private Date loginTime;
// Erforderlicher parameterloser Konstruktor für Externalizable
public SecureUserSession() {}
// Behoben: Externalisierung explizit verweigern
@Override
public void writeExternal(ObjectOutput out) throws IOException {
throw new IOException("Benutzersitzungen können nicht serialisiert werden");
}
@Override
public void readExternal(ObjectInput in) throws IOException {
throw new IOException("Benutzersitzungen können nicht deserialisiert werden");
}
}
// Behoben: JPA-Entity mit geschützter Serialisierung
@Entity
public class SecureUser implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long id;
private String username;
// Behoben: Transient - nicht serialisiert
@Transient
private transient String passwordHash;
// Behoben: Verschlüsselt in Datenbank gespeichert, transient für Serialisierung
@Column(name = "encrypted_security_answer")
private String encryptedSecurityAnswer;
@Transient
private transient String securityAnswer;
// Behoben: Java-Serialisierung verhindern
private void writeObject(ObjectOutputStream out)
throws NotSerializableException {
throw new NotSerializableException("Benutzer können nicht serialisiert werden");
}
// Behoben: Sichere DTO-Konvertierung
public UserDTO toDTO() {
UserDTO dto = new UserDTO();
dto.setId(id);
dto.setUsername(username);
// Niemals sensible Daten in DTO aufnehmen
return dto;
}
}
// Behoben: Bankkonto mit Serialisierungsschutz
public final class SecureBankAccount implements Serializable {
private static final long serialVersionUID = 1L;
private String accountNumber;
private String routingNumber;
// Behoben: Sensible Felder sind transient
private transient double balance;
private transient String pin;
private transient List<Transaction> transactionHistory;
// Behoben: Bei jedem Serialisierungsversuch Exception werfen
private void writeObject(ObjectOutputStream out)
throws NotSerializableException {
throw new NotSerializableException(
"Bankkonten können aus Sicherheitsgründen nicht serialisiert werden"
);
}
private void readObject(ObjectInputStream in)
throws NotSerializableException {
throw new NotSerializableException(
"Bankkonten können nicht deserialisiert werden"
);
}
// Behoben: Wenn Transfer benötigt wird, sicheres DTO verwenden
public AccountSummaryDTO toSummary() {
return new AccountSummaryDTO(
maskAccountNumber(accountNumber),
balance // Kontostand nur im authentifizierten Kontext angezeigt
);
}
private String maskAccountNumber(String account) {
return "****" + account.substring(account.length() - 4);
}
}
CVE-Beispiele
Keine spezifischen CVEs sind in der MITRE-Datenbank für diese CWE aufgeführt. Das Schwachstellenmuster ist jedoch verwandt mit:
- Java-Deserialisierungs-Schwachstellen (CWE-502)
- CERT Secure Coding Standard for Java (SER00-J)
Referenzen
- MITRE Corporation. "CWE-499: Serializable Class Containing Sensitive Data." https://cwe.mitre.org/data/definitions/499.html
- CERT Oracle Secure Coding Standard for Java. "SER03-J. Do not serialize unencrypted sensitive data."
- Oracle. "Secure Coding Guidelines for Java SE - Serialization."