Foreword

This article is a continuation of "Setup and Tune an OT SOC (Part 1): Lab Setup and Malcolm Installation with Hedgehog Sensor" so I encourage you to read it before reading this article.

For a better understanding of the LAB and the refinement of rules, I strongly recommend installing Burp Suite for the rest of this series.

We will mainly be creating Suricata rules for web interfaces The creation of rules related to industrial protocols will be covered in part 3 of the series.

Glossary

Introductions

Great, we now have an environment in which we can tune Malcolm and the hedgehog sensor. But how and why do we do that?

Imagine that we currently have a block of marble that is completely raw (it's a metaphore), Whe have equipment configured only for the bare minimum (network interconnections) and Malcolm with the default rules and configurations.

In theory, we have no CMDB/asset inventory, and the flow matrix indicates that traffic between the two zones is completely open.

The idea is therefore to adapt all the tools to the network in front of him (to carve the block). The element we will focus on in this article is the Suricata alerts, which will enable clear and standardized reporting of potential incidents or problems.

Suricata

Before landing into the technical stuff we need to understand what is suricata and it's alerting system so here a short explainations of what is suricata alerting:

Suricata is a free and open source, mature, fast and robust network threat detection engine. The Suricata engine is capable of real time intrusion detection (IDS), inline intrusion prevention (IPS), network security monitoring (NSM) and offline pcap processing. Suricata can act as a high-level content firewall.

Suricata alerting refers to the process by which the Suricata Intrusion Detection and Prevention System (IDS/IPS) identifies and reports potentially malicious network activity. Suricata continuously monitors network traffic and compares it against a database of rules or signatures that describe known threats and suspicious patterns. When traffic matches a rule, Suricata generates an alert message containing details such as the source, destination, type of threat, and timestamp.

These alerts serve as key indicators for network defenders, allowing them to detect intrusions, identify compromised systems, and respond rapidly to security incidents. Depending on its configuration, Suricata may also take real-time action, such as blocking harmful traffic (in IPS mode), or simply logging alerts for later analysis (in IDS mode).

In practice, Suricata alerting provides an automated early-warning system that enhances network visibility and threat detection across enterprise environments

Before you start, I strongly recommend that you read the introduction to the Suricata wiki. It explains everything very clearly, and as hackers say, "RTFM."

The objective of this article, as you have understood, is to integrate new Suricata rules that are adapted to the needs of the network we are on.

Setup suricata debug environnement.

In line with the logic of not developing in the production area (the hedgehog itself), which could create a lot of false positives, it's mainly an excuse to learn how to install Suricata manually (it's still a tutorial, so you have to find excuses to learn stuff).

The goal here is to temporarily place a Linux machine in place of the hedgehog to retrieve the network flow and be able to develop Suricata alerts specific to the equipment (the machine can be a VM; you just have to turn off the others and redirect the flows to it).

None

Here is a tutorial that explains how to set up Suricata on Debian.

NOTE: Here are some errors I encountered. Suricata warned me that I had not entered the correct path.

None

The solution is to modify and add the path of the Suricata rules in the configuration file.

None

I update the new rules using these commands.

suricata-update
systemctl restart suricata

You can directly access new alerts that are reported in the /var/log/suricata/fast.log file. I check for new Suricata alerts using the following command

tail -n 300 -f /var/log/suricata/fast.log

With all this, you should be able to see the new rules that are coming and move on to the next sections.

Testing Suricata rules.

NOTE: My results are related to my equipment. If you have reached this stage of the tutorial, you probably have access to other equipment. Try to adapt the methodology I am going to use here to your own equipment.

For now, we will focus on GUDE 2301. Normally, the networks should look like this (I have removed the equipment with which we do not interact).

None

Detection of security flaw by desing in the equipment.

If we launch Suricata while trying to connect and interact with (for example) the PLC web interfaces, there is a chance that an alert will sound. And that is what is supposed to happen with GUDE 2301 (see photo bellow).

None
Exemple of suricata alerte generate from the interactions with the GUDE 2301

First, let's understand what this rule says , but where did whe find this rules ? The source of this rule is found in one of the files in the following path:

cd /etc/suricata/rules
# then we can search for the exact line with a grep on the rules forlder
cat * | grep "Base64 HTTP Password detected"

So here the suricata rule in his full glory

alert http $HOME_NET any -> any any (msg:"ET INFO Outgoing Basic Auth Base64 HTTP Password detected unencrypted"; flow:established,to_server; http.header; header_lowercase; content:"authorization|3a 20|Basic"; fast_pattern; nocase; content:!"YW5vbnltb3VzOg=="; within:32; content:!"proxy-authorization|3a 20|Basic"; nocase; content:!"KG51bGwpOihudWxsKQ=="; threshold: type both, count 1, seconds 300, track by_src; classtype:policy-violation; sid:2006380; rev:18; metadata:created_at 2010_07_30, performance_impact Significant, confidence Medium, signature_severity Informational, updated_at 2024_08_07;)

Here is a clear description of the rule that raised the alert.

  • Protocol: httptargets HTTP traffic, focusing on unencrypted Basic Auth credentials.
  • Direction: $HOME_NET any -> any any monitors outbound traffic from internal hosts to any remote destination.
  • Message (msg): "ET INFO Outgoing Basic Auth Base64 HTTP Password detected unencrypted" the alert message displayed when triggered.
  • Flow: established,to_server applies only to established TCP sessions sending data to servers.
  • HTTP Header Match: checks for the presence of the Authorization header containing the keyword Basic, which indicates Base64-encoded credentials.
  • Exclusions:
  • content:!"YW5vbnltb3VzOg==" ignores traffic where the username is "anonymous".
  • content:!"proxy-authorization|3a 20|Basic" excludes proxy auth headers.
  • content:!"KG51bGwpOihudWxsKQ==" ignores empty credential cases like "(null):(null)".
  • Threshold: type both, count 1, seconds 300, track by_src limits the alert frequency to once every 300 seconds per source to prevent excessive triggering.
  • Classification: policy-violation indicates this activity breaks acceptable security policy rather than representing direct malicious behavior.
  • SID/Rev: sid:2006380; rev:18 uniquely identifies the rule and its revision number.
  • Metadata: includes creation and update timestamps, performance impact, and severity indicators.​

Now, if we try to connect to GUDE 2301 via the web interface and place a Burpsuit proxy between the two, we can see the following request.

GET /ov.html? HTTP/1.1
Host: 10.1.1.20
Cache-Control: max-age=0
Authorization: Basic YWRtaW46YWRtaW4=
Accept-Language: fr-FR,fr;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://10.1.1.20/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

The following observations can be made:

  • Presence of the HTTP protocol
  • The Basic auth string is present
  • the presence of a string in base 64 encoding format

You should now begin to understand the concept of Suricata alerts.

Now that we know how to write new ones, I suggest you write one or two and try to implement them in the Hedgehog Linux sensor.

A quick note about the equipment (GUDE 2301)

With the example I just gave you, you can see one of the biggest problems with ICS/IoT systems: they are devices that were designed to be functional. Security in most of the use case is quite complicated to associate with this concept of functionality.

Here in the real world, the equipment (GUDE 2301) has been replaced by its successor, the GUDE 2302 and discontinued. There does not appear to have been a firmware update for a long time, and the documentation for this PLC (in its original version) only exists in German.

The problem is therefore as follows: we cannot rely on the manufacturer to update the product that it itself has distributed, which is why the implementation of detection rules is essential.

We cannot modify the equipment, so let's build a strong network around it and secure the paths and equipment that we control better. This doctrine is called defense in depth, and this is how industrial security is achieved: a succession of layers of security that will slow down or discourage the attacker.

None

If you are interested in the subject of defense in depth and its applications in industrial environments, I recommend the following document: (NIS SP 800–82).

development of new rules

NOTE

Let's start by deciding what we want to detect. For now, we'll limit ourselves to the attack surface represented by the login page. We currently have two equipement.

  • SIEMENS LOGO
  • GUDE 2301

So what would be a relatively simple TTP to detect and easy to implement in Suricata rules? To start with, I can think of two that are fairly simple.

The two equipement have quite different authentication systems. As we will see, detecting one of the default passwords on SIMENS LOGO! is not possible (as far as I understand).

None
The actual setup for testing rule on the web login page with burpsuit.

GUDE 2301 (default credential usage detection)

There are therefore two easily detectable attacks for GUDE 2301. We will start with the default credential usage.

You may be wondering, "What's the point of setting a rule to detect the use of default credentials if we're going to change them?"

The answer is yes, but you have to understand the context in which GUDE 2301 is used. It is used in the management of electrical installations (in my case, I recovered it from a solar panel installation).

Let's imagine you work for a company that owns buildings, and over time they want to use the roofs of their buildings to install solar panels.

The solar panels will be added gradually, and this installation may precede Malcolm's installation. So we can make it "future proof" against human error (a technician who forgot to change their password) in this case by keeping this rule.

But how do you do that? It's quite simple: you connect to GUDE 2301 as normal, while having a Burp proxy that retrieves the requests.

None

As already mentioned, we send a GET request to the URL "ov.html."

None
Monitoring web panel of the

But there is a problem: it is a GET request and not a POST request that is used for authentication, and furthermore, "ov.html" is not specific to authentication. How can we create a rule for specific behavior on a page that is used by common behaviors ?

What we can do is create two requests: one that is the authentication request and points to "ov.html," and one that is already authenticated and goes to "ov.html." Then we compare them in Burp Suite.

Spoiler, it looks like this.

NOTE: the default password for this equipement is admin:admin

None

On the left side of the image, you can see the authentication request, and on the right side, you can see the request once authenticated.

So you can probably see where I'm going with this. We create a rule that sets the standard parameters (basic auth, default password encoded in base 64, GET request on "ov.html") and a parameter specific to authentication and HTTP header "Cache-Control: max-age="

NOTE: To add a file to /etc/suricata/rules, don't forget to apply chmod 644 filename and add the path in the Suricata configurations. If that doesn't work, you can always put your custom rules in a file that Suricata reads and takes into account, such as "http-events.rules."

So this is what the suricata rule looks like, formulated with our need to finish earlier.

alert tcp any any -> any any (msg:"GUDE 2301 PLC Login default password"; content:"GET /ov.html"; content:"Cache-Control: max-age=0"; content:"Authorization: Basic YWRtaW46YWRtaW4="; sid:100045; rev:1;)

As explained, we can simply replay the request in a loop via the repeater section of Burp Suite.

None

So if we have implemented the rule correctly, and when we replay the request via Burp Suite, ta-daaa, we get the alert.

None

GUDE 2301 (Bruteforce on login page detections)

For this rule, a new concept will be developed: that of the "threshold" and the "!" (not).

Our requirement here is as follows: we want to detect if

  • There are more than 5 login attempts in less than 3 seconds
  • Using a password that is not the default one
  • Containing the same characteristics as the previous rule on sections inherent to a high-authentication request.
alert tcp any any -> any any (msg:"GUDE 2301 PLC Login page bruteforce."; content:"GET /ov.html"; content:"Cache-Control: max-age=0";content:!"Authorization: Basic YWRtaW46YWRtaW4=" ;threshold: type limit, track by_src, count 5, seconds 3; sid:100046; rev:1;)

Here the detailed explanations , note tha tyou can define a time between detected packet to see a patern a parterne and tunr that to an alert and that via the parameter Threshold.

  • Action: alert Generates an alert when the rule conditions are met.
  • Protocol: tcp Inspects TCP network traffic.
  • Traffic Direction: any any -> any anyApplies to any source and destination IP addresses and ports.
  • Message (msg): "GUDE 2301 PLC Login page bruteforce."The alert message for detection events.
  • Match Conditions:
  • content:"GET /ov.html" Looks for HTTP GET requests for the /ov.html login page
  • content:"Cache-Control: max-age=0"Confirms the presence of this HTTP header, typical for login requests.
  • content:!"Authorization: Basic YWRtaW46YWRtaW4=" Ignores requests that already include a known admin credential ("admin:admin" in Base64), focusing detection on failed or repeated attempts.
  • Threshold: type limit, track by_src, count 5, seconds 3 Limits alerts to 5 matches per source IP within 3 seconds, which is indicative of brute-force activity by quickly repeating login attempts.
  • Rule identifiers: sid:100046; rev:1 — Unique identifiers for tracking and management.

To test this rule, just remove one character from the password encorded in base 64 (in the Burp Suite repeater and spam the send button). You should see the alert appear in the Suricata "fast.log."

None

SIEMENS LOGO! (Bruteforce on login page detections)

For this one, it's a little different. We can't really rely on a system with a basic password sent in base 64, so we can't really detect if the user is using the default password.

If you're interested, here's a resource that goes much further in exploring the Siemens LOGO! authentication system.

The only thing you need to see is that the first part of the authentication at the SIEMENS LOGO! , resembles these.

POST /AJAX HTTP/1.1
Host: 10.1.1.15
Content-Length: 19
Accept-Language: fr-FR,fr;q=0.9
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
Security-Hint: p
Content-Type: text/plain;charset=UTF-8
App-Language: 1
Accept: */*
Origin: http://10.1.1.15
Referer: http://10.1.1.15/logo_login.shtm?!App-Language=1
Accept-Encoding: gzip, deflate, br
Cookie: Security-Hint=p
Connection: keep-alive

UAMCHAL:3,4,1,2,3,4

The element that interests us here is the section named "UAMCHAL." It is a constant element linked to the authentication system, as well as the POST request on the path "/AJAX." Add to that the threshold of 5 attempts in 10 seconds, and you get the following rule:

alert tcp any any -> any 80 (msg:"SIEMENS LOGO Attempted bruteforce on the web panel"; content:"POST /AJAX"; content:"UAMCHAL" ;threshold: type limit, track by_src, count 5, seconds 10; sid:100040; rev:1;)

NOTE: ELITEWOLF is a GitHub repository owned by the NSA. Two years ago, I tried to make a pull request to their repository, but they never merged it. The Suricata rule is mine, formatted for the pull request I submitted to the ELITEWOLF repository.

To test the rule, I will use the following GitHub repository.

Install the tool and launch the attack (from the attack machine), having first added the brute force rule defined just before, and you will have these from the attacker's point of view.

None

And those from the defender's point of view

One thing should jump out at you: the tool actually triggers another additional Suricata alert, "HTTP host header Invalid." This may be related to the fact that the tool does not take this HTTP header into account.

The interesting thing here is that if the tool is patched (adding the Host Header), it will reduce its footprint in terms of rules, but not completely. It will still remain.

Integration of rules into the hedgehog sensor

NOTE: Start Malcolm in parallel beforehand, giving it time to start up and properly receive the data sent by the sensor.

Great, we have new rules to implement in the hedgehog sensor, but how do we do it? To add rules, there is a document that says this

On Hedgehog Linux, the Suricata custom rules directory is /opt/sensor/sensor_ctl/suricata/rules/, and the SURICATA_CUSTOM_RULES_ONLY environment variable can be found in /opt/sensor/sensor_ctl/control_vars.conf. New rules can be applied by restarting capture processes:

/opt/sensor/sensor_ctl/shutdown && sleep 30 && /opt/sensor/sensor_ctl/supervisor.sh
None

NOTE: If you are curious, the GUDE file contains the two rules for GUDE 2301, the LOGO file contains the rule for SIEMENS LOGO!, and the scada-scan.rules folder contains the rules for the following devices.

Wait a minute for the system to reset, then restart the attacks.

Once the attacks have been launched, you can go to the opensearch section of your Malcolm and go to the discover section. Apply the two filters shown in the screenshot below, and you should see the rules reappear. Congratulations, you now have the hability to custom Malcolm to your needs.

None

Conclusions

In this article, we saw how to create, test, and deploy custom Suricata rules on Malcolm. It focused on fairly basic TTPs and was fundamentally linked to the web interface. The following section will focus on slightly different and less conventional TTPs.

We did not really exploit the dashboard and the OpenSearch query system.

Continue to part 3 of the series on setting up and tuning an OT SOC.

Part 3

About the Author

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — I am a freelance ICS security consultant and a teacher available for assignments in this field.

If you are interested, DM me on LinkedIn. https://www.linkedin.com/in/erwan-cordier/