CVE-2022-29180: Server-Side Request Forgery (SSRF) (CWE-918)
value: A vulnerability in which attackers could forge HTTP requests to manipulate the `charm` data directory to access or delete anything on the server. This has been patched and is available in release [v0.12.1](https://github.com/charmbracelet/charm/releases/tag/v0.12.1). We recommend that all users running self-hosted `charm` instances update immediately. This vulnerability was found in-house and we haven't been notified of any potential exploiters. ### Additional notes * Encrypted user data uploaded to the Charm server is safe as Charm servers cannot decrypt user data. This includes filenames, paths, and all key-value data. * Users running the official Charm [Docker images](https://github.com/charmbracelet/charm/blob/main/docker.md) are at minimal risk because the exploit is limited to the containerized filesystem.
CWE description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
CVSS v3 base score: 9.8
Published: 2022-05-07T04:15Z
Repository: https://github.com/charmbracelet/charm
Language: Go
Files changed: server/http.go
Commit date: 2022-05-06 04:52:29 +0200
Commit message: fix: clean path before accessing file store

Diff: diff --git a/server/http.go b/server/http.go
index ea6e0e5..73ae459 100644
--- a/server/http.go
+++ b/server/http.go
@@ -8,10 +8,11 @@ import (
 	"io"
 	"io/fs"
 	"io/ioutil"
 	"log"
 	"net/http"
+	"path/filepath"
 	"strconv"
 	"strings"
 
 	charmfs "github.com/charmbracelet/charm/fs"
 	charm "github.com/charmbracelet/charm/proto"
@@ -277,11 +278,11 @@ func (s *HTTPServer) handlePostSeq(w http.ResponseWriter, r *http.Request) {
 	_ = json.NewEncoder(w).Encode(&charm.SeqMsg{Seq: seq})
 }
 
 func (s *HTTPServer) handlePostFile(w http.ResponseWriter, r *http.Request) {
 	u := s.charmUserFromRequest(w, r)
-	path := pattern.Path(r.Context())
+	path := filepath.Clean(pattern.Path(r.Context()))
 	ms := r.URL.Query().Get("mode")
 	m, err := strconv.ParseUint(ms, 10, 32)
 	if err != nil {
 		log.Printf("file mode not a number: %s", err)
 		s.renderError(w)
@@ -314,11 +315,11 @@ func (s *HTTPServer) handlePostFile(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
 func (s *HTTPServer) handleGetFile(w http.ResponseWriter, r *http.Request) {
 	u := s.charmUserFromRequest(w, r)
-	path := pattern.Path(r.Context())
+	path := filepath.Clean(pattern.Path(r.Context()))
 	f, err := s.cfg.FileStore.Get(u.CharmID, path)
 	if errors.Is(err, fs.ErrNotExist) {
 		s.renderCustomError(w, "file not found", http.StatusNotFound)
 		return
 	}
@@ -351,11 +352,11 @@ func (s *HTTPServer) handleGetFile(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
 func (s *HTTPServer) handleDeleteFile(w http.ResponseWriter, r *http.Request) {
 	u := s.charmUserFromRequest(w, r)
-	path := pattern.Path(r.Context())
+	path := filepath.Clean(pattern.Path(r.Context()))
 	err := s.cfg.FileStore.Delete(u.CharmID, path)
 	if err != nil {
 		log.Printf("cannot delete file: %s", err)
 		s.renderError(w)
 		return

Vulnerable code: 	path := pattern.Path(r.Context())
	path := pattern.Path(r.Context())
	path := pattern.Path(r.Context())
Fixed code: 	"path/filepath"
	path := filepath.Clean(pattern.Path(r.Context()))
	path := filepath.Clean(pattern.Path(r.Context()))
	path := filepath.Clean(pattern.Path(r.Context()))