VYPR
High severityNVD Advisory· Published Dec 13, 2022· Updated Apr 22, 2025

CVE-2022-45688

CVE-2022-45688

Description

Hutool-json v5.8.10 XML.toJSONObject stack overflow via deeply nested XML/JSON strings can cause Denial of Service.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Hutool-json v5.8.10 XML.toJSONObject stack overflow via deeply nested XML/JSON strings can cause Denial of Service.

Vulnerability

CVE-2022-45688 describes a stack overflow vulnerability in the XML.toJSONObject component of hutool-json v5.8.10 [1][4]. The root cause is the lack of depth limiting during recursive parsing of deeply nested XML or JSON strings [1][2].

Exploitation

An attacker can craft a payload consisting of deeply nested, self-referential XML elements (e.g., <a><a>...) or equivalent JSON strings [1][2]. When the vulnerable code parses such input, it recurses deeply until the call stack overflows, triggering a StackOverflowError and causing the Java application to crash [1][2].

Impact

Successful exploitation results in a Denial of Service (DoS) condition: the target application becomes unavailable [1][2][4]. No authentication or special privileges are required—merely sending the malicious payload to a service that parses untrusted XML/JSON with hutool-json can trigger the crash [1][2].

Mitigation

The vulnerability affects hutool-json v5.8.10 [1]. A fix was introduced in the upstream stleary/JSON-java repository by limiting the nesting depth during parsing [3]. Users should upgrade hutool-json to a patched version (e.g., 5.8.11 or later) or apply an equivalent workaround [3]. The issue is publicly disclosed with proof-of-concept code [1][2].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
cn.hutool:hutool-jsonMaven
< 5.8.255.8.25
org.json:jsonMaven
< 2023022720230227

Affected products

5

Patches

2
6a2b585de0a3

增加ParseConfig,通过增加maxNestingDepth参数避免StackOverflowError问题,修复CVE-2022-45688漏洞

https://github.com/dromara/hutoolLoolyJan 5, 2024via ghsa
5 files changed · +170 6
  • CHANGELOG.md+1 0 modified
    @@ -15,6 +15,7 @@
     * 【core  】      修复RandomUtil.randomInt,RandomUtil.randomLong边界问题(pr#3450@Github)
     * 【db    】      修复Druid连接池无法设置部分属性问题(issue#I8STFC@Gitee)
     * 【core  】      修复金额转换为英文时缺少 trillion 单位问题(pr#3454@Github)
    +* 【json  】      增加ParseConfig,通过增加maxNestingDepth参数避免StackOverflowError问题,修复CVE-2022-45688漏洞(issue#2748@Github)
     
     -------------------------------------------------------------------------------------------------------------
     # 5.8.24(2023-12-23)
    
  • hutool-json/src/main/java/cn/hutool/json/XML.java+33 0 modified
    @@ -3,6 +3,7 @@
     import cn.hutool.core.util.CharUtil;
     import cn.hutool.json.xml.JSONXMLParser;
     import cn.hutool.json.xml.JSONXMLSerializer;
    +import cn.hutool.json.xml.ParseConfig;
     
     /**
      * 提供静态方法在XML和JSONObject之间转换
    @@ -86,6 +87,22 @@ public static JSONObject toJSONObject(String string, boolean keepStrings) throws
     		return toJSONObject(new JSONObject(), string, keepStrings);
     	}
     
    +	/**
    +	 * 转换XML为JSONObject
    +	 * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
    +	 * Content text may be placed in a "content" member. Comments, prologs, DTDs, and {@code <[ [ ]]>} are ignored.
    +	 * All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document.
    +	 *
    +	 * @param string      XML字符串
    +	 * @param parseConfig XML解析选项
    +	 * @return A JSONObject containing the structured data from the XML string.
    +	 * @throws JSONException Thrown if there is an errors while parsing the string
    +	 * @since 5.8.25
    +	 */
    +	public static JSONObject toJSONObject(final String string, final ParseConfig parseConfig) throws JSONException {
    +		return toJSONObject(new JSONObject(), string, parseConfig);
    +	}
    +
     	/**
     	 * 转换XML为JSONObject
     	 * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
    @@ -102,6 +119,22 @@ public static JSONObject toJSONObject(JSONObject jo, String xmlStr, boolean keep
     		return jo;
     	}
     
    +	/**
    +	 * 转换XML为JSONObject
    +	 * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
    +	 *
    +	 * @param jo          JSONObject
    +	 * @param xmlStr      XML字符串
    +	 * @param parseConfig XML解析选项
    +	 * @return A JSONObject 解析后的JSON对象,与传入的jo为同一对象
    +	 * @throws JSONException 解析异常
    +	 * @since 5.8.25
    +	 */
    +	public static JSONObject toJSONObject(final JSONObject jo, final String xmlStr, final ParseConfig parseConfig) throws JSONException {
    +		JSONXMLParser.parseJSONObject(jo, xmlStr, parseConfig);
    +		return jo;
    +	}
    +
     	/**
     	 * 转换JSONObject为XML
     	 *
    
  • hutool-json/src/main/java/cn/hutool/json/xml/JSONXMLParser.java+29 6 modified
    @@ -24,9 +24,22 @@ public class JSONXMLParser {
     	 * @throws JSONException 解析异常
     	 */
     	public static void parseJSONObject(JSONObject jo, String xmlStr, boolean keepStrings) throws JSONException {
    -		XMLTokener x = new XMLTokener(xmlStr, jo.getConfig());
    +		parseJSONObject(jo, xmlStr, ParseConfig.of().setKeepStrings(keepStrings));
    +	}
    +
    +	/**
    +	 * 转换XML为JSONObject
    +	 * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。
    +	 *
    +	 * @param xmlStr      XML字符串
    +	 * @param jo          JSONObject
    +	 * @param parseConfig 解析选项
    +	 * @throws JSONException 解析异常
    +	 */
    +	public static void parseJSONObject(final JSONObject jo, final String xmlStr, final ParseConfig parseConfig) throws JSONException {
    +		final XMLTokener x = new XMLTokener(xmlStr, jo.getConfig());
     		while (x.more() && x.skipPast("<")) {
    -			parse(x, jo, null, keepStrings);
    +			parse(x, jo, null, parseConfig, 0);
     		}
     	}
     
    @@ -36,10 +49,12 @@ public static void parseJSONObject(JSONObject jo, String xmlStr, boolean keepStr
     	 * @param x       The XMLTokener containing the source string.
     	 * @param context The JSONObject that will include the new material.
     	 * @param name    The tag name.
    +	 * @param parseConfig 解析选项
    +	 * @param currentNestingDepth 当前层级
     	 * @return true if the close tag is processed.
     	 * @throws JSONException JSON异常
     	 */
    -	private static boolean parse(XMLTokener x, JSONObject context, String name, boolean keepStrings) throws JSONException {
    +	private static boolean parse(XMLTokener x, JSONObject context, String name, ParseConfig parseConfig, int currentNestingDepth) throws JSONException {
     		char c;
     		int i;
     		JSONObject jsonobject;
    @@ -112,6 +127,7 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool
     			tagName = (String) token;
     			token = null;
     			jsonobject = new JSONObject();
    +			final boolean keepStrings = parseConfig.isKeepStrings();
     			for (; ; ) {
     				if (token == null) {
     					token = x.nextToken();
    @@ -155,14 +171,21 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool
     							return false;
     						} else if (token instanceof String) {
     							string = (String) token;
    -							if (string.length() > 0) {
    +							if (!string.isEmpty()) {
     								jsonobject.accumulate("content", keepStrings ? token : InternalJSONUtil.stringToValue(string));
     							}
     
     						} else if (token == XML.LT) {
     							// Nested element
    -							if (parse(x, jsonobject, tagName, keepStrings)) {
    -								if (jsonobject.size() == 0) {
    +							// issue#2748 of CVE-2022-45688
    +							final int maxNestingDepth = parseConfig.getMaxNestingDepth();
    +							if (maxNestingDepth > -1 && currentNestingDepth >= maxNestingDepth) {
    +								throw x.syntaxError("Maximum nesting depth of " + maxNestingDepth + " reached");
    +							}
    +
    +							// Nested element
    +							if (parse(x, jsonobject, tagName, parseConfig, currentNestingDepth + 1)) {
    +								if (jsonobject.isEmpty()) {
     									context.accumulate(tagName, "");
     								} else if (jsonobject.size() == 1 && jsonobject.get("content") != null) {
     									context.accumulate(tagName, jsonobject.get("content"));
    
  • hutool-json/src/main/java/cn/hutool/json/xml/ParseConfig.java+88 0 added
    @@ -0,0 +1,88 @@
    +/*
    + * Copyright (c) 2024. looly(loolly@aliyun.com)
    + * Hutool is licensed under Mulan PSL v2.
    + * You can use this software according to the terms and conditions of the Mulan PSL v2.
    + * You may obtain a copy of Mulan PSL v2 at:
    + *          https://license.coscl.org.cn/MulanPSL2
    + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
    + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
    + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
    + * See the Mulan PSL v2 for more details.
    + */
    +
    +package cn.hutool.json.xml;
    +
    +import java.io.Serializable;
    +
    +/**
    + * XML解析为JSON的可选选项<br>
    + * 参考:https://github.com/stleary/JSON-java/blob/master/src/main/java/org/json/ParserConfiguration.java
    + *
    + * @author AylwardJ, Looly
    + */
    +public class ParseConfig implements Serializable {
    +	private static final long serialVersionUID = 1L;
    +
    +	/**
    +	 * 默认最大嵌套深度
    +	 */
    +	public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
    +
    +	/**
    +	 * 创建ParseConfig
    +	 *
    +	 * @return ParseConfig
    +	 */
    +	public static ParseConfig of() {
    +		return new ParseConfig();
    +	}
    +
    +	/**
    +	 * 是否保持值为String类型,如果为{@code false},则尝试转换为对应类型(numeric, boolean, string)
    +	 */
    +	private boolean keepStrings;
    +	/**
    +	 * 最大嵌套深度,用于解析时限制解析层级,当大于这个层级时抛出异常,-1表示无限制
    +	 */
    +	private int maxNestingDepth = -1;
    +
    +	/**
    +	 * 是否保持值为String类型,如果为{@code false},则尝试转换为对应类型(numeric, boolean, string)
    +	 *
    +	 * @return 是否保持值为String类型
    +	 */
    +	public boolean isKeepStrings() {
    +		return keepStrings;
    +	}
    +
    +	/**
    +	 * 设置是否保持值为String类型,如果为{@code false},则尝试转换为对应类型(numeric, boolean, string)
    +	 *
    +	 * @param keepStrings 是否保持值为String类型
    +	 * @return this
    +	 */
    +	public ParseConfig setKeepStrings(final boolean keepStrings) {
    +		this.keepStrings = keepStrings;
    +		return this;
    +	}
    +
    +	/**
    +	 * 获取最大嵌套深度,用于解析时限制解析层级,当大于这个层级时抛出异常,-1表示无限制
    +	 *
    +	 * @return 最大嵌套深度
    +	 */
    +	public int getMaxNestingDepth() {
    +		return maxNestingDepth;
    +	}
    +
    +	/**
    +	 * 设置最大嵌套深度,用于解析时限制解析层级,当大于这个层级时抛出异常,-1表示无限制
    +	 *
    +	 * @param maxNestingDepth 最大嵌套深度
    +	 * @return this
    +	 */
    +	public ParseConfig setMaxNestingDepth(final int maxNestingDepth) {
    +		this.maxNestingDepth = maxNestingDepth;
    +		return this;
    +	}
    +}
    
  • hutool-json/src/test/java/cn/hutool/json/xml/Issue2748Test.java+19 0 added
    @@ -0,0 +1,19 @@
    +package cn.hutool.json.xml;
    +
    +import cn.hutool.core.util.StrUtil;
    +import cn.hutool.json.JSONException;
    +import cn.hutool.json.XML;
    +import org.junit.Assert;
    +import org.junit.Test;
    +
    +public class Issue2748Test {
    +
    +	@Test
    +	public void toJSONObjectTest() {
    +		final String s = StrUtil.repeat("<a>", 600);
    +
    +		Assert.assertThrows(JSONException.class, () -> {
    +			XML.toJSONObject(s, ParseConfig.of().setMaxNestingDepth(512));
    +		});
    +	}
    +}
    
a6e412bded7a

fix: limit the nesting depth in JSONML

https://github.com/stleary/JSON-javaTamas PergerFeb 10, 2023via ghsa
3 files changed · +322 59
  • src/main/java/org/json/JSONML.java+94 19 modified
    @@ -27,7 +27,32 @@ private static Object parse(
             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 @@ private static Object parse(
                                 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 @@ private static Object parse(
                             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 @@ private static Object parse(
                 } 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 @@ private static Object parse(
          * @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 static JSONArray toJSONArray(String string) throws JSONException {
          * 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 static JSONArray toJSONArray(String string) throws JSONException {
          * @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 static JSONArray toJSONArray(String string, boolean keepStrings) throws J
          * 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 static JSONArray toJSONArray(String string, boolean keepStrings) throws J
          * @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 static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JS
          * @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 static JSONArray toJSONArray(XMLTokener x) throws JSONException {
          * @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 static JSONObject toJSONObject(String string) throws JSONException {
          * @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 static JSONObject toJSONObject(String string, boolean keepStrings) throws
          * @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 static JSONObject toJSONObject(XMLTokener x) throws JSONException {
          * @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 static String toString(JSONArray ja) throws JSONException {
             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,
    
  • src/main/java/org/json/XMLtoJSONMLParserConfiguration.java+128 0 added
    @@ -0,0 +1,128 @@
    +package org.json;
    +/*
    +Public Domain.
    +*/
    +
    +/**
    + * Configuration object for the XML to JSONML parser. The configuration is immutable.
    + */
    +@SuppressWarnings({""})
    +public class XMLtoJSONMLParserConfiguration {
    +    /**
    +     * Used to indicate there's no defined limit to the maximum nesting depth when parsing a XML
    +     * document to JSONML.
    +     */
    +    public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
    +
    +    /**
    +     * The default maximum nesting depth when parsing a XML document to JSONML.
    +     */
    +    public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
    +
    +    /** Original Configuration of the XML to JSONML Parser. */
    +    public static final XMLtoJSONMLParserConfiguration ORIGINAL
    +        = new XMLtoJSONMLParserConfiguration();
    +    /** Original configuration of the XML to JSONML Parser except that values are kept as strings. */
    +    public static final XMLtoJSONMLParserConfiguration KEEP_STRINGS
    +        = new XMLtoJSONMLParserConfiguration().withKeepStrings(true);
    +
    +    /**
    +     * When parsing the XML into JSONML, specifies if values should be kept as strings (<code>true</code>), or if
    +     * they should try to be guessed into JSON values (numeric, boolean, string)
    +     */
    +    private boolean keepStrings;
    +
    +    /**
    +     * The maximum nesting depth when parsing a XML document to JSONML.
    +     */
    +    private int maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
    +
    +    /**
    +     * Default parser configuration. Does not keep strings (tries to implicitly convert values).
    +     */
    +    public XMLtoJSONMLParserConfiguration() {
    +        this.keepStrings = false;
    +    }
    +
    +    /**
    +     * Configure the parser string processing and use the default CDATA Tag Name as "content".
    +     * @param keepStrings <code>true</code> to parse all values as string.
    +     *      <code>false</code> to try and convert XML string values into a JSON value.
    +     * @param maxNestingDepth <code>int</code> to limit the nesting depth
    +     */
    +    public XMLtoJSONMLParserConfiguration(final boolean keepStrings, final int maxNestingDepth) {
    +        this.keepStrings = keepStrings;
    +        this.maxNestingDepth = maxNestingDepth;
    +    }
    +
    +    /**
    +     * Provides a new instance of the same configuration.
    +     */
    +    @Override
    +    protected XMLtoJSONMLParserConfiguration clone() {
    +        // future modifications to this method should always ensure a "deep"
    +        // clone in the case of collections. i.e. if a Map is added as a configuration
    +        // item, a new map instance should be created and if possible each value in the
    +        // map should be cloned as well. If the values of the map are known to also
    +        // be immutable, then a shallow clone of the map is acceptable.
    +        return new XMLtoJSONMLParserConfiguration(
    +                this.keepStrings,
    +                this.maxNestingDepth
    +        );
    +    }
    +
    +    /**
    +     * When parsing the XML into JSONML, specifies if values should be kept as strings (<code>true</code>), or if
    +     * they should try to be guessed into JSON values (numeric, boolean, string)
    +     *
    +     * @return The <code>keepStrings</code> configuration value.
    +     */
    +    public boolean isKeepStrings() {
    +        return this.keepStrings;
    +    }
    +
    +    /**
    +     * When parsing the XML into JSONML, specifies if values should be kept as strings (<code>true</code>), or if
    +     * they should try to be guessed into JSON values (numeric, boolean, string)
    +     *
    +     * @param newVal
    +     *      new value to use for the <code>keepStrings</code> configuration option.
    +     *
    +     * @return The existing configuration will not be modified. A new configuration is returned.
    +     */
    +    public XMLtoJSONMLParserConfiguration withKeepStrings(final boolean newVal) {
    +        XMLtoJSONMLParserConfiguration newConfig = this.clone();
    +        newConfig.keepStrings = newVal;
    +        return newConfig;
    +    }
    +
    +    /**
    +     * The maximum nesting depth that the parser will descend before throwing an exception
    +     * when parsing the XML into JSONML.
    +     * @return the maximum nesting depth set for this configuration
    +     */
    +    public int getMaxNestingDepth() {
    +        return maxNestingDepth;
    +    }
    +
    +    /**
    +     * Defines the maximum nesting depth that the parser will descend before throwing an exception
    +     * when parsing the XML into JSONML. The default max nesting depth is 512, which means the parser
    +     * will throw a JsonException if the maximum depth is reached.
    +     * Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
    +     * which means the parses will go as deep as the maximum call stack size allows.
    +     * @param maxNestingDepth the maximum nesting depth allowed to the XML parser
    +     * @return The existing configuration will not be modified. A new configuration is returned.
    +     */
    +    public XMLtoJSONMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
    +        XMLtoJSONMLParserConfiguration newConfig = this.clone();
    +
    +        if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
    +            newConfig.maxNestingDepth = maxNestingDepth;
    +        } else {
    +            newConfig.maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH;
    +        }
    +
    +        return newConfig;
    +    }
    +}
    
  • src/test/java/org/json/junit/JSONMLTest.java+100 40 modified
    @@ -11,19 +11,19 @@
     
     /**
      * Tests for org.json.JSONML.java
    - * 
    + *
      * Certain inputs are expected to result in exceptions. These tests are
      * executed first. JSONML provides an API to:
    - *     Convert an XML string into a JSONArray or a JSONObject. 
    + *     Convert an XML string into a JSONArray or a JSONObject.
      *     Convert a JSONArray or JSONObject into an XML string.
      * Both fromstring and tostring operations operations should be symmetrical
    - * within the limits of JSONML. 
    + * within the limits of JSONML.
      * It should be possible to perform the following operations, which should
      * result in the original string being recovered, within the limits of the
      * underlying classes:
      *  Convert a string -> JSONArray -> string -> JSONObject -> string
      *  Convert a string -> JSONObject -> string -> JSONArray -> string
    - * 
    + *
      */
     public class JSONMLTest {
     
    @@ -56,7 +56,7 @@ public void emptyXMLException() {
     
         /**
          * Attempts to call JSONML.toString() with a null JSONArray.
    -     * Expects a NullPointerException. 
    +     * Expects a NullPointerException.
          */
         @Test(expected=NullPointerException.class)
         public void nullJSONXMLException() {
    @@ -69,7 +69,7 @@ public void nullJSONXMLException() {
     
         /**
          * Attempts to call JSONML.toString() with a null JSONArray.
    -     * Expects a JSONException. 
    +     * Expects a JSONException.
          */
         @Test
         public void emptyJSONXMLException() {
    @@ -125,7 +125,7 @@ public void emptyTagException() {
                 "[\"addresses\","+
                     "{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
                         "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
    -                // this array has no name 
    +                // this array has no name
                     "["+
                         "[\"name\"],"+
                         "[\"nocontent\"],"+
    @@ -180,7 +180,7 @@ public void spaceInTagException() {
         }
     
         /**
    -     * Attempts to transform a malformed XML document 
    +     * Attempts to transform a malformed XML document
          * (element tag has a frontslash) to a JSONArray.\
          * Expects a JSONException
          */
    @@ -191,7 +191,7 @@ public void invalidSlashInTagException() {
              * In this case, the XML is invalid because the 'name' element
              * contains an invalid frontslash.
              */
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                 "   xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
    @@ -216,7 +216,7 @@ public void invalidSlashInTagException() {
          */
         @Test
         public void invalidBangInTagException() {
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                 "   xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
    @@ -246,7 +246,7 @@ public void invalidBangNoCloseInTagException() {
              * In this case, the XML is invalid because an element
              * starts with '!' and has no closing tag
              */
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                 "   xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
    @@ -276,7 +276,7 @@ public void noCloseStartTagException() {
              * In this case, the XML is invalid because an element
              * has no closing '>'.
              */
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                 "   xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
    @@ -306,7 +306,7 @@ public void noCloseEndTagException() {
              * In this case, the XML is invalid because an element
              * has no name after the closing tag '</'.
              */
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                 "   xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
    @@ -336,7 +336,7 @@ public void noCloseEndBraceException() {
              * In this case, the XML is invalid because an element
              * has '>' after the closing tag '</' and name.
              */
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                 "   xsi:noNamespaceSchemaLocation=\"test.xsd\">\n"+
    @@ -364,9 +364,9 @@ public void invalidCDATABangInTagException() {
             /**
              * xmlStr contains XML text which is transformed into a JSONArray.
              * In this case, the XML is invalid because an element
    -         * does not have a complete CDATA string. 
    +         * does not have a complete CDATA string.
              */
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                 "   xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
    @@ -388,7 +388,7 @@ public void invalidCDATABangInTagException() {
         /**
          * Convert an XML document into a JSONArray, then use JSONML.toString()
          * to convert it into a string. This string is then converted back into
    -     * a JSONArray. Both JSONArrays are compared against a control to 
    +     * a JSONArray. Both JSONArrays are compared against a control to
          * confirm the contents.
          */
         @Test
    @@ -405,7 +405,7 @@ public void toJSONArray() {
              * which is used to create a final JSONArray, which is also compared
              * against the expected JSONArray.
              */
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                      "xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
    @@ -414,7 +414,7 @@ public void toJSONArray() {
                          "<nocontent/>>\n"+
                      "</address>\n"+
                 "</addresses>";
    -        String expectedStr = 
    +        String expectedStr =
                 "[\"addresses\","+
                     "{\"xsi:noNamespaceSchemaLocation\":\"test.xsd\","+
                         "\"xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"},"+
    @@ -434,12 +434,12 @@ public void toJSONArray() {
         }
     
         /**
    -     * Convert an XML document into a JSONObject. Use JSONML.toString() to 
    +     * Convert an XML document into a JSONObject. Use JSONML.toString() to
          * convert it back into a string, and then re-convert it into a JSONObject.
          * Both JSONObjects are compared against a control JSONObject to confirm
          * the contents.
          * <p>
    -     * Next convert the XML document into a JSONArray. Use JSONML.toString() to 
    +     * Next convert the XML document into a JSONArray. Use JSONML.toString() to
          * convert it back into a string, and then re-convert it into a JSONArray.
          * Both JSONArrays are compared against a control JSONArray to confirm
          * the contents.
    @@ -452,23 +452,23 @@ public void toJSONObjectToJSONArray() {
             /**
              * xmlStr contains XML text which is transformed into a JSONObject,
              * restored to XML, transformed into a JSONArray, and then restored
    -         * to XML again. Both JSONObject and JSONArray should contain the same 
    +         * to XML again. Both JSONObject and JSONArray should contain the same
              * information and should produce the same XML, allowing for non-ordered
              * attributes.
    -         * 
    +         *
              * Transformation to JSONObject:
              *      The elementName is stored as a string where key="tagName"
              *      Attributes are simply stored as key/value pairs
              *      If the element has either content or child elements, they are stored
              *      in a jsonArray with key="childNodes".
    -         * 
    +         *
              * Transformation to JSONArray:
              *      1st entry = elementname
              *      2nd entry = attributes object (if present)
              *      3rd entry = content (if present)
              *      4th entry = child element JSONArrays (if present)
              */
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<addresses xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+
                     "xsi:noNamespaceSchemaLocation='test.xsd'>\n"+
    @@ -585,7 +585,7 @@ public void toJSONObjectToJSONArray() {
                     "\"tagName\":\"addresses\""+
                 "}";
     
    -        String expectedJSONArrayStr = 
    +        String expectedJSONArrayStr =
                 "["+
                     "\"addresses\","+
                     "{"+
    @@ -645,12 +645,12 @@ public void toJSONObjectToJSONArray() {
             JSONObject finalJsonObject = JSONML.toJSONObject(jsonObjectXmlToStr);
             Util.compareActualVsExpectedJsonObjects(finalJsonObject, expectedJsonObject);
     
    -        // create a JSON array from the original string and make sure it 
    +        // create a JSON array from the original string and make sure it
             // looks as expected
             JSONArray jsonArray = JSONML.toJSONArray(xmlStr);
             JSONArray expectedJsonArray = new JSONArray(expectedJSONArrayStr);
             Util.compareActualVsExpectedJsonArrays(jsonArray,expectedJsonArray);
    -    
    +
             // restore the XML, then make another JSONArray and make sure it
             // looks as expected
             String jsonArrayXmlToStr = JSONML.toString(jsonArray);
    @@ -668,14 +668,14 @@ public void toJSONObjectToJSONArray() {
          * Convert an XML document which contains embedded comments into
          * a JSONArray. Use JSONML.toString() to turn it into a string, then
          * reconvert it into a JSONArray. Compare both JSONArrays to a control
    -     * JSONArray to confirm the contents. 
    +     * JSONArray to confirm the contents.
          * <p>
          * This test shows how XML comments are handled.
          */
         @Test
         public void commentsInXML() {
     
    -        String xmlStr = 
    +        String xmlStr =
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
                 "<!-- this is a comment -->\n"+
                 "<addresses>\n"+
    @@ -734,7 +734,7 @@ public void testToJSONArray_reversibility2() {
             final String expectedJsonString = "[\"root\",[\"id\",\"01\"],[\"id\",\"1\"],[\"id\",\"00\"],[\"id\",\"0\"],[\"item\",{\"id\":\"01\"}],[\"title\",\"True\"]]";
             final JSONArray json = JSONML.toJSONArray(originalXml,true);
             assertEquals(expectedJsonString, json.toString());
    -        
    +
             final String reverseXml = JSONML.toString(json);
             assertEquals(originalXml, reverseXml);
         }
    @@ -749,7 +749,7 @@ public void testToJSONArray_reversibility3() {
             final String revertedXml = JSONML.toString(jsonArray);
             assertEquals(revertedXml, originalXml);
         }
    -    
    +
         /**
          * JSON string cannot be reverted to original xml. See test result in
          * comment below.
    @@ -770,7 +770,7 @@ public void testToJSONObject_reversibility() {
     // 1. Our XML parser does not handle generic HTML entities, only valid XML entities. Hence &nbsp;
     //     or other HTML specific entities would fail on reversability
     // 2. Our JSON implementation for storing the XML attributes uses the standard unordered map.
    -//     This means that <tag attr1="v1" attr2="v2" /> can not be reversed reliably.  
    +//     This means that <tag attr1="v1" attr2="v2" /> can not be reversed reliably.
     //
     //    /**
     //     * Test texts taken from jsonml.org. Currently our implementation FAILS this conversion but shouldn't.
    @@ -783,13 +783,13 @@ public void testToJSONObject_reversibility() {
     //        final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"\u00A0\",[\"span\",{ \"style\" : \"background-color:maroon\" },\"\u00A9\"],\"\u00A0\"]]]";
     //        final JSONArray json = JSONML.toJSONArray(originalXml,true);
     //        final String actualJsonString = json.toString();
    -//        
    +//
     //        final String reverseXml = JSONML.toString(json);
     //        assertNotEquals(originalXml, reverseXml);
     //
     //        assertNotEquals(expectedJsonString, actualJsonString);
     //    }
    -//    
    +//
     //    /**
     //     * Test texts taken from jsonml.org but modified to have XML entities only.
     //     */
    @@ -799,15 +799,15 @@ public void testToJSONObject_reversibility() {
     //        final String expectedJsonString = "[\"table\",{\"class\" : \"MyTable\",\"style\" : \"background-color:yellow\"},[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#550758\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:red\"},\"Example text here\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#993101\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:green\"},\"127624015\"]],[\"tr\",[\"td\",{\"class\" : \"MyTD\",\"style\" : \"border:1px solid black\"},\"#E33D87\"],[\"td\",{\"class\" : \"MyTD\",\"style\" : \"background-color:blue\"},\"&\",[\"span\",{ \"style\" : \"background-color:maroon\" },\">\"],\"<\"]]]";
     //        final JSONArray jsonML = JSONML.toJSONArray(originalXml,true);
     //        final String actualJsonString = jsonML.toString();
    -//        
    +//
     //        final String reverseXml = JSONML.toString(jsonML);
     //        // currently not equal because the hashing of the attribute objects makes the attribute
    -//        // order not happen the same way twice 
    +//        // order not happen the same way twice
     //        assertEquals(originalXml, reverseXml);
     //
     //        assertEquals(expectedJsonString, actualJsonString);
     //    }
    -    
    +
         @Test (timeout = 6000)
         public void testIssue484InfinteLoop1() {
             try {
    @@ -819,11 +819,11 @@ public void testIssue484InfinteLoop1() {
                         ex.getMessage());
             }
         }
    -    
    +
         @Test (timeout = 6000)
         public void testIssue484InfinteLoop2() {
             try {
    -            String input = "??*\n" + 
    +            String input = "??*\n" +
                         "??|?CglR??`??>?w??PIlr??D?$?-?o??O?*??{OD?Y??`2a????NM?bq?:O?>S$?J?B.gUK?m\b??zE???!v]???????c??????h???s???g???`?qbi??:Zl?)?}1^??k?0??:$V?$?Ovs(}J??????2;gQ????Tg?K?`?h%c?hmGA?<!C*?9?~?t?)??,zA???S}?Q??.q?j????]";
              JSONML.toJSONObject(input);
                 fail("Exception expected for invalid JSON.");
    @@ -833,4 +833,64 @@ public void testIssue484InfinteLoop2() {
                         ex.getMessage());
             }
         }
    +
    +    @Test
    +    public void testMaxNestingDepthOf42IsRespected() {
    +        final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", "<a>");
    +
    +        final int maxNestingDepth = 42;
    +
    +        try {
    +            JSONML.toJSONObject(wayTooLongMalformedXML, XMLtoJSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
    +
    +            fail("Expecting a JSONException");
    +        } catch (JSONException e) {
    +            assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
    +                e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
    +        }
    +    }
    +
    +    @Test
    +    public void testMaxNestingDepthIsRespectedWithValidXML() {
    +        final String perfectlyFineXML = "<Test>\n" +
    +            "  <employee>\n" +
    +            "    <name>sonoo</name>\n" +
    +            "    <salary>56000</salary>\n" +
    +            "    <married>true</married>\n" +
    +            "  </employee>\n" +
    +            "</Test>\n";
    +
    +        final int maxNestingDepth = 1;
    +
    +        try {
    +            JSONML.toJSONObject(perfectlyFineXML, XMLtoJSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
    +
    +            fail("Expecting a JSONException");
    +        } catch (JSONException e) {
    +            assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">",
    +                e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth));
    +        }
    +    }
    +
    +    @Test
    +    public void testMaxNestingDepthWithValidFittingXML() {
    +        final String perfectlyFineXML = "<Test>\n" +
    +            "  <employee>\n" +
    +            "    <name>sonoo</name>\n" +
    +            "    <salary>56000</salary>\n" +
    +            "    <married>true</married>\n" +
    +            "  </employee>\n" +
    +            "</Test>\n";
    +
    +        final int maxNestingDepth = 3;
    +
    +        try {
    +            JSONML.toJSONObject(perfectlyFineXML, XMLtoJSONMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth));
    +        } catch (JSONException e) {
    +            e.printStackTrace();
    +            fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " +
    +                "parameter of the XMLtoJSONMLParserConfiguration used");
    +        }
    +    }
    +
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.