Reducing your attack surface is often far more effective than layering additional security on top to prevent threats. This post looks at ways you can harden your security effectively. Most of these are not about “making the application secure”, but about “avoiding making the application insecure”. It’s always easier in the long run to set up your network correctly the first time, than to try to clean it up later. Speaking from experience, security is one of those things that once opened, cannot be easily closed.

  1. Secure your data 

1a. Don’t put your data on the internet if you don’t have to.

This one should be obvious, but the internet is full of threats. If you aren’t running an intentionally public web server, you probably don’t want your applications exposed to internet traffic.

The first step in preventing an attacker from accessing the data on a machine is to simply prevent them from accessing the machine at all. There should always be a firewall between your application and the internet, and in most cases nothing should be allowed through. 

Even if you are running a public web server, you should have a firewall blocking all other access to that machine.

If you also include blocking outbound access, you can make it much harder to exfiltrate data, or modify a system without going through your change control process.

1b. Isolate your network.

This concept can be extended to internal networks as well. For example, there is no reason that your customer support page should have access to your payroll server. Isolating your internal networks from each other is very effective at preventing lateral movement.

  1. Keep your software up to date.

New vulnerabilities are found constantly and unless they are zero-day exploits that you can’t prevent, the overwhelming majority of hacks make use of older software that simply wasn’t updated.

If you are using Linux, patches are available just hours after the vulnerability is found. Modern Linux systems can run for years without crashing. If you’ve got years of uptime on a server, that probably means your server is vulnerable.

If you’re on Windows, this is even more critical. Do not skip Patch Tuesday.

This should be done proactively on a schedule, not just reactively once you find out about a vulnerability. It should be assumed that all software that hasn’t had security updates applied recently could be at risk. Turn on the auto-update software functionality whenever possible to make sure you have a new and updated version of the software. 

  1. Run a local firewall and don’t open ports you don’t need.

Even when you’re not on the internet, there is no reason to allow everything into the machine.

If an attacker (or insider threat) has access to one host on your network, it should be hard for them to move laterally to other assets.

Applications can listen on arbitrary ports above 1024 without root privileges. You don’t want people to add new entry points to the machine without your knowledge.

  1. Don’t install or run services you don’t need.

Assume every service running on your machine could be insecure. The system that runs fewer services will likely have fewer vulnerabilities. Lower surface area is usually more secure.

If you’re using containers, try to use a base image that pulls in fewer dependencies, like Alpine, rather than larger OS-style base images like Ubuntu or CentOS. This will also make your image smaller, and therefore faster to pull/push and deploy.

  1. Don’t run applications as root or as an administrator.

If your application runs as root, it has access to everything on the box. It could open the firewall, start new services, read sensitive information, listen on the network, etc… really anything it wants.

If your application has a vulnerability, everything else on that host is completely exposed.

In general, you want your application to run with as few privileges as possible. Consider controlling which capabilities you need using setcap. It can be used to grant applications running as unprivileged users the ability to do limited things that are normally reserved for root, without granting complete control of the system the way it would have if you just run it with sudo.

If you want your application to listen on a low-numbered port on Linux, you can use setcap cap_net_bind_service to allow the application to bind the port as an unprivileged user or an iptables DNAT rule to forward the low-numbered port to a higher one.

If you’re using containers, be aware that root in the container is basically the same as root on the host, so this applies there too. You can also run containers with dropped capabilities they don’t need, which can help limit the damage if there is a privilege escalation inside the container.

  1. Sanitize configurations and inputs

Users, at best, are error-prone humans. At worst, they are malicious.

Always sanitize your inputs, especially when passing them to a deeper part of your application, such as a database. Failure to sanitize inputs could lead to application crashes, leakage of sensitive data, or even remote code execution.

SQL injection is still one of the most common application vulnerabilities. Try to let your database library handle the sanitation for you, rather than rolling your own.

Don’t do (python sqlite3 example):

```

cursor.execute('INSERT INTO mytable VALUES ({}, {}, {}, {})'.format(a, b, c, d))

```

Do (python sqlite3 example):

```

cursor.execute('INSERT INTO mytable VALUES (?, ?, ?, ?)', (a, b, c, d))

```

  1. Don’t use the shell

The shell is an incredibly powerful tool, but it should only be used for administrative purposes, not application logic.

If you need to run other processes from your application, execute the other process directly. Don’t use the shell to parse additional arguments, especially if those arguments come from an untrusted source.

Launching another process from the shell is actually launching multiple processes: the shell and your subprocess.

Ninety-nine percent of the time, you only want to run your subprocess and don’t need the capabilities of the shell.

It is incredibly easy for a malicious or malformed input to escape if it’s passed to a shell. Take this python example:

```

subprocess.check_output('my_cmd {}'.format(my_arg), shell=True)

```

If a malicious user can control what is set in my_arg (for example: ; my_malicious_cmd), they could obtain arbitrary code execution.

Even if the user is not malicious, certain arguments would crash this program or have unpredictable behavior (ie: if there is a space in my_arg, it would be interpreted as two arguments).

Instead, just call the command directly, without a shell:

```

subprocess.check_output(['my_cmd', my_arg])

```

This way, you don’t need to worry about the shell improperly parsing your arguments, or a malicious user escaping your control.

Your code will even run faster, since it doesn’t need to spawn a shell.

  1. Monitor 

Monitoring is more art than science. It’s always a balancing act between not catching the important things and drowning in a flood of irrelevant information. Most organizations tend to be on the under-logging side.

If you aren’t aggregating your logs, you probably aren’t actually monitoring them. Aggregators and alerting tools will help filter the flood if you find yourself in the “drowning in logs” category.

For full visibility you should be monitoring both your application, and its environment (OS, network, hardware). It’s often easy to see an attack from a huge spike in network traffic or disk activity.

Monitoring the health of your application is often a huge step toward monitoring the security of your application.

Try to use standardized metrics and log formats when possible, so it is easy to monitor new services that meet your standard.

There are three main parts to good monitoring:

  • Aggregating the data,
  • Alerting on abnormalities,
  • Responding

In most organizations, there is an incomplete implementation of the first two, but very few organizations have a comprehensive response plan, and even fewer have it automated.

Shameless plug: Exabeam has products to help you with all three of these (Data Lake, Advanced Analytics, and Incident Responder, respectively).

  1. Use TLS for all network communication, even internally.

This isn’t just a compliance thing. An insider threat or compromised machine could read any unencrypted data sent on that network. A self-signed certificate is much better than no certificate. For externally facing services, the availability and simplicity of Lets Encrypt makes adding TLS easy, and is trusted almost everywhere.

Setting up PKI infrastructure can be a daunting task, but putting the effort in earlier in the process will make it much easier for you. You probably want to use something higher level than directly calling openssl every time. Many PKI tools also support advanced login and token management features, such as one-time use, short-expiration tokens so you immediately know if it’s been leaked. If your application fails to authenticate using a one-time token, you know that something else has used it.

Having a standard way for individuals to generate certificates removes the incentive to share certificates, and can help create trust zones between applications that have a common CA. Providing the correct path for users will help automate the process of obtaining low-privilege (read: not automatically trusted) certificates and help ensure they use encryption.

  1. Use infrastructure as code

Do not manually deploy anything. In addition to being massively easier to maintain, having the definition of what is installed and how it is configured in code allows you to compare the deployed state to the defined state. If they are different, you know something suspicious is happening. You can also rebuild in case of failure, compromise, or corruption.

Infrastructure as code also allows you to easily duplicate environments with common standards. No more “it worked in staging, but not production”.

For cloud infrastructure, Terraform is excellent, but the tool you use for this doesn’t matter so much as that you have the code in version control somewhere. If you use Terraform, do your future self a favor and set it up with remote state, and make modules for everything, for example a module for VPCs, a module for VPNs.

  1. Follow best practices for configuration and use of applications

Securing the network and OS is a fantastic start, but your application will likely have domain-specific security concerns.

If you’re building a web application (and for many non-web applications), the OWASP Top Ten is a great place to start.

For third party applications, the vendor or maintainer will likely have a security best practices page.

Do not run applications you do not understand. Know how they talk on the network and how they store data. You don’t have to know every detail, but at least know how they handle replication/failures, backups, what ports it talks on, and how/where it is encrypted (network and/or on disk).

If you don’t have a backup of your important data, there may not be a way to get a clean copy of it in case of an attack.

If you haven’t tested restoring your backup recently, you don’t actually have a backup.

Your applications are critical and deserve the best security. The above list should cover the basics, but for everything else, there’s Exabeam.

More like this

If you’d like to see more content like this, subscribe to the Exabeam Blog

Subscribe