Defensive Programming for Industrial Control Systems

Matthew J Scott
Author: Matthew J. Scott, CICP, CM, Lean Six Sigma Black Belt
Date Published: 30 May 2023
Related: Protecting Industrial Control System Software With Secure Coding Practices

Two days before the Tampa Bay Buccaneers and the Kansas City Chiefs squared off against each other in 2021 in the US National Football League (NFL) Super Bowl LV at Raymond James Stadium in Tampa Bay, Florida, the sodium hydroxide levels at a local drinking water treatment plant spiked to dangerous levels. The industrial control system (ICS) had been hacked. Luckily for the 15,000 residents and tens of thousands of football fans, a watchful plant employee intervened, preventing a mass poisoning.

Today, virtually every ICS is exposed to the internet. Open-source frameworks are standard in ICS products. The number of ransomware attacks against critical infrastructure has risen significantly in the past 20 years. Even air-gapped ICS are vulnerable. It is common for new ICS hardware to contain vulnerabilities and even malware. The Cybersecurity and Infrastructure Security Agency (CISA) ICS-Cyber Emergency Response Team (CERT) currently lists more than 2,300 known vulnerabilities within ICS components.

But there is hope. Like avoiding accidents through defensive driving, you can defensively program your ICS. Establishing the proper code environment, defining rule sets and conducting verification will ensure a resilient ICS.

Establish Your Code Environment

Defensive programming follows the requirements and design development phases of the secure software development lifecycle. It begins with establishing the specific code environment for your ICS application. There are four steps required to establish a code environment:

  1. Compare functional requirements for weaknesses and vulnerability to exploitation. The goal is to eliminate use of software weaknesses or known exploitations. The Common Weakness Enumeration (CWE) and ATT&CK ICS Matrix are tools that can be used to complete this step.
  2. Revise your requirements to eliminate any weaknesses or vulnerabilities. Note the recommended mitigations for any weaknesses that cannot be eliminated.
  3. Review your design for known product or system vulnerabilities. The Common Vulnerability Enumeration (CVE) and the CISA ICS-CERT Advisories are tools that can be used to complete this step.
  4. Establish the most secure code environment for the ICS application. Select and standardize with everyone in the supply and development chain products, firmware and software with the fewest vulnerabilities.

Define Your Rule Sets

At the heart of defensive programming is definition of rule sets. You should not only document techniques but also develop and proof units of reusable code that protect your control logic from exploitation:

  1. Ensure developers know what is allowed and not allowed. This includes specific methods, routines and product features found to be either weak or exploitable in the CWE/CVE research. Both a disallowed list and an allowed with mitigation list, including recommended mitigation techniques, should be published.
  2. Identify defensive techniques that match your functional requirements and abuse cases from your design. You can employ the appropriate elements from the programmable logic controllers (PLC) top 20 coding practices. These include a variety of validations (inputs, timers, counters, indirect references) that prevent most ICS exploitations.
  3. Standardize defensive techniques as units of reusable code. Most major PLC manufacturers provide the ability to encapsulate complex repetitive code in a single unit for instantiation throughout a program. The most common of these are function blocks (FB) in Siemens PLCs and add-on instructions (AOIs) in Rockwell Automation PLCs. Create and enforce the instantiation of standard security FBs or AOIs throughout your code for input validation, access control and verification of integrity.
  4. Rigorously test your reusable code units. Proofing your FB/AOI code before mass instantiation will provide you a resilient library of code for use on multiple projects. Do not forget to test and confirm the security elements of your code units, including locking against revision with passwords or digital signatures.

Verify Your Initial Code

Having developed your ICS control logic per your rule sets and within your established code environment, it is time to conduct a verification of your completed initial code. Of course, much of this is verification of functional requirements, but the security controls also need to be verified:

  1. Confirm the stability of the established code environment. New vulnerabilities are discovered almost daily in ICS products. Before you can deploy the code, you may need to advance your firmware or software versions or revise your rule sets.
  2. Confirm adherence to all defined rule sets. It does not do any good to have standard secure techniques if they are not applied. Review the code and ensure that all appropriate FBs/AOIs are instantiated and that mitigations are in place for any instances of weak or vulnerable elements demanded by your functional requirements.
  3. Simulate penetration of perimeter defenses. Although you may always employ a defense in depth, your job now is to assume that the outer defenses of your intrusion detection system (network intrusion detection, workstation access control, message authentication) have all failed. You must test and confirm that each instance of your security FBs/AOIs are preventing exploitation of the control logic.
  4. Enable and pen test your subsystem perimeter. Although it is not directly part of the PLC code, the perimeter defenses of your subsystem (real time I/O [RIO] network, operator workstation, enterprise network interface) require testing to ensure a resilient defense in depth. Pay special attention to effects on your PLC environment when these fail (e.g., change processor from run to program mode upon a denial-of-service attack on the ethernet port).

Resilient Code and More

Although defensive programming is only a part of a secure development lifecycle for ICS, it is the most critical. The PLC is the last line of defense before an ICS can be fully exploited. Yes, it is a lot of work to defensively program, but it pays huge dividends. The result is not only resilient code that prevents exploitations but also a safe ICS that prevents damage to assets and harm to human life.

Editor’s note: For further insights on this topic, read Matthew J. Scott’s recent Journal article, “Protecting Industrial Control System Software With Secure Coding Practices,” ISACA Journal, volume 2 2023.

ISACA Journal