Logs from text files to Syslog traffic

EDITED: added a Layer3 socket code to speed up the script

I have written this python script that reads log files and re-transmit this data as [network] Syslog traffic .. it may help somebody else who is trying to solve the same problem!

#!/usr/bin/python
# Description: this script retransmits syslog data from flat files to network traffic
# Non-standard dependencies: Scapy libraries
# To use this script on own environment, adjust the GLOBAL VARIABLES section below accordingly
# It will also need to run under sudo because of the use of Scapy
###########

from scapy.all import *
from os import walk
from re import search
import subprocess
import time

#
# As this script will be running from a cronjob once every X minutes, i'll check if it's already
# running before launching a new instance. If one is already running, this script will exit
# The reason it gets relaunched via a cronjob is to have some resilience in case it crashes or forced
# to exit by other external factors
#

pids = subprocess.check_output('pidof -x "textlogs2syslog.py"', shell=True, text=True)
pids = pids.split()

if len(pids) > 1:
   print ("Another process is running .. I'm exiting")
   exit(1)

#
# GLOBAL VARIABLES
#
   
source_dir = '/path/to/incoming/'         # directory where log files will be picked up from
dest_ip = '10.0.0.63'                     # destination syslog server
source_port = 10000                       # can be changed
dest_port = 514                           # destination port number for the SIEM syslog server
archive_path = '/path/to/archive/'        # where files will be moved to after processing

#
# this script will run until SIGINT
#

while True:

   f = []
   for (dirpath, dirnames, filenames) in walk(source_dir):
       f.extend(filenames)
       for file in f:
         if search("^logs_", file):
            try: # extract the IP address from the file name, filenames have this naming convention: logs_[IPv4_ADDRESS]_*.*
               ip = re.search('^.*_(.+?)_', file).group(1)
               source_ip = ip
            except AttributeError:
               pass
         else: # ignores and skip non-conformant filenames
            continue
    
         # open the log file and assign a handle to it
         file_handle = open(source_dir + file, 'r')
         Lines = file_handle.readlines()

         # iterate through the text file line by line before transferring every line as a Syslog event
         # this is where Scapy gets used to generate the network traffic

         s = conf.L3socket()

         for line in Lines:
            payload = "<134>1 " + line.strip() # strips the newline character
                                               # <134> prepend this to reinstate the syslog event facility header
                                               # 1     prepend this to reinstate the syslog event priority header
            spoofed_packet = IP(src=source_ip, dst=dest_ip) / UDP(sport=source_port, dport=dest_port) / payload
            s.send(spoofed_packet)
           # iterating through a single log file ends here


         # when done with transmitting the file content, the log file will be moved to an archive directory
         os.system("mv " + source_dir + file + " " + archive_path)      

         break
         print("finished iterating through files, will sleep and try again")
         time.sleep(0.01)

Upgrading Docker-based Netbox (IPAM) while preserving stored data

Tested to upgrade from v. 0.19 to v. 0.23

At the time of writing this, the Docker containers of Netbox are not being provided by the vendor, rather a “community” project.

There are 6 containers in total that work together to provide the IPAM service:

  • netbox
  • netbox-worker
  • nginx
  • postgres (sql)
  • redis
  • redis-cache

All the IPAM data is stored inside the postgres container; hence, it’s important to backup that data before proceeding with any update.

Step #1

Get postgres container id

docker ps

Back-up IPAM data

docker exec -t POSTGRES_CONTAINER_ID pg_dumpall -c -U netbox > dump_`date +%d-%m-%Y"_"%H_%M_%S`.sql

Now protect this scared dump file

Step #2

Go to /opt/netbox-docker/

Stop netbox-docker

docker-compose down

Step #3

Rename docker-composer.yml and configuration/ldap_config.py 

Renaming will preserve the config in those files from being overwritten. Also, you won’t be able to progress to the next steps with those files have a different baseline from the public git repository

Step #4

Pull the latest netbox from the git repository

git pull origin release

docker-compose rm -fs netbox netbox-worker postgres

docker-compose pull

Step #5

Clean up old data

docker volume rm netbox-docker_netbox-postgres-data

docker volume rm netbox-docker_netbox-static-files

Step #6

Restore postgres database content

docker-compose up postgres

cat your_dump.sql | docker exec -i POSTGRES_CONTAINER_ID psql -U netbox

Step #7

Finally, bring everything up again

docker-compose up -d 

How to monitor your IP space for BGP hijacking

I’ve written the following scripts that will assist anyone in monitoring their (or anyone’s) IP space (IPv4, IPv6 and ASNs) against BGP hijacking.

The scripts rely on data provided by the Routing Information Service (RIS) which RIPE NCC have kindly opened up to the public to use. For years, RIPE have been running 25 Route Collectors around the world where they collect routing information and allow the public to download this data for analysis. In recent years, they started RIS to help users query this data via a REST API.

The script below (ris-live-prefix.py) will allow users to monitor an IPv4 or IPv6 prefix. The REST API will return JSON objects that match this request, as well as more specific BGP announcements.

ris-live-prefix.py

 import json
 import websocket
 import ssl
 ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
 ws.connect("wss://ris-live.ripe.net/v1/ws/?client=YOUR_ORG_NAME")
 params = {
     "moreSpecific": True,
     "host": "",
     "prefix": "45.146.72.0/22",
     "require": "", # can use "announcements" or "withdrawls"
     "socketOptions": {
         "includeRaw": True
     }
 }
 ws.send(json.dumps({
         "type": "ris_subscribe",
         "data": params
 }))
 for data in ws:
     parsed = json.loads(data)
     print(parsed["type"], parsed["data"])

Optional step: change “YOUR_ORG_NAME” to anything unique to you. RIS Live maintainers will appreciate that.

This RIS Live stream has the tendency of breaking on a frequent basis; hence, use the below script to start it when it breaks. The script will also fix a problem with the python client not being able to verify the REST API SSL certificate authenticity

ris-live-prefix.sh

#!/bin/bash 
while true 
do 
WEBSOCKET_CLIENT_CA_BUNDLE=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem  /usr/bin/python /path/to/your/ris-live-prefix.py | /usr/bin/sed  s/u\'/\"/g | /usr/bin/sed s/\'/\"/g | /usr/bin/sed  's/^................//g' | /usr/bin/sed  's/)$//g' > /path/to/output/prefix-monitoring.json
done

The additional sed commands are to there to filter the output and make it Logstash ready. You may have to alter that in accordance to whatever log analysis tool you’re using.

Moreover, if you want this script to run at boot time, you will have to create a cronjob for it, or run it as a service.

You can also monitor whether your ASN is being hijacked by modifying the above code slightly. The below script shows an example to monitor BGP updates for ASN 205986.

ris-live-asn.py

 import json
 import websocket
 import ssl
 ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
 ws.connect("wss://ris-live.ripe.net/v1/ws/?client=YOUR_ORG_NAME")
 params = {
     "moreSpecific": True,
     "host": "",
     "path": "205986",
     "require": "", # can use "announcements" or "withdrawls"
     "socketOptions": {
         "includeRaw": True
     }
 }
 ws.send(json.dumps({
         "type": "ris_subscribe",
         "data": params
 }))
 for data in ws:
     parsed = json.loads(data)
     print(parsed["type"], parsed["data"])

and you’ll need to use the below script to keep it persistant

ris-live-asn.sh

#!/bin/bash 
while true 
do 
WEBSOCKET_CLIENT_CA_BUNDLE=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem  /usr/bin/python /path/to/your/ris-live-asn.py | /usr/bin/sed  s/u\'/\"/g | /usr/bin/sed s/\'/\"/g | /usr/bin/sed  's/^................//g' | /usr/bin/sed  's/)$//g' > /path/to/output/asn-monitoring.json
done

Final notes

  • If your organisation has multiple ASNs and multiple prefixes, you’ll need a script for each one of them
  • RIS will show you all seen updates, including legitimate ones. So you need to use a filter in your favourite log analysis tool in order to highlight unexpected BGP updates.
    • For prefix hijacking, your fliter needs to look for annoucements not originating from your as_path
    • For ASN hijacking, your filter needs to look for annoucements where your next hop isn’t one of your transit ASNs.

How to iterate through Exchange/Office365 mailbox to extract attachments by using Powershell

The below code will iterate through the logged on user’s mailbox and extract attachments that has CSV in the filename. It will then save the attachment on disk with a prepended random number (this is useful if you’re using this for automation where you keep getting CSVs using the same file name)

$o = New-Object -comobject outlook.application
$n = $o.GetNamespace("MAPI")
$f = $n.GetDefaultFolder(6) #6 is Inbox
$msgs = $f.Folders.Item("Subfolder_Name") #this needs to be under Inbox!
$msgs_archive = $f.Folders.Item("Subfolder_Name_Archived")
$filepath = "C:\location\where\attachments\will\be\saved\"

while ($msgs.Items.Count -gt 0) {
    $msgs.Items | foreach {
            $_.attachments | foreach {
                $rand = Get-Random
                $a = $_.filename
                If ($a.Contains("csv")) {
                    $_.saveasfile((Join-Path $filepath "$rand.$a"))
                }
            }
            $_.Move($msgs_archive)
    }
}

Using Powershell to read Sysmon events

If you install Sysmon as a service, it will start logging events into Windows Event Log. These events are stored in a binary format; hence extraction is a bit tricky.

You can use Powershell to read these events using the following:

Get-WinEvent @{logname="Microsoft-Windows-Sysmon/Operational"} | Select-Object -Property TimeCreated,ID,Message -First 5 | Format-List

The above command and filters will show you the last 5 events logged. Bear in mind that what goes into this event log is controlled by Sysmon XML configuration file. Hence, if you’re not seeing something you’re expecting in the Windows Event Logs, you may need to tweak Sysmon configurations.

Geolocation mapping on Kibana

Tested with ELK 7.2

The below method has been deprecated in ELK v. 7.6. I will post an alternative approach at some point in the future.

Recently, I have attempted to get geolocation mapping on a Kibana visualization/dashboard. That was incredibly hard because of the little documentation around on how to get this working (I guess if you’re sole job is maintaining an ELK stack; then it’s a known territory).

Anyhow, I needed to map locations of physical assets given a CSV file that contains UK postcodes.

  1. The first step was to find GPS coordinates for UK postcodes, and thankfully the UK Office for National Statistics publish a list of those details. (Look for “National Statistics Postcode Lookup (NSPL)” if you’re interested in finding the same data).
  2. In this example, my csv file has the following headers: carMake, CarModel, postcode, lat, lon. (I have done the latitude and longitude enrichment to the CSV file prior to processing in logstash)
  3. Define a field in your elasticsearch index to have a “geo_point” data type.
    1. You cannot define/cast your data into this data type from logstash.
    2. You cannot change the field data type once it’s defined. Hence, you can only do this while creating an index. This also means you have to define all other fields you’re expecting at the same time!
    3. Create this index from the “Dev Console” interface in Kibana. Check example below that has been tested on elasticsearch 7.2

PUT indexCars

PUT indexCars/_mapping/doc

{
"doc":{
"properties":{
"location":{
"type":"geo_point"
},
"lat":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
},
"lon":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
},
"@timestamp":{
"type":"date"
},
"@version":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
},
"carMake":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
},
"carModel":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
},
"postcode":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
},
"tags":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
}
}
} }

4. Use the following logstash configuration file to process your CSV file

input {
file {
path => “/path/to/*.csv"
start_position => "beginning"
sincedb_path => "/dev/null"
}
}

filter {
csv {
separator => ","
autodetect_column_names => "true"
skip_empty_columns => "true"
}

mutate {
add_field => { "location" => "%{lat},%{lon}" }
}

} #end of filter

output {
elasticsearch {
hosts => "http://localhost:9200"
index => "indexCars"
}

stdout {}

}

5. Once you have some data ingested, go to Kibana to create a geomapping visualisation. You will find the new “location” field that has the correct data type “geo_point”.

Google’s silent text/SMS

Google has been for a few months now sending text messages from Android handsets silently and without users’ explicit consent or knowledge to validate that they’re still the owners of the phone numbers they have entered into their Google account settings.

While they have explained their rationale here; it’s bizarre that that they decided to utilise users’ SMS quota without their knowledge.

These messages take the form of “(SOME_CODE) Google is verifying the phone# of this device as part of setup. Learn more: https://goo.gl/LHCS9W”.

Other forms of messages also exist.

From my observation; Google only collects these messages between midnight to 08:00am; and send those messages to phone numbers from subscribers range (e.g. in the UK, mobile subscribers ranges start with the 07 prefix).

This behaviour has also left some users confused upon the discovery of those SMS messages within their sent items, leaving them to think that their handsets have been compromised.


How to build a dual-homed malware testing machine

While doing dynamic analysis for malware, researchers often start by running their suspected samples in isolated environments in order to stop accidental leakage of information (such as alerting the malware author in case the malware is calling back home to the C&C server).

While it’s possible to run these samples in complete isolation, it’s useful to have a server simulating real internet services in order to give malware the illusion (or ability) to continue running. INetSim is the project that would give you this functionality.

However, if the researcher decided that Internet access is required, then she needs to move her network settings to another network that can serve real internet traffic.

The below solution describes how a researcher can implement both of these scenarios using a simple setup and the ability to move between INetSim or real Internet with a click of a button from their malware test (goat) machine.

 

  • Effectively, your test machine needs to be dual-homed. Each of its interfaces need to reside on a separate subnet. (use the above diagram as a guide)
  • The default gateway of this “Research Machine” is a linux machine with enabled ip_forward (no need to use NAT’ing; unless you want to!)
  • We will be utilising source based routing in this “Linux router” by using iproute2 ability to run multiple routing tables in parallel
  • Settings of network interfaces inside “Research Machine”
    • Real Internet interface:
      • IP Address: 192.168.1.2
      • Netmask: 255.255.255.0
      • Gateway: 192.168.1.1
    • Fake Internet interface (going to INetSIm)
      • IP Address: 192.168.2.2
      • Netmask: 255.255.255.0
      • Gateway: 192.168.2.1
    • Linux router interfaces to Research Machine (Real Internet)
      • IP Address: 192.168.1.1
      • Netmask: 255.255.255.0
    • Linux router interfaces to Research Machine (INetSim)
      • IP Address: 192.168.2.1
      • Netmask: 255.255.255.0
    • Linux router interfaces to INetSim
      • IP Address: 172.26.0.1
      • Netmask: 255.255.255.0
    • Linux router interfaces to Real Internet Gateway
      • IP Address: 10.0.0.1
      • Netmask: 255.255.255.0
    • INetSim
      • IP Address: 172.26.0.2
      • Netmask: 255.255.255.0
      • Gateway: 172.26.0.1
    • Internet Gateway
      • As the diagram, or use your existing network setup

Note: If your Linux router isn’t implementing NAT towards Internet Gateway and INetSim, then you need static routes on both of these devices to send traffic back to the 192.168.1.0/24 and 192.168.2.0/24 subnets through the Linux router.

Now, to the reason you’re reading this blog post…

Finally, you need to implement source based routing on the Linux router.

When you run:

  • ip route

which is the equivalent of

  • ip route list table main

in a Linux machine, you get to see the “main” routing table. You can run other routing tables in parallel to this one with different default gateways.

  • First, edit /etc/iproute2/rt_tables
    • Add “100 fakenet” to the end of the file. 100 here is a random number. You may choose something else.
    • Now, you have a new routing table called fakenet. Run the following to inspect its content:
    • ip route list table fakenet

Table is empty right now.

  • Add a default gateway to “fakenet”
  • ip route add default via 172.26.0.2 table fakenet
  • Add a rule to the Routing Policy Database (RPDB) in order to process routing for the Researcher’s fakenet interface/subnet to the new routing table
    • ip rule add from 192.168.2.0/24 table fakenet

    • ip route add 192.168.2.0/24 dev eth1 proto kernel scope link src 192.168.2.1 metric 100 table fakenet

  • Flush the routing cache (rarely needed, but worth using if in doubt)
    • ip route flush cache

       

Finally, all you need to do to send traffic to Real Internet or INetSim from your Research Machine is to keep one of these interfaces up and shutdown the other.

What does SSL/TLS reveal about your session

The common understanding is that by using SSL/TLS your web browsing session will be entirely private. This is not entirely true.

Some data will have to be revealed in clear text before encryption can start.

The client will have to reveal the domain name is trying to reach.

Server Name Indication field assists virtually hosted webserver in identifying the intended websites that the client wishes to visit

Also, the server will be sending its certificate back in clear text with all its meta data readable off the wire.

This can be a problem in the following scenarios:

  • If someone intercepting data at your ISP level or somewhere in the upstream can see what websites you’re browsing (URLs will not be known though)
  • If you are browsing to less known websites, then your activity may raise an alarm
  • When using OpenVPN, you need to choose your hostname and certificate details very carefully. The more unique your settings, the easier your traffic can be picked up in the haystack (even if you keep cycling your IP addresses)

For the last point (OpenVPN or any SSL based VPN), if anonymity is one of your objectives., it’s worth selecting the default values while building your OpenSSL certificates.

ssl anonymity