Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Os committed Dec 22, 2021
0 parents commit 67afa92
Show file tree
Hide file tree
Showing 10 changed files with 2,790 additions and 0 deletions.
675 changes: 675 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

305 changes: 305 additions & 0 deletions README.md

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions containerd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"github.com/tidwall/gjson"
"io/ioutil"
"os"
"regexp"
)

// *findContainerdContainers* cycles through our matches and checks to see goes through out matches and checks
// to see if any are containerd containers (overlayfs). If so, it extracts the image name
// and adds it to the record
// this will match entries such as this:
// /run/containerd/io.containerd.runtime.v2.task/k8s.io/dc2c9c214809f506283c917244cd126a9b056ac7274322d12b59c9196d95dd9b/rootfs/app/spring-boot-application.jar
// I originally used the client https://pkg.go.dev/github.com/containerd/containerd but then decided to just parse on the on-disk file to remove the extra dependencies
// To extract the image name:
// # cat /run/containerd/io.containerd.runtime.v2.task/k8s.io/dc2c9c214809f506283c917244cd126a9b056ac7274322d12b59c9196d95dd9b/config.json | jq '.annotations."io.kubernetes.cri.image-name"'
// "ghcr.io/christophetd/log4shell-vulnerable-app:latest"
func findContainerdContainers() {
re := regexp.MustCompile(`\/run\/containerd\/io.containerd.runtime.v2.task\/k8s.io\/(?P<Hash>\S{64})\/`)
for i := range matches {
res := re.FindStringSubmatch(matches[i].fullPath)
if len(res) > 0 {
match := &matches[i]
match.isContainer = true
hash := res[1]

jsonFile, err := os.Open("/run/containerd/io.containerd.runtime.v2.task/k8s.io/" + hash + "/config.json")
if err == nil {
byteValue, _ := ioutil.ReadAll(jsonFile)
imageName := gjson.GetBytes(byteValue, `annotations.io\.kubernetes\.cri\.image-name`)
if imageName.String() != "" {
match.containerImage = imageName.String()
}
}
defer jsonFile.Close()
}
}
}
61 changes: 61 additions & 0 deletions crio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"io/ioutil"
"os"
"os/user"
"path/filepath"
"strings"

"github.com/tidwall/gjson"
)

// *findCrioContainers* will check to see if any of our matches are CRI-O containers and
// will append image-related metadata. If an image is not found, it will add the pod name
// instead
// the default client does not support extracting metadata as mentioned here:
// https://github.com/cri-o/cri-o/issues/3567 so I resorted to parsing the on-disk files
func findCrioContainers() {
var containerImage string
currUser, _ := user.Current()
path := "/var/lib/containers/storage/overlay-containers/"
filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
// suppress errors / ignore files we can't read
if err == nil {
if !info.IsDir() && (filepath.Base(path) == "config.json") {
jsonFile, err := os.Open(path)
if err == nil {
byteValue, _ := ioutil.ReadAll(jsonFile)
rootPath := gjson.GetBytes(byteValue, "root.path")
imageName := gjson.GetBytes(byteValue, `annotations.io\.kubernetes\.cri-o\.ImageName`)
podName := gjson.GetBytes(byteValue, `annotations.io\.kubernetes\.pod\.name`)
// NOTE containers run directly from kubelet do not have a corresponding ImageName. As backup,
// I'll use the pod name if there is no ImageName
if imageName.String() == "" {
containerImage = podName.String()
} else {
containerImage = imageName.String()
}
for i := range matches {
match := &matches[i]
if strings.Contains(match.fullPath, rootPath.String()) {
match.containerImage = containerImage
match.isContainer = true
// check if it is running or not. Requires root + crictl
// we ignore any containers that are not running
if err == nil {
if checkBinary("crictl") && currUser.Username == "root" {
if crictlCheckContainer(match.containerImage) == false {
match.ignore = true
}
}
}
}
}
}
defer jsonFile.Close()
}
}
return nil
})
}
63 changes: 63 additions & 0 deletions docker_aufs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main

import (
"bufio"
"context"

// "github.com/docker/docker/api/types"
"os"
"path/filepath"
"regexp"

"github.com/docker/docker/client"
)

// *findDockerAufsContainers* will map Docker containers using the *aufs* storage driver to the corresponding
// image
// https://stackoverflow.com/questions/47400479/find-to-which-container-or-image-a-docker-aufs-diff-folder-belongs-to
// it will match entries such as: /var/lib/docker/aufs/diff/b3e8f4a721f46384260c55daf33ae52e1026bf130a10bbe3150485a2de32d573/...
func findDockerAufsContainers() {
re := regexp.MustCompile(`\/var\/lib\/docker\/aufs\/diff\/(?P<Hash>\S{64})\/`)
re_path := regexp.MustCompile(`\/var\/lib\/docker\/image\/aufs\/layerdb\/mounts\/(?P<Hash>\S{64})\/mount-id`)
files, err := filepath.Glob("/var/lib/docker/image/aufs/layerdb/mounts/*/mount-id")
if err != nil {
return
}

// 1. iterate over the files
for _, file := range files {
f, err := os.Open(file)
if err == nil {

defer f.Close()
scanner := bufio.NewScanner(f)

// 2. for each file, open and read its contents (I'm not sure if I have to remove a newline or not)
for scanner.Scan() {
// 3. for each file, iterate over the matches
for i := range matches {
res := re.FindStringSubmatch(matches[i].fullPath)
if len(res) > 0 {
match := &matches[i]
match.isContainer = true
hash := res[1]
fileTxt := scanner.Text()
if fileTxt == hash {
res_path := re_path.FindStringSubmatch(file)
if len(res_path) > 0 {
// 4. get container image
cli, err := client.NewClientWithOpts(client.FromEnv)
if err == nil {
container, err := cli.ContainerInspect(context.Background(), res_path[1])
if err == nil {
match.containerImage = container.Config.Image
}
}
}
}
}
}
}
}
}
}
49 changes: 49 additions & 0 deletions docker_overlayfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"context"
"fmt"
"regexp"
"strings"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)

// *findDockerOverContainers* goes through Docker containers backed by the
// overlayfs storage driver and maps the corresponding container image
// this will match entries such as this: /var/lib/docker/overlay2/3839945137d898a38d7c91666d06ca99324d2858667439288cf6978d2829be5d/...
// https://docs.docker.com/storage/storagedriver/overlayfs-driver/
// https://pkg.go.dev/github.com/docker/[email protected]+incompatible/api/types#GraphDriverData
// NOTE:
// - you'll typically have two entries for a single container:
// 1. the merged layer
// 2. the diff layer
// for example:
// /var/lib/docker/overlay2/192768f471818601094bf4edd96d14bfc0e2b178a04a2efd00b2231ad4e46b33/merged/app/spring-boot-application.jar
// /var/lib/docker/overlay2/9e570f0cec8dcff5662a940f205600b541f82bd7d5d9c9bea8975ecb072506f4/diff/app/spring-boot-application.jar
// we'll match just the *merged* layer as this indicates a running container
func findDockerOverlayContainers() {
re := regexp.MustCompile(`\/var\/lib\/docker\/overlay2?\/(?P<Hash>\S{64})\/merged\/`)
cli, err := client.NewClientWithOpts(client.FromEnv)
if err == nil {
for i := range matches {
res := re.FindStringSubmatch(matches[i].fullPath)
if len(res) > 0 {
match := &matches[i]
match.isContainer = true
hash := res[1]
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
if err == nil {
for _, container := range containers {
res, _ := cli.ContainerInspect(context.Background(), container.ID)
// fmt.Sprint is ugly but does the job
if strings.Contains(fmt.Sprint(res.GraphDriver), hash) {
match.containerImage = container.Image
}
}
}
}
}
}
}
41 changes: 41 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module github.com/ossie-git/log4shell_sentinel

go 1.17

require (
github.com/deckarep/golang-set v1.8.0
github.com/docker/docker v20.10.12+incompatible
github.com/fatih/color v1.13.0
github.com/tidwall/gjson v1.12.1
github.com/urfave/cli/v2 v2.3.0
)

require (
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/containerd/containerd v1.5.8 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)
Loading

0 comments on commit 67afa92

Please sign in to comment.