Using eBPF to enhance security, a simple use case

ยท

4 min read

Using eBPF to enhance security, a simple use case

In this blog we are going to explore a POC that I did with eBPF . I have a basic express server which has a route /env which exposes a secret key, I restricted access to that route with the help of ePBF

Environment setup

Used VirtualBox to run a Debina image (12.5.0) and setup a lab.

Now once the virtual OS was running, installed the below dependencies for eBPF development.

sudo apt-get update 

sudo apt-get install linux-headers-$(uname -r)

sudo apt-get install llvm clang

sudo apt-get install bpfcc-tools libbpfcc-dev libbpf-dev

sudo aptitude -y install bpftool

to install node

sudo apt-get install node npm

Application server

now that the environment is setup it's time to write some code let's start with the express server.


const express = require('express')
const app = express()
app.use(express.json())

app.all('/',(req,res)=>{
    res.status(200).json({'message' : "hello there !"})
})

app.get('/env',(req,res)=>{
    return res.status(200).json({'key':'secret'});
})

app.listen(3001,()=>{
    console.log( --- server started on 3001 ----)
})

the server has two routes one is the base route the other one is /env which exposes a secret key.

now with the help of eBPF we will try to block request to the /env path on our machine.

injecting eBPF code

In the eBPF code logic what I am going to do is check every http packet on the xdp data path, if the request path contains /env drop the packet else let it through.

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <bpf/bpf_helpers.h>

// Define IPPROTO_TCP if not already defined
#ifndef IPPROTO_TCP
#define IPPROTO_TCP 6
#endif

static int strlen(const char *str) {
    int len = 0;
    while (*str != '\0') {
        len++;
        str++;
    }
    return len;
}

static int strncmp(const char *s1, const char *s2, __u64 len) {
    for (__u64 i = 0; i < len; i++) {
        if (s1[i] != s2[i])
            return s1[i] - s2[i];
        if (s1[i] == '\0')
            return 0;
    }
    return 0;
}

SEC("xdp")
int hello(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;


    struct ethhdr *eth = data;

    if (data + sizeof(*eth) > data_end)
        return XDP_PASS;

    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = data + sizeof(struct ethhdr);

    if (data + sizeof(struct ethhdr) + sizeof(struct iphdr) > data_end)
        return XDP_PASS;

    if (ip->protocol != IPPROTO_TCP)
        return XDP_PASS;

    struct tcphdr *tcp = data + sizeof(struct ethhdr) + (ip->ihl << 2);

    if (data + sizeof(struct ethhdr) + (ip->ihl << 2) + sizeof(struct tcphdr) > data_end)
        return XDP_PASS;

    // Pointer to the start of TCP payload
    void *tcp_payload = data + sizeof(struct ethhdr) + (ip->ihl << 2) + (tcp->doff << 2);
    void *tcp_payload_end = data_end;

    // HTTP request line for the specific path
    char *request_line = "GET /env HTTP/1.1";
    int request_line_len = strlen(request_line);
    bpf_printk("%s",tcp_payload);
    // Check if the HTTP request line matches the specific path
    if (tcp_payload + request_line_len <= tcp_payload_end &&
        strncmp(tcp_payload, request_line, request_line_len) == 0) {
        // Match found, drop the packet
        bpf_printk("Match found, dropping packet\n");
        return XDP_DROP;
    }
    bpf_printk("Match now found, allow packet\n");
    // No match found, allow the packet to pass through
    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";

now to compile the code with the makefile below

TARGETS = hello

all: $(TARGETS)
.PHONY: all

$(TARGETS): %: %.bpf.o 

%.bpf.o: %.bpf.c
    clang \
        -target bpf \
        -I/usr/include/$(shell uname -m)-linux-gnu \
        -g \
        -O2 -o $@ -c $<

clean: 
    - rm *.bpf.o
    - rm -f /sys/fs/bpf/hello

after running the makefile ( make hello ) , there will be a object file named , in this case its hello.bpf.c

now attach the eBPF program to the local loopback network interface with the below command

ip link set dev lo xdp obj hello.bpf.o sec xdp

we can see the attached program with bpftool net list

and to monitor the logs so that we can see the inner working of the eBPF program

bpftool prog trace log

now if we hit the server on base url with we can see the logs on the terminal.

now if I hit the /env

we can see that the packets are blocked.

to detach the program

bpftool net detach xdp dev lo

It was fun experiment .

and I know ....

David Brandt, the Ohio farmer behind the 'honest work' meme ...

until next time ๐Ÿ™‹๐Ÿฝโ€โ™‚๏ธ.

ย