Official exam paper
Denas case study - 2022 session, U6/E6 (SLAM), duration 4h, coeff. 4, out of 80 marks (4 parts: 12 + 26 + 24 + 18 pts). Paper code 22SI5SLAM-NC. 📄 Download the official Denas exam paper (réseaucerta.org)
Lockbay does not host the exam paper: this model answer is an original Lockbay production answering the questions from the official paper. Exam paper © Éducation nationale.
The Denas case study (aeronautical electronics) focuses on the PartEdge web application: spare parts requests sent to the ERP via a REST API. Four parts: risk analysis, authentication (hashing, anti-injection, 2FA), request validation (signature, trigger, modelling) and REST API. Question-by-question solutions.
Part A - Participation in the risk analysis workshop 12 pts
Security criteria (DICP) for use cases 2 and 6
We evaluate the four criteria: Confidentiality, Integrity, Availability, Proof (0 / + / ++).
| Case | Confidentiality | Integrity | Availability | Proof |
|---|---|---|---|---|
| 2 - View catalogue | + | ++ | ++ | 0 |
| 6 - Sign electronically | ++ | ++ | + | ++ |
Justification: for case 2, the catalogue (prices, stock levels) is commercially sensitive, but above all it must be accurate (integrity ++) and available 24/7 (++); no proof is required for simple viewing. For case 6, the signature legally binds the customer: proof is paramount (++), as are the confidentiality and integrity of contractual documents.
Business impact of dreaded events nos. 4 and 5
- ER no. 4 (customer confirms requests for parts incompatible with the aircraft): undue immobilisation of parts removed from stock for an unusable request, stock shortages for genuine needs, aircraft grounded at other customers (up to €25,000/day), and loss of revenue and reputation → severity ++.
- ER no. 5 (attacker accesses ERP data via the request interface): compromise of the core IS (orders, stock, invoicing, customers), leak or alteration of contractual and financial data, falsified aeronautical traceability jeopardising aircraft safety → severity ++.
Two measures for risk scenarios nos. 2 and 5
- Scenario 2 (password attack / brute force tool):
- Hash passwords with salting (Bcrypt) - a database breach does not reveal plain-text passwords;
- Limit attempts (lockout/delay after N failed attempts) and enforce a strong password policy + 2FA (OTP).
- Scenario 5 (ERP corruption via code injection in requests):
- Validate and type all data received by the REST API (whitelists, regular expressions) before transmission to the ERP system;
- Isolate the API in a DMZ with a least-privilege service account on the ERP side and parameterised queries on the database side.
Motivations for scenarios nos. 3 and 4
Both scenarios fall under competitive cyber-influence: an unscrupulous customer reserves parts they will not confirm (scenario 3) or requests incompatible parts (scenario 4) to deplete available stock. As stock levels are very low (pandemic, extended supplier lead times), the aim is to deprive competitors of the parts they need and ground their aircraft (with losses of up to €25,000/day), thereby gaining an unfair commercial advantage.
Part B - Improving authentication 26 pts
Encryption (AES) or hashing (SHA) for passwords?
Hashing is the most secure solution. Symmetric encryption (AES) is reversible: the decryption key must be stored somewhere; if it is stolen with the database, all passwords can be recovered in plain text. Hashing is one-way (irreversible): passwords are never stored, only hashes are compared. Furthermore, the application never needs to retrieve a password, only verify it - hashing is sufficient.
Importance of salting (salt) with Bcrypt
The salt is a random, unique value per user, added to the password before hashing. It ensures that two customers with the same password obtain different hashes, which:
- renders precomputed tables (rainbow tables) useless;
- prevents identifying identical passwords across accounts;
- forces the attacker to crack each hash individually.
Bcrypt automatically generates and incorporates the salt into the resulting hash, and applies a cost factor (deliberate slowness) that significantly slows down brute-force attacks.
Securing readByLogin (ClientDao) against SQL injection
The original code (Document B4) concatenates $login into the query (line 6) - a direct injection vulnerability. It is replaced with a prepared statement with a bound parameter.
// Ligne 6 — modifiée : on remplace la valeur concaténée par un marqueur ?
$req = "SELECT id, nom, login, pass, courriel, telephone, adresse,
pays, estBloque, codePinOtp FROM client WHERE login = ?";
// Lignes 6bis/7 — insérées : préparation, liaison de la valeur, exécution
$prep = $connex->prepare($req);
$prep->bindValue(1, $login, PDO::PARAM_STR);
$prep->execute();
// Ligne 8 — modifiée : on lit le résultat sur l'objet préparé
$enreg = $prep->fetch(PDO::FETCH_OBJ);
// Ligne 16 — modifiée : fermeture du curseur de la requête préparée
$prep->closeCursor();The input value is never interpreted as SQL code: it is sent separately from the query. The concatenation '" . $login . "' is removed.
Two other 2FA solutions
In addition to a physical OTP key:
- OTP via an authenticator app (TOTP such as Google Authenticator / FreeOTP) or via SMS / email sent to a trusted channel;
- Biometric authentication (fingerprint, facial recognition) or a FIDO2/U2F hardware key (such as a YubiKey) plugged into the workstation.
Adding the OTP field to loginView.php
An input field for the 6-digit code is inserted between the password and the button (after line 3):
<!-- Ligne 3bis — insérée -->
<label for="otp">Code OTP :</label>
<input type="text" id="otp" name="otp" maxlength="6" pattern="[0-9]{6}" required/>OTP verification and logging in verifLogin (LoginCtrl)
The entered OTP code is retrieved, compared with the one generated by the OTP class, and each attempt is logged via syslog() in the required format (Documents B1 and B6). Insertions into verifLogin:
// Ligne 10bis — insérée : récupération du code OTP saisi
$otpForm = filter_input(INPUT_POST, 'otp', FILTER_SANITIZE_NUMBER_INT);
// Lignes 19 à 24 — remplacées : mot de passe OK -> on contrôle l'OTP, puis on journalise
if (password_verify($passForm, $client->getPass())) {
$unOtp = new OTP($client);
$codeOk = $unOtp->getCode(new DateTime());
if ($otpForm == $codeOk) {
// Connexion réussie : on journalise puis on affiche l'accueil
syslog(LOG_INFO, "Connexion client " . $client->getId());
new AccueilView();
} else {
// Mot de passe valide mais OTP erroné
syslog(LOG_WARNING, "Erreur connexion client " . $loginForm . ", cause : Erreur OTP");
$erreur = true;
}
} else {
// Couple login / mot de passe invalide
syslog(LOG_WARNING, "Erreur connexion client " . $loginForm . ", cause : Erreur login/mot de passe");
$erreur = true;
}The
$client == nullcase (unknown login, line 14) must also log aLOG_WARNINGwith the reasonErreur login/mot de passe. It ends withcloselog();.
Part C - Validating customer requests 24 pts
Condition for the admissibility of electronic exchanges in court
An electronic exchange has the same probative value as a paper document, provided that the author of the document can be reliably identified and the integrity of the content guaranteed (unaltered document), via an electronic signature based on a reliable process (certificate issued by a qualified trust service provider, in accordance with the eIDAS regulation and the Civil Code).
Technical solutions for the four requirements
| Legal department requirement | Technical solution |
|---|---|
| 1. Confidentiality of exchanges | Document encryption (TLS communication encryption and/or encryption of the document with the recipient's public key) |
| 2. Authenticity + non-modification | Electronic signature: document hash signed with the sender's private key; any modification invalidates the signature |
| 3. Proof of receipt by the customer | Acknowledgement of receipt (electronic registered delivery / qualified timestamping), ideally countersigned by the recipient |
| 4. Legal commitment of the customer | Customer electronic signature based on a qualified certificate (eIDAS), which legally binds them |
Query: customers with unconfirmed requests older than 24 h
A request is abusive if it is not confirmed (dateConfirmation IS NULL) more than 24 h after its creation. We display id, nom, and the total number of parts requested.
SELECT c.id, c.nom, COUNT(d.id) AS nbPieces
FROM Client c
JOIN Demande d ON d.idClient = c.id
WHERE d.dateConfirmation IS NULL
AND d.dateDemande < NOW() - INTERVAL 24 HOUR
GROUP BY c.id, c.nom;Trigger to block abusive customers
Before inserting a request, nb_abus(idClient) is called. If the customer reaches 2 violations, they are blocked (estBloque = true) and request creation is prevented (raising an error).
CREATE OR REPLACE TRIGGER verif_client_abus
BEFORE INSERT ON Demande
FOR EACH ROW
BEGIN
DECLARE nb INT;
SET nb = nb_abus(NEW.idClient);
IF nb >= 2 THEN
UPDATE Client SET estBloque = TRUE WHERE id = NEW.idClient;
-- on empêche l'enregistrement de la nouvelle demande
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Client bloqué pour demandes abusives';
END IF;
END;Modelling part type / aircraft model compatibility
A part type can fit several aircraft models, and a model accepts several part types: a many-to-many relationship, with a comment attached to each compatibility. We add an entity ModeleAeronef and an association table Compatibilite:
Schéma relationnel ajouté :
ModeleAeronef(id, libelle)
Compatibilite(idTypePiece, idModele, commentaire)
clé primaire : (idTypePiece, idModele)
clés étrangères : idTypePiece -> TypePiece(id)
idModele -> ModeleAeronef(id)In a class diagram: association *..* between TypePiece and ModeleAeronef, reified by an association class Compatibilite carrying the commentaire attribute.
Implementing the business constraint (without coding it)
When entering a request, check that the (requested part type, relevant aircraft model) pair exists in the Compatibilite table. This constraint can be implemented using a trigger BEFORE INSERT on Demande that rejects the request if no matching compatibility row is found (and/or via an application check on the service side before calling the database).
Part D - Sending data to the ERP / REST API 18 pts
REST method createDemande (POST) in DemandeRest
We call DemandeService.create(); on success we return 201 CREATED, otherwise 400 BAD_REQUEST (the request could not be completed).
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response createDemande(DemandeJson laDemandeJson) {
boolean ret = DemandeService.create(laDemandeJson);
if (ret == true) {
return Response.status(Status.CREATED).build();
} else {
return Response.status(Status.BAD_REQUEST).build();
}
}Checks in verifDonneesCreate (regex) + additional check
a) The provided code contains incorrect regexes: ^[0-9]4-[0-9]2-[0-9]2$ does not express repetition (it should be {4}, {2}, {2}) and INTERNE|EXTERNE is not anchored (it would accept XINTERNEY). We fix this and add the exchange type check:
// Date au format ISO YYYY-MM-DD (corrigé avec les quantificateurs {n})
if (Pattern.matches("^[0-9]{4}-[0-9]{2}-[0-9]{2}$", laDem.getDateDemande()) == false) {
res = false;
}
// Mode de transport : INTERNE ou EXTERNE uniquement (ancré ^...$)
if (Pattern.matches("^(INTERNE|EXTERNE)$", laDem.getModeTransport()) == false) {
res = false;
}
// ** Ajout : type d'échange STANDARD, SILVER ou GOLD uniquement
if (Pattern.matches("^(STANDARD|SILVER|GOLD)$", laDem.getTypeEchange()) == false) {
res = false;
}b) Additional date check: a regex validates the format but not the actual validity of the date (2022-03-33 matches the pattern). We must verify that the date actually exists (strict parsing with SimpleDateFormat.setLenient(false) or LocalDate.parse) and, from a business standpoint, that it is consistent (for example, not later than today's date).
Missing JUnit test: create with a valid request
The three existing tests cover invalid cases (transport, exchange, date). The nominal case is missing, where all data is valid and create must return true. We add a valid request to setUp(), then the test:
// Dans setUp() — ligne 16 : une 4e demande entièrement valide
this.dem4 = new DemandeJson(4, "2022-03-20", " ",
"GOLD", 0, "INTERNE", 44, "A320", 2, 3, null);
// Nouveau test — ligne 44 : demande valide -> create doit réussir
@Test
public void testCreateDemandeValide() {
boolean result = DemandeService.create(dem4);
assertEquals("Create avec une demande valide", true, result);
}Declare the
private DemandeJson dem4;field at the top of the class (alongsidedem1/dem2/dem3).
Marking scheme points (reminder)
| Section | Expectation | Points |
|---|---|---|
| A | DICP criteria (cases 2 and 6), ER 4/5 impacts, scenario 2/5 measures, cyber influence motives | 12 |
| B | Hashing vs encryption, Bcrypt salting, anti-injection prepared statement, 2FA, OTP field, OTP verification + syslog logging | 26 |
| C | Electronic signature / eIDAS, 4 requirements, abuse query, blocking trigger, compatibility modelling, business constraint | 24 |
| D | POST REST API (201/400), regex checks + date validity, nominal JUnit test | 18 |
| Total | 80 |