?
avatar Erftralle
Erftralle
20 Jun 2015

Since I am developing Joomla! extensions (environment Xampp for Windows, Eclipse, Git) I am always proceeding the same way. I clone a project to my desktop outside my web folder (e.g. C:\Sources\GitHub\joomla-cms) and establish symbolic links from my web folder to the cloned project (e.g. C:\xampp\htdocs\joomla-cms <==> C:\Sources\GitHub\joomla-cms).

This worked fine for me and with this method I had the possibility to have other clones (e.g. an extension like JoomGallery) in the same Joomla! installation by establishing similar links (working together with the help of git exclude) and there was no interference of the various clones. I am doing so for some years and I never detected any problems with it, until today. I cloned a project containing alternate layouts for an extension (JoomGallery) and proceeded the same way explained above. But the layout was never applied. I found out, that the problem was caused by the use of the symbolic links. The linked files were not detected by Joomla! because the use of realpath() in JPath::find()

        // Start looping through the path set
        foreach ($paths as $path)
        {
            // Get the path to the file
            $fullname = $path . '/' . $file;

            // Is the path based on a stream?
            if (strpos($path, '://') === false)
            {
                // Not a stream, so do a realpath() to avoid directory
                // traversal attempts on the local file system.

                // Needed for substr() later
                $path = realpath($path);
                $fullname = realpath($fullname);
            }

            /*
             * The substr() check added to make sure that the realpath()
             * results in a directory registered so that
             * non-registered directories are not accessible via directory
             * traversal attempts.
             */
            if (file_exists($fullname) && substr($fullname, 0, strlen($path)) == $path )
            {
                return $fullname;
            }

does prevent that. As a result of that the default layouts were applied (without giving any error message). For me, this is a bug or at least not the job of Joomla! to prevent the use of symbolic links (see Apache FollowSymLinks). Nearly 99% of my Joomla! installation is working perfect in a symbolic linked directory (as long JPath::find() is not involved).
What do you think?

avatar Erftralle Erftralle - open - 20 Jun 2015
avatar brianteeman
brianteeman - comment - 20 Jun 2015

From the comment block it would appear to exist for security purposes


This comment was created with the J!Tracker Application at issues.joomla.org/joomla-cms/7216.

avatar Erftralle
Erftralle - comment - 20 Jun 2015

From the comment block it would appear to exist for security purposes

I have to quote myself:

Nearly 99% of my Joomla! installation is working perfect in a symbolic linked directory (as long JPath::find() is not involved).

avatar zero-24 zero-24 - change - 20 Jun 2015
The description was changed
Labels Added: ?
avatar zero-24 zero-24 - change - 20 Jun 2015
Category Libraries
avatar instance
instance - comment - 23 Jun 2015

I believe the intention here is to prevent compromised code from accessing files outside of the web root. Any extension that accepts a path from user input would be vulnerable to this sort of compromise. This in turn could lead to cross-site hacks when the same user has more than one site under the same account.

I suppose we could add an option to override this behaviour, but having it there by default is intentional and IMO it is quite reasonable. Instead we should probably work on fixing the other 99% that allows symlinks.

avatar nikosdion
nikosdion - comment - 23 Jun 2015

I fully agree with @instance

avatar wilsonge
wilsonge - comment - 23 Jun 2015

I'm closing this based on the advice of the Security Strike Team above

avatar wilsonge wilsonge - change - 23 Jun 2015
Status New Closed
Closed_Date 0000-00-00 00:00:00 2015-06-23 14:24:25
Closed_By wilsonge
avatar wilsonge wilsonge - close - 23 Jun 2015
avatar wilsonge wilsonge - close - 23 Jun 2015
avatar Erftralle
Erftralle - comment - 24 Jun 2015

I am not an expert regarding to security things but I hope I may add some points to the statements above although this issue has been closed by @wilsonge due to advise I must have overlooked ;-) .

I believe the intention here is to prevent compromised code from accessing files outside of the web root.

While

JPath::find('C:/xampp/htdocs/joomla-cms/somefolder', 'SymLinkToFileOutsideWebRoot.php'));

returns false calls like

JPath::find('C://xampp/htdocs/joomla-cms/somefolder', 'SymLinkToFileOutsideWebRoot.php'));
JPath::find('file://C://xampp/htdocs/joomla-cms/somefolder', 'SymLinkToFileOutsideWebRoot.php'));

just work fine for symbolic linked files pointing to files outside the web root by returning $fullname.

Symbolic linked files pointing to files inside the web root

JPath::find('C:/xampp/htdocs/joomla-cms/somefolder', 'SymLinkToFileInsideWebRoot.php'));

do not work either.

Any extension that accepts a path from user input would be vulnerable to this sort of compromise. This in turn could lead to cross-site hacks when the same user has more than one site under the same account.

In my opinion JPath::find() should not be misused to filter and check user inputs.

avatar nikosdion
nikosdion - comment - 24 Jun 2015

If you ever do this:

JPath::find('C://xampp/htdocs/joomla-cms/somefolder', 'SymLinkToFileOutsideWebRoot.php'));

you should bang your head on the wall, repeatedly, because you've screwed up. What you must do instead is:

$myPath = 'C://xampp/htdocs/joomla-cms/somefolder';
$path = JPath::clean($myPath); // $path is set to 'c:/xampp/htdocs/joomla-cms/somefolder'
$files = JPath::find($path, 'whatever.php'); // no more symlink issues, whee!

Of course this works as expected, i.e. you get protection against symlinks.

The directory argument to JPath::find is supposed to be already filtered by JPath::clean. Under normal operation these paths are constructed by the various JPATH_* constants, hardcoded strings and the name of the component (which is already passed through JFilterInput's "cmd" filter) which is why you don't see them filtered: they don't need to, they are constructed in a way that guaranteed there will be no something:// prefix. Therefore under normal operation you will NOT get a c:// or file:// prefix unless you screw up and pass unvalidated, unfiltered user data. That's why you MUST go through JPath::clean when you're dealing with directories computed from user input (not just a directory they've entered, but even a directory computed from constants, hardcoded strings and user input). JPath::clean will automatically convert c:// to c:/ which is the correct representation of a drive root under Windows.

As for this:

JPath::find('file://C://xampp/htdocs/joomla-cms/somefolder', 'SymLinkToFileOutsideWebRoot.php'));

Why are you using, or allowing your users to use, a file:// stream wrapper? Sure, you can definitely stab yourself to death with a fork knife but this doesn't mean that the fork knife is unsafe, it only means that you're suicidal I guess. It's pretty much the same thing here. Please don't commit suicide by file:// stream wrapper.

avatar Erftralle
Erftralle - comment - 24 Jun 2015

Thanks @nikosdion for your detailed explanations and for letting me look and feel like a fool.

For sure you are right regarding to the use of JPath::clean() and the use of effective input filtering and validating and I fully agree with you (rather than preventing symbolic links outside or at least symbolic links inside the web root in JPath::find()).

I do admit that my examples above are maybe not all suitable to show what I wanted to.
But, as I am for sure not yet as experienced than the Security Strike Team members I will accept for now that preventing symbolic links in JPath::find() is necessary for security reasons.

avatar nikosdion
nikosdion - comment - 24 Jun 2015

Don't feel like a fool. This is just a teachable moment. Trust me, all of us have them – I'd be a damn fool pretending I don't have them ever :D

avatar shoulders
shoulders - comment - 21 Jan 2020

I know this is an old thread but others might find it like I did and use it as a reference.

I had the same issue so i am using a core override of Path class and have used:

// Is the path based on a stream?
if (strpos($path, '://') === false)
{
    // Not a stream, so do a realpath() to avoid directory
    // traversal attempts on the local file system.

    // Needed for substr() later
    $path = realpath($path);

    // Needed for substr() later (Modified to allow symbolic links to files)
    if(is_link($fullname))
    {
        $fullname = rtrim(self::clean($fullname), '/\\');
    } else {
        $fullname = realpath($fullname);                    		
    }
}

Add a Comment

Login with GitHub to post a comment