CVE-2022-23602 (published 2022-02-01T11:15Z)

Nimforum is a lightweight alternative to Discourse written in Nim. In versions prior to 2.2.0 any forum user can create a new thread/post with an include referencing a file local to the host operating system. Nimforum will render the file if able. This can also be done silently by using NimForum\'s post "preview" endpoint. Even if NimForum is running as a non-critical user, the forum.json secrets can be stolen. Version 2.2.0 of NimForum includes patches for this vulnerability. Users are advised to upgrade as soon as is possible. There are no known workarounds for this issue.

Severity: MEDIUM
CVSS v2 base score: 5.5 (AV:N/AC:L/Au:S/C:P/I:P/A:N)
CVSS v3 base score: 8.1, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/nim-lang/Nim
commit_details: repo_url: https://github.com/nim-lang/Nim
author: Dominik Picheta
author_date: 2022-01-29 13:03:01+00:00
msg: Merge pull request from GHSA-ggrq-h43f-3w7m

This fixes a CVE (currently
https://github.com/nim-lang/Nim/security/advisories/GHSA-ggrq-h43f-3w7m)
repository: repo_url: https://github.com/nim-lang/Nim
repo_name: nim-lang/Nim
description: Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
repo_language: Nim
owner: nim-lang
file_changes: filename: docgen.nim
change_type: ModificationType.MODIFY
diff: @@ -282,7 +282,7 @@ proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef,
   result.cache = cache
   result.outDir = conf.outDir.string
   result.isPureRst = isPureRst
-  var options= {roSupportRawDirective, roSupportMarkdown, roPreferMarkdown}
+  var options= {roSupportRawDirective, roSupportMarkdown, roPreferMarkdown, roSandboxDisabled}
   if not isPureRst: options.incl roNimFile
   result.sharedState = newRstSharedState(
       options, filename.string,

programming_language: Python
filename: rst.nim
change_type: ModificationType.MODIFY
diff: @@ -235,6 +235,12 @@ type
                               ## to Markdown) -- implies `roSupportMarkdown`
     roNimFile                 ## set for Nim files where default interpreted
                               ## text role should be :nim:
+    roSandboxDisabled         ## this option enables certain options
+                              ## (e.g. raw, include)
+                              ## which are disabled by default as they can
+                              ## enable users to read arbitrary data and
+                              ## perform XSS if the parser is used in a web
+                              ## app.
 
   RstParseOptions* = set[RstParseOption]
 
@@ -260,7 +266,8 @@ type
     mwBrokenLink = "broken link '$1'",
     mwUnsupportedLanguage = "language '$1' not supported",
     mwUnsupportedField = "field '$1' not supported",
-    mwRstStyle = "RST style: $1"
+    mwRstStyle = "RST style: $1",
+    meSandboxedDirective = "disabled directive: '$1'",
 
   MsgHandler* = proc (filename: string, line, col: int, msgKind: MsgKind,
                        arg: string) {.closure, gcsafe.} ## what to do in case of an error
@@ -315,6 +322,7 @@ const
     ":geek:": "icon_e_geek",
     ":ugeek:": "icon_e_ugeek"
   }
+  SandboxDirAllowlist = ["image", "code", "code-block"]
 
 type
   TokType = enum
@@ -2987,10 +2995,14 @@ proc dirCodeBlock(p: var RstParser, nimExtension = false): PRstNode =
   ##
   ## As an extension this proc will process the ``file`` extension field and if
   ## present will replace the code block with the contents of the referenced
-  ## file.
+  ## file. This behaviour is disabled in sandboxed mode and can be re-enabled
+  ## with the `roSandboxDisabled` flag.
   result = parseDirective(p, rnCodeBlock, {hasArg, hasOptions}, parseLiteralBlock)
   var filename = strip(getFieldValue(result, "file"))
   if filename != "":
+    if roSandboxDisabled notin p.s.options:
+      let tok = p.tok[p.idx-2]
+      rstMessage(p, meSandboxedDirective, "file", tok.line, tok.col)
     var path = p.findRelativeFile(filename)
     if path == "": rstMessage(p, meCannotOpenFile, filename)
     var n = newRstNode(rnLiteralBlock)
@@ -3086,6 +3098,11 @@ proc dirRaw(p: var RstParser): PRstNode =
 
 proc selectDir(p: var RstParser, d: string): PRstNode =
   result = nil
+  let tok = p.tok[p.idx-2] # report on directive in ".. directive::"
+  if roSandboxDisabled notin p.s.options:
+    if d notin SandboxDirAllowlist:
+      rstMessage(p, meSandboxedDirective, d, tok.line, tok.col)
+
   case d
   of "admonition", "attention", "caution": result = dirAdmonition(p, d)
   of "code": result = dirCodeBlock(p)
@@ -3112,7 +3129,6 @@ proc selectDir(p: var RstParser, d: string): PRstNode =
   of "title": result = dirTitle(p)
   of "warning": result = dirAdmonition(p, d)
   else:
-    let tok = p.tok[p.idx-2]  # report on directive in ".. directive::"
     rstMessage(p, meInvalidDirective, d, tok.line, tok.col)
 
 proc prefix(ftnType: FootnoteType): string =

programming_language: Markdown
filename: trstgen.nim
change_type: ModificationType.MODIFY
diff: @@ -47,6 +47,10 @@ proc optionListLabel(opt: string): string =
   opt &
   "</span></tt></div>"
 
+const
+  NoSandboxOpts = {roPreferMarkdown, roSupportMarkdown, roNimFile, roSandboxDisabled}
+
+
 suite "YAML syntax highlighting":
   test "Basics":
     let input = """.. code-block:: yaml
@@ -631,7 +635,9 @@ let x = 1
     let p3 = """<p>Par3 <tt class="docutils literal"><span class="pre">""" & id"value3" & "</span></tt>.</p>"
     let p4 = """<p>Par4 <tt class="docutils literal"><span class="pre">value4</span></tt>.</p>"""
     let expected = p1 & p2 & "\n" & p3 & "\n" & p4
-    check(input.toHtml == expected)
+    check(
+      input.toHtml(NoSandboxOpts) == expected
+    )
 
   test "role directive":
     let input = dedent"""
@@ -642,7 +648,10 @@ let x = 1
          :language: brainhelp
     """
     var warnings = new seq[string]
-    let output = input.toHtml(warnings=warnings)
+    let output = input.toHtml(
+      NoSandboxOpts,
+      warnings=warnings
+    )
     check(warnings[].len == 1 and "language 'brainhelp' not supported" in warnings[0])
 
   test "RST comments":
@@ -1187,7 +1196,9 @@ Test1
       .. tip:: endOf tip
       .. warning:: endOf warning
     """
-    let output0 = input0.toHtml
+    let output0 = input0.toHtml(
+      NoSandboxOpts
+    )
     for a in ["admonition", "attention", "caution", "danger", "error", "hint",
         "important", "note", "tip", "warning" ]:
       doAssert "endOf " & a & "</div>" in output0
@@ -1198,7 +1209,9 @@ Test1
 
       Test paragraph.
     """
-    let output1 = input1.toHtml
+    let output1 = input1.toHtml(
+      NoSandboxOpts
+    )
     doAssert "endOfError</div>" in output1
     doAssert "<p>Test paragraph. </p>" in output1
     doAssert "class=\"admonition admonition-error\"" in output1
@@ -1210,7 +1223,9 @@ Test1
 
       Test paragraph.
     """
-    let output2 = input2.toHtml
+    let output2 = input2.toHtml(
+      NoSandboxOpts
+    )
     doAssert "endOfError Test2p.</div>" in output2
     doAssert "<p>Test paragraph. </p>" in output2
     doAssert "class=\"admonition admonition-error\"" in output2
@@ -1218,7 +1233,9 @@ Test1
     let input3 = dedent """
       .. note:: endOfNote
     """
-    let output3 = input3.toHtml
+    let output3 = input3.toHtml(
+      NoSandboxOpts
+    )
     doAssert "endOfNote</div>" in output3
     doAssert "class=\"admonition admonition-info\"" in output3
 
@@ -1303,7 +1320,9 @@ Test1
 
       That was a transition.
     """
-    let output1 = input1.toHtml
+    let output1 = input1.toHtml(
+      NoSandboxOpts
+    )
     doAssert "<p id=\"target000\""     in output1
     doAssert "<ul id=\"target001\""    in output1
     doAssert "<ol id=\"target002\""    in output1
@@ -1576,3 +1595,15 @@ suite "invalid targets":
         """((<a class="reference external" href="https://nim-lang.org/">Nim</a>))""")
     check("(([Nim](javascript://nim-lang.org/)))".toHtml ==
         """((<a class="reference external" href="">Nim</a>))""")
+
+suite "local file inclusion":
+  test "cannot include files in sandboxed mode":
+    var error = new string
+    discard ".. include:: ./readme.md".toHtml(error=error)
+    check(error[] == "input(1, 11) Error: disabled directive: 'include'")
+
+  test "code-block file directive is disabled":
+    var error = new string
+    discard ".. code-block:: nim\n    :file: ./readme.md".toHtml(error=error)
+    check(error[] == "input(2, 20) Error: disabled directive: 'file'")
+

programming_language: Python