Feature #1989

Workaround broken operating systems - Displaying source code of php, ruby or any other file

Added by venatir about 5 years ago. Updated about 5 years ago.

Status:FixedStart date:2009-05-22
Priority:HighDue date:
Assignee:-% Done:

100%

Category:core
Target version:1.4.23
Missing in 1.5.x:

Description

If you put a trailing slash / after the .php and that file is a link, it will just display the content of the file.

2009-05-22 13:40:37: (response.c.221) -- splitting Request-URI
2009-05-22 13:40:37: (response.c.222) Request-URI : /index.php/
2009-05-22 13:40:37: (response.c.223) URI-scheme : http
2009-05-22 13:40:37: (response.c.224) URI-authority: localhost
2009-05-22 13:40:37: (response.c.225) URI-path : /index.php/
2009-05-22 13:40:37: (response.c.226) URI-query :
2009-05-22 13:40:37: (response.c.254) -- sanatising URI
2009-05-22 13:40:37: (response.c.255) URI-path : /index.php/
2009-05-22 13:40:37: (response.c.221) -- splitting Request-URI
2009-05-22 13:40:37: (response.c.222) Request-URI : /index.php/
2009-05-22 13:40:37: (response.c.223) URI-scheme : http
2009-05-22 13:40:37: (response.c.224) URI-authority: localhost
2009-05-22 13:40:37: (response.c.225) URI-path : /index.php/
2009-05-22 13:40:37: (response.c.226) URI-query :
2009-05-22 13:40:37: (response.c.254) -- sanatising URI
2009-05-22 13:40:37: (response.c.255) URI-path : /index.php/
2009-05-22 13:40:37: (mod_access.c.135) -- mod_access_uri_handler called
2009-05-22 13:40:37: (response.c.391) -- before doc_root
2009-05-22 13:40:37: (response.c.392) Doc-Root : /work/websites/freesoft.com/htdocs/
2009-05-22 13:40:37: (response.c.393) Rel-Path : /index.php/
2009-05-22 13:40:37: (response.c.394) Path :
2009-05-22 13:40:37: (response.c.442) -- after doc_root
2009-05-22 13:40:37: (response.c.443) Doc-Root : /work/websites/freesoft.com/htdocs/
2009-05-22 13:40:37: (response.c.444) Rel-Path : /index.php/
2009-05-22 13:40:37: (response.c.445) Path : /work/websites/freesoft.com/htdocs/index.php/
2009-05-22 13:40:37: (response.c.462) -- logical > physical
2009-05-22 13:40:37: (response.c.463) Doc-Root : /work/websites/freesoft.com/htdocs/
2009-05-22 13:40:37: (response.c.464) Rel-Path : /index.php/
2009-05-22 13:40:37: (response.c.465) Path : /work/websites/freesoft.com/htdocs/index.php/
2009-05-22 13:40:37: (response.c.482) -
handling physical path
2009-05-22 13:40:37: (response.c.483) Path : /work/websites/freesoft.com/htdocs/index.php/
2009-05-22 13:40:37: (response.c.490) -- file found
2009-05-22 13:40:37: (response.c.491) Path : /work/websites/freesoft.com/htdocs/index.php/
2009-05-22 13:40:37: (response.c.640) -- handling subrequest
2009-05-22 13:40:37: (response.c.641) Path : /work/websites/freesoft.com/htdocs/index.php/
2009-05-22 13:40:37: (mod_indexfile.c.151) -- handling the request as Indexfile
2009-05-22 13:40:37: (mod_indexfile.c.152) URI : /index.php/
2009-05-22 13:40:37: (mod_access.c.135) -- mod_access_uri_handler called
2009-05-22 13:40:37: (mod_staticfile.c.394) -- handling file as static file
2009-05-22 13:40:37: (response.c.652) -- subrequest finished
2009-05-22 13:40:37: (response.c.121) Response-Header:

stat_cache.c.patch Magnifier (463 Bytes) icy, 2009-05-22 15:28

stat_cache.c.2.patch Magnifier (526 Bytes) peto, 2009-05-22 23:40

Associated revisions

Revision 2510
Added by stbuehler about 5 years ago

Workaround broken operating systems: check for trailing '/' in filenames (fixes #1989)

History

#1 Updated by venatir about 5 years ago

# lighttpd configuration file
#
# use it as a base for lighttpd 1.0.0 and above
#
# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $

############ Options you really have to take care of ####################

## modules to load
# at least mod_access and mod_accesslog should be loaded
# all other module should only be loaded if really neccesary
# - saves some time
# - saves memory
server.modules              = (
                               "mod_rewrite",
#                               "mod_redirect",
                               "mod_alias",
                                "mod_access",
#                               "mod_cml",
#                               "mod_trigger_b4_dl",
#                               "mod_auth",
#                               "mod_status",
#                               "mod_setenv",
                               "mod_fastcgi",
#                               "mod_proxy",
                               "mod_simple_vhost",
#                               "mod_evhost",
#                               "mod_userdir",
#                               "mod_cgi",
#                               "mod_compress",
#                               "mod_ssi",
#                               "mod_usertrack",
#                               "mod_expire",
#                               "mod_secdownload",
#                               "mod_rrdtool",
                                "mod_accesslog" )

## a static document-root, for virtual-hosting take look at the
## server.virtual-* options
server.document-root        = "/work/websites/freesoft.com/htdocs/" 

## where to send error-messages to
server.errorlog             = "/var/log/lighttpd/error.log" 

# files to check for if .../ is requested
index-file.names            = ( "index.php", "index.html",
                                "index.htm", "default.htm" )

## set the event-handler (read the performance section in the manual)
# server.event-handler = "freebsd-kqueue" # needed on OS X

# mimetype mapping
mimetype.assign             = (
  ".pdf"          =>      "application/pdf",
  ".sig"          =>      "application/pgp-signature",
  ".spl"          =>      "application/futuresplash",
  ".class"        =>      "application/octet-stream",
  ".ps"           =>      "application/postscript",
  ".torrent"      =>      "application/x-bittorrent",
  ".dvi"          =>      "application/x-dvi",
  ".gz"           =>      "application/x-gzip",
  ".pac"          =>      "application/x-ns-proxy-autoconfig",
  ".swf"          =>      "application/x-shockwave-flash",
  ".tar.gz"       =>      "application/x-tgz",
  ".tgz"          =>      "application/x-tgz",
  ".tar"          =>      "application/x-tar",
  ".zip"          =>      "application/zip",
  ".mp3"          =>      "audio/mpeg",
  ".m3u"          =>      "audio/x-mpegurl",
  ".wma"          =>      "audio/x-ms-wma",
  ".wax"          =>      "audio/x-ms-wax",
  ".ogg"          =>      "application/ogg",
  ".wav"          =>      "audio/x-wav",
  ".gif"          =>      "image/gif",
  ".jar"          =>      "application/x-java-archive",
  ".jpg"          =>      "image/jpeg",
  ".jpeg"         =>      "image/jpeg",
  ".png"          =>      "image/png",
  ".xbm"          =>      "image/x-xbitmap",
  ".xpm"          =>      "image/x-xpixmap",
  ".xwd"          =>      "image/x-xwindowdump",
  ".css"          =>      "text/css",
  ".html"         =>      "text/html",
  ".htm"          =>      "text/html",
  ".js"           =>      "text/javascript",
  ".asc"          =>      "text/plain",
  ".c"            =>      "text/plain",
  ".cpp"          =>      "text/plain",
  ".log"          =>      "text/plain",
  ".conf"         =>      "text/plain",
  ".text"         =>      "text/plain",
  ".txt"          =>      "text/plain",
  ".dtd"          =>      "text/xml",
  ".xml"          =>      "text/xml",
  ".mpeg"         =>      "video/mpeg",
  ".mpg"          =>      "video/mpeg",
  ".mov"          =>      "video/quicktime",
  ".qt"           =>      "video/quicktime",
  ".avi"          =>      "video/x-msvideo",
  ".asf"          =>      "video/x-ms-asf",
  ".asx"          =>      "video/x-ms-asf",
  ".wmv"          =>      "video/x-ms-wmv",
  ".bz2"          =>      "application/x-bzip",
  ".tbz"          =>      "application/x-bzip-compressed-tar",
  ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
  # default mime type
  ""              =>      "application/octet-stream",
 )

# Use the "Content-Type" extended attribute to obtain mime type if possible
#mimetype.use-xattr        = "enable" 

## send a different Server: header
## be nice and keep it at lighttpd
# server.tag                 = "lighttpd" 

#### accesslog module
accesslog.filename          = "/var/log/lighttpd/access.log" 

## deny access the file-extensions
#
# ~    is for backupfiles from vi, emacs, joe, ...
# .inc is often used for code includes which should in general not be part
#      of the document-root
url.access-deny             = ( "~", ".inc" )

$HTTP["url"] =~ "\.pdf$" {
  server.range-requests = "disable" 
}

##
# which extensions should not be handle via static-file transfer
#
# .php, .pl, .fcgi are most often handled by mod_fastcgi or mod_cgi
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )

######### Options that are good to be but not neccesary to be changed #######

## bind to port (default: 80)
#server.port                = 81

## bind to localhost (default: all interfaces)
#server.bind                = "127.0.0.1" 

## error-handler for status 404
#server.error-handler-404   = "/error-handler.html" 
#server.error-handler-404   = "/error-handler.php" 

## to help the rc.scripts
server.pid-file            = "/opt/local/var/run/lighttpd.pid" 

###### virtual hosts
##
##  If you want name-based virtual hosting add the next three settings and load
##  mod_simple_vhost
##
## document-root =
##   virtual-server-root + virtual-server-default-host + virtual-server-docroot
## or
##   virtual-server-root + http-host + virtual-server-docroot
##
simple-vhost.server-root   = "/work/websites/" 
simple-vhost.default-host  = "freesoft.com" 
simple-vhost.document-root = "/htdocs/" 

alias.url            = (
        "/sf/"           => "/opt/local/lib/php/data/symfony/web/sf/" 
    )

    url.rewrite-once = ( 
        "^/(.*\..+(?!html))$" => "$0",
        "^/(.*)\.(.*)"        => "$0",
        "^/([^.]+)$"          => "/index.php/$1",
        "^/$"        => "/index.php" 
    )

##
## Format: <errorfile-prefix><status-code>.html
## -> ..../status-404.html for 'File not found'
#server.errorfile-prefix    = "/usr/share/lighttpd/errors/status-" 
#server.errorfile-prefix    = "/srv/www/errors/status-" 

## virtual directory listings
#dir-listing.activate       = "enable" 
## select encoding for directory listings
#dir-listing.encoding        = "utf-8" 

## enable debugging
debug.log-request-header   = "enable" 
debug.log-response-header  = "enable" 
debug.log-request-handling = "enable" 
debug.log-file-not-found   = "enable" 

### only root can use these options
#
# chroot() to directory (default: no chroot() )
#server.chroot              = "/" 

## change uid to <uid> (default: don't care)
#server.username            = "wwwrun" 

## change uid to <uid> (default: don't care)
#server.groupname           = "wwwrun" 

#### compress module
#compress.cache-dir         = "/var/cache/lighttpd/compress/" 
#compress.filetype          = ("text/plain", "text/html")

#### proxy module
## read proxy.txt for more info
#proxy.server               = ( ".php" =>
#                               ( "localhost" =>
#                                 (
#                                   "host" => "192.168.0.101",
#                                   "port" => 80
#                                 )
#                               )
#                             )

#### fastcgi module
## read fastcgi.txt for more info
## for PHP don't forget to set cgi.fix_pathinfo = 1 in the php.ini
fastcgi.server             = ( ".php" =>
                               ( "localhost" =>
                                 (
                                   "socket" => "/var/run/lighttpd/php-fastcgi.socket",
                                   "bin-path" => "/opt/local/bin/php-cgi" 
                                 )
                               )
                            )

#### CGI module
#cgi.assign                 = ( ".pl"  => "/opt/local/bin/perl",
#                               ".cgi" => "/opt/local/bin/perl" )
#

#### SSL engine
#ssl.engine                 = "enable" 
#ssl.pemfile                = "/etc/ssl/private/lighttpd.pem" 

#### status module
#status.status-url          = "/server-status" 
#status.config-url          = "/server-config" 

#### auth module
## read authentication.txt for more info
#auth.backend               = "plain" 
#auth.backend.plain.userfile = "lighttpd.user" 
#auth.backend.plain.groupfile = "lighttpd.group" 

#auth.backend.ldap.hostname = "localhost" 
#auth.backend.ldap.base-dn  = "dc=my-domain,dc=com" 
#auth.backend.ldap.filter   = "(uid=$)" 

#auth.require               = ( "/server-status" =>
#                               (
#                                 "method"  => "digest",
#                                 "realm"   => "download archiv",
#                                 "require" => "user=jan" 
#                               ),
#                               "/server-config" =>
#                               (
#                                 "method"  => "digest",
#                                 "realm"   => "download archiv",
#                                 "require" => "valid-user" 
#                               )
#                             )

#### url handling modules (rewrite, redirect, access)
#url.rewrite                = ( "^/$"             => "/server-status" )
#url.redirect               = ( "^/wishlist/(.+)" => "http://www.123.org/$1" )
#### both rewrite/redirect support back reference to regex conditional using %n
#$HTTP["host"] =~ "^www\.(.*)" {
#  url.redirect            = ( "^/(.*)" => "http://%1/$1" )
#}

#
# define a pattern for the host url finding
# %% => % sign
# %0 => domain name + tld
# %1 => tld
# %2 => domain name without tld
# %3 => subdomain 1 name
# %4 => subdomain 2 name
#
#evhost.path-pattern        = "/srv/www/vhosts/%3/htdocs/" 

#### expire module
#expire.url                 = ( "/buggy/" => "access 2 hours", "/asdhas/" => "access plus 1 seconds 2 minutes")

#### ssi
#ssi.extension              = ( ".shtml" )

#### rrdtool
#rrdtool.binary             = "/opt/local/bin/rrdtool" 
#rrdtool.db-name            = "/opt/local/var/lib/lighttpd/lighttpd.rrd" 

#### setenv
#setenv.add-request-header  = ( "TRAV_ENV" => "mysql://user@host/db" )
#setenv.add-response-header = ( "X-Secret-Message" => "42" )

## for mod_trigger_b4_dl
# trigger-before-download.gdbm-filename = "/var/lib/lighttpd/trigger.db" 
# trigger-before-download.memcache-hosts = ( "127.0.0.1:11211" )
# trigger-before-download.trigger-url = "^/trigger/" 
# trigger-before-download.download-url = "^/download/" 
# trigger-before-download.deny-url = "http://127.0.0.1/index.html" 
# trigger-before-download.trigger-timeout = 10

## for mod_cml
## don't forget to add index.cml to server.indexfiles
# cml.extension               = ".cml" 
# cml.memcache-hosts          = ( "127.0.0.1:11211" )

#### variable usage:
## variable name without "." is auto prefixed by "var." and becomes "var.bar" 
#bar = 1
#var.mystring = "foo" 

## integer add
#bar += 1
## string concat, with integer cast as string, result: "www.foo1.com" 
#server.name = "www." + mystring + var.bar + ".com" 
## array merge
#index-file.names = (foo + ".php") + index-file.names
#index-file.names += (foo + ".php")

#### include
#include /etc/lighttpd/lighttpd-inc.conf
## same as above if you run: "lighttpd -f /etc/lighttpd/lighttpd.conf" 
#include "lighttpd-inc.conf" 

#### include_shell
#include_shell "echo var.a=1" 
## the above is same as:
#var.a=1

#2 Updated by venatir about 5 years ago

This was tested on:

1.4.20 OSX Leopard 10.5.7 filesystem: hfs+
1.4.22 freebsd 7.2 stable amd64 filesystem: ufs

On linux, I tested on a centos 5.2 box and the bug does not manifest on linux.

#3 Updated by darix about 5 years ago

  • Status changed from New to Invalid

#4 Updated by icy about 5 years ago

  • File stat_cache.c.patchMagnifier added
  • Category changed from mod_access to core
  • Status changed from Invalid to Reopened
  • Target version changed from 1.4.22 to 1.4.23

Preliminary patch to work around that bug. Needs review and testing.

#5 Updated by peto about 5 years ago

darix: Real-world applications need to cope with OS bugs, particularly when there are security implications. Ignoring them ("oh well, not our problem!") when you're maintaining security critical, internet-facing software like this is very irresponsible.

icy: This patch seems to work (in FBSD 7.1), but returns the wrong error (500). That's because it doesn't set errno, probably leaving it at ESUCCESS from the stat() call. Updated patch attached; now it returns 403.

Only tested by creating a symlink, disabling follow-symlinks, and reading the file with and without a trailing slash. I havn't tried it combined with CGI of any kind.

Note that if follow-symlinks is true, it does still serve the file with a trailing slash. stat_cache_get_entry is called three times: twice with the trailing slash, returning an error, and then once with the slash removed. stat_cache_get_entry is operating correctly and I havn't looked further up the logic. This behavior is fine with follow-symlinks enabled, but someone should specifically verify it with CGI.

I assume this needs porting to 1.5, too.

#6 Updated by icy about 5 years ago

Thanks peto for enhencing the patch. I only hacked it quickly together :)

Here's a list of OSs that have been tested for this bug:

affected: FreeBSD, OSX, Solaris < 10
not affected: Linux, NetBSD, OpenBSD, DragonflyBSD, Solaris 10

Thanks also to everyone who tested on those systems at this point.

#7 Updated by darix about 5 years ago

peto wrote:

darix: Real-world applications need to cope with OS bugs, particularly when there are security implications. Ignoring them ("oh well, not our problem!") when you're maintaining security critical, internet-facing software like this is very irresponsible.

Well. The fbsd behavior is just broken and it is known since 2000. while we can add a workaround for this. imho the real fix should be applied to the broken OS.

#8 Updated by icy about 5 years ago

While it might not be our "job" to fix it, providing a fix (which doesn't break anything and is not complex) is a good thing for sure.
The enhenced patch by peto seems to work well after some quick testing.

#9 Updated by stbuehler about 5 years ago

Use this to prevent the source code leakage (returns 403 instead of using "/" as PATH_INFO for a normal request):

static-file.exclude-extensions += ( "/" )

Imho it is the job of the lighty package maintainers of the broken OS to apply the patch. I don't like workarounds upstream (they often make things worse...); they broke it, they can fix it easily as peto provided a patch (thx peto).

Btw: There are similar issues with alternate data streams - NTFS ::$DATA; and we don't care about them either.

And as it looks like some people didn't get that: 1.4.23 is neither the affected version (it isn't even released yet...) nor is it a lighty bug.

#10 Updated by stbuehler about 5 years ago

  • Subject changed from SECURITY ISSUE - Displaying source code of php, ruby or any other file to Workaround broken operating systems - Displaying source code of php, ruby or any other file
  • Priority changed from Immediate to High

#11 Updated by stbuehler about 5 years ago

  • Tracker changed from Bug to Feature

#12 Updated by stbuehler about 5 years ago

  • Status changed from Reopened to Fixed
  • % Done changed from 0 to 100

Applied in changeset r2510.

Also available in: Atom