GHSA-X49M-3CW7-GQ5Q
Vulnerability from github – Published: 2023-06-23 21:44 – Updated: 2023-06-26 16:31Summary
A configuration injection happens when user input is considered by the application in an unsanitized format and can reach the configuration file. A malicious user may craft a special payload that may lead to a command injection.
PoC
The vulnerable code snippet is /jcvi/apps/base.py#LL2227C1-L2228C41. Under some circumstances a user input is retrieved and stored within the fullpath variable which reaches the configuration file ~/.jcvirc.
fullpath = input(msg).strip()
config.set(PATH, name, fullpath)
I ripped a part of the codebase into a runnable PoC as follows. All the PoC does is call the getpath() function under some circumstances.
from configparser import (
ConfigParser,
RawConfigParser,
NoOptionError,
NoSectionError,
ParsingError,
)
import errno
import os
import sys
import os.path as op
import shutil
import signal
import sys
import logging
def is_exe(fpath):
return op.isfile(fpath) and os.access(fpath, os.X_OK)
def which(program):
"""
Emulates the unix which command.
>>> which("cat")
"/bin/cat"
>>> which("nosuchprogram")
"""
fpath, fname = op.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = op.join(path, program)
if is_exe(exe_file):
return exe_file
return None
def getpath(cmd, name=None, url=None, cfg="~/.jcvirc", warn="exit"):
"""
Get install locations of common binaries
First, check ~/.jcvirc file to get the full path
If not present, ask on the console and store
"""
p = which(cmd) # if in PATH, just returns it
if p:
return p
PATH = "Path"
config = RawConfigParser()
cfg = op.expanduser(cfg)
changed = False
if op.exists(cfg):
config.read(cfg)
assert name is not None, "Need a program name"
try:
fullpath = config.get(PATH, name)
except NoSectionError:
config.add_section(PATH)
changed = True
try:
fullpath = config.get(PATH, name)
except NoOptionError:
msg = "=== Configure path for {0} ===\n".format(name, cfg)
if url:
msg += "URL: {0}\n".format(url)
msg += "[Directory that contains `{0}`]: ".format(cmd)
fullpath = input(msg).strip()
config.set(PATH, name, fullpath)
changed = True
path = op.join(op.expanduser(fullpath), cmd)
if warn == "exit":
try:
assert is_exe(path), "***ERROR: Cannot execute binary `{0}`. ".format(path)
except AssertionError as e:
sys.exit("{0!s}Please verify and rerun.".format(e))
if changed:
configfile = open(cfg, "w")
config.write(configfile)
logging.debug("Configuration written to `{0}`.".format(cfg))
return path
# Call to getpath
path = getpath("not-part-of-path", name="CLUSTALW2", warn="warn")
print(path)
To run the PoC, you need to remove the config file ~/.jcvirc to emulate the first run,
# Run the PoC with the payload
echo -e "e\rvvvvvvvv = zzzzzzzz\n" | python3 poc.py

You can notice the random key/value characters vvvvvvvv = zzzzzzzz were successfully injected.
Impact
The impact of a configuration injection may vary. Under some conditions, it may lead to command injection if there is for instance shell code execution from the configuration file values.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "jcvi"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "1.3.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2023-35932"
],
"database_specific": {
"cwe_ids": [
"CWE-1284",
"CWE-77"
],
"github_reviewed": true,
"github_reviewed_at": "2023-06-23T21:44:35Z",
"nvd_published_at": "2023-06-23T22:15:08Z",
"severity": "HIGH"
},
"details": "### Summary\nA configuration injection happens when user input is considered by the application in an unsanitized format and can reach the configuration file. A malicious user may craft a special payload that may lead to a command injection.\n\n### PoC\n\nThe vulnerable code snippet is [/jcvi/apps/base.py#LL2227C1-L2228C41](https://github.com/tanghaibao/jcvi/blob/cede6c65c8e7603cb266bc3395ac8f915ea9eac7/jcvi/apps/base.py#LL2227C1-L2228C41). Under some circumstances a user input is retrieved and stored within the `fullpath` variable which reaches the configuration file `~/.jcvirc`.\n\n```python\n fullpath = input(msg).strip()\n config.set(PATH, name, fullpath)\n```\n\nI ripped a part of the codebase into a runnable PoC as follows. All the PoC does is call the `getpath()` function under some circumstances.\n\n```python\nfrom configparser import (\n ConfigParser,\n RawConfigParser,\n NoOptionError,\n NoSectionError,\n ParsingError,\n)\n\nimport errno\nimport os\nimport sys\nimport os.path as op\nimport shutil\nimport signal\nimport sys\nimport logging\n\n\ndef is_exe(fpath):\n return op.isfile(fpath) and os.access(fpath, os.X_OK)\n\n\ndef which(program):\n \"\"\"\n Emulates the unix which command.\n\n \u003e\u003e\u003e which(\"cat\")\n \"/bin/cat\"\n \u003e\u003e\u003e which(\"nosuchprogram\")\n \"\"\"\n fpath, fname = op.split(program)\n if fpath:\n if is_exe(program):\n return program\n else:\n for path in os.environ[\"PATH\"].split(os.pathsep):\n exe_file = op.join(path, program)\n if is_exe(exe_file):\n return exe_file\n\n return None\n\n\ndef getpath(cmd, name=None, url=None, cfg=\"~/.jcvirc\", warn=\"exit\"):\n \"\"\"\n Get install locations of common binaries\n First, check ~/.jcvirc file to get the full path\n If not present, ask on the console and store\n \"\"\"\n p = which(cmd) # if in PATH, just returns it\n if p:\n return p\n\n PATH = \"Path\"\n config = RawConfigParser()\n cfg = op.expanduser(cfg)\n changed = False\n if op.exists(cfg):\n config.read(cfg)\n\n assert name is not None, \"Need a program name\"\n\n try:\n fullpath = config.get(PATH, name)\n except NoSectionError:\n config.add_section(PATH)\n changed = True\n\n try:\n fullpath = config.get(PATH, name)\n except NoOptionError:\n msg = \"=== Configure path for {0} ===\\n\".format(name, cfg)\n if url:\n msg += \"URL: {0}\\n\".format(url)\n msg += \"[Directory that contains `{0}`]: \".format(cmd)\n fullpath = input(msg).strip()\n config.set(PATH, name, fullpath)\n changed = True\n\n path = op.join(op.expanduser(fullpath), cmd)\n if warn == \"exit\":\n try:\n assert is_exe(path), \"***ERROR: Cannot execute binary `{0}`. \".format(path)\n except AssertionError as e:\n sys.exit(\"{0!s}Please verify and rerun.\".format(e))\n\n if changed:\n configfile = open(cfg, \"w\")\n config.write(configfile)\n logging.debug(\"Configuration written to `{0}`.\".format(cfg))\n\n return path\n\n\n# Call to getpath\npath = getpath(\"not-part-of-path\", name=\"CLUSTALW2\", warn=\"warn\")\nprint(path)\n\n```\n\nTo run the PoC, you need to remove the config file `~/.jcvirc` to emulate the first run, \n\n```bash\n# Run the PoC with the payload\necho -e \"e\\rvvvvvvvv = zzzzzzzz\\n\" | python3 poc.py\n```\n\n\n\nYou can notice the random key/value characters `vvvvvvvv = zzzzzzzz` were successfully injected.\n\n### Impact\n\nThe impact of a configuration injection may vary. Under some conditions, it may lead to command injection if there is for instance shell code execution from the configuration file values.\n",
"id": "GHSA-x49m-3cw7-gq5q",
"modified": "2023-06-26T16:31:23Z",
"published": "2023-06-23T21:44:35Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/tanghaibao/jcvi/security/advisories/GHSA-x49m-3cw7-gq5q"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-35932"
},
{
"type": "PACKAGE",
"url": "https://github.com/tanghaibao/jcvi"
},
{
"type": "WEB",
"url": "https://github.com/tanghaibao/jcvi/blob/cede6c65c8e7603cb266bc3395ac8f915ea9eac7/jcvi/apps/base.py#LL2227C1-L2228C41"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:L",
"type": "CVSS_V3"
}
],
"summary": "jcvi vulnerable to Configuration Injection due to unsanitized user input "
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.