From a9bc9aae46f5ce40e2b69a29da12f5795e8b1085 Mon Sep 17 00:00:00 2001 From: kychen Date: Thu, 7 Aug 2025 17:58:02 +0800 Subject: [PATCH 1/4] fix: cherry pick form https://github.com/apache/commons-beanutils/commit/bd20740da25b69552ddef8523beec0837297eaf9 to fix vul CVE-2025-48734 --- pom.xml | 6 +- src/changes/changes.xml | 4 + .../commons/beanutils/PropertyUtilsBean.java | 3 +- .../SuppressPropertiesBeanIntrospector.java | 10 ++ .../apache/commons/beanutils/TestEnum.java | 33 +++++ .../bugs/EnumDeclaringClassTest.java | 127 ++++++++++++++++++ 6 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/apache/commons/beanutils/TestEnum.java create mode 100644 src/test/java/org/apache/commons/beanutils/bugs/EnumDeclaringClassTest.java diff --git a/pom.xml b/pom.xml index 1a4c70d26..4db3846eb 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 4.0.0 commons-beanutils commons-beanutils - 1.9.4 + 1.9.5 Apache Commons BeanUtils 2000 @@ -32,8 +32,8 @@ https://commons.apache.org/proper/commons-beanutils/ - 1.6 - 1.6 + 1.8 + 1.8 beanutils 1.9.4 BEANUTILS diff --git a/src/changes/changes.xml b/src/changes/changes.xml index fc21a894e..72afad444 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -28,6 +28,10 @@ Release Notes + + Add org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS. + propertyNames; diff --git a/src/test/java/org/apache/commons/beanutils/TestEnum.java b/src/test/java/org/apache/commons/beanutils/TestEnum.java new file mode 100644 index 000000000..d66f2d977 --- /dev/null +++ b/src/test/java/org/apache/commons/beanutils/TestEnum.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package org.apache.commons.beanutils; + + /** + * An {@code enum} test fixture. + */ + public enum TestEnum { + + /** Test fixture. */ + A, + + /** Test fixture. */ + B, + + /** Test fixture. */ + C + } diff --git a/src/test/java/org/apache/commons/beanutils/bugs/EnumDeclaringClassTest.java b/src/test/java/org/apache/commons/beanutils/bugs/EnumDeclaringClassTest.java new file mode 100644 index 000000000..509c50d49 --- /dev/null +++ b/src/test/java/org/apache/commons/beanutils/bugs/EnumDeclaringClassTest.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.beanutils.bugs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.apache.commons.beanutils.BeanUtilsBean; +import org.apache.commons.beanutils.PropertyUtilsBean; +import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector; +import org.apache.commons.beanutils.TestEnum; +import org.junit.Test; + +public class EnumDeclaringClassTest { + + public static class Fixture { + + String name = "default"; + TestEnum testEnum = TestEnum.A; + + public String getName() { + return name; + } + + public TestEnum getTestEnum() { + return testEnum; + } + + public void setName(final String name) { + this.name = name; + } + + public void setTestEnum(final TestEnum day) { + this.testEnum = day; + } + } + + /** + * Allow opt-out to make your app less secure but allow access to "declaringClass". + */ + @Test + public void testAllowAccessToClassPropertyFromBeanUtilsBean() throws ReflectiveOperationException { + final BeanUtilsBean bub = new BeanUtilsBean(); + final PropertyUtilsBean propertyUtilsBean = bub.getPropertyUtils(); + propertyUtilsBean.removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS); + final Fixture fixture = new Fixture(); + final String string = bub.getProperty(fixture, "testEnum.declaringClass"); + assertEquals("class " + TestEnum.class.getName(), string); + final Object cls = propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass"); + assertTrue(cls instanceof Class); + final Class teClass = (Class) cls; + final ClassLoader classLoader = teClass.getClassLoader(); + assertNotNull(classLoader); + assertNotNull(bub.getProperty(fixture, "testEnum.declaringClass.classLoader")); + final Object classLoaderObj = propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass.classLoader"); + assertTrue(classLoaderObj instanceof ClassLoader); + } + + /** + * Allow opt-out to make your app less secure but allow access to "declaringClass". + */ + @Test + public void testAllowAccessToClassPropertyFromPropertyUtilsBean() throws ReflectiveOperationException { + final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); + propertyUtilsBean.removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_DECLARING_CLASS); + final Fixture fixture = new Fixture(); + final Object cls = propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass"); + assertTrue(cls instanceof Class); + final Class teClass = (Class) cls; + final ClassLoader classLoader = teClass.getClassLoader(); + assertNotNull(classLoader); + final Object classLoaderObj = propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass.classLoader"); + assertTrue(classLoaderObj instanceof ClassLoader); + } + + /** + * By default opt-in to security that does not allow access to "declaringClass". + */ + @Test + public void testSuppressClassPropertyByDefaultFromBeanUtilsBean() throws ReflectiveOperationException { + final Fixture fixture = new Fixture(); + final BeanUtilsBean bub = new BeanUtilsBean(); + try { + bub.getProperty(fixture, "testEnum.declaringClass.classLoader"); + assertTrue("Expected NoSuchMethodException", false); + } catch (NoSuchMethodException e) { + // Expected + } + try { + bub.getPropertyUtils().getNestedProperty(fixture, "testEnum.declaringClass.classLoader"); + assertTrue("Expected NoSuchMethodException", false); + } catch (NoSuchMethodException e) { + // Expected + } + } + + /** + * By default opt-in to security that does not allow access to "declaringClass". + */ + @Test + public void testSuppressClassPropertyByDefaultFromPropertyUtilsBean() throws ReflectiveOperationException { + final Fixture fixture = new Fixture(); + final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); + try { + propertyUtilsBean.getNestedProperty(fixture, "testEnum.declaringClass.classLoader"); + assertTrue("Expected NoSuchMethodException", false); + } catch (NoSuchMethodException e) { + // Expected + } + } +} From bfe9f686de4b5637dd82bae3ce382e25a54d6884 Mon Sep 17 00:00:00 2001 From: kychen Date: Fri, 8 Aug 2025 10:59:12 +0800 Subject: [PATCH 2/4] feat: add github action from upstream. --- .github/workflows/codeql-analysis.yml | 77 +++++++++++++++++++++++ .github/workflows/dependency-review.yml | 31 +++++++++ .github/workflows/maven.yaml | 60 ++++++++++++++++++ .github/workflows/scorecards-analysis.yml | 69 ++++++++++++++++++++ 4 files changed, 237 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/dependency-review.yml create mode 100644 .github/workflows/maven.yaml create mode 100644 .github/workflows/scorecards-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..815a8bffc --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,77 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: "CodeQL" +on: [push, pull_request] + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + with: + persist-credentials: false + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # 3.29.5 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@51f77329afa6477de8c49fc9c7046c15b9a4e79d # 3.29.5 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # 3.29.5 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 000000000..33573cf94 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: 'Dependency Review PR' + uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml new file mode 100644 index 000000000..e6a73fdee --- /dev/null +++ b/.github/workflows/maven.yaml @@ -0,0 +1,60 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Java CI + +on: + push: + branches: [ $default-branch ] + pull_request: + branches: [ $default-branch ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + strategy: + matrix: + java: [ 8, 11, 17, 21 ] + experimental: [false] + include: + - java: 24 + experimental: true + - java: 25-ea + experimental: true + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Build with Maven + run: mvn --errors --show-version --batch-mode --no-transfer-progress -e + +# For Java 11, you can be more strict: -DadditionalJOption=-Xdoclint/package:-org.apache.commons.configuration2.plist diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml new file mode 100644 index 000000000..19cb2ca3a --- /dev/null +++ b/.github/workflows/scorecards-analysis.yml @@ -0,0 +1,69 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. + +name: "Scorecards supply-chain security" + +on: + branch_protection_rule: + schedule: + - cron: "30 1 * * 6" # Weekly on Saturdays + push: + branches: [ $default-branch ] + +permissions: read-all + +jobs: + + analysis: + + name: "Scorecards analysis" + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to the code-scanning dashboard. + security-events: write + actions: read + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + + steps: + + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # 2.4.2 + with: + results_file: results.sarif + results_format: sarif + # A read-only PAT token, which is sufficient for the action to function. + # The relevant discussion: https://github.com/ossf/scorecard-action/issues/188 + repo_token: ${{ secrets.GITHUB_TOKEN }} + # Publish the results for public repositories to enable scorecard badges. + # For more details: https://github.com/ossf/scorecard-action#publishing-results + publish_results: true + + - name: "Upload artifact" + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # 3.29.5 + with: + sarif_file: results.sarif From 4a12dd99dc30c7cdd78a2450dd32fd6091c88d1e Mon Sep 17 00:00:00 2001 From: kychen Date: Fri, 8 Aug 2025 11:11:28 +0800 Subject: [PATCH 3/4] feat: add GitHub Packages repository and workflow for publishing --- .../workflows/publish-github-packages.yaml | 20 +++++++++++++++++++ pom.xml | 5 +++++ 2 files changed, 25 insertions(+) create mode 100644 .github/workflows/publish-github-packages.yaml diff --git a/.github/workflows/publish-github-packages.yaml b/.github/workflows/publish-github-packages.yaml new file mode 100644 index 000000000..b01404aca --- /dev/null +++ b/.github/workflows/publish-github-packages.yaml @@ -0,0 +1,20 @@ +name: Publish package to GitHub Packages +on: + release: + types: [created] +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + java-version: '24' + distribution: 'temurin' + - name: Publish package + run: mvn --batch-mode deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pom.xml b/pom.xml index 4db3846eb..c8c2c87e1 100644 --- a/pom.xml +++ b/pom.xml @@ -90,6 +90,11 @@ Apache Commons Beanutils Site scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-beanutils + + github + GitHub Packages + https://maven.pkg.github.com/apache/commons-beanutils + From 4fc6ecf35b692cf49a177734ce57a7490b7d05c8 Mon Sep 17 00:00:00 2001 From: kychen Date: Fri, 8 Aug 2025 11:20:54 +0800 Subject: [PATCH 4/4] chore: update commons.release.version to 1.9.5 and adjust GitHub Actions workflow for default branch --- .github/workflows/maven.yaml | 14 +++++++------- pom.xml | 2 +- .../SuppressPropertiesBeanIntrospector.java | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/maven.yaml b/.github/workflows/maven.yaml index e6a73fdee..7cc818376 100644 --- a/.github/workflows/maven.yaml +++ b/.github/workflows/maven.yaml @@ -17,9 +17,9 @@ name: Java CI on: push: - branches: [ $default-branch ] + branches: [ ${{ github.event.repository.default_branch }} ] pull_request: - branches: [ $default-branch ] + branches: [ ${{ github.event.repository.default_branch }} ] permissions: contents: read @@ -34,10 +34,10 @@ jobs: java: [ 8, 11, 17, 21 ] experimental: [false] include: - - java: 24 - experimental: true - - java: 25-ea - experimental: true + - java: 24 + experimental: true + - java: 25-ea + experimental: true steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -55,6 +55,6 @@ jobs: distribution: 'temurin' java-version: ${{ matrix.java }} - name: Build with Maven - run: mvn --errors --show-version --batch-mode --no-transfer-progress -e + run: mvn --errors --show-version --batch-mode --no-transfer-progress verify -e # For Java 11, you can be more strict: -DadditionalJOption=-Xdoclint/package:-org.apache.commons.configuration2.plist diff --git a/pom.xml b/pom.xml index c8c2c87e1..8f6d44bfa 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ 1.8 1.8 beanutils - 1.9.4 + 1.9.5 BEANUTILS 12310460 diff --git a/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java index 80cb91aa3..dc51ed2dd 100644 --- a/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java +++ b/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java @@ -49,9 +49,9 @@ public class SuppressPropertiesBeanIntrospector implements BeanIntrospector { new SuppressPropertiesBeanIntrospector(Collections.singleton("class")); /** - * A specialized instance which is configured to suppress the special {@code class} properties of Java beans. Unintended access to the call for - * {@code declaringClass} (which is common to all Java {@code enum}) can be a security risk because it also allows access to the class loader. Adding this - * instance as {@code BeanIntrospector} to an instance of {@code PropertyUtilsBean} suppresses the {@code class} property; it can then no longer be + * A specialized instance which is configured to suppress the special {@code declaringClass} property of Java enums. Unintended access to the + * {@code declaringClass} property (which is common to all Java {@code enum}s) can be a security risk because it also allows access to the class loader. Adding this + * instance as {@code BeanIntrospector} to an instance of {@code PropertyUtilsBean} suppresses the {@code declaringClass} property; it can then no longer be * accessed. * * @since 1.9.5