CVE-2016-6639: 7PK - Security Features (CWE-254)
value: Cloud Foundry PHP Buildpack (aka php-buildpack) before 4.3.18 and PHP Buildpack Cf-release before 242, as used in Pivotal Cloud Foundry (PCF) Elastic Runtime before 1.6.38 and 1.7.x before 1.7.19 and other products, place the .profile file in the htdocs directory, which might allow remote attackers to obtain sensitive information via an HTTP GET request for this file.
CWE description: Software security is not security software. Here we're concerned with topics like authentication, access control, confidentiality, cryptography, and privilege management.
CVSS v3 base score: 7.5
Published: 2016-09-18T02:59Z
Repository: https://github.com/cloudfoundry/php-buildpack
Language: Python
Files changed: cf_spec/fixtures/php_app_with_profile_script/.profile, cf_spec/fixtures/php_app_with_profile_script/index.php, cf_spec/integration/profile_d_sets_environment_variables_spec.rb, lib/compile_helpers.py, tests/data/app-with-all-possible-system-files-that-should-not-move/README.txt, tests/test_compile_helpers.py
Commit date: 2016-08-15 14:30:46 -0400
Commit message: Execute .profile script at runtime

- Add integration showing that .profile script runs.
- Add unit test for all top-level system files which are not meant to
  be moved into the htdocs directory.
- Move a stray assertion about not moving manifest.yml to the new test.
- Add .profile to the list of files/dirs which should not be moved from
  the toplevel into htdocs/

[#127923697]

Signed-off-by: Sam Smith <sesmith177@gmail.com>
Signed-off-by: Jacques Chester <jchester@pivotal.io>

Diff: diff --git a/cf_spec/fixtures/php_app_with_profile_script/.profile b/cf_spec/fixtures/php_app_with_profile_script/.profile
new file mode 100755
index 0000000..66f1ae2
--- /dev/null
+++ b/cf_spec/fixtures/php_app_with_profile_script/.profile
@@ -0,0 +1 @@
+export PROFILE_SCRIPT_IS_PRESENT_AND_RAN="Hooray for environment variables!"
\ No newline at end of file
diff --git a/cf_spec/fixtures/php_app_with_profile_script/index.php b/cf_spec/fixtures/php_app_with_profile_script/index.php
new file mode 100644
index 0000000..7436d67
--- /dev/null
+++ b/cf_spec/fixtures/php_app_with_profile_script/index.php
@@ -0,0 +1 @@
+<?php echo phpinfo() ?>
diff --git a/cf_spec/integration/profile_d_sets_environment_variables_spec.rb b/cf_spec/integration/profile_d_sets_environment_variables_spec.rb
index 3bd81b9..d04804b 100644
--- a/cf_spec/integration/profile_d_sets_environment_variables_spec.rb
+++ b/cf_spec/integration/profile_d_sets_environment_variables_spec.rb
@@ -15,6 +15,20 @@ describe 'CF PHP Buildpack' do
     it "sets environment variables via .profile.d script" do
       browser.visit_path('/')
       expect(browser).to have_body('TEST_ENV_VAR')
     end
   end
+
+  context 'deploying a PHP app with .profile script' do
+    let(:app_name) { 'php_app_with_profile_script' }
+
+    it "sets environment variables via .profile script" do
+      cf_api_output = Machete::CF::API.new.execute
+
+      cf_api_version = cf_api_output.match( /API version: (?<version_number>\d+\.\d+\.\d+)/ )['version_number']
+      minimum_acceptable_cf_api_version = Gem::Version.new('2.57.0')
+      skip(".profile script functionality not supported before CF API version #{minimum_acceptable_cf_api_version}") if Gem::Version.new(cf_api_version) < minimum_acceptable_cf_api_version
+      browser.visit_path('/')
+      expect(browser).to have_body('PROFILE_SCRIPT_IS_PRESENT_AND_RAN')
+    end
+  end
 end
diff --git a/lib/compile_helpers.py b/lib/compile_helpers.py
index f063e8e..6a82cb4 100644
--- a/lib/compile_helpers.py
+++ b/lib/compile_helpers.py
@@ -52,10 +52,12 @@ def setup_webdir_if_it_doesnt_exist(ctx):
                 '^%s$' % os.path.join(ctx['BUILD_DIR'], 'manifest.yml'))
             fu.where_name_does_not_match(
                 '^%s/.*$' % os.path.join(ctx['BUILD_DIR'], ctx['LIBDIR']))
             fu.where_name_does_not_match(
                 '^%s/.*$' % os.path.join(ctx['BUILD_DIR'], '.profile.d'))
+            fu.where_name_does_not_match(
+                '^%s$' % os.path.join(ctx['BUILD_DIR'], '.profile'))
             fu.done()
 
 
 def log_bp_version(ctx):
     version_file = os.path.join(ctx['BP_DIR'], 'VERSION')
diff --git a/tests/data/app-with-all-possible-system-files-that-should-not-move/.bp-config/.gitkeep b/tests/data/app-with-all-possible-system-files-that-should-not-move/.bp-config/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/app-with-all-possible-system-files-that-should-not-move/.bp/.gitkeep b/tests/data/app-with-all-possible-system-files-that-should-not-move/.bp/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/app-with-all-possible-system-files-that-should-not-move/.extensions/.gitkeep b/tests/data/app-with-all-possible-system-files-that-should-not-move/.extensions/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/app-with-all-possible-system-files-that-should-not-move/.profile b/tests/data/app-with-all-possible-system-files-that-should-not-move/.profile
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/app-with-all-possible-system-files-that-should-not-move/.profile.d/.gitkeep b/tests/data/app-with-all-possible-system-files-that-should-not-move/.profile.d/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/app-with-all-possible-system-files-that-should-not-move/README.txt b/tests/data/app-with-all-possible-system-files-that-should-not-move/README.txt
new file mode 100644
index 0000000..b781642
--- /dev/null
+++ b/tests/data/app-with-all-possible-system-files-that-should-not-move/README.txt
@@ -0,0 +1 @@
+This directory is not a "real" app. It is a fixture intended to support test_system_files_not_moved_into_webdir. That's why all the files and folders are empty.
\ No newline at end of file
diff --git a/tests/data/app-with-all-possible-system-files-that-should-not-move/manifest.yml b/tests/data/app-with-all-possible-system-files-that-should-not-move/manifest.yml
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_compile_helpers.py b/tests/test_compile_helpers.py
index 06821d2..7ff87c9 100644
--- a/tests/test_compile_helpers.py
+++ b/tests/test_compile_helpers.py
@@ -118,11 +118,10 @@ class TestCompileHelpers(object):
         self.assert_exists(self.build_dir, 'lib', 'test.php')
         self.assert_exists(self.build_dir, '.bp-config')
         self.assert_exists(self.build_dir, '.bp-config', 'options.json')
         self.assert_exists(self.build_dir, '.bp-config', 'httpd', 'extra',
                            'httpd-remoteip.conf')
-        self.assert_exists(self.build_dir, 'manifest.yml')
         eq_(4, len(os.listdir(self.build_dir)))
         eq_(4, len(os.listdir(os.path.join(self.build_dir, 'htdocs'))))
 
     def test_setup_if_custom_webdir_does_not_exist(self):
         shutil.copytree('tests/data/app-2', self.build_dir)
@@ -193,10 +192,24 @@ class TestCompileHelpers(object):
         self.assert_exists(self.build_dir, '.extensions', 'some-ext',
                            'extension.py')
         eq_(4, len(os.listdir(self.build_dir)))
         eq_(3, len(os.listdir(os.path.join(self.build_dir, 'public'))))
 
+    def test_system_files_not_moved_into_webdir(self):
+        shutil.copytree('tests/data/app-with-all-possible-system-files-that-should-not-move', self.build_dir)
+        setup_webdir_if_it_doesnt_exist(utils.FormattedDict({
+            'BUILD_DIR': self.build_dir,
+            'WEBDIR': 'htdocs',
+            'LIBDIR': 'lib'
+        }))
+        self.assert_exists(self.build_dir, '.bp')
+        self.assert_exists(self.build_dir, '.extensions')
+        self.assert_exists(self.build_dir, '.bp-config')
+        self.assert_exists(self.build_dir, 'manifest.yml')
+        self.assert_exists(self.build_dir, '.profile.d')
+        self.assert_exists(self.build_dir, '.profile')
+
     def test_setup_if_htdocs_with_stand_alone_app(self):
         shutil.copytree('tests/data/app-5', self.build_dir)
         setup_webdir_if_it_doesnt_exist(utils.FormattedDict({
             'BUILD_DIR': self.build_dir,
             'WEB_SERVER': 'none'

Vulnerable code:         self.assert_exists(self.build_dir, 'manifest.yml')
Fixed code: export PROFILE_SCRIPT_IS_PRESENT_AND_RAN="Hooray for environment variables!"
<?php echo phpinfo() ?>

  context 'deploying a PHP app with .profile script' do
    let(:app_name) { 'php_app_with_profile_script' }

    it "sets environment variables via .profile script" do
      cf_api_output = Machete::CF::API.new.execute

      cf_api_version = cf_api_output.match( /API version: (?<version_number>\d+\.\d+\.\d+)/ )['version_number']
      minimum_acceptable_cf_api_version = Gem::Version.new('2.57.0')
      skip(".profile script functionality not supported before CF API version #{minimum_acceptable_cf_api_version}") if Gem::Version.new(cf_api_version) < minimum_acceptable_cf_api_version
      browser.visit_path('/')
      expect(browser).to have_body('PROFILE_SCRIPT_IS_PRESENT_AND_RAN')
    end
  end
            fu.where_name_does_not_match(
                '^%s$' % os.path.join(ctx['BUILD_DIR'], '.profile'))
This directory is not a "real" app. It is a fixture intended to support test_system_files_not_moved_into_webdir. That's why all the files and folders are empty.
    def test_system_files_not_moved_into_webdir(self):
        shutil.copytree('tests/data/app-with-all-possible-system-files-that-should-not-move', self.build_dir)
        setup_webdir_if_it_doesnt_exist(utils.FormattedDict({
            'BUILD_DIR': self.build_dir,
            'WEBDIR': 'htdocs',
            'LIBDIR': 'lib'
        }))
        self.assert_exists(self.build_dir, '.bp')
        self.assert_exists(self.build_dir, '.extensions')
        self.assert_exists(self.build_dir, '.bp-config')
        self.assert_exists(self.build_dir, 'manifest.yml')
        self.assert_exists(self.build_dir, '.profile.d')
        self.assert_exists(self.build_dir, '.profile')