Development

Blue Monday 2: bouw een dashboard met alerts in ExpressJS

Blue Monday 2: bouw een dashboard met alerts in ExpressJS

Deze post is onderdeel van een serie. Bekijk hier de eerste post over het opzetten van een mini-SOC door het installeren van Suricata en ElasticSearch

Een VPS met Suricata en ElasticSearch staat en werkt. Top! Tools als ElasticHQ maken inzichtelijk welke alerts er zoal binnenkomen op je VPS, maar een handig overzicht staat er nog niet. Nu zijn er redelijk wat opties om dit wel inzichtelijk te maken, zoals via LogStash en Kibana. Maar onder het mom van #zellufdoen (levensmotto, vraag maar aan mijn moeder) kan dat natuurlijk ook door zelf een dashboard te maken. Het voordeel is dan uiteraard dat je gemakkelijk zelf kunt bepalen wat je laat zien of wat je filtert, maar het kost wel tijd.

Ook dit was een iteratief proces: nadat ElasticSearch eenmaal stond en vol begon te lopen met alerts, was een dashboard een volgende logische keuze. Dit dashboard moet goed werken met ElasticSearch: een volledige implementatie zelf schrijven ging zelfs dat #zellufdoen iets te ver. Laravel (superfavo) viel dus helaas af, maar NodeJS / ExpressJS had daarentegen een ElasticSearch-module die out of the box werkte. NodeJS, tien punten voor jou.

Begin een nieuw project met npm init. De ElasticSearch en dotenv modules zijn nodig om de connectie te testen, deze kunnen worden geinstalleerd met npm install elasticsearch dotenv. Het testen of de module werkt én connectie kan maken met je ElasticSearch instance, kan met de volgende regels code. Sla deze op als check.js.

const elasticsearch = require('elasticsearch');
const dotenv        = require('dotenv');
const client        = new elasticsearch.Client({
   hosts: [process.env.ELASTIC_URL]
});

client.ping({
     requestTimeout: 30000,
 }, function(error) {
     if (error) {
         console.error('Can not find Elastic DB');
     } else {
         console.log('Everything is ok!');
     }
 });

Maak daarnaast in dezelfde folder de file .env aan. Hier staan de variabelen van de applicatie, zoals URLs, usernames en passwords zodat deze niet gehardcode worden. De .env-file ziet er zo uit:

ELASTIC_URL=<URL>:9200

Pas aan naar localhost of een extern IP en run node check.js in je terminal. Krijg je in je console dat alles OK is, dan is alles OK ;).

Aangezien praktisch alles wordt gelogd hiermee - dus ook de keren dat je je dashboard opent inclusief alles assets - is het handig om een soort van filter te maken. Hierin geef je op welke termen niet in de zoekwoorden voor mogen komen. Door een mapje aan te maken in je project folder met de naam ‘queries’ met daarin verschillende files voor protocollen, kun je deze files opgeven. Een voorbeeld van http.json is:

{
  "index": "<INDEX NAME>",
  "type": "events",
  "from": 0,
  "size": 100,
  "body": {
    "query": {
      "bool": {
        "must": [
          { "match": { "event_type": "http"} }
        ],
        "must_not": [
          { "match": { "src_ip": "<SRC IP>"} },
          { "match": { "http.url": "/robots.txt"} },
          { "match": { "http.url": "/_search"} }
        ]
      }
    },
    "collapse": {
      "field": "http.url.keyword",
      "inner_hits": {
      "name": "latest",
        "size": 1
      }
    }
  }
}

De src_ip onder de noemer must_not geeft aan dat alle alerts vanaf de eigen VPS genegeerd kunnen worden, de http.url geeft aan welke requests niet in je zoekoverzicht hoeven te staan. Deze lijst is natuurlijk vrij basic, maar check vooral zelf wat voor info je er uit of in wilt hebben. Het onderste stuk code zorgt ervoor dat je alleen unieke URLs weergeeft.

Om de applicatie de juiste informatie te laten zien die als resultaat uit de query komen, zijn er nog een aantal packages nodig. Deze kun je installeren met npm install express body-parser path ejs fs geoip-lite moment. GeoIP-lite zorgt bijvoorbeeld voor het toevoegen van de landcode waar het request vandaan kwam, Express is het framework, moment zorgt voor goede weergave van tijd en EJS is het templating engine dat gebruikt wordt. Hieronder worden deze packages ingeladen en de juiste waarden gezet. Dit is het begin van je index.js:

var elasticsearch = require("elasticsearch");
const dotenv      = require('dotenv')
const express     = require('express');
const app         = express();
const bodyParser  = require('body-parser');
const path        = require('path');
const ejs         = require('ejs');
const fs          = require('fs');

app.locals.geoip  = require('geoip-lite');
app.locals.moment = require('moment');

dotenv.config();

var client = new elasticsearch.Client({
  hosts: [process.env.ELASTIC_URL]
});

app.set('view engine', 'ejs');

app.use(bodyParser.json())
app.set('port', process.env.PORT || 3001);
app.use(express.static(path.join( __dirname, 'public')));

De applicatie werkt met een zoekfunctie om je query, gedefinieerd in de queriesmap, uit te voeren. Deze werkt door search?query=PROTOCOL aan te roepen. Op basis van het protocol dat je hebt ingevoerd zoekt de applicatie de juiste JSON met de query voor dat protocol erbij en plaatst de resultaten in een pagina. De zoekfunctie ziet er zo uit:

app.get('/search', function (req, res){
  client.search(JSON.parse(fs.readFileSync('queries/' + path.normalize(req.query['protocol']).replace(/^(\.\.(\/|\\|$))+/, '') + '.json')), function (error, response,status) {
    if (error){
      console.log("Error: " + error)
    }
    else {
      res.render('template', {
        data: response.hits.hits,
        protocol: req.query['protocol'],
      });
    }
  })
});

Om deze applicatie te testen wil je ‘m runnen in je browser. Bij het initialiseren van de applicatie wordt er een poortnummer opgegeven, maar er daadwerkelijk naar luisteren kan met het volgende stuk code. Voeg deze onderaan je index toe.

app.listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

Het zoekgedeelte en verbinding met de ElasticSearch werkt. De applicatie kan getest worden op localhost:3001, maar zal errors geven aangezien er nog geen template-bestand is gemaakt. In de tabel1 in het bestand template.ejs zal de data worden weergeven. Met onderstaande code loop je aardig gemakkelijk door je data heen. Dit kan een stuk netter #lui (maar wel #zellufdoen), maar eigenlijk doet dit kleine stukje niets meer dan checken welk protocol is meegegeven als parameter, op basis daarvan wordt bepaald welk stuk data weergegeven moet worden. Andere info die je kunt meegeven, kun je hier vinden.

<% for(var i=0; i < data.length; i++) { %>
  <% if(data[i]._source.event_type == protocol) { %>
  <tr>
    <td><%= moment(data[i]._source.timestamp).format('DD-MM-YYYY') %></td>
    <td><%= data[i]._source.src_ip %></td>
    <td><%= geoip.lookup(data[i]._source.src_ip).country %></td>
    <% if(data[i]._source.http != null) { %>
      <td><%= data[i]._source.http.url %></td>
    <% } %>
    <% if(data[i]._source.event_type == "dns") { %>
      <td><%= data[i]._source.dns.rrname %></td>
    <% } %>
    <% if(data[i]._source.event_type == "ssh") { %>
      <td><%= data[i]._source.ssh.client.software_version %></td>
    <% } %>
  </tr>
  <% } %>
<% } %>

Een volledige versie van de HTML-pagina (en de andere pagina’s) is in de GitHub repository te vinden onderaan deze post trouwens, je hoeft niet zelf te CSS’en :). Als je al bovenstaande stukken samenvoegt, kom je uit op de volgende bestandsstructuur:

- project
  - queries
    - http.json
  - views
    - template.ejs
  - check.js
  - index.js

..en op het volgende resultaat als je naar /search?query=http gaat (voor nu ingesteld op maximaal 10 resultaten, aan te passen in de query). Het protocol kun je in het zoekveld invoeren en op enter klikken, dan ga je naar de resultatenpagina van dit protocol.

Wallpaper

Ready to go. Houd het nu goed in de gaten en kijk of er URLs of andere info voorbij komt die gefilterd kan worden, deze kan per protocol in de query-map toegevoegd worden. Next up: maak van je VPS een hacker-walhalla (ofja, laat het zo lijken) en schrijf custom regels.

SOCDash

  1. http://getskeleton.com