CVE-2023-34620
Description
An issue was discovered hjson thru 3.0.0 allows attackers to cause a denial of service or other unspecified impacts via crafted object that uses cyclic dependencies.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Crafted cyclic dependencies in hjson up to 3.0.0 cause stack overflow denial of service; fixed by adding nesting depth limits.
CVE-2023-34620 describes a denial-of-service vulnerability in the hjson library versions through 3.0.0. The issue stems from the parser's use of recursive function calls to handle nested objects. An attacker can craft an input string containing deeply nested or cyclic dependencies, causing the parser to exceed the call stack and result in a stack overflow (out-of-memory crash) [4].
The vulnerability can be triggered remotely by supplying a maliciously crafted JSON-like string to any application that parses untrusted hjson input. No authentication or special privileges are required; the attacker only needs to deliver the payload via an API endpoint, file upload, or other means. The exploit is straightforward: generating a document with thousands of levels of nesting or mutual references leads to a stack overflow, as demonstrated in the proof-of-concept for the Java implementation [4].
Successful exploitation causes the parser to crash, resulting in a denial of service. The official description also notes potential "other unspecified impacts," though the primary impact is a denial of service [4]. The CVSS score of 7.5 (high) reflects the low attack complexity and network-based attack vector.
Mitigations have been implemented in the form of pull requests across multiple language implementations: introducing a maximum nesting depth (default 10000) and replacing recursive calls with iterative loops [1][2][3]. Users are advised to update to the latest patched versions or apply the fixes manually. No workaround other than limiting input depth is available.
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 products
3- hjson/hjsondescription
- osv-coords2 versions
< 2.11.913-r28+ 1 more
- (no CPE)range: < 2.11.913-r28
- (no CPE)range: < 2.11.913-r28
Patches
21 file changed · +20 −2
src/HJSON/HJSONParser.php+20 −2 modified@@ -11,6 +11,8 @@ class HJSONParser private $ch; // The current character private $escapee = []; private $keepWsc; // keep whitespace + private $maxNestingDepth = 10000; + private $nestingDepth; public function __construct() { @@ -44,6 +46,7 @@ public function parse($source, $options = []) private function resetAt() { + $this->nestingDepth = 0; $this->at = 0; $this->ch = ' '; } @@ -99,9 +102,15 @@ private function value() $this->white(); switch ($this->ch) { case '{': - return $this->object(); + $this->nestingDepth++; + $ret = $this->object(); + $this->nestingDepth--; + return $ret; case '[': - return $this->_array(); + $this->nestingDepth++; + $ret = $this->_array(); + $this->nestingDepth--; + return $ret; case '"': return $this->string('"'); case '\'': @@ -157,6 +166,10 @@ private function _array() // Parse an array value. // assumeing ch === '[' + if ($this->nestingDepth > $this->maxNestingDepth) { + $this->error("Exceeded max depth (".$this->maxNestingDepth.")"); + } + $array = []; $kw = null; $wat = null; @@ -210,6 +223,11 @@ private function _array() private function object($withoutBraces = false) { // Parse an object value. + + if ($this->nestingDepth > $this->maxNestingDepth) { + $this->error("Exceeded max depth (".$this->maxNestingDepth.")"); + } + $key = null; $object = new \stdClass(); $kw = null;
2 files changed · +30 −2
decode.go+20 −0 modified@@ -12,6 +12,9 @@ import ( const maxPointerDepth = 512 +// This limits the max nesting depth to prevent stack overflow. +const maxNestingDepth = 10000 + type commentInfo struct { hasComment bool cmStart int @@ -73,6 +76,7 @@ type hjsonParser struct { structTypeCache map[reflect.Type]structFieldMap willMarshalToJSON bool nodeDestination bool + nestingDepth int } var unmarshalerText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() @@ -95,6 +99,7 @@ func (p *hjsonParser) setComment2(pCm *string, ciA, ciB commentInfo) { func (p *hjsonParser) resetAt() { p.at = 0 + p.nestingDepth = 0 p.next() } @@ -549,6 +554,11 @@ func getElemTyperType(rv reflect.Value, t reflect.Type) reflect.Type { func (p *hjsonParser) readArray(dest reflect.Value, t reflect.Type) (value interface{}, err error) { var node Node + + if p.nestingDepth > maxNestingDepth { + return nil, p.errAt(fmt.Sprintf("Exceeded max depth (%d)", maxNestingDepth)) + } + array := make([]interface{}, 0, 1) // Skip '['. @@ -624,6 +634,11 @@ func (p *hjsonParser) readObject( // Parse an object value. var node Node var elemNode *Node + + if p.nestingDepth > maxNestingDepth { + return nil, p.errAt(fmt.Sprintf("Exceeded max depth (%d)", maxNestingDepth)) + } + object := NewOrderedMap() // If withoutBraces == true we use the input argument ciBefore as @@ -775,9 +790,13 @@ func (p *hjsonParser) readValue(dest reflect.Value, t reflect.Type) (ret interfa // Parse an Hjson value. It could be an object, an array, a string, a number or a word. switch p.ch { case '{': + p.nestingDepth++ ret, err = p.readObject(false, dest, t, ciBefore) + p.nestingDepth-- case '[': + p.nestingDepth++ ret, err = p.readArray(dest, t) + p.nestingDepth-- case '"', '\'': s, err := p.readString(true) if err != nil { @@ -942,6 +961,7 @@ func orderedUnmarshal( structTypeCache: map[reflect.Type]structFieldMap{}, willMarshalToJSON: willMarshalToJSON, nodeDestination: nodeDestination, + nestingDepth: 0, } parser.resetAt() value, err := parser.rootValue(rv)
hjson_test.go+10 −2 modified@@ -65,23 +65,25 @@ func run(t *testing.T, file string) { if err := Unmarshal(testContent, &data); err != nil { if !shouldFail { t.Error(err) - } else { - return } + return } else if shouldFail { t.Error(errors.New(name + " should_fail!")) + return } rjson, rhjson, cm2, cm3 := getResultContent(name) actualHjson, err := Marshal(data) if err != nil { t.Error(err) + return } actualHjson = append(actualHjson, '\n') actualJSON, err := json.MarshalIndent(data, "", " ") if err != nil { t.Error(err) + return } actualJSON = append(actualJSON, '\n') actualJSON = fixJSON(actualJSON) @@ -92,10 +94,12 @@ func run(t *testing.T, file string) { decOpt.WhitespaceAsComments = false if err := UnmarshalWithOptions(testContent, &node, decOpt); err != nil { t.Error(err) + return } actualCm2, err = Marshal(node) if err != nil { t.Error(err) + return } if len(actualCm2) > 0 && actualCm2[len(actualCm2)-1] != '\n' { actualCm2 = append(actualCm2, '\n') @@ -106,10 +110,12 @@ func run(t *testing.T, file string) { var node Node if err := Unmarshal(testContent, &node); err != nil { t.Error(err) + return } actualCm3, err = Marshal(node) if err != nil { t.Error(err) + return } if len(actualCm3) > 0 && actualCm3[len(actualCm3)-1] != '\n' { actualCm3 = append(actualCm3, '\n') @@ -146,6 +152,7 @@ func run(t *testing.T, file string) { err = Unmarshal(actualCm2, &roundTrip) if err != nil { t.Error(err) + return } if !reflect.DeepEqual(data, roundTrip) { t.Errorf("cm2 roundtrip failed!") @@ -163,6 +170,7 @@ func run(t *testing.T, file string) { err = Unmarshal(actualCm3, &roundTrip) if err != nil { t.Error(err) + return } if !reflect.DeepEqual(data, roundTrip) { t.Errorf("cm3 roundtrip failed!")
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
8- github.com/advisories/GHSA-5wfc-hjrc-gq87ghsaADVISORY
- github.com/hjson/hjson-cpp/pull/54ghsa
- github.com/hjson/hjson-go/commit/326599cebc6ef759892f473bf1439b98466a99faghsa
- github.com/hjson/hjson-go/pull/67ghsa
- github.com/hjson/hjson-java/issues/24ghsa
- github.com/hjson/hjson-php/commit/2d1b8b4b158a8d841f3a228f267c7cd84fe5a4faghsa
- github.com/hjson/hjson-php/pull/45ghsa
- nvd.nist.gov/vuln/detail/CVE-2023-34620ghsa
News mentions
0No linked articles in our index yet.