J'ai besoin de créer un fichier de configuration pour mon propre script: voici un exemple:
scénario:
#!/bin/bash
source /home/myuser/test/config
echo "Name=$nam" >&2
echo "Surname=$sur" >&2
Contenu de /home/myuser/test/config
:
nam="Mark"
sur="Brown"
ça marche!
Ma question: est-ce la bonne façon de procéder ou y a-t-il d'autres façons?
source
n'est pas sécurisé car il exécutera du code arbitraire. Cela peut ne pas vous préoccuper, mais si les autorisations de fichiers sont incorrectes, il peut être possible pour un attaquant disposant d'un accès au système de fichiers d'exécuter du code en tant qu'utilisateur privilégié en injectant du code dans un fichier de configuration chargé par un script sécurisé tel qu'un script d'initialisation.
Jusqu'à présent, la meilleure solution que j'ai pu identifier est la solution maladroite de réinvention du volant:
myscript.conf
password=bar
echo rm -rf /
Prompt_COMMAND='echo "Sending your last command $(history 1) to my email"'
hostname=localhost; echo rm -rf /
En utilisant source
, cela exécuterait echo rm -rf /
deux fois, ainsi que modifier l'utilisateur en cours d'exécution $Prompt_COMMAND
. Au lieu de cela, procédez comme suit:
myscript.sh (Bash 4)
#!/bin/bash
typeset -A config # init array
config=( # set default values in config array
[username]="root"
[password]=""
[hostname]="localhost"
)
while read line
do
if echo $line | grep -F = &>/dev/null
then
varname=$(echo "$line" | cut -d '=' -f 1)
config[$varname]=$(echo "$line" | cut -d '=' -f 2-)
fi
done < myscript.conf
echo ${config[username]} # should be loaded from defaults
echo ${config[password]} # should be loaded from config file
echo ${config[hostname]} # includes the "injected" code, but it's fine here
echo ${config[Prompt_COMMAND]} # also respects variables that you may not have
# been looking for, but they're sandboxed inside the $config array
myscript.sh (compatible Mac/Bash 3)
#!/bin/bash
config() {
val=$(grep -E "^$1=" myscript.conf 2>/dev/null || echo "$1=__DEFAULT__" | head -n 1 | cut -d '=' -f 2-)
if [[ $val == __DEFAULT__ ]]
then
case $1 in
username)
echo -n "root"
;;
password)
echo -n ""
;;
hostname)
echo -n "localhost"
;;
esac
else
echo -n $val
fi
}
echo $(config username) # should be loaded from defaults
echo $(config password) # should be loaded from config file
echo $(config hostname) # includes the "injected" code, but it's fine here
echo $(config Prompt_COMMAND) # also respects variables that you may not have
# been looking for, but they're sandboxed inside the $config array
Veuillez répondre si vous trouvez un exploit de sécurité dans mon code.
Voici une version propre et portable compatible avec Bash 3 et plus, sur Mac et Linux.
Il spécifie toutes les valeurs par défaut dans un fichier séparé, pour éviter d'avoir à utiliser une énorme fonction de configuration "par défaut" encombrée et dupliquée dans tous vos scripts Shell. Et il vous permet de choisir entre la lecture avec ou sans repli par défaut:
config.cfg:
myvar=Hello World
config.cfg.defaults:
myvar=Default Value
othervar=Another Variable
config.shlib (c'est une bibliothèque, donc il n'y a pas de ligne Shebang):
config_read_file() {
(grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2-;
}
config_get() {
val="$(config_read_file config.cfg "${1}")";
if [ "${val}" = "__UNDEFINED__" ]; then
val="$(config_read_file config.cfg.defaults "${1}")";
fi
printf -- "%s" "${val}";
}
test.sh (ou tout script où vous souhaitez lire les valeurs de configuration):
#!/usr/bin/env bash
source config.shlib; # load the config library functions
echo "$(config_get myvar)"; # will be found in user-cfg
printf -- "%s\n" "$(config_get myvar)"; # safer way of echoing!
myvar="$(config_get myvar)"; # how to just read a value without echoing
echo "$(config_get othervar)"; # will fall back to defaults
echo "$(config_get bleh)"; # "__UNDEFINED__" since it isn't set anywhere
Explication du script de test:
printf
? Eh bien, c'est quelque chose que vous devez savoir: echo
est une mauvaise commande pour imprimer du texte sur lequel vous n'avez aucun contrôle. Même si vous utilisez des guillemets doubles, il interprétera les indicateurs. Essayez de définir myvar
(dans config.cfg
) Sur -e
Et vous verrez une ligne vide, car echo
pensera que c'est un drapeau. Mais printf
n'a pas ce problème. Le printf --
Dit "imprimez ceci, et n'interprétez rien comme des drapeaux", et le "%s\n"
Dit "formatez la sortie comme une chaîne avec une nouvelle ligne de fin, et enfin le paramètre final est le valeur pour printf à formater.myvar="$(config_get myvar)";
. Si vous allez les imprimer à l'écran, je suggère d'utiliser printf pour être totalement sûr contre toutes les chaînes incompatibles avec l'écho qui peuvent être dans la configuration utilisateur. Mais l'écho est bien si la variable fournie par l'utilisateur n'est pas le premier caractère de la chaîne que vous faites écho, car c'est la seule situation où "flags" pourrait être interprété, donc quelque chose comme echo "foo: $(config_get myvar)";
est sûr, car "foo" ne commence pas par un tiret et indique donc à echo que le reste de la chaîne n'est pas non plus un indicateur pour lui. :-)Analysez le fichier de configuration, ne l'exécutez pas.
J'écris actuellement une application au travail qui utilise une configuration XML extrêmement simple:
<config>
<username>username-or-email</username>
<password>the-password</password>
</config>
Dans le script Shell ("l'application"), voici ce que je fais pour obtenir le nom d'utilisateur (plus ou moins, je l'ai mis dans une fonction Shell):
username="$( xml sel -t -v '/config/username' "$config_file" )"
La commande xml
est XMLStarlet , qui est disponible pour la plupart des Unices.
J'utilise XML car d'autres parties de l'application traitent également des données encodées dans des fichiers XML, donc c'était plus simple.
Si vous préférez JSON, il y a jq
qui est un analyseur JSON Shell facile à utiliser.
Mon fichier de configuration ressemblerait à ceci en JSON:
{
"username": "username-or-email",
"password": "the-password"
}
Et puis j'obtiendrais le nom d'utilisateur dans le script:
username="$( jq -r '.username' "$config_file" )"
La manière la plus courante, efficace et correcte consiste à utiliser source
ou .
comme forme abrégée. Par exemple:
source /home/myuser/test/config
ou
. /home/myuser/test/config
Cependant, il convient de prendre en compte les problèmes de sécurité que l'utilisation d'un fichier de configuration externe supplémentaire peut soulever, étant donné que du code supplémentaire peut être inséré. Pour plus d'informations, y compris sur la façon de détecter et de résoudre ce problème, je recommanderais de jeter un œil à la section 'Secure it' de http://wiki.bash-hackers.org/howto/conffile#secure_it
J'utilise ceci dans mes scripts:
sed_escape() {
sed -e 's/[]\/$*.^[]/\\&/g'
}
cfg_write() { # path, key, value
cfg_delete "$1" "$2"
echo "$2=$3" >> "$1"
}
cfg_read() { # path, key -> value
test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" | sed "s/^$(echo "$2" | sed_escape)=//" | tail -1
}
cfg_delete() { # path, key
test -f "$1" && sed -i "/^$(echo $2 | sed_escape).*$/d" "$1"
}
cfg_haskey() { # path, key
test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" > /dev/null
}
Devrait prendre en charge toutes les combinaisons de caractères, sauf que les clés ne peuvent pas avoir =
en eux, puisque c'est le séparateur. Tout le reste fonctionne.
% cfg_write test.conf mykey myvalue
% cfg_read test.conf mykey
myvalue
% cfg_delete test.conf mykey
% cfg_haskey test.conf mykey || echo "It's not here anymore"
It's not here anymore
En outre, cela est totalement sûr car il n'utilise aucun type de source
/eval
C'est succinct et sûr:
# Read common vars from common.vars
# the incantation here ensures (by env) that only key=value pairs are present
# then declare-ing the result puts those vars in our environment
declare $(env -i `cat common.vars`)
Le -i
garantit que vous obtenez uniquement les variables de common.vars
Mise à jour: une illustration de la sécurité est que
env -i 'touch evil1 foo=omg boo=$(touch evil2)'
Ne produira aucun fichier touché. Testé sur mac avec bash, c'est-à-dire en utilisant bsd env.
Pour mon scénario, source
ou .
allait bien, mais je voulais prendre en charge les variables d'environnement locales (c'est-à-dire FOO=bar myscript.sh
) ayant priorité sur les variables configurées. Je voulais également que le fichier de configuration soit modifiable par l'utilisateur et confortable pour quelqu'un habitué aux fichiers de configuration, et qu'il soit aussi petit/simple que possible, afin de ne pas distraire de l'objectif principal de mon tout petit script.
Voici ce que j'ai trouvé:
CONF=${XDG_CONFIG_HOME:-~/config}/myscript.sh
if [ ! -f $CONF ]; then
cat > $CONF << CONF
VAR1="default value"
CONF
fi
. <(sed 's/^\([^=]\+\) *= *\(.*\)$/\1=${\1:-\2}/' < $CONF)
Essentiellement - il vérifie les définitions des variables (sans être très flexible sur les espaces blancs) et réécrit ces lignes afin que la valeur soit convertie en valeur par défaut pour cette variable, et que la variable ne soit pas modifiée si elle est trouvée, comme le XDG_CONFIG_HOME
variable ci-dessus. Il source cette version modifiée du fichier de configuration et continue.
Les travaux futurs pourraient rendre le script sed
plus robuste, filtrer les lignes qui semblent étranges ou qui ne sont pas des définitions, etc., ne pas casser les commentaires de fin de ligne - mais cela me suffit pour le moment.