Open Redirect (Bypass Of #59d7c660-744c-4fee-88b7-6117b6846aea) in sissbruecker/linkding
Reported on
Mar 26th 2022
Description
Hello everyone,
I found an Open Redirect on linkding on remove a bookmark functionality, it is a bypass of a previously submitted report, when users are tricked into visiting the vulnerable link, they will immediately redirected to arbitrary hosts.
Proof of Concept
- Just visit the following link: https://demo.linkding.link/bookmarks/67940/remove?return_url=//evil.com
Impact
Open Redirect is used mostly on phishing campaigns ...
Occurrences
utils.py L100-L104
Now let's see the actual vulnerable code:
def get_safe_return_url(return_url: str, fallback_url: str):
# Use fallback if URL is none or URL is not on same domain
if not return_url or not return_url.startswith('/'):
return fallback_url
return return_url
what happens here is that the return_url variable is checked using startswith('/') method, it's definitely vulnerable as the attacker doesn't need to supply HTTP OR HTTPS in front of a url to redirect a user, he will simply use two forwarded slahes //evil.com and since it starts with a slash / it passes the check and the browser still understand that //evil.com is a URI that he should navigate to ...
Remediation:
I think that using a proper regex should fix the issue, but the better solution in this case is implementing a whitelist of pre-defined values, so the attacker's can't bypass the restriction, for example look for this snippet:
def get_safe_return_url(return_url: str, fallback_url: str):
# Use fallback if URL is none or URL is not on same domain
allowed_values = ['/', 'about', 'home', 'anythingelse']
if not return_url in allowed_values:
return fallback_url
return return_url
so here the if statement will check if the supplied return_url parameter is one of the allowed values, If it's not the fallback_url will be returned.
bookmarks.py L139-L147
The first occurrence of this issue is withing remove() function defined in bookmarks.py view:
def remove(request, bookmark_id: int):
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
bookmark.delete()
return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))
return HttpResponseRedirect(return_url)
as we can see it checks first if the bookmarks is exist and the user is authorized to delete it, it removes it using delete() method. Now he will get the return_url GET parameter and passes it to get_safe_return_url() function which is the actual vulnerable function, after that it uses HttpReponsesRedirect() function to redirect the user.
SECURITY.md
exists
2 years ago
Hi @admin,
I see that this issue didn't awarded a bounty ... the same issue was awarded a bounty in a previous report.
Best Regards,
Moaad
Hello @Moad - seeing as this is quite a unique and rare case, where this report was made after the initial report and is considered to be a duplicate, would you be up for splitting the bounty with the researcher, and we can mark both reports as valid?
Hi @admin, this is one of the kindest replies I encountered every in bug bounty platforms !
No worries, He can enjoy the bounty, I would like to suggest some additional features to the platform like flags, although we have Valid flag, can you please work on other flags like N/A, Informative, Duplicated !
Best Regards,
Mooad
@mdakh404 - we are very grateful for your patience and for your understanding here.
I will zero out the bounties here, and we will treat this report as a duplicate of the other report with the other report being treated as the initial report.
We are actually very soon to release new flags including N/A
, Informative
and Duplicate
as well as Spam
.
If you have any future feature requests, feel free to create an issue on our public roadmap repository, here.