Ce face os.OpenRoot
Când numele fișierelor vin din afară (un URL, o arhivă, un formular), un nume ca ../../etc/passwd sau o legătură simbolică pot face programul să citească sau să scrie în afara directorului dorit (atacul „path traversal”). os.OpenRoot deschide un director ca *os.Root; metodele lui (Open, Create, OpenFile, Mkdir, Remove, Stat, ReadFile, WriteFile și altele) refuză orice cale care iese din director, inclusiv prin legături simbolice.
os.OpenInRoot(dir, nume) este scurtătura pentru un singur fișier: echivalentă cu OpenRoot(dir) urmat de root.Open(nume). Spre deosebire de filepath.IsLocal (verificare doar pe text) și de os.DirFS, Root verifică și legăturile simbolice de pe disc.
Un *os.Root trebuie închis cu Close. root.FS() îl transformă într-un fs.FS.
Parametri
| Parametru | Descriere |
|---|---|
name |
La OpenRoot: directorul care devine rădăcină. La OpenInRoot: fișierul, relativ la dir. |
dir |
La OpenInRoot: directorul din care nu se poate ieși. |
Ce returnează
OpenRoot: un *os.Root și nil, sau o eroare. OpenInRoot: un *os.File sau o eroare.
Exemple
Exemplul 1: Fișiere încărcate, fără ieșire din director
package main
import (
"fmt"
"os"
)
func main() {
os.Mkdir("uploads", 0o755)
root, err := os.OpenRoot("uploads")
if err != nil {
fmt.Println("eroare:", err)
return
}
defer root.Close()
root.Mkdir("poze", 0o755) // și directoarele se creează prin root
for _, nume := range []string{"cv-ana.pdf", "poze/vara.jpg", "../secret.txt", "/etc/passwd"} {
err := root.WriteFile(nume, []byte("..."), 0o644)
fmt.Printf("%-16s %v\n", nume, err)
}
}
Rezultat
cv-ana.pdf <nil>
poze/vara.jpg <nil>
../secret.txt openat ../secret.txt: path escapes from parent
/etc/passwd openat /etc/passwd: path escapes from parent
Exemplul 2: Legătura simbolică spre exterior este refuzată
filepath.IsLocal acceptă numele scurtatura, dar legătura duce în afara directorului. OpenInRoot o refuză.
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
os.Mkdir("public", 0o755)
os.WriteFile("parole.txt", []byte("secret"), 0o600)
os.WriteFile("public/index.html", []byte("<h1>Salut</h1>"), 0o644)
os.Symlink("../parole.txt", "public/scurtatura")
for _, nume := range []string{"index.html", "scurtatura"} {
fmt.Println(nume, "IsLocal:", filepath.IsLocal(nume))
f, err := os.OpenInRoot("public", nume)
if err != nil {
fmt.Println(" OpenInRoot:", err)
continue
}
fmt.Println(" OpenInRoot: deschis")
f.Close()
}
}
Rezultat
index.html IsLocal: true
OpenInRoot: deschis
scurtatura IsLocal: true
OpenInRoot: openat scurtatura: path escapes from parent
Greșeli frecvente
- Te bazezi doar pe
filepath.Cleansaufilepath.Joinca să ții utilizatorii în director. Ele nu știu de legăturile simbolice de pe disc; foloseșteos.Root. - Uiți
root.Close(). Directorul rămâne deschis cât timp rulează programul.
Comenzi înrudite
filepath.IsLocal: Spune dacă o cale rămâne în interiorul directorului curent: nu e absolută, nu e goală și nu urcă cu „..”.os.DirFS: Transformă un director de pe disc într-un fs.FS, sistemul de fișiere abstract din Go.os.Open: Deschide un fișier existent doar pentru citire și returnează un *os.File.filepath.Join: Unește mai multe bucăți într-o cale, cu separatorul corect al sistemului, și curăță rezultatul.