One place for hosting & domains

      Harden

      How To Harden the Security of Your Production Django Project


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Developing a Django application can be a quick and clean experience, because its approach is flexible and scalable. Django also offers a variety of security-oriented settings that can help you seamlessly prepare your project for production. However, when it comes to production deployment, there are several ways to further secure your project. Restructuring your project by breaking up your settings will allow you to easily set up different configurations based on the environment. Leveraging dotenv for hiding environment variables or confidential settings will ensure you don’t release any details about your project that may compromise it.

      While implementing these different strategies and features might seem time-consuming at first, developing a practical workflow will allow you to deploy releases of your project without compromising on security, or your productivity.

      In this tutorial, you will leverage a security-oriented workflow for your Django development by implementing and configuring environment-based settings, dotenv, and Django’s built-in security settings. These features all complement each other and will result in a version of your Django project that is ready for different approaches you may take to deployment.

      Prerequisites

      Before you begin this guide you’ll need the following:

      Note: If you’re using an existing Django project, you may have different requirements. This tutorial suggests a particular project structure, however, you can also use each of the sections of this tutorial individually as needed.

      Step 1 — Restructuring Django’s Settings

      In this first step, you’ll start by rearranging your settings.py file into environment-specific configurations. This is a good practice when you need to move a project between different environments, for example, development and production. This arrangement will mean less reconfiguration for different environments; instead, you’ll use an environment variable to switch between configurations, which we’ll discuss later in the tutorial.

      Create a new directory called settings in your project’s sub-directory:

      • mkdir testsite/testsite/settings

      (As per the prerequisites we’re using testsite, but you can substitute your project’s name in here.)

      This directory will replace your current settings.py configuration file; all of your environment-based settings will be in separate files contained in this folder.

      In your new settings folder, create three Python files:

      • cd testsite/testsite/settings
      • touch base.py development.py production.py

      The development.py file will contain settings you’ll normally use during development. And production.py will contain settings for use on a production server. You should keep these separate because the production configuration will use settings that will not work in a development environment; for example, forcing the use of HTTPS, adding headers, and using a production database.

      The base.py settings file will contain settings that development.py and production.py will inherit from. This is to reduce redundancy and to help keep your code cleaner. These Python files will be replacing settings.py, so you’ll now remove settings.py to avoid confusing Django.

      While still in your settings directory, rename settings.py to base.py with the following command:

      • mv ../settings.py base.py

      You’ve just completed the outline of your new environment-based settings directory. Your project won’t understand your new configuration yet, so next, you’ll fix this.

      Step 2 — Using python-dotenv

      Currently Django will not recognize your new settings directory or its internal files. So, before you continue working with your environment-based settings, you need to make Django work with python-dotenv. This is a dependency that loads environment variables from a .env file. This means that Django will look inside a .env file in your project’s root directory to determine which settings configuration it will use.

      Go to your project’s root directory:

      Install python-dotenv:

      • pip install python-dotenv

      Now you need to configure Django to use dotenv. You’ll edit two files to do this: manage.py, for development, and wsgi.py, for production.

      Let’s start by editing manage.py:

      Add the following code:

      testsite/manage.py

      import os
      import sys
      import dotenv
      
      def main():
          os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development')
      
          if os.getenv('DJANGO_SETTINGS_MODULE'):
          os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')
      
          try:
              from django.core.management import execute_from_command_line
          except ImportError as exc:
              raise ImportError(
                  "Couldn't import Django. Are you sure it's installed and "
                  "available on your PYTHONPATH environment variable? Did you "
                  "forget to activate a virtual environment?"
              ) from exc
          execute_from_command_line(sys.argv)
      
      
      if __name__ == '__main__':
          main()
      
      dotenv.load_dotenv(
          os.path.join(os.path.dirname(__file__), '.env')
      )
      

      Save and close manage.py and then open wsgi.py for editing:

      Add the following highlighted lines:

      testsite/testsite/wsgi.py

      
      import os
      import dotenv
      
      from django.core.wsgi import get_wsgi_application
      
      dotenv.load_dotenv(
          os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
      )
      
      os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testsite.settings.development')
      
      if os.getenv('DJANGO_SETTINGS_MODULE'):
       os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')
      
      application = get_wsgi_application()
      

      The code you’ve added to both of these files does two things. First, whenever Django runs—manage.py for running development, wsgi.py for production—you’re telling it to look for your .env file. If the file exists, you instruct Django to use the settings file that .env recommends, otherwise you use the development configuration by default.

      Save and close the file.

      Finally, let’s create a .env in your project’s root directory:

      Now add in the following line to set the environment to development:

      testsite/.env

      DJANGO_SETTINGS_MODULE="testsite.settings.development"
      

      Note: Add .env to your .gitignore file so it is never included in your commits; you’ll use this file to contain data such as passwords and API keys that you do not want visible publicly. Every environment your project is running on will have its own .env with settings for that specific environment.

      It is recommended to create a .env.example to include in your project so you can easily create a new .env wherever you need one.

      So, by default Django will use testsite.settings.development, but if you change DJANGO_SETTINGS_MODULE to testsite.settings.production for example, it will start using your production configuration. Next, you’ll populate your development.py and production.py settings configurations.

      Step 3 — Creating Development and Production Settings

      Next you’ll open your base.py and add the configuration you want to modify for each environment in the separate development.py and production.py files. The production.py will need to use your production database credentials, so ensure you have those available.

      Note: It is up to you to determine which settings you need to configure, based on environment. This tutorial will only cover a common example for production and development settings (that is, security settings and separate database configurations).

      In this tutorial we’re using the Django project from the prerequisite tutorial as the example project. We’ll move settings from from base.py to development.py. Begin by opening development.py:

      • nano testsite/settings/development.py

      First, you will import from base.py—this file inherits settings from base.py. Then you’ll transfer the settings you want to modify for the development environment:

      testsite/testsite/settings/development.py

      from .base import *
      
      DEBUG = True
      
      DATABASES = {
          'default': {
              'ENGINE': 'django.db.backends.sqlite3',
              'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
          }
      }
      

      In this case, the settings specific to development are: DEBUG, you need this True in development, but not in production; and DATABASES, a local database instead of a production database. You’re using an SQLite database here for development.

      Note: For security purposes, Django’s DEBUG output will never display any settings that may contain the strings: API, KEY, PASS, SECRET, SIGNATURE, or TOKEN.

      This is to ensure secrets will not be revealed, if you accidentally deploy a project to production with DEBUG still enabled.

      With that being said, never deploy a project publicly with DEBUG enabled. It will only ever put the security of your project at risk.

      Next, let’s add to production.py:

      • nano testsite/settings/production.py

      Production will be similar to development.py, but with a different database configuration and DEBUG set to False:

      testsite/testsite/settings/production.py

      from .base import *
      
      DEBUG = False
      
      ALLOWED_HOSTS = []
      
      DATABASES = {
          'default': {
              'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
              'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')),
              'USER': os.environ.get('SQL_USER', 'user'),
              'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'),
              'HOST': os.environ.get('SQL_HOST', 'localhost'),
              'PORT': os.environ.get('SQL_PORT', ''),
          }
      }
      

      For the example database configuration given, you can use dotenv to configure each of the given credentials, with defaults included. Assuming you’ve already set up a database for the production version of your project, please use your configuration instead of the example provided.

      You have now configured your project to use different settings based on DJANGO_SETTINGS_MODULE in .env. Given the example settings you’ve used, when you set your project to use production settings, DEBUG becomes False, ALLOWED_HOSTS is defined, and you start using a different database that you’ve (already) configured on your server.

      Step 4 — Working with Django’s Security Settings

      Django includes security settings ready for you to add to your project. In this step, you’ll add security settings to your project that are considered essential for any production project. These settings are intended for use when your project is available to the public. It’s not recommended to use any of these settings in your development environment; hence, in this step you’re limiting these settings to the production.py configuration.

      For the most part these settings are going to enforce the use of HTTPS for various web features, such as session cookies, CSRF cookies, upgrading HTTP to HTTPS, and so on. Therefore, if you haven’t already set up your server with a domain pointing to it, hold off on this section for now. If you need to set up your server ready for deployment, check out the Conclusion for suggested articles on this.

      First open production.py:

      In your file, add the highlighted settings that work for your project, according to the explanations following the code:

      testsite/testsite/settings/production.py

      from .base import *
      
      DEBUG = False
      
      ALLOWED_HOSTS = ['your_domain', 'www.your_domain']
      
      DATABASES = {
          'default': {
              'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
              'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')),
              'USER': os.environ.get('SQL_USER', 'user'),
              'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'),
              'HOST': os.environ.get('SQL_HOST', 'localhost'),
              'PORT': os.environ.get('SQL_PORT', ''),
          }
      }
      
      SECURE_SSL_REDIRECT = True
      
      SESSION_COOKIE_SECURE = True
      
      CSRF_COOKIE_SECURE = True
      
      SECURE_BROWSER_XSS_FILTER = True
      
      • SECURE_SSL_REDIRECT redirects all HTTP requests to HTTPS (unless exempt). This means your project will always try to use an encrypted connection. You will need to have SSL configured on your server for this to work. Note that if you have Nginx or Apache configured to do this already, this setting will be redundant.
      • SESSION_COOKIE_SECURE tells the browser that cookies can only be handled over HTTPS. This means cookies your project produces for activities, such as logins, will only work over an encrypted connection.
      • CSRF_COOKIE_SECURE is the same as SESSION_COOKIE_SECURE but applies to your CSRF token. CSRF tokens protect against Cross-Site Request Forgery. Django CSRF protection does this by ensuring any forms submitted (for logins, signups, and so on) to your project were created by your project and not a third party.
      • SECURE_BROWSER_XSS_FILTER sets the X-XSS-Protection: 1; mode=block header on all responses that do not already have it. This ensures third parties cannot inject scripts into your project. For example, if a user stores a script in your database using a public field, when that script is retrieved and displayed to other users it will not run.

      If you would like to read more about the different security settings available within Django, check out their documentation.

      Warning: Django’s documentation states you shouldn’t rely completely on SECURE_BROWSER_XSS_FILTER. Never forget to validate and sanitize input.

      Additional Settings

      The following settings are for supporting HTTP Strict Transport Security (HSTS)—this means that your entire site must use SSL at all times.

      • SECURE_HSTS_SECONDS is the amount of time in seconds HSTS is set for. If you set this for an hour (in seconds), every time you visit a web page on your website, it tells your browser that for the next hour HTTPS is the only way you can visit the site. If during that hour you visit an insecure part of your website, the browser will show an error and the insecure page will be inaccessible.
      • SECURE_HSTS_PRELOAD only works if SECURE_HSTS_SECONDS is set. This header instructs the browser to preload your site. This means that your website will be added to a hardcoded list, which is implemented in popular browsers, like Firefox and Chrome. This requires that your website is always encrypted. It is important to be careful with this header. If at anytime you decide to not use encryption for your project, it can take weeks to be manually removed from the HSTS Preload list.
      • SECURE_HSTS_INCLUDE_SUBDOMAINS applies the HSTS header to all subdomains. Enabling this header, means that both your_domain and unsecure.your_domain will require encryption even if unsecure.your_domain is not related to this Django project.

      Warning: Incorrectly configuring these additional settings can break your site for a significant amount of time.

      Please read the Django documentation on HSTS before implementing these settings.

      It is necessary to consider how these settings will work with your own Django project; overall the settings discussed here are a good foundation for most Django projects. Next you’ll review some further usage of dotENV.

      Step 5 — Using python-dotenv for Secrets

      The final part of this tutorial will help you leverage python-dotenv. This will allow you to hide certain information such as your project’s SECRET_KEY or the admin’s login URL. This is a great idea if you intend to publish your code on platforms like GitHub or GitLab since these secrets won’t be published. Instead, whenever you initially set up your project on a local environment or a server, you can create a new .env file and define those secret variables.

      You must hide your SECRET_KEY so you’ll start working on that in this section.

      Open your .env file:

      And add the following line:

      testsite/.env

      DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
      SECRET_KEY="your_secret_key"
      

      And in your base.py:

      • nano testsite/settings/base.py

      Let’s update the SECRET_KEY variable like so:

      testsite/testsite/settings/base.py

      . . .
      SECRET_KEY = os.getenv('SECRET_KEY')
      . . .
      

      Your project will now use the SECRET_KEY located in .env.

      Lastly, you’ll hide your admin URL by adding a long string of random characters to it. This will ensure bots can’t brute force the login fields and strangers can’t try guessing the login.

      Open .env again:

      And add a SECRET_ADMIN_URL variable:

      testsite/.env

      DJANGO_SETTINGS_MODULE="django_hardening.settings.development"
      SECRET_KEY="your_secret_key"
      SECRET_ADMIN_URL="very_secret_url"
      

      Now let’s tell Django to hide your admin URL with SECRET_ADMIN_URL:

      Note: Don’t forget to replace your_secret_key and very_secret_url with your own secret strings. Also don’t forget to replace very_secret_url with your own secret URL.

      If you want to use random strings for these variables, Python provides a fantastic secrets.py library for generating such strings. The examples they give are great ways to create small Python programs for generating secure random strings.

      Edit the admin URL like so:

      testsite/testsite/urls.py

      import os
      from django.contrib import admin
      from django.urls import path
      
      urlpatterns = [
          path(os.getenv('SECRET_ADMIN_URL') + '/admin/', admin.site.urls),
      ]
      

      You can now find the admin login page at very_secret_url/admin/ instead of just /admin/.

      Conclusion

      In this tutorial you have configured your current Django project for easy use with different environments. Your project now leverages python-dotenv for handling secrets and settings. And your production settings now have Django’s built-in security features enabled.

      If you’ve enabled all the recommended security components and re-implemented settings as directed, your project has these key features:

      • SSL/HTTPS for all communications (for example, subdomains, cookies, CSRF).
      • XSS (cross-site scripting) attacks prevention.
      • CSRF (cross-site request forgery) attacks prevention.
      • Concealed project secret key.
      • Concealed admin login URL preventing brute-force attacks.
      • Separate settings for development and production.

      If you are interested in learning more about Django, check out our tutorial series on Django development.

      Also, if you haven’t already put your project into production, here is a tutorial on How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 20.04. You can also check out our Django topic page for further tutorials.

      And, of course, please read over Django’s settings documentation, for further information.



      Source link

      How To Harden OpenSSH Client on Ubuntu 18.04


      The author selected the Electronic Frontier Foundation Inc to receive a donation as part of the Write for DOnations program.

      Introduction

      Linux servers are often administered remotely using SSH by connecting to an OpenSSH server, which is the default SSH server software used within Ubuntu, Debian, CentOS, FreeBSD, and most other Linux/BSD-based systems. Significant effort is put into securing the server-side aspect of SSH, as SSH acts as the entry into your server.

      However, it is also important to consider security on the client-side, such as OpenSSH client.

      OpenSSH client is the “client” side of SSH, also known as the ssh command. You can learn more about the SSH client-server model in SSH Essentials: Working with SSH Servers, Clients, and Keys.

      When hardening SSH at the server side, the primary objective is to make it harder for malicious actors to access your server. However, hardening at the client side is very different, as instead you are working to defend and protect your SSH connection and client from various different threats, including:

      • Attackers on the network, known as “person-in-the-middle” attacks.
      • Compromised or malicious servers sending malformed data packets, nefarious control sequences, or large amounts of data to overload your client.
      • Human error, such as mistyping server addresses or configuration values.

      In this tutorial, you will harden your OpenSSH client in order to help ensure that outgoing SSH connections are as secure as possible.

      Prerequisites

      To complete this tutorial, you will need:

      • A device that you use as an SSH client, for example:

      • An SSH server that you want to connect to, for example:

        • A cloud server
        • A public service such as GitHub or GitLab
        • A third-party device that you are permitted to access

      Once you have these ready, log in to your SSH client device as a non-root user to begin.

      Step 1 — General Hardening

      In this first step, you will implement some initial hardening configurations in order to improve the overall security of your SSH client.

      The exact hardening configuration that is most suitable for your client depends heavily on your own threat model and risk threshold. However, the configuration described in this step is a general, all-round secure configuration that should suit the majority of users.

      Many of the hardening configurations for OpenSSH client are implemented using the global OpenSSH client configuration file, which is located at /etc/ssh/ssh_config. In addition to this, some configurations may also be set using the local SSH configuration file for your user, located at ~/.ssh/config.

      Before continuing with this tutorial, it is recommended to take a backup of both of these files, so that you can restore them in the unlikely event that something goes wrong.

      Take a backup of the files using the following commands:

      • sudo cp /etc/ssh/ssh_config /etc/ssh/ssh_config.bak
      • cp ~/.ssh/config ~/.ssh/config.bak

      This will save a backup copy of the files in their default location, but with the .bak extension added.

      Note that your local SSH configuration file (~/.ssh/config) may not exist if you haven’t used it in the past. If this is the case, it can be safely ignored for now.

      You can now open the global configuration file using your favorite text editor to begin implementing the initial hardening measures:

      • sudo nano /etc/ssh/ssh_config

      Note: The OpenSSH client configuration file includes many default options and configurations. Depending on your existing client setup, some of the recommended hardening options may already have been set.

      When editing your configuration file, some options may be commented out by default using a single hash character (#) at the start of the line. To edit these options, or have the commented option be recognized, you’ll need to uncomment them by removing the hash.

      Firstly, you should disable X11 display forwarding over SSH by setting the following options:

      /etc/ssh/ssh_config

      ForwardX11 no
      ForwardX11Trusted no
      

      X11 forwarding allows for the display of remote graphical applications over an SSH connection, however this is rarely used in practice. By disabling it, you can prevent potentially malicious or compromised servers from attempting to forward an X11 session to your client, which in some cases can allow for filesystem permissions to be bypassed, or for local keystrokes to be monitored.

      Next, you can consider disabling SSH tunneling. SSH tunneling is quite widely used, so you may need to keep it enabled. However, if it isn’t required for your particular setup, you can safely disable it as a further hardening measure:

      /etc/ssh/ssh_config

      Tunnel no
      

      You should also consider disabling SSH agent forwarding if it isn’t required, in order to prevent servers from requesting to use your local SSH agent to authenticate onward SSH connections:

      /etc/ssh/ssh_config

      ForwardAgent no
      

      In the majority of cases, your SSH client will be configured to use password authentication or public-key authentication when connecting to servers. However, OpenSSH client also supports other authentication methods, some of which are enabled by default. If these are not required, they can be disabled to further reduce the potential attack surface of your client:

      /etc/ssh/ssh_config

      GSSAPIAuthentication no
      HostbasedAuthentication no
      

      If you’d like to know more about some of the additional authentication methods available within SSH, you may wish to review these resources:

      OpenSSH client allows you to automatically pass custom environment variables when connecting to servers, for example, to set a language preference or configure terminal settings. However, if this isn’t required in your setup, you can prevent any variables being sent by ensuring that the SendEnv option is commented out or completely removed:

      /etc/ssh/ssh_config

      # SendEnv
      

      Finally, you should ensure that strict host key checking is enabled, to ensure that you are appropriately warned when the host key/fingerprint of a remote server changes, or when connecting to a new server for the first time:

      /etc/ssh/ssh_config

      StrictHostKeyChecking ask
      

      This will prevent you from connecting to a server when the known host key has changed, which could mean that the server has been rebuilt or upgraded, or could be indicative of an ongoing person-in-the-middle attack.

      When connecting to a new server for the first time, your SSH client will ask you whether you want to accept the host key and save it in your ~/.ssh/known_hosts file. It’s important that you verify the host key before accepting it, which usually involves asking the server administrator or browsing the documentation for the service (in the case of GitHub/GitLab and other similar services).

      Save and exit the file.

      Now that you’ve completed your initial configuration file hardening, you should validate the syntax of your new configuration by running SSH in test mode:

      You can substitute the . with any hostname to test/simulate any settings contained within Match or Host blocks.

      If your configuration file has a valid syntax, the options that will apply to that specific connection will be printed out. In the event of a syntax error, there will be an output describing the issue.

      You do not need to restart any system services for your new configuration to take effect, although existing SSH sessions will need to be re-established if you want them to inherit the new settings.

      In this step, you completed some general hardening of your OpenSSH client configuration file. Next, you’ll restrict the ciphers that are available for use in SSH connections.

      Step 2 — Restricting Available Ciphers

      Next, you will configure the cipher suites available within your SSH client to disable support for those that are deprecated/legacy.

      Begin by opening your global configuration file in your text editor:

      • sudo nano /etc/ssh/ssh_config

      Next, ensure that the existing Ciphers configuration line is commented out by prefixing it with a single hash (#).

      Then, add the following to the top of the file:

      /etc/ssh/ssh_config

      Ciphers -arcfour*,-*cbc
      

      This will disable the legacy Arcfour ciphers, as well as all ciphers using Cipher Block Chaining (CBC), which is no longer recommended for use.

      If there is a requirement to connect to systems that only support these legacy ciphers, you can explicitly re-enable the required ciphers for specific hosts by using a Match block. For example, to enable the 3des-cbc cipher for a specific legacy host, the following configuration could be used:

      /etc/ssh/ssh_config

      Match host legacy-server.your-domain
        Ciphers +3des-cbc
      

      Save and exit the file.

      Finally, as you did in Step 1, you may wish to test your SSH client configuration again to check for any potential errors:

      If you have added a Match block to enable legacy ciphers for a specific host, you can also specifically target that configuration during the test by specifying the associated host address:

      • ssh -G legacy-server.your-domain

      You’ve secured the ciphers available to your SSH client. Next, you will review the access permissions for files used by your SSH client.

      Step 3 — Securing Configuration File and Private Key Permissions

      In this step, you’ll lock down the permissions for your SSH client configuration files and private keys to help prevent accidental or malicious changes, or private key disclosure. This is especially useful when using a shared client device between multiple users.

      By default on a fresh installation of Ubuntu, the OpenSSH client configuration file(s) are configured so that each user can only edit their own local configuration file (~/.ssh/config), and sudo/administrative access is required to edit the system-wide configuration (/etc/ssh/ssh_config).

      However, in some cases, especially on systems that have been in existence for a long time, these configuration file permissions may have been accidentally modified or adjusted, so it’s best to reset them to make sure that the configuration is secure.

      You can begin by checking the current permissions value for the system-wide OpenSSH client configuration file using the stat command, which you can use to show the status or files and/or filesystem objects:

      • stat -c "%a %A %U:%G" /etc/ssh/ssh_config

      You use the -c argument to specify a custom output format.

      Note: On some operating systems, such as macOS, you will need to use the -f option to specify a custom format rather than -c.

      In this case, the %A %a %U:%G option will print the permissions for the file in octal and human-readable format, as well as the user/group that owns the file.

      This will output something similar to the following:

      Output

      644 -rw-r--r-- root:root

      In this case, the permissions are correct, root owns the file entirely, and only root has permission to write to/modify it.

      Note: If you’d like to refresh your knowledge on Linux permissions before continuing, you may wish to review An Introduction to Linux Permissions.

      However, if your own output is different, you should reset the permissions back to the default using the following commands:

      • sudo chown root:root /etc/ssh/ssh_config
      • sudo chmod 644 /etc/ssh/ssh_config

      If you repeat the stat command from earlier in this step, you will now receive the correct values for your system-wide configuration file.

      Next, you can carry out the same checks for your own local SSH client configuration file, if you have one:

      • stat -c "%a %A %U:%G" ~/.ssh/config

      This will output something similar to the following:

      Output

      644 -rw--r--r-- user:user

      If the permissions for your own client configuration file permissions are any different, you should reset them using the following commands, similarly to earlier in the step:

      • chown user:user ~/.ssh/config
      • chmod 644 ~/.ssh/config

      Next, you can check the permissions for each of the SSH private keys that you have within your ~/.ssh directory, as these files should only be accessible by yourself, and not any other users on the system.

      Begin by printing the current permission and ownership values for each private key:

      • stat -c "%a %A %U:%G" ~/.ssh/id_rsa

      This will output something similar to the following:

      Output

      600 -rw------- user:user

      It is extremely important that you properly lock down the permissions for your private key files, as failing to do so could allow other users of your device to steal them and access the associated servers or remote user accounts.

      If the permissions aren’t properly configured, use the following commands on each private key file to reset them to the secure defaults:

      • chown user:user ~/.ssh/id_rsa
      • chmod 600 ~/.ssh/id_rsa

      In this step, you assessed and locked down the file permissions for your SSH client configuration files and private keys. Next, you will implement an outbound allowlist to limit which servers your client is able to connect to.

      Step 4 — Restricting Outgoing Connections Using a Host Allowlist

      In this final step, you will implement an outgoing allowlist in order to restrict the hosts that your SSH client is able to connect to. This is especially useful for shared/multi-user systems, as well as SSH jump hosts or bastion hosts.

      This security control is specifically designed to help protect against human error/mistakes, such as mistyped server addresses or hostnames. It can be easily bypassed by the user by editing their local configuration file, and so isn’t designed to act as a defense against malicious users/actors.

      If you want to restrict outbound connections at the network level, the correct way to do this is using firewall rules. This is beyond the scope of this tutorial, but you can check out UFW Essentials: Common Firewall Rules and Commands.

      However, if you want to add some additional fail-safes, then this security control may be of benefit to you.

      It works by using a wildcard rule within your SSH client configuration file to null route all outbound connections, apart from those to specific addresses or hostnames. This means that if you were ever to accidentally mistype a server address, or attempt to connect to a server that you’re not supposed to, the request would be stopped immediately, giving you the opportunity to realize your mistake and take corrective action.

      You can apply this at either the system-level (/etc/ssh/ssh_config) or using your local user configuration file (~/.ssh/config). In this example, we will use the local user configuration file.

      Begin by opening the file, creating it if it doesn’t already exist:

      At the bottom of the file, add the following content, substituting in your own list of allowed IP addresses and hostnames:

      ~/.ssh/config

      Match host !203.0.113.1,!192.0.2.1,!server1.your-domain,!github.com,*
        Hostname localhost
      

      You must prefix IP addresses or hostnames with an exclamation point (!), and use commas to separate each item in the list. The final list item should be a single asterisk (*) without a prefixed exclamation point.

      If you’re running an SSH server on your machine too, you may wish to use a hostname value other than localhost, as this will cause the null routed connections to be sent to your own local SSH server, which could be counterproductive or confusing. Any nullrouted hostname is acceptable, such as null, do-not-use, or disallowed-server.

      Save and close the file once you’ve made your changes.

      You can now test that the configuration is working by attempting to connect to a disallowed destination using your SSH client. For example:

      • ssh disallowed.your-domain

      If the configuration is working properly, you will immediately receive an error similar to the following:

      Output

      Cannot connect to localhost: connection refused

      However, when you attempt to connect to an allowed destination, the connection will succeed as normal.

      In this final step, you implemented some additional fail-safes to help protect against human error and mistakes when using your SSH client.

      Conclusion

      In this article you reviewed your OpenSSH client configuration and implemented various hardening measures.

      This will have improved the security of your outgoing SSH connections, as well as helping to ensure that your local configuration files cannot be accidentally or maliciously modified by other users.

      You may wish to review the manual pages for OpenSSH client and its associated configuration file to identify any potential further tweaks that you want to make:

      Finally, if you want to harden OpenSSH at the server side too, check out How To Harden OpenSSH on Ubuntu 18.04.



      Source link

      How To Harden OpenSSH on Ubuntu 18.04


      The author selected the Electronic Frontier Foundation Inc to receive a donation as part of the Write for DOnations program.

      Introduction

      Linux servers are often administered remotely using SSH by connecting to an OpenSSH server, which is the default SSH server software used within Ubuntu, Debian, CentOS, FreeBSD, and most other Linux/BSD-based systems.

      OpenSSH server is the server side of SSH, also known as SSH daemon or sshd. You can connect to an OpenSSH server using the OpenSSH client—the ssh command. You can learn more about the SSH client-server model in SSH Essentials: Working with SSH Servers, Clients, and Keys. Properly securing your OpenSSH server is very important, as it acts as the front door or entry into your server.

      In this tutorial, you will harden your OpenSSH server by using different configuration options to ensure that remote access to your server is as secure as possible.

      Prerequisites

      To complete this tutorial, you will need:

      Once you have this ready, log in to your server as your non-root user to begin.

      Step 1 — General Hardening

      In this first step, you will implement some initial hardening configurations to improve the overall security of your SSH server.

      The exact hardening configuration that is most suitable for your own server depends heavily on your own threat model and risk threshold. However, the configuration you’ll use in this step is a general secure configuration that will suit the majority of servers.

      Many of the hardening configurations for OpenSSH you implement using the standard OpenSSH server configuration file, which is located at /etc/ssh/sshd_config. Before continuing with this tutorial, it is recommended to take a backup of your existing configuration file, so that you can restore it in the unlikely event that something goes wrong.

      Take a backup of the file using the following command:

      • sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

      This will save a backup copy of the file to /etc/ssh/sshd_config.bak.

      Before editing your configuration file, you can review the options that are currently set. To do this, run the following command:

      This will run OpenSSH server in extended test mode, which will validate the full configuration file and print out the effective configuration values.

      You can now open the configuration file using your favorite text editor to begin implementing the initial hardening measures:

      • sudo nano /etc/ssh/sshd_config

      Note: The OpenSSH server configuration file includes many default options and configurations. Depending on your existing server configuration, some of the recommended hardening options may already have been set.

      When editing your configuration file, some options may be commented out by default using a single hash character (#) at the start of the line. In order to edit these options, or have the commented option be recognized, you’ll need to uncomment them by removing the hash.

      Firstly, disable logging in via SSH as the root user by setting the following option:

      sshd_config

      PermitRootLogin no
      

      This is massively beneficial, as it will prevent a potential attacker from logging in directly as root. It also encourages good operational security practices, such as operating as a non-privileged user and using sudo to escalate privileges only when absolutely needed.

      Next, you can limit the maximum number of authentication attempts for a particular login session by configuring the following:

      sshd_config

      MaxAuthTries 3
      

      A standard value of 3 is acceptable for most setups, but you may wish to set this higher or lower depending on your own risk threshold.

      If required, you can also set a reduced login grace period, which is the amount of time a user has to complete authentication after initially connecting to your SSH server:

      sshd_config

      LoginGraceTime 20
      

      The configuration file specifies this value in seconds.

      Setting this to a lower value helps to prevent certain denial-of-service attacks where multiple authentication sessions are kept open for a prolonged period of time.

      If you have configured SSH keys for authentication, rather than using passwords, disable SSH password authentication to prevent leaked user passwords from allowing an attacker to log in:

      sshd_config

      PasswordAuthentication no
      

      As a further hardening measure related to passwords, you may also wish to disable authentication with empty passwords. This will prevent logins if a user’s password is set to a blank or empty value:

      sshd_config

      PermitEmptyPasswords no
      

      In the majority of use cases, SSH will be configured with public key authentication as the only in-use authentication method. However, OpenSSH server also supports many other authentication methods, some of which are enabled by default. If these are not required, you can disable them to further reduce the attack surface of your SSH server:

      sshd_config

      ChallengeResponseAuthentication no
      KerberosAuthentication no
      GSSAPIAuthentication no
      

      If you’d like to know more about some of the additional authentication methods available within SSH, you may wish to review these resources:

      X11 forwarding allows for the display of remote graphical applications over an SSH connection, but this is rarely used in practice. It is recommended to disable it if it isn’t needed on your server:

      sshd_config

      X11Forwarding no
      

      OpenSSH server allows connecting clients to pass custom environment variables, that is, to set a $PATH or to configure terminal settings. However, like X11 forwarding, these are not commonly used, so can be disabled in most cases:

      sshd_config

      PermitUserEnvironment no
      

      If you decide to configure this option, you should also make sure to comment out any references to AcceptEnv by adding a hash (#) to the beginning of the line.

      Next, you can disable several miscellaneous options related to tunneling and forwarding if you won’t be using these on your server:

      sshd_config

      AllowAgentForwarding no
      AllowTcpForwarding no
      PermitTunnel no
      

      Finally, you can disable the verbose SSH banner that is enabled by default, as it shows various information about your system, such as the operating system version:

      sshd_config

      DebianBanner no
      

      Note that this option most likely won’t already be present in the configuration file, so you may need to add it manually. Save and exit the file once you’re done.

      Now validate the syntax of your new configuration by running sshd in test mode:

      If your configuration file has a valid syntax, there will be no output. In the event of a syntax error, there will be an output describing the issue.

      Once you’re satisfied with your configuration file, you can reload sshd to apply the new settings:

      In this step, you completed some general hardening of your OpenSSH server configuration file. Next, you’ll implement an IP address allowlist to further restrict who can log in to your server.

      Step 2 — Implementing an IP Address Allowlist

      You can use IP address allowlists to limit the users who are authorized to log in to your server on a per-IP address basis. In this step, you will configure an IP allowlist for your OpenSSH server.

      In many cases, you will only be logging on to your server from a small number of known, trusted IP addresses. For example, your home internet connection, a corporate VPN appliance, or a static jump box or bastion host in a data center.

      By implementing an IP address allowlist, you can ensure that people will only be able to log in from one of the pre-approved IP addresses, greatly reducing the risk of a breach in the event that your private keys and/or passwords are leaked.

      Note: Please take care in identifying the correct IP addresses to add to your allowlist, and ensure that these are not floating or dynamic addresses that may regularly change, for example as is often seen with consumer internet service providers.

      You can identify the IP address that you’re currently connecting to your server with by using the w command:

      This will output something similar to the following:

      Output

      14:11:48 up 2 days, 12:25, 1 user, load average: 0.00, 0.00, 0.00 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT your_username pts/0 203.0.113.1 12:24 1.00s 0.20s 0.00s w

      Locate your user account in the list and take a note of the connecting IP address. Here we use the example IP of 203.0.113.1

      In order to begin implementing your IP address allowlist, open the OpenSSH server configuration file in your favorite text editor:

      • sudo nano /etc/ssh/sshd_config

      You can implement IP address allowlists using the AllowUsers configuration directive, which restricts user authentications based on username and/or IP address.

      Your own system setup and requirements will determine which specific configuration is the most appropriate. The following examples will help you to identify the most suitable one:

      • Restrict all users to a specific IP address:
      AllowUsers *@203.0.113.1
      
      AllowUsers *@203.0.113.0/24
      
      • Restrict all users to a specific IP address range (using wildcards):
      AllowUsers *@203.0.113.*
      
      • Restrict all users to multiple specific IP addresses and ranges:
      AllowUsers *@203.0.113.1 *@203.0.113.2 *@192.0.2.0/24 *@172.16.*.1
      
      • Disallow all users except for named users from specific IP addresses:
      AllowUsers sammy@203.0.113.1 alex@203.0.113.2<^>
      
      • Restrict a specific user to a specific IP address, while continuing to allow all other users to log in without restrictions:
      Match User ashley
        AllowUsers ashley@203.0.113.1
      

      Warning: Within an OpenSSH configuration file, all configurations under a Match block will only apply to connections that match the criteria, regardless of indentation or line breaks. This means that you must be careful and ensure that configurations intended to apply globally are not accidentally put within a Match block. It is recommended to put all Match blocks at the bottom/end of your configuration file to help avoid this.

      Once you have finalized your configuration, add it to the bottom of your OpenSSH server configuration file:

      sshd_config

      AllowUsers *@203.0.113.1
      

      Save and close the file, and then proceed to test your configuration syntax:

      If no errors are reported, you can reload OpenSSH server to apply your configuration:

      In this step, you implemented an IP address allowlist on your OpenSSH server. Next, you will restrict the shell of a user to limit the commands that they are allowed to use.

      Step 3 — Restricting the Shell of a User

      In this step, you’ll look at the various options for restricting the shell of an SSH user.

      In addition to providing remote shell access, SSH is also great for transferring files and other data, for example, via SFTP. However, you may not always want to grant full shell access to users when they only need to be able to carry out file transfers.

      There are multiple configurations within OpenSSH server that you can use to restrict the shell environment of particular users. For instance, in this tutorial, we will use these to create SFTP-only users.

      Firstly, you can use the /usr/sbin/nologin shell to disable interactive logins for certain user accounts, while still allowing non-interactive sessions to function, like file transfers, tunneling, and so on.

      To create a new user with the nologin shell, use the following command:

      • sudo adduser --shell /usr/sbin/nologin alex

      Alternatively, you can change the shell of an existing user to be nologin:

      • sudo usermod --shell /usr/sbin/nologin sammy

      If you then attempt to interactively log in as one of these users, the request will be rejected:

      This will output something similar to the following message:

      Output

      This account is currently not available.

      Despite the rejection message on interactive logins, other actions such as file transfers will still be allowed.

      Next, you should combine your usage of the nologin shell with some additional configuration options to further restrict the relevant user accounts.

      Begin by opening the OpenSSH server configuration file in your favorite text editor again:

      • sudo nano /etc/ssh/sshd_config

      There are two configuration options that you can implement together to create a tightly restricted SFTP-only user account: ForceCommand internal-sftp and ChrootDirectory.

      The ForceCommand option within OpenSSH server forces a user to execute a specific command upon login. This can be useful for certain machine-to-machine communications, or to forcefully launch a particular program.

      However, in this case, the internal-sftp command is particularly useful. This is a special function of OpenSSH server that launches a basic in-place SFTP daemon that doesn’t require any supporting system files or configuration.

      This should ideally be combined with the ChrootDirectory option, which will override/change the perceived root directory for a particular user, essentially restricting them to a specific directory on the system.

      Add the following configuration section to your OpenSSH server configuration file for this:

      sshd_config

      Match User alex
        ForceCommand internal-sftp
        ChrootDirectory /home/alex/
      

      Warning: As noted in Step 2, within an OpenSSH configuration file, all configurations under a Match block will only apply to connections that match the criteria, regardless of indentation or line breaks. This means that you must be careful and ensure that configurations intended to apply globally are not accidentally put within a Match block. It is recommended to put all Match blocks at the bottom/end of your configuration file to help avoid this.

      Save and close your configuration file, and then test your configuration again:

      If there are no errors, you can then apply your configuration:

      This has created a robust configuration for the alex user, where interactive logins are disabled, and all SFTP activity is restricted to the home directory of the user. From the perspective of the user, the root of the system, that is, /, is their home directory, and they will not be able to traverse up the file system to access other areas.

      You’ve implemented the nologin shell for a user and then created a configuration to restrict SFTP access to a specific directory.

      Step 4 — Advanced Hardening

      In this final step, you will implement various additional hardening measures to make access to your SSH server as secure as possible.

      A lesser-known feature of OpenSSH server is the ability to impose restrictions on a per-key basis, that is restrictions that apply only to specific public keys present in the .ssh/authorized_keys file. This is particularly useful to control access for machine-to-machine sessions, as well as providing the ability for non-sudo users to control the restrictions for their own user account.

      You can apply most of these restrictions at the system or user level too, however it is still advantageous to implement them at the key-level as well, to provide defence-in-depth and an additional failsafe in the event of accidental system-wide configuration errors.

      Note: You can only implement these additional security configurations if you’re using SSH public-key authentication. If you’re only using password authentication, or have a more complex setup such as an SSH certificate authority, unfortunately these will not be usable.

      Begin by opening your .ssh/authorized_keys file in your favorite text editor:

      • nano ~/.ssh/authorized_keys

      Note: Since these configurations apply on a per-key basis, you’ll need to edit each individual key within each individual authorized_keys file that you want them to apply to, for all users on your system. Usually you will only need to edit one key/file, but this is worth considering if you have a complex multi-user system.

      Once you’ve opened your authorized_keys file, you will see that each line contains an SSH public key, which will most likely begin with something like ssh-rsa AAAB.... Additional configuration options can be added to the beginning of the line, and these will only apply to successful authentications against that specific public key.

      The following restriction options are available:

      • no-agent-forwarding: Disable SSH agent forwarding.
      • no-port-forwarding: Disable SSH port forwarding.
      • no-pty: Disable the ability to allocate a tty (i.e. start a shell).
      • no-user-rc: Prevent execution of the ~/.ssh/rc file.
      • no-X11-forwarding: Disable X11 display forwarding.

      You can apply these to disable specific SSH features for specific keys. For example, to disable agent forwarding and X11 forwarding for a key, you would use the following configuration:

      ~/.ssh/authorized_keys

      no-agent-forwarding,no-X11-forwarding ssh-rsa AAAB...
      

      By default, these configurations work using an “allow by default, block by exception” methodology; however, it is also possible to use “block by default, allow by exception,” which is generally preferable for ensuring security.

      You can do this by using the restrict option, which will implicitly deny all SSH features for the specific key, requiring them to be explicitly re-enabled only where absolutely needed. You can re-enable features using the same configuration options described earlier in this tutorial, but without the no- prefix.

      For example, to disable all SSH features for a particular key, apart from X11 display forwarding, you can use the following configuration:

      ~/.ssh/authorized_keys

      restrict,X11-forwarding ssh-rsa AAAB...
      

      You may also wish to consider using the command option, which is very similar to the ForceCommand option described in Step 3. This doesn’t provide a direct benefit if you’re already using ForceCommand, but it is good defense-in-depth to have it in place, just in the unlikely event that your main OpenSSH server configuration file is overwritten, edited, and so on.

      For example, to force users authenticating against a specific key to execute a specific command upon login, you can add the following configuration:

      ~/.ssh/authorized_keys

      command="top" ssh-rsa AAAB...
      

      Warning: The command configuration option acts purely as a defense-in-depth method, and should not be solely relied on to restrict the activities of an SSH user, as there are potentially ways to override or bypass it depending on your environment. Instead, you should use the configuration in tandem with the other controls described in this article.

      Finally, to best use the per-key restrictions for the SFTP-only user that you created in Step 3, you can use the following configuration:

      ~/.ssh/authorized_keys

      restrict,command="false" ssh-rsa AAAB...
      

      The restrict option will disable all interactive access, and the command="false" option acts as a second line of defense in the event that the ForceCommand option or nologin shell were to fail.

      Save and close the file to apply the configuration. This will take effect immediately for all new logins, so you don’t need to reload OpenSSH manually.

      In this final step, you implemented some additional advanced hardening measures for OpenSSH server by using the custom options within your .ssh/authorized_keys file(s).

      Conclusion

      In this article, you reviewed your OpenSSH server configuration and implemented various hardening measures to help secure your server.

      This will have reduced the overall attack surface of your server by disabling unused features and locking down the access of specific users.

      You may wish to review the manual pages for OpenSSH server and its associated configuration file, to identify any potential further tweaks that you want to make.



      Source link