lucidiot's cybrecluster

À la quête des enquêteurs d'accidents maritimes

Lucidiot Recherches 2021-01-18
Un exemple du travail qu'il faut faire pour exploiter les données de bases de données inexploitables de sécurité des transports.


Dans le cadre de mon projet ITSB, je suis constamment à la recherche des organisations chargées des enquêtes techniques sur les accidents en tous genres dans tous les modes de transport. C'est beaucoup plus difficile de les trouver qu'il n'y paraît ; dans certains pays, certains modes de transport sont si peu développés qu'il n'existe quasiment pas d'organisation indépendante qui en serait chargée, et les rapports se retrouvent disséminés parmi toutes les publications d'un ministère par exemple, ou alors l'organisation n'a pas de site internet du tout, ou un site très mal référencé, ou un site qui ne fonctionne tout simplement plus.

J'essaie de trouver toutes ces organisations pour en collecter les rapports et me construire encore plus de flux RSS. J'ai déjà plusieurs dizaines de flux et une liste de nouveaux flux à construire, mais j'en veux toujours plus, et je lis tous les rapports que je récupère. Il me reste encore au moins 2000 rapports à lire actuellement, et j'ai lu plus de 2000, peut-être 3000 articles en tous genres (dont des rapports d'enquête) en 2020.

Récemment, j'ai lu 50 rapports de la SAIA, l'agence suédoise d'investigation des accidents, via un flux RSS que je construis moi-même à partir des pages HTML. En lisant ces rapports, je suis notamment tombé sur une liste des organismes d'enquête d'accidents maritimes européens que j'ai gardé en marque-page. Je me suis mis un peu plus tard à la lire et à cliquer sur tous les liens pour découvrir de nouvelles organisations.

Moult liens sont morts: les serveurs sont en panne, les organisations sont renommées, etc. D'autres liens mènent seulement aux ministères ou à des sites trop génériques qui ne me permettent pas d'obtenir les rapports, et seulement les rapports. D'autres encore ne publient tout simplement pas les rapports, alors que le but de ce type d'enquête technique est d'éviter à tout le monde de refaire les mêmes erreurs.

Cela fait maintenant plus d'un an que j'ai commencé ce projet de flux RSS, et j'ai toujours autant de mal à trouver les bons sites. Comme ça a été le cas ici, je trouve le plus souvent de nouvelles organisations par le biais de liens glissés dans des rapports ou dans les sites d'autres agences.

Va voir là-bas si GISIS

Sur le site de la Marine Safety Investigation Unit de Malte, j'ai trouvé une page consacrée aux enquêtes d'autres pays où la MSIU était impliquée. La page m'a permis de découvrir des agences d'autres pays, comme la Mercantile Marine Office du Bangladesh, dont le site m'a au début fait penser à du spam plutôt qu'à une agence gouvernementale. Ce bureau ne publie pas les rapports d'enquête et le seul que j'ai pu donc trouver est le rapport republié par la MSIU, imprimé et re-scanné à l'aide de CamScanner

Je suis ensuite tombé sur le MAIIF, qui n'est pas une faute d'orthographe sur une compagnie d'assurances française mais un forum international d'enquêteurs d'accidents maritimes. J'y ai trouvé un manuel d'enquête maritime, que j'ai lu par curiosité. Le dernier chapitre mentionne le format de publication d'un rapport et les bases de données d'accidents, en indiquant que certaines agences ont développé leurs propres bases de données nationales, dont aucune n'est publique, et que l'organisation maritime internationale dispose d'une section dédiée aux incidents dans son Global Integrated Shipping Information System.

Une partie du GISIS est accessible au public sous conditions d'inscription. Le formulaire d'inscription ne permet pas de coller du texte dans les champs d'adresse e-mail ou de mot de passe, donc j'ai utilisé la console JavaScript pour pouvoir utiliser mon gestionnaire de mots de passe. Finalement, j'aurais pu juste mettre n'importe quoi vu que le formulaire n'a quasiment aucune validation et qu'il n'y a pas besoin de faire vérifier son e-mail. Le champ de civilité peut être ce qu'on veut et est facultatif, et il suggérait notamment Capt. Merci donc de m'appeler à présent Capitaine Lucidiot.

Le GISIS est une application ASP.NET plutôt vieillote, où presque chaque clic effectue une requête POST juste pour que le serveur le prenne en compte. J'ai trouvé la section des incidents, et les options de recherche sont quand même plutôt poussées. Il y a notamment la possibilité de voir uniquement les incidents pour lesquels des rapports d'enquête ont été produits, et de tout exporter au format CSV ou PDF. Cela dit, vu la quantité d'informations stockées pour chaque incident et la lenteur des exports, je ne fais qu'avoir des erreurs du serveur parce que les requêtes prennent trop de temps.

Je ne me suis donc pas attardé trop longtemps sur cette section, et je me suis plutôt dit que le filtrage par autorité maritime devait bien prendre ses données de quelque part, donc j'ai cherché la section sur les autorités maritimes ou les organismes d'enquête. Il y en a bien une, Contact Points, et des sites internet sont référencés pour une grande partie des 282 organisations listées.

Les exports CSV et PDF marchent cette fois-ci, et incluent un numéro de téléphone, un e-mail, un numéro de fax, ou même un numéro de télex, mais pas le site internet, qui est pourtant un champ affiché dans les détails de chaque point de contact. Je dispose donc comme seule option d'ouvrir les détails de chaque agence et de récupérer le site, que ce soit à la main ou avec un script.

Les mains dans le cambouis

Dans la liste des résultats, cliquer sur une des lignes déclenche une fonction appelée __doPostBack, dont j'ai pu trouver la définition directement dans la page :

var theForm = document.forms['aspnetForm'];
if (!theForm) {
    theForm = document.aspnetForm;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}

La fonction est appellée directement dans du JavaScript glissé dans des attributs HTML, le signe parfait pour détecter du code d'une ancienne époque :

<tr
    class="gridviewer_row"
    onmouseup="if (RowMouseUp(event)) { __doPostBack('ctl00$bodyPlaceHolder$ctl00$gridResults','_rc6') }"
    onmouseover="$(this).addClass('gridviewer_hoverrow');"
    onmouseout="$(this).removeClass('gridviewer_hoverrow');"
>

onmouseover et onmouseout sont utilisés seulement pour colorer les lignes en jaune quand on passe la souris dessus. Ça aurait tout à fait été possible sans JavaScript. onmouseup est le code qui se lance au clic : le premier argument est constant sur ce tableau, et le second change en fonction de la ligne: _rc0 d'abord, puis _rc1, etc.

J'aurais pu écrire un script shell utilisant curl pour m'authentifier, ouvrir la liste, aller dans chaque page, utiliser pup ou une XSLT pour obtenir les arguments de la fonction et reconstruire d'autres requêtes curl qui obtiendraient les URLs des pages de détail, puis récupérer les URL des sites, mais avant d'aller aussi loin, je voulais essayer de tout faire dans le navigateur. Après tout, j'ai accès à du JavaScript moderne, et le code de l'application est assez vieux pour utiliser des variables globales partout.

L'application envoie plusieurs dizaines de champs, la plupart inutiles, dans les requêtes POST. J'avais fait précédemment une tentative de créer un flux pour le GCAA, l'aviation civile des Émirats Arabes Unis, où là aussi une application ASP.NET envoyait plusieurs mégaoctets de données pour pas grand chose. Comme mon code allait s'exécuter dans la console du navigateur et allait donc s'effacer si la page change, je voulais tout faire avec l'API Fetch sans changer de page. J'ai donc besoin de construire les données du formulaire, dont j'ai accès via theForm, en POST moi-même. Fort heureusement, FormData me le permet très facilement :

const getUrl = () => fetch(document.location, {
    method: 'POST',
    body: new FormData(theForm),
    redirect: 'follow'
})

Un peu d'observation dans la console du navigateur me fait constater que les requêtes pour cliquer sur les lignes de tableaux font juste une redirection vers la page de détail, donc ajouter redirect: 'follow' me permet d'obtenir tout de suite leur contenu. J'utilise aussi document.location comme URL, car j'ai remarqué que toutes les requêtes se font à la page sur laquelle on est.

Il me faut ensuite récupérer les infos de site web. Ça n'a heureusement pas été bien difficile vu que la page de détails a un minimum de structure :

<div id="ctl00_bodyPlaceHolder_ctl00_divWeb" class="contact">
    <div>
        <div class="label">Web</div>
        <div class="content">
            <a href="http://www.dpdetare.gov.al">http://www.dpdetare.gov.al</a>
        </div>
    </div>
</div>

J'ai donc chaîné des Promise à la fonction précédente pour récupérer le texte de la page, traiter le HTML, et récupérer le site web, sachant qu'il n'est pas forcément présent pour tout le monde :

const getUrl = () => fetch(...)
    .then(response => response.text())
    .then(text => {
        const div = (new DOMParser())
            .parseFromString(text, "text/html")
            .getElementById('ctl00_bodyPlaceHolder_ctl00_divWeb')
        if (!div) return
        const a = div.querySelector('a')
        if (a) console.log(a.href)
    })

Il me reste encore une chose à faire : déclencher d'une façon ou d'une autre cette fonction sur toutes les lignes. Au lieu de faire ça dans ma console en traitant la chaîne de caractères de l'attribut onmouseup des lignes du tableau, j'ai relu la fonction __doPostBack, et j'ai réécrit la méthode theForm.submit. Comme ça, je peux juste cliquer en JavaScript sur toutes les lignes, le formulaire est envoyé, et ma fonction se lance.

theForm.submit = getUrl
document
    .querySelectorAll('tr.gridviewer_row, tr.gridviewer_altrow')
    .forEach(function (tr) {
        tr.onmouseup({ target: tr, type: 'mouseup' })
    })

Je ne pouvais pas utiliser dispatchEvent puisque ça n'exécute pas le code JavaScript défini dans des attributs HTML comme onmouseup. Je ne pouvais pas non plus utiliser MouseEvent pour l'événement car target n'est pas définissable et est normalement défini par dispatchEvent. Et je ne pouvais pas utiliser de sucre syntaxique pour la fonction (tr => tr.onmouseup(…)) car ce sont des fonctions en mode strict, qui n'autorisent pas l'accès à arguments, qui était visiblement utilisé par le code étrange de JavaScript.

Avec tout ça, les seules manipulations restantes étaient de cliquer sur le bouton de page suivante, lancer le code, copier les URLs, et ce 19 fois. C'est déjà mieux que de faire des centaines de clics.

J'ai mentionné précédemment que le second argument de __doPostBack est un nombre entier avec un préfixe. J'ai essayé d'appeler la fonction directement pour les 282 résultats, ce qui m'aurait permis de récupérer toutes les URLs d'un seul coup :

[...Array(282)].map(function (_, i) {
    __doPostBack('ctl00$bodyPlaceHolder$ctl00$gridResults', `_rc${i}`)
})

Techniquement, ça fonctionne, vu que ça déclenche bien les requêtes; mais la redirection ne se fait plus vers ContactDetails.aspx mais vers Error.aspx dès qu'on sort de la première page. Le serveur garde dans les tas de cookies et de paramètres de formulaire un état de la page, donc il sait qu'on n'est qu'à la première page de résultats et on ne peut pas en sortir aussi facilement.

Sites obtenus

La liste n'est pas énorme et n'est certainement pas exhaustive ; il me faudra tenter d'extraire plus tard les domaines des adresses e-mail des points de contact sur lesquels des sites n'ont pas été indiqués. Je n'ai aussi pas inclus dans la liste le grand nombre de liens morts, de sites à certificats expirés, de sites où aucun rapport d'enquête n'est publié, ou de sites dont les pages HTML ne seront jamais raisonnablement exploitables pour créer des flux RSS automatiquement.

Réflexions

Je suis plutôt impressionné de la difficulté qu'on peut avoir à trouver des informations sur la sécurité des transports en général dès lors qu'on n'est pas une institution gouvernementale. Si je suis marin, je ne peux pas si facilement apprendre à assurer la sécurité de mon bateau — il peut y avoir des guides et des régulations, mais elles sont aussi difficiles d'accès que tous les rapports que j'ai pu voir jusqu'à présent, et lire des cas concrets d'accidents peut rendre plus importantes certaines règles, faire prendre conscience de leur importance.

Je suis aussi étonné du manque de « données ouvertes » sur le sujet, c'est-à-dire des formats plus faciles à traiter informatiquement. J'ai vu sur certains des sites des sections concernant l'Open Data qui ne contenaient presque rien ou qui ne concernaient pas la sécurité des transports.

Il existe pour l'aviation des sites créés par des amateurs, des communautés ou par des institutions qui essaient de palier un peu à ce problème, et de permettre un meilleur partage des informations. Pour le domaine ferroviaire, tout ce que j'ai pu voir jusqu'à présent, c'est une base de données d'accidents de l'agence ferroviaire européenne, mais qui ne fournit pas non plus d'accès facile pour des développeurs. Pour le domaine maritime, la seule base de données est GISIS et la grande quantité de données qu'elle contient reste hélas inexploité.

J'ai commencé mon projet de flux RSS seulement parce que j'étais fan d'émissions comme Mayday dangers dans le ciel et que j'apprends beaucoup sur les aspects techniques des transports, mais plus j'explore, et plus ça m'inquiète. Les accidents en eux-mêmes ne m'inquiètent pas, mais vraiment la façon dont ils sont gérés. Dans le domaine maritime notamment, dans presque chaque rapport de chaque agence d'enquête, tous pays confondus, les enquêteurs notent que des régulations ne sont pas respectées, que des risques déjà connus et documentés ont été oubliés ou mal gérés.

Le pire domaine de tous est certainement le domaine routier, où des enquêtes techniques sont rarement menées, alors que c'est là où on voit le plus de morts. La France est l'un des rares pays au monde qui en réalise, via BEA-TT. Il y a des statistiques au niveau national certes, des rapports annuels, etc., mais je n'ai pas encore vu de mise en commun des problèmes de sûreté routière au niveau international. Pourtant, c'est probablement le moyen le plus proche des citoyens, et le plus meurtrier.

En fait, même le domaine de l'aviation, où la sûreté prime, est concerné. Les deux crashs du Boeing 737 MAX remettent en question la crédibilité de la FAA, l'aviation civile américaine, qui était en fait l'aviation civile du monde jusqu'à présent. Le crash de la Germanwings aurait peut-être pu évité si on avait plus prêté attention à LAM 470, mais le crash n'a pas été beaucoup médiatisé en Occident et les réactions internationales ont été lentes.

Plusieurs organisations européennes impliquées dans la sûreté aérienne ont lancé SKYbrary, un système utilisant Semantic MediaWiki pour fournir des données un minimum structurées sur les accidents aériens importants survenus partout dans le monde. Semantic MediaWiki leur permet de structurer un minimum leur données et de fournir des filtres de recherche qui n'étaient souvent jamais disponibles ailleurs. Cela permet aussi de fournir une API, que j'ai utilisé pour me créer un flux, mais les administrateurs ont décidé de la désactiver il y a quelques mois, probablement parce qu'ils en ont eu peur. Quelques jours après la préparation du premier brouillon de cet article, ils ont mis à jour MediaWiki et ne peuvent donc plus désactiver l'API, mais je crains qu'ils n'essaient bientôt d'utiliser de nouvelles méthodes pour la bloquer.

Je me dis que j'aimerais bien participer un peu à rendre la sûreté aussi importante dans tous les domaines technologiques, même pas seulement les transports, qu'elle l'est dans l'aviation commerciale. Et l'outil que je maîtrise le mieux, c'est un ordinateur. J'essaierai probablement bientôt d'entrer en contact avec certains organismes pour les inviter à fournir des données ouvertes, des formats plus sensés, et permettre d'utiliser les compétences de n'importe qui sur Internet pour mieux présenter les données, pour sensibiliser, pour archiver, sans avoir à contourner des systèmes trop verrouillés pour obtenir des données de base. Je n'ai aucune idée de si ça peut améliorer grand chose, mais au moins, j'aurai essayé.


Commentaires

Il n'y a pour l'instant aucun commentaire. Soyez le premier !