Category Archives: Programacion

TLS Certificates 101

Over the last couple years I’ve been involved in many projects that require TLS certificates and some other technologies to provide security and establish encryption in transit for network communications. These technologies involve different concepts, protocols and standards such as mTLS, X509, PKI, digital signatures, hmac, symmetric and asymmetric encryption, different cryptographic algorithms etc and can feel very overwhelming especially for people that are new in the topic. 

I decided to write a quick blog post and share some of the lessons I’ve learned over the years. It is my intention to provide some guidance to newcomers when it comes to debugging these kinds of issues.

I don’t consider myself an expert in the whole TLS topic (I’m not a Cryptographer), however I’ve spent considerable time on this to the point that I feel very confident when debugging most issues related to TLS.

I firmly believe an expert is a person who has made so many errors and mistakes in the past and because of that he has accumulated so much knowledge to the point that he knows almost all the answers. I still need to make more 😉

Having said that, let’s start our journey into the TLS world.

What is TLS?

TLS, short for Transport Layer Security, is a cryptographic protocol designed to provide communications security over a computer network.

The TLS protocol is a complex piece of engineering with many security mechanisms and moving parts used to achieve security of data in transit by combining symmetric and asymmetric encryption techniques, if you want to learn more details about this technology I highly recommend to look at the TLS Wikipedia page, also here is an awesome diagram by Wazen Shbair that shows the different steps that happen during the TLS handshake process such as choosing a cipher suite or exchanging a shared key.

There are many different versions for this protocol but for practical purposes the only thing you need to know is:

  • TLS 1.0 and TLS 1.1 are known to be very vulnerable so you should never use them
  • TLS 1.2 is considered to be much more secure and is recommended to use
  • TLS 1.3 is an improved version of TLS 1.2 in terms of performance

What are TLS Certificates?

Excellent, now that you know TLS is the technology behind TLS certificates you may be wondering, what are TLS certificates?.

A TLS certificate is an implementation of a X509 Certificate, X509 is a standard that defines the file format used to store information related to an entity (among other things). This is very important because the main purpose of a certificate is to provide an identity to an entity, an entity can be anything, such as a website domain, a server, a piece of software, a workstation, a laptop or even a person. Similar to real life, people (entities) have birth certificates and driver’s licenses that prove who they are, this documents are backed up by government institution that we all trust (well … kinda), if you ask someone to prove their identity that person will probably show you their ID and if the ID looks damaged or you think there’s something phishy you can ask for additional forms of identification until you are convinced that you can trust them.

A TLS certificate will looks like this:

The TLS certificate contains many different fields like:

  • Subject name: the entity name, person name, website domain, etc
  • Issuer name: the authority that issued the certificate
  • Period of validity: the certificate is not valid before or after this dates
  • Additional cryptographic information and digital signatures

When an application (like your web browser) connects to a website by typing the IP address or the domain, ie: www.alevsk.com, the server behind will return a TLS certificate, the browser will then look at it and decide what to do next (exchange keys and establish a secure connection) based on the fields above.

As you can see, it is the client’s responsibility to verify these TLS certificates, the server may offer a perfectly fine certificate and the client could still choose to throw it away. With that said, I decided to write this blog post to share my experience debugging server applications during countless hours just to find the issue was on the client side or in the certificate itself, customers will swear the server is broken and not working when in reality it was their client not trusting the certificate authority or the clock their was misconfigured.

But before doing that I want to show you a couple of examples of TLS certificates being used on some applications, for that I have prepared a couple of code snippets.

Suppose you are running a simple web server written in go like this.

package main

import (
  "fmt"
  "net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
  secret := req.URL.Query().Get("secret")
  fmt.Fprintf(w, fmt.Sprintf("pong %s\n", secret))
}

func main() {
  address := "0.0.0.0:8080"
  http.HandleFunc("/ping", hello)
  fmt.Println("Starting server on", address)
  if err := http.ListenAndServe(address, nil); err != nil {
     return
  }
}

Let’s query the server running on port 8080 via CURL.

curl http://localhost:8080/ping\?secret\=eaeae
pong eaeae

With curl I’m performing a GET request and the server is replying fine, notice how I’m passing a secret via the secret parameter in the URL, everything looks good so far but there’s a problem.

This is an insecure web server, hence all the traffic going from the client (curl command) to the server (Go application) can be captured and the secret can be retrieved in plain text.

Let’s fix this by adding a TLS certificate to this web server, but first, how do we get one? Well, there are two types of certificates: self-signed certificates and certificates issued/signed by a certificate authority.

A certificate authority is an entity that stores, signs, and issues digital certificates. Going back to the society metaphor, it will be the equivalent to a government institution that many people trust. 

  • Self-signed certificate: ID document that you crafted yourself and its not valid
  • Certificate signed by a certificate authority: birth certificate, drivers license or ID document that is backed up by the government institution
  • Certificate authority: The government institution that most people trust by default

Using a TLS certificate signed by a certificate authority has many advantages that I’m going to discuss in some other blog post, for now it’s enough to say that you have to pay in most cases to get one of those but it’s worth it. Now let’s generate some certificates.

Tools for playing with TLS certificates

I’ll focus on self-signed certificates for this example and I’m going to show you how to generate them using three different tools.

Mkcert

Mkcert is a tool created by Filippo Valsorda. Mkcert is a simple zero-config tool to make locally trusted development certificates with any names you’d like, you can download it and installed following the instructions directly from this Github repository https://github.com/FiloSottile/mkcert

mkcert "localhost"

#Created a new certificate valid for the following names 📜
# - "localhost"

#The certificate is at "./localhost.pem" and the key at #"./localhost-key.pem" ✅

#It will expire on 14 December 2024 🗓

Certgen

Certgen is a tool created by MinIO, the startup I currently work with. Certgen is a dead simple tool to generate self signed certificates, you can download it and installed following the instructions directly from this Github repository https://github.com/minio/certgen

certgen --host localhost
#2022/09/14 21:58:54 wrote public.crt
#2022/09/14 21:58:54 wrote private.key

OpenSSL 

OpenSSL is a tool that doesn’t require any introduction, it has been part of the TLS arsenal of system administrators and network engineers for decades, if you wish to use openssl to generate certificates the process is a little bit more manual than with the tools above but still is fairly simple to use.

openssl genrsa -out private.key 2048

cat >> certificate.cnf << 'END'
[req]
distinguished_name = req_distinguished_name
req_extensions = req_ext
prompt = no

[req_distinguished_name]
O = "http-server"
C = US
CN  = "localhost"

[req_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
END

openssl req -new -config certificate.cnf -key private.key -out certificate.csr

openssl x509 -signkey private.key -in certificate.csr -req -days 365 -out public.crt

#Certificate request self-signature ok
#subject=O = http-server, C = US, CN = localhost

Doesn’t really matter which tools you use to get your certificates as long as they are not malformed. Going back to our Go code I made a couple of changes and the code looks like this now.

package main

import (
  "fmt"
  "net/http"
)

func helloTLS(w http.ResponseWriter, req *http.Request) {
  secret := req.URL.Query().Get("secret")
  fmt.Fprintf(w, fmt.Sprintf("pong %s\n", secret))
}

func main() {
  address := "0.0.0.0:8080"
  http.HandleFunc("/ping", helloTLS)
  fmt.Println("Starting server on", address)

  if err := http.ListenAndServeTLS(address, "public.crt", "private.key", nil); err != nil {
     return
  }
}

The most relevant change is that I’m using the function http.ListenAndServeTLS now which is pretty explanatory. This function allows me to pass two files: the TLS certificate (also called the public key) and the corresponding private key. As mentioned before the TLS certificate contains information about the expiration date, valid domains, digital signature, certificate authority, among other, that will be relevant to the client while the private key will remain secret in the server and be used by the Go application to decrypt encrypted messages sent by the client during the TLS handshake. If you want to learn more about Public Key Cryptography I highly recommend looking at the PKI Wikipedia page.

I’ll run the Go program again and this time I’ll use the https protocol in the curl command.

curl https://localhost:8080/ping\?secret\=eaeae -k
pong eaeae

Before continuing I have to mention, just for the sake of the example and to demonstrate how TLS is securing the communication, I’ve included the -k flag in the curl command, passing this flag will make curl disable all TLS verifications. If you inspect the traffic on wireshark this time you’ll only see the TLS handshake and encrypted data after that, no more parameters in plain text.

TLS certificates

If you are not able to inspect SSL/TLS traffic in wireshark try adding the custom server port under Edit -> Preferences -> Protocols -> HTTP -> SSL/TLS Ports. You can add your custom port, ie: 8080. Change it to: 443,8080.

Debugging TLS certificate issues

Now comes the fun part, and where I’ve “invested” countless hours trying to fix other people’s problems, debugging TLS certificates issues.

If I go back and remove the -k flag from my curl command I get the following output.

curl https://localhost:8080/ping\?secret\=eaeae                                     
#curl: (60) SSL certificate problem: self-signed certificate
#More details here: https://curl.se/docs/sslcerts.html

#curl failed to verify the legitimacy of the server and therefore could not
#establish a secure connection to it. To learn more about this situation and
#how to fix it, please visit the web page mentioned above.

The error message is pretty clear, curl is failing to verify the certificate because this is a self-signed certificate, meaning it cannot be trusted. How does curl know this is a self-signed certificate? 

Well it is very simple, remember when I said there were only two types of certificates? 

There are two fields in the certificate: Subject Name and Issuer Name, if both fields match then this is a self-signed certificate. If they don’t then this is a certificate issued/signed by a certificate authority, that may or may not be trusted by the client.

The solution for this is quite simple, in your client application use the same certificate itself to verify its authenticity, since the certificate is technically its own authority, with curl you need to include the –cacert flag and specify the path to the public.crt used by the server.

curl https://localhost:8080/ping\?secret\=eaeae --cacert public.crt
pong eaeae

The above is one of the most common TLS issues I’ve encountered in the past few years and I hope I did a good job explaining the root cause, how to approach the problem and finally how to find a solution.

Next is a list of the most common TLS issues I’ve seen and some advices on how to debug them:

TLS ErrorDebugging advice
SSL certificate problem: self-signed certificateAsk people to provide you with the public key of the server (or download it yourself with openssl, ie: echo | openssl s_client -servername www.alevsk.com -connect www.alevsk.com:443 | sed -n ‘/^—–BEGIN CERT/,/^—–END CERT/p’ > public.crt) and then pass it with the –cacert flag in the curl command
SSL certificate problem: unable to get local issuer certificateUpdate ca certificates in the client machine (sudo update-ca-certificates) or ask for the ca.crt (certificate authority certificate) and pass it with the –cacert flag in the curl command
X.509 Certificate Signed by Unknown AuthorityClient doesn’t trust the certificate authority that issued the certificate, if you have the ca.crt files you can use openssl to verify the chain of trust: openssl verify -verbose -CAfile root.pem -untrusted intermediate.pem server.pem 
SSL: no alternative certificate subject name matches target host name ‘XXXX’The certificate used by the server is not valid for any of the domains the client is trying to use to connect, are you using an IP address instead of a domain name? Check the whole list of domains inside the certificate and make sure they match to what you are using in your curl command
SSL certificate problem: certificate has expiredMake sure the client and server clocks and correctly configured and in sync
SSL certificate problem: certificate is not yet validMake sure the client and server clocks and correctly configured and in sync
SSL: certificate subject name ‘XXXX’ does not match target host name YYYYThe certificate used by the server is not valid for any of the domains the client is trying to use to connect, are you using an IP address instead of a domain name? Check the whole list of domains inside the certificate and make sure they match to what you are using in your curl command
Public key certificate and private key doesn’t matchPretty explanatory, I’ve seen this happening mostly when people copy a bunch of keypairs around and get confused, you can use openssl to verify this

As I remember more errors and ways of how I approach the problem to find a solution I may add them to the list, most of the errors are very explanatory but for some reason users struggle with them, when it comes to TLS errors they may think it’s some kind of obscure or arcane technology but its not.

Takeaway

As I said before, I’ve spent countless hours debugging this kind of issues, my main advice will be: instead of jumping directly into pulling server/application logs first look as the client side, always use the curl command first for testing, if the customer provide you with some client certificates for mTLS authentication or a ca.crt file and you are not even make them work with curl then definitely the issue is in the client side and not in your application.

  • Pay attention to how the client is referencing the server application, what domain the client is using in the curl command?
  • Make sure the client and server clocks and correctly configured and in sync
  • If the certificate contains wildcards make sure those are valid for the domain the server is using and the client is referencing
  • Make sure the public key and the private key matches, you can use OpenSSL to verify this

Here are some useful TLS certificates debugging tools I use:

Happy hacking

Weak TLS cipher suites

HTTP and HTTPS are well known Internet protocols that don’t require any introduction. The other day at work as part of a daily security scan one of our servers got tagged as using weak cipher suites during TLS negotiation. In this quick post I’ll explain what a weak cipher suite means and how to fix it.

There are many tools out there to check if you are following the security best practices when it comes to SSL/TLS server configuration (supported versions, accepted cipher suites, certificate transparency, expiration, etc.) but one of my favorites is https://www.ssllabs.com/ssltest/analyze.html and drwetter/testssl.sh.

SSLlabs.com is easy to use, you just have to enter a Hostname and the website will analyze all possible TLS configuration and calculate a score for you, this tool will also tell you what you can do to improve that score.

There are many TLS protocol versions: 1.0, 1.1, 1.2, 1.3. The first two are considered insecure and should not be used so I will focus on 1.2 and 1.3 only.
In my case SSLlabs.com was complaining about weak cipher suites were supported for TLS 1.2:

The above report is showing ECDHE-RSA-AES256-SHA384E and ECDHE-RSA-AES256-SHA as weak cipher suites.

First let’s clarify a couple of things, according to Wikipedia:

Cipher suite: A set of algorithms that help secure a network connection.

So a weak cipher suite will be algorithms with known vulnerabilities that can be used by attackers to downgrade connections or other nefarious things.

Fixing this is very easy and will require changing a line or two on the server configuration, deploying the changes and then testing again using SSLlabs.com.

Of course, the above only applies if you know exactly what you are doing, otherwise it will take you many attempts and you are going to waste precious time.

Reproduce and fix the issue locally

Suppose you have the same issue I had, because this issue was reported on an Nginx Server now the task is to reproduce the issue locally and come up with a fix. With modern technologies like docker containers it’s very easy to run a local Nginx server, the only thing you need is a copy of the nginx.conf, to run a docker container the command will be:

docker run --name mynginx -v ./public.pem:/etc/nginx/public.pem -v ./private.pem:/etc/nginx/private.pem -v ./nginx.conf:/etc/nginx/nginx.conf:ro -p 1337:443 --rm nginx

The above command is telling docker to run an Nginx container; binding the local port 1337 to the container port 443 and also mounting the public.pem (public key), private.pem (private key) and nginx.conf (the server configuration) files inside the container.

The nginx.conf looks like this:

    server {
        listen 443 ssl;
        server_name www.alevsk.com;
        ssl_certificate /etc/nginx/public.pem;
        ssl_certificate_key /etc/nginx/private.pem;
        ssl_protocols TLSv1.3 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
        ssl_ciphers EECDH+AESGCM:EECDH+AES256:EECDH+CHACHA20;
        ssl_session_cache shared:TLS:2m;
        ssl_buffer_size 4k;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;
    }

You can test the server is working fine with a simple curl command:

curl https://localhost:1337/ -v -k
*   Trying 127.0.0.1:1337...
* Connected to localhost (127.0.0.1) port 1337 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
....
....
....
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Server: nginx
< Date: Sun, 15 May 2022 23:14:23 GMT
< Content-Type: text/html
< Content-Length: 146
< Connection: keep-alive
< Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
< 
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host localhost left intact

The next step is to reproduce the report from https://www.ssllabs.com/ but that will involve somehow exposing our local Nginx server to the internet and that’s time consuming. Fortunately there’s an amazing open source tool that will help you to run all these TLS tests locally.

drwetter/testssl.sh is a tool for testing TLS/SSL encryption anywhere on any port and the best part is that runs on a container too, go to your terminal again and run the following command:

docker run --rm -ti --net=host drwetter/testssl.sh localhost:1337

The above command will run the drwetter/testssl.sh container, the –rm flag will automatically delete the container once it’s done running (keep your system nice and clean), -ti means interactive mode and –net=host will allow the container to use the parent host network namespace.

After a couple of seconds you will see a similar result as in the website, something like this:

Testing server’s cipher preferences.

Hexcode  Cipher Suite Name (OpenSSL)       KeyExch.   Encryption  Bits     Cipher Suite Name (IANA/RFC)                        
-----------------------------------------------------------------------------------------------------------------------------  
SSLv2                                                                                                                          
 -                                                                                                                             
SSLv3                                                                                                                          
 -                                                                                                                             
TLSv1                                                                                                                          
 -                                                                                                                             
TLSv1.1                                                                                                                        
 -                                                                                                                             
TLSv1.2 (server order)                                                                                                         
 xc030   ECDHE-RSA-AES256-GCM-SHA384       ECDH 521   AESGCM      256      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384               
 xc02f   ECDHE-RSA-AES128-GCM-SHA256       ECDH 521   AESGCM      128      TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256               
 xc028   ECDHE-RSA-AES256-SHA384           ECDH 521   AES         256      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384               
 xc014   ECDHE-RSA-AES256-SHA              ECDH 521   AES         256      TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA                  
 xcca8   ECDHE-RSA-CHACHA20-POLY1305       ECDH 521   ChaCha20    256      TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256         
TLSv1.3 (server order)                                        
 x1302   TLS_AES_256_GCM_SHA384            ECDH 256   AESGCM      256      TLS_AES_256_GCM_SHA384                              
 x1303   TLS_CHACHA20_POLY1305_SHA256      ECDH 256   ChaCha20    256      TLS_CHACHA20_POLY1305_SHA256                        
 x1301   TLS_AES_128_GCM_SHA256            ECDH 256   AESGCM      128      TLS_AES_128_GCM_SHA256

Even on this test we see the weak cipher suites (ECDHE-RSA-AES256-SHA384 and ECDHE-RSA-AES256-SHA).

Great, now you are able to fully reproduce the issue locally and test as many times as you want until you have the perfect configuration. It’s time to do the actual fix.

Open the nginx.conf file one more time and locate the line that starts with ssl_ciphers and just add !ECDHE-RSA-AES256-SHA384:!ECDHE-RSA-AES256-SHA at the end, ie:

 server {
        listen 443 ssl;
        server_name www.alevsk.com;
        ssl_certificate /etc/nginx/public.pem;
        ssl_certificate_key /etc/nginx/private.pem;
        ssl_protocols TLSv1.3 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
        ssl_ciphers EECDH+AESGCM:EECDH+AES256:EECDH+CHACHA20:!ECDHE-RSA-AES256-SHA384:!ECDHE-RSA-AES256-SHA;
        ssl_session_cache shared:TLS:2m;
        ssl_buffer_size 4k;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload' always;
    }

If you run the Nginx container with the new configuration and then run the drwetter/testssl.sh test again this time you will see no weak cipher suites anymore.

TLSv1.2 (server order)                                                                                                         
 xc030   ECDHE-RSA-AES256-GCM-SHA384       ECDH 521   AESGCM      256      TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384              
 xc02f   ECDHE-RSA-AES128-GCM-SHA256       ECDH 521   AESGCM      128      TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256              
 xcca8   ECDHE-RSA-CHACHA20-POLY1305       ECDH 521   ChaCha20    256      TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256        
TLSv1.3 (server order)          
 x1302   TLS_AES_256_GCM_SHA384            ECDH 256   AESGCM      256      TLS_AES_256_GCM_SHA384                              
 x1303   TLS_CHACHA20_POLY1305_SHA256      ECDH 256   ChaCha20    256      TLS_CHACHA20_POLY1305_SHA256                        
 x1301   TLS_AES_128_GCM_SHA256            ECDH 256   AESGCM      128      TLS_AES_128_GCM_SHA256

Conclusion

Congratulations, you just fixed your first security engineer issue and now you can push the fix to production. In general when it comes to fixing any kind of problem in tech it is better to start by reproducing the issue locally and work on a fix from there (of course this is debatable if you are facing an issue that only happens in a specific environment). SSL/TLS  and cipher suites are one of those technologies that you have to learn by heart or at least have a very good understanding if you want to work in application security, but not only that, once you understand it it will completely change the way you approach problems and debug security applications .

Happy hacking.

Compilation of open-source security tools & platforms for your Startup

This compilation of open-source tools aim to provide resources you can use for some of the step of the secure development life cycle of your organization, ie:

  1. Security Training
  2. Security Architecture Review
  3. Security Requirements
  4. Threat Modeling
  5. Static Analysis
  6. OpenSource Analysis
  7. Dynamic Analysis
  8. Penetration Testing

If you think I should add a new tool to the list you can open a Github issue or send a PR directly.

User management

Secret management

IDS, IPS, Firewalls and Host/Network monitoring

Data visualization

Web Application Firewall

Object Storage

VPN

Security training platforms

Static analysis tools

Dynamic analysis tools

Misc

Stop passing secrets via environment variables to your application

docker inspect command showing container secrets

Environment variables are great to configure and change the behavior of your applications, however there’s a downside for that, if someone uses the `docker inspect` command your precious secrets will get revealed there, because of that you should never pass any sensitive data to your container using environment variables (the `-e` flag), I’ll show you an example.

Suppose you have a simple python application (Download the source code of the app here: https://github.com/Alevsk/docker-containers-env-vars-security ) that returns the hmac signature for a provided message using a configured secret, the code will look like this:

app_secret = os.environ.get('APP_SECRET')
if app_secret is None:
    app_secret = ""

# derive key based on configured APP_SECRET
salt = binascii.unhexlify('aaef2d3f4d77ac66e9c5a6c3d8f921d1')
secret = app_secret.encode("utf8")
key = pbkdf2_hmac("sha256", secret, salt, 4096, 32)

app = Flask(__name__)

@app.route("/")
def hello():
    message = request.args.get('message')
    if message is None:
        return "Give me a message and I'll sign it for you"
    else:
        h = hmac.new(key, message.encode(), hashlib.sha256)
        return "<b>Original message:</b> {}<br/><b>Signature:</b> {}".format(message, h.hexdigest())

if __name__ == "__main__":
    app.run()

Pretty straightforward, then you build the docker image with:

docker build -t alevsk/app-env-vars:latest .

And run the application on a container with:

docker run --rm --name hmac-signature-service -p 5000:5000 -e APP_SECRET=mysecret alevsk/app-env-vars:latest

Test the endpoints works fine running a `curl` command:

curl http://localhost:5000/?message=eaeae

<b>Original message:</b> eaeae<br/><b>Signature:</b> cce4625d3d586470bc84ac088b6e2ae24c012944832d54ab42a999de94252849%

Perfect, everything works as intended, however if you inspect the running container the content of `APP_SECRET` is leaked.

docker inspect hmac-signature-service
    ..
    ...
    "Env": [
        "APP_SECRET=mysecret",
        "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "LANG=C.UTF-8",
        "GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568",
        "PYTHON_VERSION=3.9.9",
        "PYTHON_PIP_VERSION=21.2.4",
        "PYTHON_SETUPTOOLS_VERSION=57.5.0",
        "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.py",
        "PYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309"
    ],
    ...
    ..

Additionally, you can get the `process id` of the app inside the container (`1869799` in this case) and then look at the content of the `/proc/[pid]/environ` file and your application secret will be there too.

docker inspect hmac-signature-service
       ...
       ..
       .
       "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 1869799,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2021-12-14T08:40:24.338541774Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },

pstree -sg 1869799

systemd(1)───containerd-shim(1869776)───gunicorn(1869799)─┬─gunicorn(1869799)
                                                          ├─gunicorn(1869799)
                                                          ├─gunicorn(1869799)
                                                          └─gunicorn(1869799)

sudo cat /proc/1869799/environ

PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=6a9110ff90e1APP_SECRET=mysecretLANG=C.UTF-8GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568PYTHON_VERSION=3.9.9PYTHON_PIP_VERSION=21.2.4PYTHON_SETUPTOOLS_VERSION=57.5.0PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.pyPYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309HOME=/root%

Mount secret file to the container and read from there instead

You can fix that by doing a small change in the application source code, mainly how the application reads the secret, this time instead of reading from the `APP_SECRET` environment variable the app will read from a file located at `/tmp/app/secret`.

secret_path = "/tmp/app/secret"
app_secret = open(secret_path).readline().rstrip() if os.path.exists(secret_path) else ""

Build the docker image and run the container mounting the secret file.

docker build -t alevsk/app-env-vars:latest .
docker run --rm --name hmac-signature-service -p 5000:5000 -v $(pwd)/secret:/tmp/app/secret alevsk/app-env-vars:latest

The secret file looks like this:

mysecret

Try `curl` again:

curl http://localhost:5000/?message=eaeae

<b>Original message:</b> eaeae<br/><b>Signature:</b> cce4625d3d586470bc84ac088b6e2ae24c012944832d54ab42a999de94252849%

The generated signature looks good, if you inspect the container you will not see the secret used by the application.

docker inspect hmac-signature-service
    ..
    ...
    "Env": [
        "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "LANG=C.UTF-8",
        "GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568",
        "PYTHON_VERSION=3.9.9",
        "PYTHON_PIP_VERSION=21.2.4",
        "PYTHON_SETUPTOOLS_VERSION=57.5.0",
        "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.py",
        "PYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309"
    ],
    ...
    ..

Also, if you exec into the container by running `docker exec -it hmac-signature-service sh` the `APP_SECRET` environment variable is not there, nor in the `/proc/1/environ` file or in the `printenv` command output.

cat /proc/1/environ

PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=afe80d4cafcaLANG=C.UTF-8GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568PYTHON_VERSION=3.9.9PYTHON_PIP_VERSION=21.2.4PYTHON_SETUPTOOLS_VERSION=57.5.0PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.pyPYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309HOME=/root

printenv

HOSTNAME=afe80d4cafca
PYTHON_PIP_VERSION=21.2.4
SHLVL=1
HOME=/root
GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.py
TERM=xterm
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=C.UTF-8
PYTHON_VERSION=3.9.9
PYTHON_SETUPTOOLS_VERSION=57.5.0
PWD=/app
PYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309

What if I cannot change the source code of my application?

In case you don’t have access or cannot change the source code of the application not all is lost This time, instead of passing the `APP_SECRET` environment variable via docker `-e` flags, you will mount a `secret` file directly into the container and then modify the container entry point to source from that secret file.

The `secret` file will contain something like this:

export APP_SECRET="mysecret"

Launch the container like this:

docker run --rm --name hmac-signature-service -p 5000:5000 -v $(pwd)/secret:/tmp/app/secret --entrypoint="sh" alevsk/app-env-vars:latest -c "source /tmp/app/secret && gunicorn -w 4 -b 0.0.0.0:5000 main:app"

This will prevent the `APP_SECRET` environment variable to be displayed if someone runs the `docker inspect` command.

docker inspect hmac-signature-service
    ..
    ...
    "Env": [
        "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "LANG=C.UTF-8",
        "GPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568",
        "PYTHON_VERSION=3.9.9",
        "PYTHON_PIP_VERSION=21.2.4",
        "PYTHON_SETUPTOOLS_VERSION=57.5.0",
        "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.py",
        "PYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309"
    ],
    ...
    ..

However `APP_SECRET` will still be visible inside `/proc/[container-parent-pid]/environ` (requires to be inside the container or root privileges on the machine running the container)

Takeaway

Environment variables are great but the risk of leaking secrets is just not worth it, if you give access to the docker command to somebody on your system that person can pretty much look what’s running inside the container by running the docker inspect command (having docker access is equivalent to have root access on the system anyways), because of this reason it’s preferable that applications read configurations and secrets directly from files and to leverage on the OS file system permissions.

Download the source code of the app here: https://github.com/Alevsk/docker-containers-env-vars-security

Happy hacking

Just enough cryptography for better securing your apps

I’m not a cryptographer myself but I have always admired their work because literally they make the Internet a better place by creating technology that allows us our right to privacy and cybersecurity plus I enjoy playing basic crypto CTF challenges. At my current job I’m a weird mixture between Software developer and Information Security guy (finally the best of two worlds) that means I work a lot with security and crypto related matters and I’m also very fortunate for  being able to work very close to a real cryptographer, so a couple months ago we were talking about security and I asked him if he could share some resources about cryptography but focusing on Software Engineers, meaning people without a heavy background in mathematics, this is what I learned.

If you are a Software Engineer curious about Information Security chances are you have crossed paths with a task that involves adding some kind of security mechanisms to protect data in your application, my friend explained me that in practice, cryptography is about choosing the right tool for the job and as a Security Software Engineer the most common tasks you will face are:

  • Encrypt a data blob or data stream
  • Exchange a secret key with a peer
  • Verify that some data blob or data stream is not modified
  • Verify that some data blob or data stream has been produced by someone specific.
  • Generate a secret key from another secret key
  • Generate a secret key from a (low-entropy) value – e.g. password

There are out there many cryptographic mechanisms that will make our lives easier when it comes to software engineering and we need to learn how to pick the right tool for the job

I’m not saying you should completely ignore the theory and jump directly into the practice, theory is important and you should learn it or at least be aware of the different types of cryptographic primitives, the most important classes/types are:

  • Pseudo-Random Permutation (PRP) – e.g. AES
  • Pseudo-Random Function (PRF) – e.g. ChaCha
  • Random Oracle (RO) – e.g. SHA-256

Modern cryptographic algorithms usually follow a more theory-based approach when it comes to achieve its goals and test its security, they usually do that by reducing the security relative to the primitives they use, it’s very common to read things like: 

The scheme X achieves the security goals A, B and C if the underlying primitive Y is in-fact a K.

Primitives usually have a condition in the form of a mathematical proof or very hard problems to solve e.g. prime-factor representation for RSA asymmetric primitive or the discrete logarithm problem for the Diffie-Hellman key exchange algorithm, therefore If you want to break a cryptographic scheme you will first need to break the assumptions used by its primitives, if no one can do that then it’s safe to assume the cryptographic scheme is secure.

My point here is you need to understand what are the goals you want to achieve first, what is your requirement, that’s the only way you can pick the right cryptographic scheme based on the primitives that will solve your problem.

Key Derivation

Let’s say you have an application that calculates the hmac-sha256 signature of a message using the password provided by a user as a key:

This works but there’s a problem with this approach, calculate hmac-sha256 signatures its trivial, with the help of a good dictionary an attacker can easily brute force the user password and if he succeed on obtaining the original secret he can impersonate the user in your application

Therefore, in order to make the job of the attacker more expensive in terms of time, computation and memory resources, the recommended approach is to use a Key-derivation function (KDF) or password-based key derivation function (PBKDF) when deriving a key from a password 

When I learned this concept from my friend it was mind-blowing, In general you have to distinguish between deriving a secret key from a high-entropy source, like a cryptographic key, or from a low-entropy source, like a password. PBKDF usage is about trade-offs, try to hit a parameter such that the PBKDF is relatively cheap to compute for you in your scenario but expensive for the attacker that tries to brute-force the secret password.

func deriveKey(key []byte, salt []byte) string {
	derivedKey := pbkdf2.Key(key, salt, 4096, 32, sha1.New)
	return base64.StdEncoding.EncodeToString(derivedKey)
}

func main() {
	message := []byte("hello world")
	key := []byte("super secret key")
	fmt.Println(string(key), deriveKey(key, message))
	// super secret key UJS9n/48gSEHyVK8UZPcC6vKGpsyI6mNrWexmvdtCB4=
}

Data Integrity

Preserving data integrity is a crucial part when working with information, the easiest way to achieve data integrity is by encrypting the data, however sometimes you don’t want to do that because encrypting and decrypting data it’s an expensive operation and you only want to preserve data integrity, then the most straightforward solution is HMAC with a RO – like SHA-256.

func computeHmac256(message []byte, key []byte) string {
	h := hmac.New(sha256.New, key)
	h.Write(message)
	return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

func main() {
	message := []byte("hello world")
	key := []byte("super secret key")
	messageHmac := computeHmac256(message, key)
	fmt.Println(string(message), messageHmac)
	// hello world yFgx2zBmFCpq9N6JuGAMRnTBN5cUwTkHBtqcAyGR2bw=
}

Encryption

Symmetric cryptographic schemes are better for encrypting a data blob or data stream vs asymmetric schemes due to performance advantages, the recommendation is to use Authenticated Encryption (AE) or Authenticated Encryption with Associated Data (AEAD). There are two main AEAD schemes used in practice: AES-GCM and ChaCha20-Poly1305. Both belong to the same class of cryptographic objects: AEAD.

func encryptAES_GCM(key []byte, message []byte) string {
	block, _ := aes.NewCipher(key)
	nonce := make([]byte, 12)
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		panic(err.Error())
	}
	aesgcm, err := cipher.NewGCM(block)
	if err != nil {
		panic(err.Error())
	}
	ciphertext := aesgcm.Seal(nil, nonce, message, nil)
	return base64.StdEncoding.EncodeToString(ciphertext)
}

func main() {
	message := []byte("hello world")
	key := []byte("super secret key")
	fmt.Println(string(message), encryptAES_GCM(key, message))
	// hello world gBoJuHdAm5PjNGFdr+B/8Eq58IFZKcrzz6JQ
}

Github example: https://gist.github.com/Alevsk/0c296f230279bd399a244d4f7d1d7b84

Happy hacking 🙂