diff --git a/.github/badges/branches.svg b/.github/badges/branches.svg deleted file mode 100644 index a491f0b..0000000 --- a/.github/badges/branches.svg +++ /dev/null @@ -1 +0,0 @@ -branches67.8% \ No newline at end of file diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg deleted file mode 100644 index 4d8f34e..0000000 --- a/.github/badges/jacoco.svg +++ /dev/null @@ -1 +0,0 @@ -coverage84.1% \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4f4b4c..43dfa47 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,29 +15,4 @@ jobs: distribution: 'adopt' - name: "Build with Maven" - run: mvn -Pci verify - - - name: "Generate Coverage Badge" - id: jacoco - uses: cicirello/jacoco-badge-generator@v2 - with: - generate-branches-badge: true - - - name: "Log coverage percentage" - run: | - echo "coverage = ${{ steps.jacoco.outputs.coverage }}" - echo "branch coverage = ${{ steps.jacoco.outputs.branches }}" - - name: "Commit the JaCoCo badge (if it changed)" - run: | - if [[ `git status --porcelain` ]]; then - git config --global user.name 'Jacoco Coverage Update Action' - git config --global user.email 'pixee@users.noreply.github.com' - git add -A - git commit -m "Autogenerated JaCoCo coverage badge" - git push - fi - - name: "Upload JaCoCo coverage report" - uses: actions/upload-artifact@v2 - with: - name: jacoco-report - path: target/site/jacoco/ + run: mvn -Pci verify \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9823d5a..d9af093 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Compiled class file *.class +.DS_Store + # Log file *.log diff --git a/pom.xml b/pom.xml index 5d1001c..34646bf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.github.pixee java-security-toolkit - 1.0.7 + 1.0.8 java-security-toolkit a library with common security controls @@ -58,17 +58,16 @@ 3.0.1 0.8.7 - 2.9.1 + 3.6.0 1.6.13 - - + - com.coverity.security - coverity-escapers - 1.1.1 + commons-io + commons-io + 2.13.0 @@ -78,24 +77,18 @@ 2.1 - - - commons-io - commons-io - 2.11.0 - - - org.codehaus.mojo - animal-sniffer-annotations - 1.23 - true - commons-fileupload commons-fileupload 1.5 test + + + commons-io + commons-io + + org.junit.jupiter @@ -162,30 +155,9 @@ maven-compiler-plugin ${versions.maven-compiler-plugin} - 8 - 8 - - - - org.codehaus.mojo - animal-sniffer-maven-plugin - 1.23 - - - org.codehaus.mojo.signature - java18 - 1.0 - + 11 + 11 - - - animal-sniffer - test - - check - - - org.jacoco @@ -276,10 +248,6 @@ maven-compiler-plugin - - org.codehaus.mojo - animal-sniffer-maven-plugin - org.jacoco jacoco-maven-plugin diff --git a/src/main/java/io/github/pixee/security/HtmlEncoder.java b/src/main/java/io/github/pixee/security/HtmlEncoder.java index 6e5e072..0a71f18 100644 --- a/src/main/java/io/github/pixee/security/HtmlEncoder.java +++ b/src/main/java/io/github/pixee/security/HtmlEncoder.java @@ -1,7 +1,5 @@ package io.github.pixee.security; -import com.coverity.security.Escape; - /** * This type exposes helper methods that will help defend against XSS attacks with HTML encoding. * @@ -21,4 +19,292 @@ private HtmlEncoder() {} public static String encode(final String s) { return Escape.html(s); } + + /* + * This code was originally brought in as the BSD-2 licensed dependency from Coverity. However, it didn't publish any automatic module name, and we didn't really need any of the rest of it, so we just copied the code here, including the license. + */ + + /** + * Copyright (c) 2012-2016, Coverity, Inc. All rights reserved. + * + *

Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: - Redistributions of source code must + * retain the above copyright notice, this list of conditions and the following disclaimer. - + * Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. - Neither the name of Coverity, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without specific prior + * written permission from Coverity, Inc. + * + *

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND INFRINGEMENT ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /** + * Escape is a small set of methods for escaping tainted data. These escaping methods are useful + * in transforming user-controlled ("tainted") data into forms that are safe from being + * interpreted as something other than data, such as JavaScript. + * + *

At this time most of these escaping routines focus on cross-site scripting mitigations. Each + * method is good for a different HTML context. For a primer on HTML contexts, see OWASP's XSS + * Prevention Cheat Sheet (note however that the escaping routines are not implemented exactly + * according to OWASP's recommendations) or the Coverity Security Advisor documentation. Also see + * the Coverity Security Research Laboratory blog on how to properly use each function. + * + *

While Coverity's static analysis product references these escaping routines as exemplars and + * understands their behavior, there is no dependency on Coverity products and these routines are + * completely standalone. Feel free to use them! Just make sure you use them correctly. + * + * @author Romain Gaucher + * @author Andy Chou + * @author Jon Passki + * @author Alex Kouzemtchenko + */ + private static class Escape { + + /** + * HTML entity escaping for text content and attributes. + * + *

HTML entity escaping that is appropriate for the most common HTML contexts: PCDATA and + * "normal" attributes (non-URI, non-event, and non-CSS attributes).
+ * Note that we do not recommend using non-quoted HTML attributes since the security obligations + * vary more between web browser. We recommend to always quote (single or double quotes) HTML + * attributes.
+ * This method is generic to HTML entity escaping, and therefore escapes more characters than + * usually necessary -- mostly to handle non-quoted attribute values. If this method is somehow + * too slow, such as you output megabytes of text with spaces, please use the {@link + * #htmlText(String)} method which only escape HTML text specific characters. + * + *

The following characters are escaped: + * + *

    + *
  • HTML characters: ' (U+0022), " (U+0027), \ (U+005C) + * , / (U+002F), < (U+003C), > (U+003E) + * , & (U+0026) + *
  • Control characters: \t (U+0009), \n (U+000A), + * \f (U+000C), \r (U+000D), SPACE (U+0020) + *
  • Unicode newlines: LS (U+2028), PS (U+2029) + *
+ * + * @param input the string to be escaped + * @return the HTML escaped string or null if input is null + * @since 1.0 + */ + private static String html(String input) { + if (input == null) return null; + + int length = input.length(); + StringBuilder output = allocateStringBuilder(length); + + for (int i = 0; i < length; i++) { + char c = input.charAt(i); + switch (c) { + // Control chars + case '\t': + output.append(" "); + break; + case '\n': + output.append(" "); + break; + case '\f': + output.append(" "); + break; + case '\r': + output.append(" "); + break; + // Chars that have a meaning for HTML + case '\'': + output.append("'"); + break; + case '\\': + output.append("\"); + break; + case ' ': + output.append(" "); + break; + case '/': + output.append("/"); + break; + case '"': + output.append("""); + break; + case '<': + output.append("<"); + break; + case '>': + output.append(">"); + break; + case '&': + output.append("&"); + break; + // Unicode new lines + case '\u2028': + output.append("
"); + break; + case '\u2029': + output.append("
"); + break; + + default: + output.append(c); + break; + } + } + return output.toString(); + } + + /** + * URI encoder. + * + *

URI encoding for query string values of the URI: + * /example/?name=URI_ENCODED_VALUE_HERE
+ * Note that this method is not sufficient to protect for cross-site scripting in a generic URI + * context, but only for query string values. If you need to escape a URI in an href + * attribute (for example), ensure that: + * + *

    + *
  • The scheme is allowed (restrict to http, https, or mailto) + *
  • Use the HTML escaper {@link #html(String)} on the entire URI + *
+ * + * This URI encoder processes the following characters: + * + *
    + *
  • URI characters: ' (U+0022), " (U+0027), \ (U+005C) + * , / (U+002F), < (U+003C), > (U+003E) + * , & (U+0026), < (U+003C), > (U+003E) + * , ! (U+0021), # (U+0023), $ (U+0024), + * % (U+0025), ( (U+0028), ) (U+0029), + * * (U+002A), + (U+002B), , (U+002C), . (U+002E) + * , : (U+003A), ; (U+003B), = (U+003D), + * ? (U+003F), @ (U+0040), [ (U+005B), + * ] (U+005D) + *
  • Control characters: \t (U+0009), \n (U+000A), + * \f (U+000C), \r (U+000D), SPACE (U+0020) + *
+ * + * @param input the string to be escaped + * @return the URI encoded string or null if input is null + * @since 1.0 + */ + private static String uriParam(String input) { + if (input == null) return null; + + int length = input.length(); + StringBuilder output = allocateStringBuilder(length); + + for (int i = 0; i < length; i++) { + char c = input.charAt(i); + switch (c) { + // Control chars + case '\t': + output.append("%09"); + break; + case '\n': + output.append("%0A"); + break; + case '\f': + output.append("%0C"); + break; + case '\r': + output.append("%0D"); + break; + // RFC chars to encode, plus % ' " < and >, and space + case ' ': + output.append("%20"); + break; + case '!': + output.append("%21"); + break; + case '"': + output.append("%22"); + break; + case '#': + output.append("%23"); + break; + case '$': + output.append("%24"); + break; + case '%': + output.append("%25"); + break; + case '&': + output.append("%26"); + break; + case '\'': + output.append("%27"); + break; + case '(': + output.append("%28"); + break; + case ')': + output.append("%29"); + break; + case '*': + output.append("%2A"); + break; + case '+': + output.append("%2B"); + break; + case ',': + output.append("%2C"); + break; + case '.': + output.append("%2E"); + break; + case '/': + output.append("%2F"); + break; + case ':': + output.append("%3A"); + break; + case ';': + output.append("%3B"); + break; + case '<': + output.append("%3C"); + break; + case '=': + output.append("%3D"); + break; + case '>': + output.append("%3E"); + break; + case '?': + output.append("%3F"); + break; + case '@': + output.append("%40"); + break; + case '[': + output.append("%5B"); + break; + case ']': + output.append("%5D"); + break; + + default: + output.append(c); + break; + } + } + return output.toString(); + } + + /** Compute the allocation size of the StringBuilder based on the length. */ + private static StringBuilder allocateStringBuilder(int length) { + // Allocate enough temporary buffer space to avoid reallocation in most + // cases. If you believe you will output large amount of data at once + // you might need to change the factor. + int buflen = length; + if (length * 2 > 0) buflen = length * 2; + return new StringBuilder(buflen); + } + } } diff --git a/src/main/java/io/github/pixee/security/ObjectInputFilters.java b/src/main/java/io/github/pixee/security/ObjectInputFilters.java index 9f2d7ab..58b42ae 100644 --- a/src/main/java/io/github/pixee/security/ObjectInputFilters.java +++ b/src/main/java/io/github/pixee/security/ObjectInputFilters.java @@ -6,7 +6,6 @@ import java.io.ObjectInputStream; import java.util.Objects; import org.apache.commons.io.serialization.ValidatingObjectInputStream; -import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; /** * This type exposes helper methods that will help defend against Java deserialization attacks. @@ -15,7 +14,6 @@ * href="https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html">OWASP * Cheat Sheet. */ -@IgnoreJRERequirement public final class ObjectInputFilters { private ObjectInputFilters() {} @@ -80,7 +78,6 @@ public static ObjectInputFilter createCombinedHardenedObjectFilter( return new CombinedObjectInputFilter(existingFilter); } - @IgnoreJRERequirement private static class CombinedObjectInputFilter implements ObjectInputFilter { private final ObjectInputFilter originalFilter; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..38e9e30 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,10 @@ +/** Export our package so that it can be used by other modules. */ +open module io.github.pixee.security { + exports io.github.pixee.security; + exports io.github.pixee.security.jakarta; + + requires org.apache.commons.io; + requires java.xml; + requires java.desktop; + requires java.base; +}