Créer un graphe réseau à partir d'un scan Qualys

Qualys VM est un scanner de vulnérabilités (au même titre que Nessus ou OpenVAS) disposant d'une fonctionnalité de cartographie réseau : Map.
L'objectif de cet article est de construire et exploiter une représentation graphique des équipements découverts. Les transformations XSL seront utilisées pour exploiter les données produites.

Données issues de Qualys Map

Dans un premier temps, exporter le résultat au format XML (via l'interface web, l'API ne fournissant pas l'exhaustivité des données).
Sans détailler le format du fichier obtenu (https://qualysguard.qualys.eu/map_report.dtd), les informations que nous utiliserons sont :
  • l'adresse IP
  • le nom d'hôte
  • le nom NetBios
  • le système d'exploitation
  • le routeur sur lequel est relié cet équipement
  • 3 autres informations permettant de savoir si l'équipement est "vivant", enregistré dans Qualys ou pouvant être scanné

Ci-dessous un extrait de ce fichier XML :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!--?xml version="1.0" encoding="UTF-8" ?-->
 
<mapreport>
  <header>
    <domain>mon.domaine.fr:[1.2.0.0-1.2.255.255]</domain>
    <username>username_qualys</username>
    <report_template><!--[CDATA[Map Results]]></REPORT_TEMPLATE-->
    <report_title><!--[CDATA[Map Results]]></REPORT_TITLE-->
    <map_result_list>
      <map_result>
        <map_result_title><!--[CDATA[internet]]></MAP_RESULT_TITLE-->
        <map_date>2013-04-02T12:29:37Z</map_date>
        <option_profile><!--[CDATA[ADSI Profile]]></OPTION_PROFILE-->
        <map_reference>map/15789415574.5789</map_reference>
      </option_profile></map_result_title></map_result>
    </map_result_list>
  </report_title></report_template></header>
  <host_list>
    <host>
      <ip>1.2.198.21</ip>
      <hostname><!--[CDATA[BASTDEB]]></HOSTNAME-->
      <netbios><!--[CDATA[]]></NETBIOS-->
      <router>1.2.198.128</router>
      <os>Linux 2.6</os>
      <approved>0</approved>
      <scannable>0</scannable>
      <in_netblock>1</in_netblock>
      <live>1</live>
      <discovery_list>
        <discovery>
          <discovery_name>ICMP</discovery_name>
          <port></port>
        </discovery>
        <discovery>
          <discovery_name>TCP</discovery_name>
          <port>22</port>
[...]
</discovery></discovery_list></netbios></hostname></host></host_list></mapreport>

Agrégation de plusieurs scans

Dans le cas où plusieurs scans sont exécutés, il faut agréger les résultats dans un seul fichier XML.
La feuille de style MergeQualysMap.xsl va concaténer (ici 3) plusieurs fichiers de scan, dans une grammaire identique à celle des fichiers sources :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" encoding="UTF-8">
 
 <xsl:template match="/">
        <mapreport>
                <host_list>
 
                        <xsl:copy-of select="document('Map_Results_username_qualys_20130405_0.xml')/MAPREPORT/HOST_LIST/HOST">
                        <xsl:copy-of select="document('Map_Results_username_qualys_20130405_1.xml')/MAPREPORT/HOST_LIST/HOST">
                        <xsl:copy-of select="document('Map_Results_username_qualys_20130405_2.xml')/MAPREPORT/HOST_LIST/HOST">
 
                </xsl:copy-of></xsl:copy-of></xsl:copy-of></host_list>
        </mapreport>
 </xsl:template>
</xsl:output></xsl:stylesheet>

Le programme xsltproc est utilisé pour réaliser la transformation :
1
$ xsltproc --novalid MergeQualysMap.xsl empty.xml > QualysMapFull.xml

Note : le fichier empty.xml est simplement un nœud vide.

Transformation des données au format GraphML

Pourquoi transformer un fichier XML (au format Qualys Map) dans un autre format : pour s'abstraire du logiciel permettant d'exploiter les données. Gephi sera par exemple utilisé dans cet article.
1
$ xsltproc --novalid QualysMap2GraphML.xsl QualysMapFull.xml > QualysMapFull.graphml

La feuille de style QualysMap2GraphML.xsl est la suivante :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" encoding="UTF-8">
 
<xsl:template match="/">
        <!-- Definition des keys -->
        <key id="hostname" for="node" attr.name="hostname" attr.type="string">
        <key id="netbios" for="node" attr.name="netbios" attr.type="string">
        <key id="os" for="node" attr.name="os" attr.type="string">
        <key id="live" for="node" attr.name="live" attr.type="int">
        <key id="scannable" for="node" attr.name="scannable" attr.type="int">
        <key id="approved" for="node" attr.name="approved" attr.type="int">
        <!-- Debut du graphique -->
        <graph id="Qualys-MAP" edgedefault="undirected">
             <!-- Recuperation de tous les HOST -->
             <xsl:apply-templates select="/MAPREPORT/HOST_LIST/HOST">
        </xsl:apply-templates></graph>
</key></key></key></key></key></key></graphml>
</xsl:template>
 
<xsl:template match="HOST" xmlns="http://graphml.graphdrawing.org/xmlns">
         <!-- Pour chaque HOST, on definit un node ayant comme id l'adresse IP, donc unique -->
         <node id="{IP}">
                <data key="hostname"><xsl:value-of select="HOSTNAME"></xsl:value-of></data>
                <data key="netbios"><xsl:value-of select="NETBIOS"></xsl:value-of></data>
                <data key="os"><xsl:value-of select="OS"></xsl:value-of></data>
                <data key="live"><xsl:value-of select="LIVE"></xsl:value-of></data>
                <data key="scannable"><xsl:value-of select="SCANNABLE"></xsl:value-of></data>
                <data key="approved"><xsl:value-of select="APPROVED"></xsl:value-of></data>
         </node>
         <!-- Si cet HOST a un ROUTER, ajouter un node ayant comme id l'IP du ROUTER,
               ainsi qu'un arc entre le ROUTER (target) et l'HOST (source) -->
         <xsl:if test="ROUTER != ''">
                <node id="{ROUTER}">
                <edge source="{IP}" target="{ROUTER}">
        </edge></node></xsl:if>
</xsl:template>
 
</xsl:output></xsl:stylesheet>

Suppression des doublons

Plusieurs nœuds peuvent être dupliqués :
  • équipement ajouté à la fois via un nœud HOST et un nœud ROUTER,
  • arc déclaré plusieurs fois, sachant que l'orientation n'est pas un critère retenu,
  • équipements et arcs présents dans plusieurs scans pour lesquels les périmètres se recoupent partiellement.

La feuille de style removeDuplicateNode.xsl permet de supprimer tous ces doublons, qui ne respectent pas la grammaire GraphML :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:graphml="http://graphml.graphdrawing.org/xmlns">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" encoding="UTF-8">
 
<!--
Identity transform: copy elements and attributes from input file as is
-->
<xsl:template match="node() | @*">
        <xsl:copy>
                <xsl:apply-templates select="node() | @*">
        </xsl:apply-templates></xsl:copy>
</xsl:template>
 
<!--
Drop <node> elements with a preceding <node> sibling that has the same
@id attribute value as the current element
-->
<xsl:template match="graphml:node[preceding-sibling::graphml:node[@id = current()/@id]]">
 
<!--
Drop <edge> elements with a preceding <edge> sibling that has the same
@target AND @source attributes values as the current element
@target and @source can be swaped
-->
<xsl:template match="graphml:edge[preceding-sibling::graphml:edge[(@target = current()/@target and @source = current()/@source) or (@source = current()/@target and @target = current()/@source)]]">
 
</xsl:template></xsl:template></xsl:output></xsl:stylesheet>

Attention : Contrairement à précédemment, et pour des raisons de performance, saxon est utilisé à la place de xsltproc.
1
$ saxonb-xslt -xsl:removeDuplicateNode.xsl -s:QualysMapFull.graphml -o:QualysMapFullClean.graphml

Exploitation des résultats

Le fichier obtenu est maintenant représentatif des résultats de scan Qualys Map, et conforme à la grammaire GraphML. Le logiciel Gephi va être utilisé pour visualiser, par exemple, la répartition des systèmes d'exploitation, comme le montre l'image ci-dessous.

Répartition également visible directement sur le graphe réseau.