fix(api): prevent path traversal in ScreenShot endpoint

Anchor the token regex to ^[A-Za-z0-9]+$ so partial matches on
traversal strings like "../../etc/passwd" are rejected. Refactor
the handler to use early returns so execution stops immediately
on validation failure instead of falling through to os.Create
with tainted input.
This commit is contained in:
Houmgaor
2026-02-18 14:10:45 +01:00
parent e353906e1c
commit 898ada3d99

View File

@@ -330,83 +330,77 @@ func (s *APIServer) ScreenShotGet(w http.ResponseWriter, r *http.Request) {
} }
} }
func (s *APIServer) ScreenShot(w http.ResponseWriter, r *http.Request) { func (s *APIServer) ScreenShot(w http.ResponseWriter, r *http.Request) {
// Create a struct representing the XML result
type Result struct { type Result struct {
XMLName xml.Name `xml:"result"` XMLName xml.Name `xml:"result"`
Code string `xml:"code"` Code string `xml:"code"`
} }
// Set the Content-Type header to specify that the response is in XML format
w.Header().Set("Content-Type", "text/xml")
result := Result{Code: "200"}
if !s.erupeConfig.Screenshots.Enabled {
result = Result{Code: "400"}
} else {
if r.Method != http.MethodPost { writeResult := func(code string) {
result = Result{Code: "405"} w.Header().Set("Content-Type", "text/xml")
} xmlData, err := xml.Marshal(Result{Code: code})
// Get File from Request
file, _, err := r.FormFile("img")
if err != nil { if err != nil {
result = Result{Code: "400"} http.Error(w, "Unable to marshal XML", http.StatusInternalServerError)
} return
var tokenPattern = regexp.MustCompile(`[A-Za-z0-9]+`)
token := r.FormValue("token")
if !tokenPattern.MatchString(token) || token == "" {
result = Result{Code: "401"}
}
// Validate file
img, _, err := image.Decode(file)
if err != nil {
result = Result{Code: "400"}
}
safePath := s.erupeConfig.Screenshots.OutputDir
path := filepath.Join(safePath, fmt.Sprintf("%s.jpg", token))
verified, err := verifyPath(path, safePath)
if err != nil {
result = Result{Code: "500"}
} else {
_, err = os.Stat(safePath)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(safePath, os.ModePerm)
if err != nil {
s.logger.Error("Error writing screenshot, could not create folder")
result = Result{Code: "500"}
}
} else {
s.logger.Error("Error writing screenshot")
result = Result{Code: "500"}
}
}
// Create or open the output file
outputFile, err := os.Create(verified)
if err != nil {
result = Result{Code: "500"}
}
defer func() { _ = outputFile.Close() }()
// Encode the image and write it to the file
err = jpeg.Encode(outputFile, img, &jpeg.Options{Quality: s.erupeConfig.Screenshots.UploadQuality})
if err != nil {
s.logger.Error("Error writing screenshot, could not write file", zap.Error(err))
result = Result{Code: "500"}
}
} }
w.WriteHeader(http.StatusOK)
_, _ = w.Write(xmlData)
} }
// Marshal the struct into XML
xmlData, err := xml.Marshal(result) if !s.erupeConfig.Screenshots.Enabled {
if err != nil { writeResult("400")
http.Error(w, "Unable to marshal XML", http.StatusInternalServerError)
return return
} }
// Write the XML response with a 200 status code if r.Method != http.MethodPost {
w.WriteHeader(http.StatusOK) writeResult("405")
_, _ = w.Write(xmlData) return
}
var tokenPattern = regexp.MustCompile(`^[A-Za-z0-9]+$`)
token := r.FormValue("token")
if !tokenPattern.MatchString(token) {
writeResult("401")
return
}
file, _, err := r.FormFile("img")
if err != nil {
writeResult("400")
return
}
img, _, err := image.Decode(file)
if err != nil {
writeResult("400")
return
}
safePath := s.erupeConfig.Screenshots.OutputDir
path := filepath.Join(safePath, fmt.Sprintf("%s.jpg", token))
verified, err := verifyPath(path, safePath)
if err != nil {
writeResult("500")
return
}
if err := os.MkdirAll(safePath, os.ModePerm); err != nil {
s.logger.Error("Error writing screenshot, could not create folder", zap.Error(err))
writeResult("500")
return
}
outputFile, err := os.Create(verified)
if err != nil {
s.logger.Error("Error writing screenshot, could not create file", zap.Error(err))
writeResult("500")
return
}
defer func() { _ = outputFile.Close() }()
if err := jpeg.Encode(outputFile, img, &jpeg.Options{Quality: s.erupeConfig.Screenshots.UploadQuality}); err != nil {
s.logger.Error("Error writing screenshot, could not write file", zap.Error(err))
writeResult("500")
return
}
writeResult("200")
} }