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) {
|
func TestPathTraversalRefused(t *testing.T) {
|
||||||
srv, _ := newTestServer(t)
|
srv, _ := newTestServer(t)
|
||||||
for _, sel := range []string{
|
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)) {
|
if target != base && !strings.HasPrefix(target, base+string(os.PathSeparator)) {
|
||||||
return errResp("forbidden")
|
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)
|
info, err := os.Stat(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue