Skip to content

Commit

Permalink
Add a new plugin for container detection
Browse files Browse the repository at this point in the history
  • Loading branch information
mtardy committed Sep 30, 2022
1 parent 712ecdc commit df47a5e
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 9 deletions.
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ pentesting process.
* [Why another tool?](#why-another-tool)
* [How is this tool built?](#how-is-this-tool-built)
* [Areas for improvement](#areas-for-improvement)
* [How can I experience with this tool?](#how-can-i-experience-with-this-tool)
* [How to experiment with this tool?](#how-to-experiment-with-this-tool)
* [Buckets](#buckets)
* [Admission](#admission)
* [API Resources](#api-resources)
* [Authorization](#authorization)
* [Capabilities](#capabilities)
* [Cgroups](#cgroups)
* [CloudMetadata](#cloudmetadata)
* [ContainerDetect](#containerdetect)
* [Devices](#devices)
* [Environment](#environment)
* [Mount](#mount)
Expand Down Expand Up @@ -72,7 +73,7 @@ page](https://github.com/quarkslab/kdigger/releases).

### Build from source

Just type `make` to build with the
Just type `make` to build with the
[default build target](https://github.com/quarkslab/kdigger/blob/main/Makefile#L20).
```bash
git clone https://github.com/quarkslab/kdigger
Expand Down Expand Up @@ -332,11 +333,11 @@ format via array lines does not fit all the use cases perfectly but is simple to
generalize without having each plugin to implement their format. The tool also
proposes a JSON output format.

### How can I experience with this tool?
### How to experiment with this tool?

Good news! We created a mini Kubernetes CTF with basic steps to experience with
the tool and resolve quick challenges. For more information go to the
[minik8s-ctf repository](https://github.com/quarkslab/minik8s-ctf).
Good news! We created a mini Kubernetes CTF with basic steps to try the tool
and resolve quick challenges. For more information go to the [minik8s-ctf
repository](https://github.com/quarkslab/minik8s-ctf).

## Buckets

Expand Down Expand Up @@ -531,6 +532,20 @@ that's the case, further research, using available endpoints for that cloud, can
be conducted. You can potentially retrieve an authentication token or simply
more metadata to pivot within the cloud account.

### ContainerDetect

ContainerDetect retrieves hints that the process is running inside a typical
container. This bucket follows a discussion on Twitter about detection technics
https://twitter.com/g3rzi/status/1564594977220562945.

For now, it's composed of six hints:
- systemd is not PID 1
- kthreadd is not PID 2
- inode number of root is not 2
- root is an overlay fs
- /etc/fstab is empty
- /boot is empty

### Devices

Devices show the list of devices available in the container. This one is
Expand Down Expand Up @@ -615,7 +630,7 @@ in the cluster.
Note: [This feature was removed](https://github.com/coredns/coredns/pull/5019)
starting as of CoreDNS v1.9.0 because it was mostly used by bad actors (like
this tool). See the associated discussion on [the corresponding Github
issue](https://github.com/coredns/coredns/issues/4984). Kubernetes v1.24 was
issue](https://github.com/coredns/coredns/issues/4984). Kubernetes v1.24 was
still using CoreDNS v1.8.6, but the v1.25 version updated CoreDNS to v1.9.3.
That's why this plugin no longer works on v1.25 and above.

Expand Down
2 changes: 2 additions & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/quarkslab/kdigger/pkg/plugins/capabilities"
"github.com/quarkslab/kdigger/pkg/plugins/cgroups"
"github.com/quarkslab/kdigger/pkg/plugins/cloudmetadata"
"github.com/quarkslab/kdigger/pkg/plugins/containerdetect"
"github.com/quarkslab/kdigger/pkg/plugins/devices"
"github.com/quarkslab/kdigger/pkg/plugins/environment"
"github.com/quarkslab/kdigger/pkg/plugins/mount"
Expand Down Expand Up @@ -94,6 +95,7 @@ func registerBuckets() {
node.Register(buckets)
apiresources.Register(buckets)
cloudmetadata.Register(buckets)
containerdetect.Register(buckets)
}

// printResults prints results with the output format selected by the flags
Expand Down
134 changes: 134 additions & 0 deletions pkg/plugins/containerdetect/containerdetect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package containerdetect

import (
"bytes"
"fmt"
"os"
"syscall"

"github.com/mitchellh/go-ps"
"github.com/quarkslab/kdigger/pkg/bucket"
"github.com/quarkslab/kdigger/pkg/plugins/mount"
)

// this bucket follows a discussion on twitter
// https://twitter.com/g3rzi/status/1564594977220562945

const (
bucketName = "containerdetect"
bucketDescription = "ContainerDetect retrieves hints that the process is running inside a typical container."
)

var bucketAliases = []string{"container", "cdetect"}

type Bucket struct{}

func (n Bucket) Run() (bucket.Results, error) {
res := bucket.NewResults(bucketName)
res.SetHeaders([]string{"hint", "result"})

// there is a systemd with pid 1 on the host
systemdFirstPID, err := isProcessPID("systemd", 1)
if err != nil {
return *res, err
}
res.AddContent([]interface{}{"systemd is not PID 1", !systemdFirstPID})

// there is a kthreadd with pid 2 on the host
kthreadSecondPID, err := isProcessPID("kthreadd", 2)
if err != nil {
return *res, err
}
res.AddContent([]interface{}{"kthreadd is not PID 2", !kthreadSecondPID})

// the inode of root on the host should be 2
root, err := os.Stat("/")
if err != nil {
return *res, err
}
if stat, ok := root.Sys().(*syscall.Stat_t); ok {
res.AddContent([]interface{}{"inode number of root is not 2", !(stat.Ino == 2)})
}

// root as an overlay filesystem might imply running in a container
mnts, err := mount.Mounts()
if err != nil {
return *res, err
}
isRootOverlayFS := false
for _, mnt := range mnts {
if mnt.Filesystem == "overlay" && mnt.Path == "/" {
isRootOverlayFS = true
}
}
res.AddContent([]interface{}{"root is an overlay fs", isRootOverlayFS})

// /etc/fstab might be empty in a container
isFstabEmpty, err := isFstabEmpty()
if err != nil {
return *res, err
}
res.AddContent([]interface{}{"/etc/fstab is empty", isFstabEmpty})

// /boot might be empty in a container
isBootEmpty, err := isBootFolderEmpty()
if err != nil {
return *res, err
}
res.AddContent([]interface{}{"/boot is empty", isBootEmpty})

res.AddComment("A majority of true hints might imply running in a container.")

return *res, nil
}

func isProcessPID(process string, pid int) (bool, error) {
p, err := ps.FindProcess(pid)
if err != nil {
return false, fmt.Errorf("failed to find process %d: %w", pid, err)
}
if p != nil && p.Executable() == process {
return true, nil
}
return false, nil
}

func isFstabEmpty() (bool, error) {
file, err := os.ReadFile("/etc/fstab")
if err != nil {
return false, err
}
lines := bytes.Split(file, []byte("\n"))
for _, line := range lines {
if len(line) != 0 && line[0] != '#' {
return false, nil
}
}
return true, nil
}

func isBootFolderEmpty() (bool, error) {
files, err := os.ReadDir("/boot")
if err != nil {
return false, err
}
return len(files) == 0, nil
}

// Register registers a plugin
func Register(b *bucket.Buckets) {
b.Register(bucket.Bucket{
Name: bucketName,
Description: bucketDescription,
Aliases: bucketAliases,
Factory: func(config bucket.Config) (bucket.Interface, error) {
return NewContainerDetectBucket(config)
},
SideEffects: false,
RequireClient: false,
})
}

func NewContainerDetectBucket(config bucket.Config) (*Bucket, error) {
return &Bucket{}, nil
}
4 changes: 2 additions & 2 deletions pkg/plugins/mount/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var bucketAliases = []string{"mounts", "mn"}
type Bucket struct{}

func (m Bucket) Run() (bucket.Results, error) {
values, err := mounts()
values, err := Mounts()
if err != nil {
return bucket.Results{}, err
}
Expand Down Expand Up @@ -59,7 +59,7 @@ type Mount struct {
Flags string
}

func mounts() ([]Mount, error) {
func Mounts() ([]Mount, error) {
file, err := os.Open(mountPath)
if err != nil {
return nil, err
Expand Down

0 comments on commit df47a5e

Please # to comment.