Skip to content

Instantly share code, notes, and snippets.

@smoser
Last active July 11, 2022 20:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save smoser/8c65b8771d5ab1d99c44c285323dfff6 to your computer and use it in GitHub Desktop.
Save smoser/8c65b8771d5ab1d99c44c285323dfff6 to your computer and use it in GitHub Desktop.
backdoor-image

backdoor-image

Description

backdoor-image can be used to easily add user with passwordless sudo access to a image or a root filesystem.

Operating on an image requires the 'mount-image-callback' tool from cloud-utils. That can be installed on ubuntu via apt-get install -qy cloud-image-utils.

Usage

Usage: backdoor-image [ options ] target

   add a 'backdoor' user to a image or filesystem at 'target'

   options:
      --import-id U      use 'ssh-import-id' to get ssh public keys
                         may be used more than once.
      --force            required to operate on / filesystem
      --password P       set password P, implies --password-auth
      --password-auth    enable password auth
      --pubkeys  F       add public keys from file 'F'
                         default: ~/.ssh/id_rsa.pub unless --password
                         or --import-id specified
      --user      U      use user 'U' (default: 'backdoor')
#!/bin/bash
VERBOSITY=0
TEMP_D=""
DEFAULT_USER="backdoor"
error() { echo "$@" 1>&2; }
Usage() {
cat <<EOF
Usage: ${0##*/} [ options ] target
add a 'backdoor' user to a image or filesystem at 'target'
options:
--import-id U use 'ssh-import-id' to get ssh public keys
may be used more than once.
--force required to operate on / filesystem
--password P set password P, implies --password-auth
--password-auth enable password auth
--pubkeys F add public keys from file 'F'
default: ~/.ssh/id_rsa.pub unless --password
or --import-id specified
--user U use user 'U' (default: '${DEFAULT_USER}')
EOF
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; exit 1; }
cleanup() {
[ -z "${TEMP_D}" -o ! -d "${TEMP_D}" ] || rm -Rf "${TEMP_D}"
}
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
mod_sshd_bool() {
local cfg="$1" kn="$2" target="$3" dry=${4:-false}
local ws=$' \t' msg=""
local match="^\([#]\{0,1\}\)[#$ws]*$kn\([$ws]\+\)\(yes\|no\)"
local cur="" hsh="#"
cur=$(sed -n "s/$match/\1\3/p" "$cfg") ||
{ error "failed to read $cfg"; return 1; }
if [ -n "$cur" ]; then
case "$cur" in
"#$target") msg="uncommenting, '$target' line";;
"#*") msg="uncommenting, changing '${cur#$hsh}' to '$target'";;
"$target") msg="nochange";;
"*") msg="changing '$cur' to '$target'";;
esac
if [ "$msg" = "nochange" ]; then
debug 1 "no change to $cfg necessary"
else
debug 1 "updating $cfg: $msg"
$dry && return
sed -i "s/$match/$kn\2${target}/" "$cfg" ||
{ error "failed to update $cfg"; return 1; }
fi
else
debug 1 "appending entry for '$kn $target' to $cfg"
$dry && return
echo "$kn $target" >> "$cfg" ||
{ error "failed to append entry to $cfg"; return 1; }
fi
return 0
}
test_mod_sshd_cfg() {
local kn="PasswordAuthentication"
echo "#$kn yes" > f1
echo "#$kn no" > f2
echo "$kn yes" > f3
echo "$kn no" > f4
: > f5
for f in f1 f2 f3 f4 f5; do
mod_sshd_bool "$f" PasswordAuthentication yes true
done
}
add_group_ent() {
local group="$1" gid="$2" fgroup="$3" dry="${4:-false}"
local grent="$group:x:$gid:"
if grep -q "^$group:" "$fgroup"; then
debug 1 "remove $group from group file"
$dry || sed -i "/^$group:/d" "$fgroup" ||
{ error "failed to remove user from group"; return 1; }
fi
debug 1 "append entry to group: $grent"
if ! $dry; then
echo "$grent" >> "$fgroup" ||
{ error "failed to update group file"; return 1; }
fi
return 0
}
add_passwd_ent() {
local user="$1" uid="$2" gid="$3" home="$4" fpasswd="$5" dry=${6:-false}
if grep -q "^$user:" "$fpasswd"; then
debug 1 "remove $user from password file"
$dry || sed -i "/^$user:/d" "$fpasswd" ||
{ error "failed to remove user from password file"; return 1; }
fi
local pwent="$user:x:$uid:$gid:backdoor:$home:/bin/bash"
debug 1 "append entry to passwd: $pwent"
if ! $dry; then
echo "$pwent" >> "$fpasswd" ||
{ error "failed to update passwd file"; return 1; }
fi
}
encrypt_pass() {
local pass="$1" fmt="${2-\$6\$}"
enc=$(echo "$pass" |
perl -e '
$p=<STDIN>; chomp($p);
$salt = join "", map { (q(a)..q(z))[rand(26)] } 1 .. 8;
if (${ARGV[0]}) { $salt = "${ARGV[0]}$salt\$"; }
print crypt($p, "$salt") . "\n";' "$fmt") || return
[ -n "${enc}" ] && [ -z "${fmt}" -o "${enc#${fmt}}" != "${fmt}" ] &&
_RET="$enc"
}
add_shadow_ent() {
local user="$1" pass="$2" fshadow="$3" dry="$4"
local encrypt_pre="\$6\$" shent="" encpass="" pwchange=""
# if input was '$6$' format, just use it verbatum
if [ "${pass#${encrypt_pre}}" != "${pass}" ]; then
debug 1 "using encrypted password from cmdline"
encpass="$pass"
else
encrypt_pass "$pass" && encpass="$_RET" ||
{ error "failed to encrypt password"; return 1; }
fi
# pwchange is number of days since 1970
pwchange=$(($(date +"(%Y-1970)*365 + 10#%j")))
shent="$user:$encpass:$pwchange:0:99999:7:::"
if grep -q "^$user:" "$fshadow"; then
debug 1 "remove $user from shadow file"
$dry || sed -i "/^$user:/d" "$fshadow" ||
{ error "failed to remove user from shadow"; return 1; }
fi
debug 1 "append entry to shadow: $shent"
if ! $dry; then
echo "$shent" >> "$fshadow" ||
{ error "failed to update shadow file"; return 1; }
fi
return 0
}
add_sudo_ent() {
local user="$1" mp="$2" dry="$3"
local target="/etc/sudoers.d/99-$user"
local ent="$user ALL=(ALL) NOPASSWD:ALL"
local start="#BACKDOOR_START_${user}"
local end="#BACKDOOR_end_${user}"
local content=$(printf "%s\n%s\n%s\n" "$start" "$ent" "$end")
if [ -f "$mp/etc/lsb-release" ] &&
grep -i lucid -q "$mp/etc/lsb-release"; then
target="/etc/sudoers"
debug 2 "$mp does not seem to support sudoers.d"
debug 1 "add sudoers ($mp,$target): $ent"
if grep -q "^$start$" "$mp/$target"; then
debug 2 "removing $user entry from $target"
if ! $dry; then
sed -i "/^${start}$/,/^${end}$/d" "$target" ||
{ error "failed update $target"; return 1; }
fi
fi
if ! $dry; then
( umask 226 && echo "$content" >> "$mp/$target" ) ||
{ error "failed to add sudoers entry to $target"; return 1; }
fi
else
debug 1 "add sudoers ($mp,$target): $ent"
if ! $dry; then
rm -f "$mp/$target" &&
( umask 226 && echo "$content" > "$mp/$target" ) ||
{ error "failed to add sudoers entry to $target"; return 1; }
fi
fi
}
add_user() {
local user="$1" pass="$2" uid="$3" gid="$4" home="$5"
local rootd="$6" dry="${7:-false}"
local fpasswd="$rootd/etc/passwd" fshadow="$rootd/etc/shadow"
local fgroup="$rootd/etc/group"
[ -f "$fpasswd" ] || { error "no password file"; return 1; }
[ -f "$fshadow" ] || { error "no shadow file"; return 1; }
[ -f "$fgroup" ] || { error "no group file"; return 1; }
local group="$user" f="" t=""
add_passwd_ent "$user" "$uid" "$gid" "$home" "$fpasswd" "$dry" || return 1
add_group_ent "$group" "$gid" "$fgroup" "$dry" || return 1
add_shadow_ent "$user" "$pass" "$fshadow" "$dry" || return 1
debug 1 "create $rootd/home/$user"
if ! $dry; then
mkdir -p "$rootd/home/$user" &&
chown $uid:$gid "$rootd/home/$user" ||
{ error "failed to make home dir"; return 1; }
for f in "$rootd/etc/skel/".* "$rootd/etc/skel/"*; do
[ -e "$f" ] || continue
t="$rootd/home/$user/${f##*/}"
[ ! -e "$t" ] || continue
cp -a "$f" "$t" && chown -R "$uid:$gid" "$t" ||
{ error "failed to copy $f to $t"; return 1; }
done
fi
}
add_user_keys() {
local keys="$1" dir="$2" ownership="$3" dry="${4:-false}"
debug 1 "add ssh keys to $dir with $ownership"
$dry && return
mkdir -p "$dir" &&
cp "$keys" "$dir/authorized_keys" &&
chmod 600 "$dir/authorized_keys" &&
chown "$ownership" "$dir" "$dir/authorized_keys" &&
chmod 700 "$dir" ||
{ error "failed to add user keys"; return 1; }
if [ $VERBOSITY -ge 1 ]; then
debug 1 "added ssh keys:"
sed "s,^,| ," "$keys"
fi
}
gen_ssh_keys() {
local mp="$1" types="${2:-rsa}" dry="${3:-false}"
local ktype="" file="" ftmpl="/etc/ssh/ssh_host_%s_key" out=""
for ktype in $types; do
file=${ftmpl//%s/$ktype}
if [ -f "$mp/$file" ]; then
debug 2 "existing key for $mp/$file"
continue
fi
debug 1 "ssh-keygen -t $ktype -N '' -f '$file' -C backdoor"
$dry && continue
out=$(ssh-keygen -t "$ktype" -N '' -f "$mp/$file" -C backdoor 2>&1) || {
error "$out"
error "failed generate keytype $ktype";
return 1;
}
out=$(ssh-keygen -l -f "$mp/$file")
debug 1 "$out"
done
}
apply_changes() {
local mp="$1" user="$2" password="$3" pwauth="$4" pubkeys="$5"
local dry="${6:-false}"
local home="/home/$user" key=""
local uid="9999" gid="9999"
local sshcfg="$mp/etc/ssh/sshd_config"
[ -f "$sshcfg" ] ||
{ error "$sshcfg did no exist"; return 1; }
key="PubkeyAuthentication"
mod_sshd_bool "$sshcfg" "$key" "yes" "$dry" ||
{ error "failed to set $key to yes"; return 1; }
if $pwauth; then
key="PasswordAuthentication"
mod_sshd_bool "$sshcfg" "$key" "yes" "$dry" ||
{ error "failed to set $key to yes"; return 1; }
fi
gen_ssh_keys "$mp" "rsa" "$dry" || return 1
add_user "$user" "$password" "$uid" "$gid" "$home" "$mp" "$dry" || return 1
[ -z "$pubkeys" ] ||
add_user_keys "$pubkeys" "$mp/$home/.ssh" "$uid:$gid" || return 1
add_sudo_ent "$user" "$mp" "$dry" || return 1
}
main() {
short_opts="hv"
long_opts="help,dry-run,force,import-id:,password:,password-auth,pubkeys:,user:,verbose"
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
bad_Usage
local user="" password="" pwauth=false pubkeys="" import_ids="" dry=false
local target="" pkfile="" force=false
user="${DEFAULT_USER}"
local args=""
args=( "$@" )
unset args[${#args[@]}-1]
while [ $# -ne 0 ]; do
cur=${1}; next=${2};
case "$cur" in
-h|--help) Usage ; exit 0;;
--dry-run) dry=true;;
--force) force=true;;
--import-id)
import_ids="${import_ids:+${import_ids} }$next";
shift;;
--password) password=$next; shift;;
--password-auth) pwauth=true;;
--pubkeys) pubkeys=$next; shift;;
--user) user=$next; shift;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--) shift; break;;
esac
shift;
done
[ $# -ne 0 ] || { bad_Usage "must provide image"; return 1; }
[ $# -ge 2 ] && { bad_Usage "too many arguments: $*"; return 1; }
[ "$(id -u)" = "0" ] ||
{ error "sorry, must be root"; return 1; }
target="$1"
if [ -d "$target" ]; then
if [ "$target" -ef "/" ] && ! $force; then
error "you must specify --force to operate on /"
return 1
fi
elif [ -f "$target" ]; then
local vopt="" mcu="mount-callback-umount"
local mic="mount-image-callback"
if [ ${VERBOSITY} -ge 2 ]; then
vopt="-v"
fi
if command -v "$mic" >/dev/null 2>&1; then
:
elif [ -x "${0%/*}/$mcu" ]; then
PATH="${0%/*}:$PATH"
mic="$mcu"
else
error "No '$mcu' or '$mic' in PATH"
return 1
fi
exec "$mic" $vopt -- "$target" "$0" "${args[@]}" _MOUNTPOINT_
else
[ -f "$target" ] || { error "$target: not a file"; return 1; }
fi
if [ -n "$password" ] && ! which perl >/dev/null 2>&1; then
{ error "perl required for making password"; return 1; }
pwauth=true
fi
{ [ -z "$import_ids" ] || which ssh-import-id >/dev/null 2>&1; } ||
{ error "you do not have ssh-import-id"; return 1; }
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
{ error "failed to make tempdir"; return 1; }
trap cleanup EXIT
pkfile="${TEMP_D}/pubkeys"
if [ -z "$password" -a -z "$pubkeys" -a -z "$import_ids" ]; then
[ -f ~/.ssh/id_rsa.pub ] || {
error "must specify one of --password, --pubkeys, --import-id"
error "either pass an argument or create ~/.ssh/id_rsa.pub"
return 1
}
debug 1 "set pubkeys to ~/.ssh/id_rsa.pub"
pubkeys=$(echo ~/.ssh/id_rsa.pub)
fi
if [ -n "$pubkeys" ]; then
cp "$pubkeys" "$pkfile" ||
{ error "failed to copy $pubkeys"; return 1; }
fi
if [ -n "$import_ids" ]; then
ssh-import-id --output "$pkfile.i" ${import_ids} &&
cat "$pkfile.i" >> "$pkfile" ||
{ error "failed to import ssh users: $import_ids"; return 1; }
fi
[ -f "$pkfile" ] || pkfile=""
apply_changes "$target" "$user" "$password" "$pwauth" "$pkfile"
[ $? -eq 0 ] || { error "failed to apply changes"; return 1; }
error "added user '$user' to $target"
[ -n "$password" ] && error "set password to $password."
$pwauth && error "enabled password auth" ||
error "did not enable password auth"
[ -n "$pubkeys" ] && error "added pubkeys from $pubkeys."
[ -n "$import_ids" ] && error "imported ssh keys for $import_ids"
return 0
}
main "$@"
# vi: ts=4 noexpandtab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment