Skip to content
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
78 changes: 78 additions & 0 deletions builtins/find/builtin_find_gtfobins_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2026-present Datadog, Inc.

package find_test

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/DataDog/rshell/builtins/testutil"
"github.com/DataDog/rshell/interp"
)

func findGTFORun(t *testing.T, script, dir string) (string, string, int) {
t.Helper()
return testutil.RunScript(t, script, dir, interp.AllowedPaths([]string{dir}))
}

// --- GTFOBins validation ---

// TestFindGTFOBinsExecShellBlocked verifies that the GTFOBins shell-escape
// technique for find (-exec /bin/sh) is blocked because /bin/sh is not an
// allowed command in the restricted shell.
//
// GTFOBins: https://gtfobins.org/gtfobins/find/
// Technique: find . -exec /bin/sh \; -quit
func TestFindGTFOBinsExecShellBlocked(t *testing.T) {
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "f.txt"), []byte("x"), 0644))
_, stderr, code := findGTFORun(t, `find . -exec /bin/sh \;`, dir)
assert.NotEqual(t, 0, code)
assert.NotEmpty(t, stderr)
}

// TestFindGTFOBinsFprintfBlocked verifies that the GTFOBins file-write
// technique for find (-fprintf) is blocked during expression parsing.
//
// GTFOBins: https://gtfobins.org/gtfobins/find/
// Technique: find . -fprintf /path/to/output %p
func TestFindGTFOBinsFprintfBlocked(t *testing.T) {
dir := t.TempDir()
_, stderr, code := findGTFORun(t, `find . -fprintf /tmp/evil %p`, dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "blocked")
}

// TestFindGTFOBinsDeleteBlocked verifies that -delete is blocked.
//
// GTFOBins: https://gtfobins.org/gtfobins/find/
// Technique: find . -delete
func TestFindGTFOBinsDeleteBlocked(t *testing.T) {
dir := t.TempDir()
_, stderr, code := findGTFORun(t, `find . -delete`, dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "blocked")
}

// TestFindGTFOBinsSandboxEscape verifies that find cannot traverse outside
// the AllowedPaths sandbox.
//
// GTFOBins: https://gtfobins.org/gtfobins/find/
// Technique: find /path/to/secret -name '*'
func TestFindGTFOBinsSandboxEscape(t *testing.T) {
allowed := t.TempDir()
secret := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(secret, "secret.txt"), []byte("secret\n"), 0644))
secretPath := strings.ReplaceAll(secret, `\`, `/`)
_, stderr, code := findGTFORun(t, "find "+secretPath+" -name '*'", allowed)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "find:")
}
17 changes: 17 additions & 0 deletions builtins/grep/builtin_grep_pentest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,20 @@ func TestGrepPentestQuietWithMatch(t *testing.T) {
assert.Equal(t, "", stdout)
assert.Equal(t, "", stderr)
}

// --- GTFOBins validation ---

// TestGrepGTFOBinsFileReadSandboxEscape verifies that the GTFOBins file-read
// technique for grep is blocked by the AllowedPaths sandbox.
//
// GTFOBins: https://gtfobins.org/gtfobins/grep/
// Technique: grep ” /path/to/file
func TestGrepGTFOBinsFileReadSandboxEscape(t *testing.T) {
allowed := t.TempDir()
secret := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(secret, "secret.txt"), []byte("secret data\n"), 0644))
secretPath := strings.ReplaceAll(filepath.Join(secret, "secret.txt"), `\`, `/`)
_, stderr, code := grepRun(t, "grep '' "+secretPath, allowed)
assert.Equal(t, 2, code) // grep uses exit code 2 for errors
assert.Contains(t, stderr, "grep:")
}
17 changes: 17 additions & 0 deletions builtins/strings_cmd/strings_pentest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,20 @@ func TestStringsLargeOffsetDecimal(t *testing.T) {
// Offset 10000000 has 8 digits — fmtOffset must not truncate to 7.
assert.Equal(t, "10000000 findme\n", stdout)
}

// --- GTFOBins validation ---

// TestStringsGTFOBinsFileReadSandboxEscape verifies that the GTFOBins file-read
// technique for strings is blocked by the AllowedPaths sandbox.
//
// GTFOBins: https://gtfobins.org/gtfobins/strings/
// Technique: strings /path/to/file
func TestStringsGTFOBinsFileReadSandboxEscape(t *testing.T) {
allowed := t.TempDir()
secret := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(secret, "secret.txt"), []byte("secret data\n"), 0644))
secretPath := strings.ReplaceAll(filepath.Join(secret, "secret.txt"), `\`, `/`)
_, stderr, code := runStrings(t, "strings "+secretPath, allowed)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "strings:")
}
17 changes: 17 additions & 0 deletions builtins/tests/cut/cut_pentest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,20 @@ func TestCutPentestBareDash(t *testing.T) {
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "cut:")
}

// --- GTFOBins validation ---

// TestCutGTFOBinsFileReadSandboxEscape verifies that the GTFOBins file-read
// technique for cut is blocked by the AllowedPaths sandbox.
//
// GTFOBins: https://gtfobins.org/gtfobins/cut/
// Technique: cut -d "" -f1 /path/to/file
func TestCutGTFOBinsFileReadSandboxEscape(t *testing.T) {
allowed := t.TempDir()
secret := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(secret, "secret.txt"), []byte("secret data\n"), 0644))
secretPath := strings.ReplaceAll(filepath.Join(secret, "secret.txt"), `\`, `/`)
_, stderr, code := cutPentestRun(t, `cut -d '' -f1 `+secretPath, allowed)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "cut:")
}
57 changes: 57 additions & 0 deletions builtins/tests/sed/sed_pentest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,60 @@ func TestPentestDanglingSymlink(t *testing.T) {
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "sed:")
}

// --- GTFOBins validation ---

// TestSedGTFOBinsFileReadSandboxEscape verifies that the GTFOBins file-read
// technique for sed is blocked by the AllowedPaths sandbox.
//
// GTFOBins: https://gtfobins.org/gtfobins/sed/
// Technique: sed ” /path/to/file
func TestSedGTFOBinsFileReadSandboxEscape(t *testing.T) {
allowed := t.TempDir()
secret := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(secret, "secret.txt"), []byte("secret data\n"), 0644))
secretPath := strings.ReplaceAll(filepath.Join(secret, "secret.txt"), `\`, `/`)
_, stderr, code := runScript(t, "sed '' "+secretPath, allowed, interp.AllowedPaths([]string{allowed}))
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "sed:")
}

// TestSedGTFOBinsShellEscapeBlocked verifies that the GTFOBins shell-escape
// technique for sed (the 'e' command) is blocked.
//
// GTFOBins: https://gtfobins.org/gtfobins/sed/
// Technique: sed -n '1e exec sh 1>&0' /dev/null
func TestSedGTFOBinsShellEscapeBlocked(t *testing.T) {
dir := pentestDir(t, map[string]string{"f.txt": "test\n"})
_, stderr, code := cmdRun(t, `sed 'e' f.txt`, dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "blocked")
}

// TestSedGTFOBinsFileWriteBlocked verifies that the GTFOBins file-write
// technique for sed (the 'w' command) is blocked.
//
// GTFOBins: https://gtfobins.org/gtfobins/sed/
// Technique: sed -n "s/.*/$data/w /path/to/output" /dev/null
func TestSedGTFOBinsFileWriteBlocked(t *testing.T) {
dir := pentestDir(t, map[string]string{"f.txt": "test\n"})
_, stderr, code := cmdRun(t, `sed 'w /tmp/evil' f.txt`, dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "blocked")
}

// TestSedGTFOBinsInPlaceBlocked verifies that the GTFOBins in-place edit
// technique for sed (-i flag) is blocked.
//
// GTFOBins: https://gtfobins.org/gtfobins/sed/
// Technique: sed -i ” /path/to/file
func TestSedGTFOBinsInPlaceBlocked(t *testing.T) {
dir := pentestDir(t, map[string]string{"f.txt": "hello\n"})
_, stderr, code := cmdRun(t, `sed -i 's/hello/bye/' f.txt`, dir)
assert.NotEqual(t, 0, code)
assert.Contains(t, stderr, "sed:")
// Verify file was NOT modified.
data, err := os.ReadFile(filepath.Join(dir, "f.txt"))
require.NoError(t, err)
assert.Equal(t, "hello\n", string(data))
}
17 changes: 17 additions & 0 deletions builtins/uniq/uniq_pentest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,20 @@ func TestUniqPentestBinaryContent(t *testing.T) {
assert.Equal(t, 0, code)
assert.Equal(t, "\xfc\x80\x80\n", stdout)
}

// --- GTFOBins validation ---

// TestUniqGTFOBinsFileReadSandboxEscape verifies that the GTFOBins file-read
// technique for uniq is blocked by the AllowedPaths sandbox.
//
// GTFOBins: https://gtfobins.org/gtfobins/uniq/
// Technique: uniq /path/to/file
func TestUniqGTFOBinsFileReadSandboxEscape(t *testing.T) {
allowed := t.TempDir()
secret := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(secret, "secret.txt"), []byte("secret data\n"), 0644))
secretPath := strings.ReplaceAll(filepath.Join(secret, "secret.txt"), `\`, `/`)
_, stderr, code := runScript(t, "uniq "+secretPath, allowed, interp.AllowedPaths([]string{allowed}))
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "uniq:")
}
Loading