BlockList iptable avec Splunk et d’AbuseIPDB

BlockList iptable avec Splunk et d'AbuseIPDB

Bonjour à tous, aujourd’hui je continue avec un article qui suit ce qu’on avait fait avec la Blacklist Iptables AbuseIPDB la dernière fois. Cette fois on va voir comment construire une BlockList iptable avec Splunk et d’AbuseIPDB.

Qu’est ce que j’entends derrière ce titre ? Simplement que la blacklist ma dernière fois n’est pas très « maline ». Dans le sens, où on verrouille juste 10 000 IP comme des bourrins et sans trop se demander si les vulns ou services qu’elles checkent nous concerne en effet. Alors, ça ne sert pas à rien hein. C’est une bonne base à bloquer facilement mais ce n’est pas forcément utile dans le sens où ces 10 000 ne se connecterons pas forcément à votre site ou infra au final.

En effet, ce qu’on souhaiterai plus, notamment dans une optique de CTI (Cyber threat Intelligence) : c’est de bloquer (sinon détecter au moins) celles qui se connectent effectivement sur nos services (genre à la fail2ban). Le problème c’est que fail2ban c’est bien en protection mono instance. Mais, quand vous protégez tout un système d’information, ça ne passe pas super bien à échelle. Par exemple, je vous laisse imaginez quand vous avez 3 boitier Anti-DDOS, 5 IPS de marques différentes, deux antivirus et 4 firewall en cluster ou vous devez poussez et synchroniser la même liste de blocage…

Ce qu’on aimerai, c’est pouvoir centraliser certains logs relatif à notre SI puis les croiser avec des sources de CTI (comme abuseIPDB) pour savoir quels accès étaient illégitimes.

C’est ce que je vous propose aujourd’hui. Ici, on va donc apprendre à sortir certains logs par API d’une instance Splunk, les croiser avec abuseIPDB (via l’API check) et les mettre en blocage avec un ipset dans iptables comme la dernière fois.

Requête Splunk par API

Bon alors je vous ai déjà fait toute un séries de TP autour de Splunk. Je ne m’attarde donc pas sur la techno. Par contre, ce sur quoi je ne me suis pas attardé jusqu’ici c’est l’API REST de Splunk. Ce dernier est effet interrogeable entièrement pas API ! Dans mon cas, la recherche SPL pour sortir les accès illégitimes au wordpress de geekeries.org est la suivante :

index=nginx earliest=-1d@d latest=@d wp-login.php OR xmlrpc.php | stats count by src | sort -count

Du coup pour la sortir en API, voici le code PowerShell à exécuter.

## SPLUNK SEARCH
#Authentication
$BaseUrl = "https://A.B.C.D:8089"
$Login = "user"
$Password = "password"
$AuthPath = "/services/auth/login"
$Response = Invoke-RestMethod -Method Post -Uri ($BaseUrl+$AuthPath) -Body @{'username'=$Login ; 'password'=$Password} -SkipCertificateCheck
$sessionkey = $Response.response.sessionKey

#Start the Search
$searchPath = "/services/search/jobs"
$Response = Invoke-RestMethod -Method Post -Uri ($BaseUrl+$searchPath) -Headers @{'Authorization'="Splunk $sessionkey"} -Body @{'search'="$SplunkSearch"} -SkipCertificateCheck
$SearchSID = $Response.response.sid

#Wait for it to finish
$SearchControlPath="/services/search/jobs/$SearchSID"
$isDone = 0
do {
    Start-Sleep -Seconds 1
    $Response = Invoke-RestMethod -Method Post -Uri ($BaseUrl+$SearchControlPath) -Headers @{'Authorization'="Splunk $sessionkey"} -SkipCertificateCheck
    $isDone = (([xml] $Response.InnerXml).entry.content.dict.key | Where-Object {$_.name -eq "isDone"})."#Text"
}while ($isDone -eq 0)

#Get the Result
$SearchResultPath="$SearchControlPath/results/"
$Response = Invoke-RestMethod -Method Get -Uri ($BaseUrl+$SearchResultPath) -Headers @{'Authorization'="Splunk $sessionkey"} -Body @{'output_mode'="json";"count"="$ProcessLimit"} -SkipCertificateCheck
$List = $Response.results

Et voilà, vos résultats de recherche Splunk sont désormais automatisables et exploitables directement en PowerShell (ou Python pour ceux qui préfère, hein, pas de sectarisme ici).

Croiser les résultats de la search Splunk avec Abuse IPDB

Du coup, vous devez me voir venir là… les résultats de ma recherches Splunk sortent une liste d’IP et de nombre d’accès qui pourrait être indésirable. J’aimerai bien savoir dans le lot lesquels sont déjà connues par Abuse IPDB comme pas bien intentionnées.

Ce qui m’amène à la seconde partie du script :

## ABUSE IPDB CORRELATION AND REPORT
#Analysing each IP
$blacklistIP = @()
$now = get-date
foreach($Ip in $List) {
    # Whitelist you own IP... just in case
    if($INTERNAL_ALLOW_IP_LIST.IndexOf($h.ipAddress) -eq -1){
        #foreach IP in list request IP status check on AbuseIPDB
        $abuseinfo = Invoke-RestMethod https://api.abuseipdb.com/api/v2/check -Headers @{"Key"="$APIKEY";"Accept"="application/json"} -Body @{"maxAgeInDays"="90";"ipAddress"="$($Ip.src)"}
        $h = $abuseinfo.data
        # Control if result match a custom logical rule
        if($h.isWhitelisted -eq $false -and $h.isPublic -eq $true -and $h.abuseConfidenceScore -ge $MinConfidenceScore -and $h.totalReports -ge $MinReportsCount -and $h.numDistinctUsers -ge $MinDistinctUserscount -and ($h.lastReportedAt) -gt $now.AddDays($MinLatestReport)){
            # If so, add to blacklist
            Write-host ("Add To Blacklist : " + $Ip.src)
            $blacklistIP += @{"ip"="$($Ip.src)";date="$now"}
        }else{
            #elseif there's enought tries report for scan
            if($Ip.count -ge $AttemptLimitToReport){
                Write-host ("Add To Reportlist : " + $Ip.src)
                #$abuseinfo = Invoke-RestMethod https://api.abuseipdb.com/api/v2/report -Headers @{"Key"="$APIKEY";"Accept"="application/json"} -Body @{"ip"="$($Ip.src)";"categories"="18,21";"comment"="Wordpress Login bruteforce, $($Ip.count) attemps in 24h"} # not working, yet
                $abuseinfo = Invoke-Expression "curl -s https://api.abuseipdb.com/api/v2/report --data-urlencode `"ip=$($Ip.src)`" -d categories=18,21 --data-urlencode `"comment=Wordpress Login bruteforce, $($Ip.count) attemps in the last 24h`" -H `"Key: $APIKEY`" -H `"Accept: application/json`""
            }else{
            # else just drop the case
                Write-host ("Ip not reported - not enought try over 24h : " + $Ip.src)
            }
        }
    }
}

Et voilà, Vous avez maintenant construit une liste des adresses connues comme malveillantes par AbuseIBPD. Vous avez au passage signalé les autres IP suspicieuses que vous avez retrouvés dans vos logs.

Il ne reste plus qu’a fusionner la liste du moment avec la sauvegarde de la liste précédente, issue du même script, pour consolider nos données et permettent à celles-ci de tourner dans le temps

#Fusion and Clean Previous blacklist with the new one
$oldlist = Get-Content $BlacklistIPJSONFile | ConvertFrom-Json
foreach ($oldip in $oldlist){
    if(($oldip.date) -gt ($now.AddDays($MinLatestReport)) -and ($blacklistIP.ip.IndexOf($oldip.ip) -eq -1)){
        # Maintain this IP in blocklist since the ioc is still Fresh
        Write-Host ("Maintaining $($oldip.src) in blocklist")
        $blacklistIP +=  @{"ip"="$($oldip.src)";date="$($oldip.date)"}
    }
}

#Backup new blacklist
$blacklistIP | ConvertTo-Json -Compress | Out-File $BlacklistIPJSONFile

Ici je construit « bêtement » un fichier json avec ma liste d’IP et je l’écrase à chaque fois avec la nouvelle version après l’avoir fusionné, c’est : simple, basique et efficace.

Si vous êtes un peu plus doué que moi, vous pouvez gérer ça avec une vraie base de CTI comme ThreatQ, Anomali ou OpenCTI et refaire du traitement derrière avec d’autres source de CTI par exemple.

Mettre à jour votre ipset de blocage

Une fois cette liste construite, il ne reste plus qu’a mettre à jour l’IPset en blocage dans iptables, comme la dernière fois en fait. Juste la je vous le fait en PowerShell

##IPTABLE SET MANAGEMENT
#Create à new blocklist
$IpsetBlockkist_new = ($IpsetBlockkist+"_new")
Invoke-Expression ("ipset create $IpsetBlockkist_new hash:ip hashsize 4096")
Foreach ($ip in $blacklistIP){ # Add Ip into it
    Invoke-Expression ("ipset add $IpsetBlockkist_new $($ip.ip)")
}
#Swap Production with the new Blocklist
Invoke-Expression ("ipset -W $IpsetBlockkist $IpsetBlockkist_new")
Invoke-Expression ("ipset -X $IpsetBlockkist_new")

Et voilà, à partir de là il y a en place sur le serveur une seconde liste de blocage basée, non plus sur des indicateurs externe mais, sur des signaux réels tiré des logs de mon splunk et consolidé par des indicateurs externes.

Le code complet

Bon vous me connaissez, je vais pas vous laissez en plan avec quatre bouts de scripts éparpillés façon puzzle. Je vous ai remis une version un peu propre (ou pas) du script sur mon GitHub (oui, oui, j’ai un github dernier commit le 30/11/2020 avant celui là…j’ai regardé !).

Bref, pour ceux qui veulent c’est par ici :

https://github.com/eladent/Update-AbuseIpDBSplunkblockList

BlockList iptable avec Splunk et d’AbuseIPDB

Bon en voilà un script qui sent bon, non ? Alors bon, d’accord ma BlockList iptable avec Splunk et d’AbuseIPDB n’est probablement pas utilisable « directement ». Ici l’idée c’était plutôt de vous montrer un cas d’usage simple de Cyber Threat Intelligence et de vous donner la démarche avec un script PowerShell à grand renfort d’API.

Au passage, ça me permet aussi de vous montrer comment fonctionne les call check et report d’abuseIPDB au passage et qui manquaient dans le dernier sujet.

Et oui, aussi, je fais du PowerShell sur mes Linux ! Personne n’est parfait, que voulez-vous… Bref, moi ma blocklist du serveur commence à être bien conséquente. Bloquer bien de votre côté aussi et d’ici la prochaine : Geekez bien !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.