mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
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:
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user