fix: limit the nesting depth in JSONML

Limit the XML nesting depth for CVE-2022-45688 when using the JsonML transform.
This commit is contained in:
Tamas Perger
2023-02-10 01:46:44 +00:00
parent 2391d248cc
commit a6e412bded
3 changed files with 322 additions and 59 deletions

View File

@@ -27,7 +27,32 @@ public class JSONML {
XMLTokener x,
boolean arrayForm,
JSONArray ja,
boolean keepStrings
boolean keepStrings,
int currentNestingDepth
) throws JSONException {
return parse(x,arrayForm, ja,
keepStrings ? XMLtoJSONMLParserConfiguration.KEEP_STRINGS : XMLtoJSONMLParserConfiguration.ORIGINAL,
currentNestingDepth);
}
/**
* Parse XML values and store them in a JSONArray.
* @param x The XMLTokener containing the source string.
* @param arrayForm true if array form, false if object form.
* @param ja The JSONArray that is containing the current tag or null
* if we are at the outermost level.
* @param config The XML parser configuration:
* XMLtoJSONMLParserConfiguration.ORIGINAL is the default behaviour;
* XMLtoJSONMLParserConfiguration.KEEP_STRINGS means Don't type-convert text nodes and attribute values.
* @return A JSONArray if the value is the outermost tag, otherwise null.
* @throws JSONException if a parsing error occurs
*/
private static Object parse(
XMLTokener x,
boolean arrayForm,
JSONArray ja,
XMLtoJSONMLParserConfiguration config,
int currentNestingDepth
) throws JSONException {
String attribute;
char c;
@@ -152,7 +177,7 @@ public class JSONML {
if (!(token instanceof String)) {
throw x.syntaxError("Missing value");
}
newjo.accumulate(attribute, keepStrings ? ((String)token) :XML.stringToValue((String)token));
newjo.accumulate(attribute, config.isKeepStrings() ? ((String)token) :XML.stringToValue((String)token));
token = null;
} else {
newjo.accumulate(attribute, "");
@@ -181,7 +206,12 @@ public class JSONML {
if (token != XML.GT) {
throw x.syntaxError("Misshaped tag");
}
closeTag = (String)parse(x, arrayForm, newja, keepStrings);
if (currentNestingDepth == config.getMaxNestingDepth()) {
throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
}
closeTag = (String)parse(x, arrayForm, newja, config, currentNestingDepth + 1);
if (closeTag != null) {
if (!closeTag.equals(tagName)) {
throw x.syntaxError("Mismatched '" + tagName +
@@ -203,7 +233,7 @@ public class JSONML {
} else {
if (ja != null) {
ja.put(token instanceof String
? keepStrings ? XML.unescape((String)token) :XML.stringToValue((String)token)
? (config.isKeepStrings() ? XML.unescape((String)token) : XML.stringToValue((String)token))
: token);
}
}
@@ -224,7 +254,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray
*/
public static JSONArray toJSONArray(String string) throws JSONException {
return (JSONArray)parse(new XMLTokener(string), true, null, false);
return (JSONArray)parse(new XMLTokener(string), true, null, XMLtoJSONMLParserConfiguration.ORIGINAL, 0);
}
@@ -235,8 +265,8 @@ public class JSONML {
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child tags.
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* but just leaves it as a string.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param string The source string.
@@ -246,7 +276,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray
*/
public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException {
return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings);
return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings, 0);
}
@@ -257,8 +287,8 @@ public class JSONML {
* attributes, then the second element will be JSONObject containing the
* name/value pairs. If the tag contains children, then strings and
* JSONArrays will represent the child content and tags.
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* As opposed to toJSONArray this method does not attempt to convert
* any text node or attribute value to any type
* but just leaves it as a string.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param x An XMLTokener.
@@ -268,7 +298,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray
*/
public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException {
return (JSONArray)parse(x, true, null, keepStrings);
return (JSONArray)parse(x, true, null, keepStrings, 0);
}
@@ -285,7 +315,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONArray
*/
public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
return (JSONArray)parse(x, true, null, false);
return (JSONArray)parse(x, true, null, false, 0);
}
@@ -303,10 +333,10 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(String string) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, false);
return (JSONObject)parse(new XMLTokener(string), false, null, false, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
@@ -323,10 +353,32 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings);
return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param string The XML source text.
* @param config The XML parser configuration:
* XMLtoJSONMLParserConfiguration.ORIGINAL is the default behaviour;
* XMLtoJSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(String string, XMLtoJSONMLParserConfiguration config) throws JSONException {
return (JSONObject)parse(new XMLTokener(string), false, null, config, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
@@ -341,7 +393,7 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
return (JSONObject)parse(x, false, null, false);
return (JSONObject)parse(x, false, null, false, 0);
}
@@ -361,7 +413,29 @@ public class JSONML {
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException {
return (JSONObject)parse(x, false, null, keepStrings);
return (JSONObject)parse(x, false, null, keepStrings, 0);
}
/**
* Convert a well-formed (but not necessarily valid) XML string into a
* JSONObject using the JsonML transform. Each XML tag is represented as
* a JSONObject with a "tagName" property. If the tag has attributes, then
* the attributes will be in the JSONObject as properties. If the tag
* contains children, the object will have a "childNodes" property which
* will be an array of strings and JsonML JSONObjects.
* Comments, prologs, DTDs, and <pre>{@code &lt;[ [ ]]>}</pre> are ignored.
* @param x An XMLTokener of the XML source text.
* @param config The XML parser configuration:
* XMLtoJSONMLParserConfiguration.ORIGINAL is the default behaviour;
* XMLtoJSONMLParserConfiguration.KEEP_STRINGS means values will not be coerced into boolean
* or numeric values and will instead be left as strings
* @return A JSONObject containing the structured data from the XML string.
* @throws JSONException Thrown on error converting to a JSONObject
*/
public static JSONObject toJSONObject(XMLTokener x, XMLtoJSONMLParserConfiguration config) throws JSONException {
return (JSONObject)parse(x, false, null, config, 0);
}
@@ -442,6 +516,7 @@ public class JSONML {
return sb.toString();
}
/**
* Reverse the JSONML transformation, making an XML text from a JSONObject.
* The JSONObject must contain a "tagName" property. If it has children,