Skip to content

Commit 17b89fb

Browse files
authored
updated style
1 parent d4d6347 commit 17b89fb

1 file changed

Lines changed: 85 additions & 72 deletions

File tree

Lines changed: 85 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
# CROSS-SITE SCRIPTING IN MINIFLUX
22

3-
### Introduction:
3+
## Introduction
44

55
Website applications often need to respond to a requested action based on input provided by a user. If that response contains a copy of the user’s input without proper neutralization, then a dangerous attack known as Cross-Site Scripting (XSS) may be possible. The underlying weakness that leads to XSS is annually one of the CWE™ Top 25 Most Dangerous Software Weaknesses, ranking at #2 in 2023 and #1 in 2024. In 2023, such a weakness was discovered in Miniflux, a web feed reader. This case study will examine the weakness, the resulting vulnerability, what it allowed an adversary to accomplish, and how the issue was eventually mitigated.
66

7-
### Software:
7+
## Software
88

99
**Name:** Miniflux
1010
**Language:** Go
11-
**URL:** https://github.com/miniflux/v2
11+
**URL:** <https://github.com/miniflux/v2>
1212

13-
### Weakness:
13+
## Weakness
1414

15-
<a href="https://cwe.mitre.org/data/definitions/79.html">CWE-79: Improper Neutralization of Input During Web Page Generation</a>
15+
[CWE-79: Improper Neutralization of Input During Web Page Generation](https://cwe.mitre.org/data/definitions/79.html)
1616

1717
The weakness exists when a web application fails to properly neutralize user-controlled input in the web application's code, and the input is then used as part of a response to a user's request.
1818

1919
There are three main kinds of cross-site scripting (XSS): reflected, stored, and DOM-based. This case study will focus on stored XSS, which is when a web application reads potentially dangerous input that was stored in a server-side location. This dangerous input is then read back into the application at some future time and included in a response that is sent back to the user. For example, an attacker could leave a comment on a forum that contains a malicious script. This comment is stored in the forum's back-end database, and then retreived anytime a user views the forum page.
2020

21-
### Vulnerability:
21+
## Vulnerability
2222

23-
<a href="https://www.cve.org/CVERecord?id=CVE-2023-27592">CVE-2023-27592</a> - Published 17 March 2023
23+
[CVE-2023-27592](https://www.cve.org/CVERecord?id=CVE-2023-27592) - Published 17 March 2023
2424

2525
Miniflux is a feed reader that supports Really Simple Syndication (RSS). Subscribing to an RSS feed can allow users to keep up to date on many different websites in a central hub without having to check each website manually. An RSS feed item is a single entry in the RSS feed that corresponds to an update from a website the feed is pulling from. Miniflux displays RSS feed items from a user's RSS subcscriptions in a central hub for users to view.
2626

@@ -36,36 +36,40 @@ A CSP is a set of instructions that a web browser follows to restrict actions th
3636

3737
Looking at the proxy.go code, the vulnerable section begins at line 85 which is triggered if the Miniflux server encounters an error while processing the request. On line 86, the Miniflux server sends an HTTP Internal Server Error response to the user by calling the function html.ServerError, which includes an addition to the error log and builds the entry that will be added to the user's Miniflux inbox.
3838

39-
vulnerable file: miniflux/v2/internal/ui/proxy.go
40-
41-
23 func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
42-
...
43-
85 if err != nil {
44-
86 html.ServerError(w, r, err)
45-
87 return
46-
88 }
47-
...
48-
118 }
39+
```
40+
vulnerable file: miniflux/v2/internal/ui/proxy.go
41+
42+
23 func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
43+
...
44+
85 if err != nil {
45+
86 html.ServerError(w, r, err)
46+
87 return
47+
88 }
48+
...
49+
118 }
50+
```
4951

5052
Note that the ServerError function in html.go does not add a CSP header to the Miniflux entry that is built. As a result, when the mediaProxy function returns on line 87 above, the entry that is added to the user's Miniflux inbox has no restrictions on what JavaScript can be embedded in any media (like an image) within the RSS feed item, despite the user seeing an HTTP 500 Iternal Server Error.
5153

52-
vulnerable file: miniflux/v2/internal/http/response/html/html.go
53-
54-
23 // ServerError sends an internal error to the client.
55-
24 func ServerError(w http.ResponseWriter, r *http.Request, err error) {
56-
25 logger.Error("[HTTP:Internal Server Error] %s => %v", r.URL, err)
57-
26
58-
27 builder := response.New(w, r)
59-
28 builder.WithStatus(http.StatusInternalServerError)
60-
29 builder.WithHeader("Content-Type", "text/html; charset=utf-8")
61-
30 builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
62-
31 builder.WithBody(err)
63-
32 builder.Write()
64-
33 }
54+
```
55+
vulnerable file: miniflux/v2/internal/http/response/html/html.go
56+
57+
23 // ServerError sends an internal error to the client.
58+
24 func ServerError(w http.ResponseWriter, r *http.Request, err error) {
59+
25 logger.Error("[HTTP:Internal Server Error] %s => %v", r.URL, err)
60+
26
61+
27 builder := response.New(w, r)
62+
28 builder.WithStatus(http.StatusInternalServerError)
63+
29 builder.WithHeader("Content-Type", "text/html; charset=utf-8")
64+
30 builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
65+
31 builder.WithBody(err)
66+
32 builder.Write()
67+
33 }
68+
```
6569

66-
### Exploit:
70+
## Exploit
6771

68-
<a href="https://capec.mitre.org/data/definitions/63.html">CAPEC-63: Cross-Site Scripting</a>
72+
[CAPEC-63: Cross-Site Scripting](https://capec.mitre.org/data/definitions/63.html)
6973

7074
To exploit this vulnerability, an adversary starts by convincing a user to subscribe to an adversary-controlled RSS feed via Miniflux. Then, the adversary can craft an item for this RSS feed that contains an inline description with an <img> tag and a srcset attribute that uses an invalid URL that contains malicious JavaScript. An example RSS feed item is provided below.
7175

@@ -85,69 +89,78 @@ To exploit this vulnerability, an adversary starts by convincing a user to subsc
8589

8690
Notice the JavaScript contained in the srcset attribute. If the Miniflux user is subscribed to the adversary's RSS feed, the code in the mediaProxy function will reach the vulnerable section and add this RSS feed item to the user's Miniflux inbox. When the web browser loads the broken image, the malicious JavaScript is executed in the context of the victim Miniflux user. Actions will be performed on the Miniflux instance as the victim. If the victim is an administrator, administrative access to the Miniflux instance can be achieved. Actions that can be taken include but are not limited to the manipulation or theft of site cookies, a compromise of confidential information, the disclosure of end user files, the installation of Trojan horse programs, or redirection of the user to another site.
8791

88-
### Fix:
92+
## Fix
8993

9094
To resolve this issue the source code was modified to remove the call to html.ServerError and thus prevent the Miniflux inbox entry from being built, replacing it with a logger error and an HTTP error on lines 86 and 87.
9195

92-
fixed file: miniflux/v2/internal/ui/proxy.go
93-
94-
23 func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
95-
...
96-
85 if err != nil {
97-
86 logger.Error(`[Proxy] Unable to initialize HTTP client: %v`, err)
98-
87 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
99-
88 return
100-
89 }
101-
...
102-
119 }
96+
```diff
97+
fixed file: miniflux/v2/internal/ui/proxy.go
98+
99+
23 func (h *handler) mediaProxy(w http.ResponseWriter, r *http.Request) {
100+
...
101+
85 if err != nil {
102+
-86 html.ServerError(w, r, err)
103+
+86 logger.Error(`[Proxy] Unable to initialize HTTP client: %v`, err)
104+
+87 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
105+
88 return
106+
89 }
107+
...
108+
119 }
109+
```
103110

104111
Now, the mediaProxy function does not build a Miniflux inbox entry like the earlier example and will instead simply log an error which avoids adding an RSS feed item to the user's inbox when this section of code is executed.
105112

106113
Additionally, a CSP was added to the header on line 29 of html.go to any Miniflux inbox entries that are built when ServerError is called. The CSP sets the default-src to 'self', which means that the browser will only load resources (like images) that originate from Miniflux.
107114

108-
fixed file: miniflux/v2/internal/http/response/html/html.go
109-
110-
23 // ServerError sends an internal error to the client.
111-
24 func ServerError(w http.ResponseWriter, r *http.Request, err error) {
112-
25 logger.Error("[HTTP:Internal Server Error] %s => %v", r.URL, err)
113-
26
114-
27 builder := response.New(w, r)
115-
28 builder.WithStatus(http.StatusInternalServerError)
116-
29 builder.WithHeader("Content-Security-Policy", `default-src 'self'`)
117-
30 builder.WithHeader("Content-Type", "text/html; charset=utf-8")
118-
31 builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
119-
32 builder.WithBody(err)
120-
33 builder.Write()
121-
34 }
115+
```diff
116+
fixed file: miniflux/v2/internal/http/response/html/html.go
117+
118+
23 // ServerError sends an internal error to the client.
119+
24 func ServerError(w http.ResponseWriter, r *http.Request, err error) {
120+
25 logger.Error("[HTTP:Internal Server Error] %s => %v", r.URL, err)
121+
26
122+
27 builder := response.New(w, r)
123+
28 builder.WithStatus(http.StatusInternalServerError)
124+
+29 builder.WithHeader("Content-Security-Policy", `default-src 'self'`)
125+
30 builder.WithHeader("Content-Type", "text/html; charset=utf-8")
126+
31 builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
127+
32 builder.WithBody(err)
128+
33 builder.Write()
129+
34 }
130+
```
122131

123132
Now, even if a Miniflux inbox entry is built with an error response, a CSP will be utilized to defend against XSS attacks.
124133

125-
### Conclusion:
134+
## Prevention
135+
136+
To prevent future issues like this take care when generating error messages and make sure that user provided data is not included within the mesage.
137+
138+
## Conclusion
126139

127140
Improper neutralization of input is a common weakness that annually ranks among the CWE™ Top 25 Most Dangerous Software Weaknesses, ranking #2 in 2023 and #1 in 2024. The weakness can lead to remote code execution and/or the reading of application data. One such weakness led to a vulnerability that was discovered in Miniflux in 2023. In response, Miniflux made changes to ensure that unneutralized input was not added to a user's RSS feed. Instead, an error would be logged, effectively mitigating the root weakness of “Improper Neutralization of Input During Web Page Generation”. Without this weakness, Miniflux can no longer be exploited in a stored XSS attack to execute JavaScript code on the Miniflux instance. Software developers should always follow secure coding practices and ensure any user-controlled input is effectively neutralized to avoid such vulnerabilities in their own projects.
128141

129-
### References:
142+
## References
130143

131-
OSV Vulnerability Report: https://osv.dev/vulnerability/GHSA-mqqg-xjhj-wfgw
144+
OSV Vulnerability Report: <https://osv.dev/vulnerability/GHSA-mqqg-xjhj-wfgw>
132145

133-
Miniflux Code Commit to Fix Issue: https://github.com/miniflux/v2/pull/1746/files
146+
Miniflux Code Commit to Fix Issue: <https://github.com/miniflux/v2/pull/1746/files>
134147

135-
GitHub Advisory Database Entry: https://github.com/miniflux/v2/security/advisories/GHSA-mqqg-xjhj-wfgw
148+
GitHub Advisory Database Entry: <https://github.com/miniflux/v2/security/advisories/GHSA-mqqg-xjhj-wfgw>
136149

137-
Miniflux Landing Page: https://miniflux.app/
150+
Miniflux Landing Page: <https://miniflux.app/>
138151

139-
Miniflux Documentation: https://miniflux.app/docs/configuration.html#proxy-images
152+
Miniflux Documentation: <https://miniflux.app/docs/configuration.html#proxy-images>
140153

141-
CWE-79 Entry: https://cwe.mitre.org/data/definitions/79.html
154+
CWE-79 Entry: <https://cwe.mitre.org/data/definitions/79.html>
142155

143-
Mozilla Documentation on Content Security Policies: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
156+
Mozilla Documentation on Content Security Policies: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP>
144157

145-
Mozilla Documentation on HTTP Status Codes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status
158+
Mozilla Documentation on HTTP Status Codes: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status>
146159

147-
### Contributions:
160+
## Contributions
148161

149-
Originally created by Travis Aldrich - The MITRE Corporation<br>
162+
Originally created by Travis Aldrich - The MITRE Corporation
150163
Reviewed by Drew Buttner - The MITRE Corporation
151164

152-
(C) 2025 The MITRE Corporation. All rights reserved.<br>
153-
This work is openly licensed under <a href="https://creativecommons.org/licenses/by/4.0/">CC-BY-4.0</a>
165+
(C) 2025 The MITRE Corporation. All rights reserved.
166+
This work is openly licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/)

0 commit comments

Comments
 (0)