CVE-2023-28370 - Open Redirect Vulnerability in Tornado (What You Should Know, How It Works, and How To Fix)
A serious security bug, CVE-2023-28370, was found in the Python web framework Tornado. Versions 6.3.1 and earlier let attackers use a specially crafted link to redirect visitors to a malicious website. This can easily lead to phishing attacks, where users believe they're logging in to a trusted site, but actually hand over passwords to hackers.
In this post, I'll explain how this bug works, show you sample exploit code, and help you patch your Tornado server.
What's an Open Redirect?
An "open redirect" is when a website lets users control where they're sent after clicking a link or logging in. If the site doesn’t properly check the destination, attackers can send victims to phishing pages or fake login forms, making phishing attacks very easy.
Result: Victim can be sent to any site, including phishing and malware sites
For the official CVE entry, see:
➡️ MITRE CVE-2023-28370
➡️ GitHub Advisory
➡️ Tornado 6.3.2 release notes
How Does the Vulnerability Work?
Many Tornado apps use handlers like RequestHandler.get_argument("next") to allow users to be sent to the right page after login. These parameters are typically named next or redirect.
Attackers can make URLs like
https://evil.com/phish" rel="nofollow">https://trusted-site.com/login?next=https://evil.com/phish
If the server just redirects the user without checking if the next value is valid, anyone clicking the link could end up at the attacker's page after logging in.
Here's a snippet showing the risky redirect pattern
import tornado.web
class LoginHandler(tornado.web.RequestHandler):
def get(self):
next_url = self.get_argument("next", "/")
self.render("login.html", next_url=next_url)
def post(self):
# Assume user verified, skip for demo
next_url = self.get_argument("next", "/")
self.redirect(next_url) # <-- VULNERABLE LINE
Suppose a user is sent to
https://badguy.com" rel="nofollow">https://mytornadoapp.com/login?next=https://badguy.com
When they log in, they're instantly redirected to https://badguy.com — the attacker's page.
You, as an attacker, send a phishing link like
https://phishingsite.com" rel="nofollow">https://mytornadoapp.com/login?next=https://phishingsite.com
They log in on the real site.
3. The server redirects them straight to https://phishingsite.com.
Here's a minimal "playable" PoC based on Tornado 6.3.1
import tornado.ioloop
import tornado.web
class LoginHandler(tornado.web.RequestHandler):
def get(self):
self.write('''
<form action="/login" method="post">
<input name="next" value="{}" type="hidden" />
<input name="username" /><input name="password" type="password" />
<input type="submit" />
</form>
'''.format(self.get_argument("next", "/")))
def post(self):
next_url = self.get_argument("next", "/")
self.redirect(next_url) # Open Redirect flaw
def make_app():
return tornado.web.Application([
(r"/login", LoginHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Try:
https://evil.com" rel="nofollow">http://localhost:8888/login?next=https://evil.com
After submitting the form, you’ll be sent to https://evil.com.
How to Patch and Mitigate
The official fix was released in Tornado 6.3.2.
Upgrade your project packages
pip install --upgrade tornado
If you can't upgrade right away, add a check before doing the redirect, such as
from urllib.parse import urlparse
def is_safe_url(url, allowed_hosts):
parsed = urlparse(url)
return not parsed.netloc or parsed.netloc in allowed_hosts
class LoginHandler(tornado.web.RequestHandler):
def post(self):
next_url = self.get_argument("next", "/")
if not is_safe_url(next_url, allowed_hosts=["mytornadoapp.com"]):
next_url = "/"
self.redirect(next_url)
Or, redirect only to local paths, never full URLs
if next_url.startswith("/"):
self.redirect(next_url)
else:
self.redirect("/")
More Reading and References
- Original Tornado security advisory
- Common Open Redirect Patterns and Fixes (OWASP)
- Tornado Documentation
- NIST NVD CVE-2023-28370 summary
In Closing
CVE-2023-28370 is a textbook example of why input validation is critical for web apps. If you're building anything in Tornado, be sure to use the latest version and double-check all places where user input can affect redirects!
Timeline
Published on: 05/25/2023 10:15:00 UTC
Last modified on: 06/01/2023 13:04:00 UTC