mirror of
https://github.com/profullstack/agentbbs.git
synced 2026-08-13 22:37:28 +00:00
Merge 671d0c5f9e into ff9eef907d
This commit is contained in:
commit
b11796dd1f
2 changed files with 40 additions and 0 deletions
|
|
@ -159,6 +159,38 @@ func TestHomepageFileAndDir(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSymlinkEscapeRefused(t *testing.T) {
|
||||
srv, dataDir := newTestServer(t)
|
||||
// Create a symlink inside alice's public_html that points to secret.txt
|
||||
// outside the member area. The selector should be refused.
|
||||
link := filepath.Join(dataDir, "users", "alice", "public_html", "leak.txt")
|
||||
if err := os.Symlink(filepath.Join(dataDir, "secret.txt"), link); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := srv.Resolve("/~alice/leak.txt", false, "")
|
||||
if r.Kind == KindText && strings.Contains(r.Text, "TOP SECRET") {
|
||||
t.Fatal("symlink escape: resolved path pointed outside member area but content was served")
|
||||
}
|
||||
// Symlink to a subdirectory should still work.
|
||||
subdir := filepath.Join(dataDir, "users", "alice", "public_html", "sub")
|
||||
if err := os.Mkdir(subdir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
inner := filepath.Join(subdir, "hello.txt")
|
||||
if err := os.WriteFile(inner, []byte("inside"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Symlink inside the area pointing to another path within the area is fine.
|
||||
sym := filepath.Join(dataDir, "users", "alice", "public_html", "ok-link")
|
||||
if err := os.Symlink(inner, sym); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r2 := srv.Resolve("/~alice/ok-link", false, "")
|
||||
if r2.Kind != KindText || !strings.Contains(r2.Text, "inside") {
|
||||
t.Fatalf("symlink within area should be allowed: got %+v", r2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathTraversalRefused(t *testing.T) {
|
||||
srv, _ := newTestServer(t)
|
||||
for _, sel := range []string{
|
||||
|
|
|
|||
|
|
@ -310,6 +310,14 @@ func (s *Server) userTree(prefix, sel, area string) Response {
|
|||
if target != base && !strings.HasPrefix(target, base+string(os.PathSeparator)) {
|
||||
return errResp("forbidden")
|
||||
}
|
||||
// Symlink escape guard: resolve symlinks and re-check the real path stays
|
||||
// within the member's area. A symlink created inside public_html that
|
||||
// points outside would pass the lexical check but leak external content.
|
||||
if resolved, err := filepath.EvalSymlinks(target); err == nil {
|
||||
if resolved != base && !strings.HasPrefix(resolved, base+string(os.PathSeparator)) {
|
||||
return errResp("forbidden")
|
||||
}
|
||||
}
|
||||
|
||||
info, err := os.Stat(target)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue