Program: CWE-20
Project: CVE-2022-2636
Buggy source:
#!/bin/bash
# info: add system user
# options: USER PASSWORD EMAIL [PACKAGE] [NAME] [LASTNAME]
#
# example: v-add-user admin2 P4$$w@rD bgates@aol.com
#
# This function creates new user account.

#----------------------------------------------------------#
#                Variables & Functions                     #
#----------------------------------------------------------#

# Argument definition
user=$1
password=$2; HIDE=2
email=$3
package=${4-default}
name=$5
# Last name has been added for backward compatibility with WHMCS / Blesta VestaCP Plugins
if [ -n "$6" ]; then 
    name="$name $6";
fi
# Includes
# shellcheck source=/etc/hestiacp/hestia.conf
source /etc/hestiacp/hestia.conf
# shellcheck source=/usr/local/hestia/func/main.sh
source $HESTIA/func/main.sh
# load config file
source_conf "$HESTIA/conf/hestia.conf"


is_user_free() {
    # these names may cause issues with MariaDB/MySQL database names and should be reserved:
    check_sysuser=$(php -r '$reserved_names=array("aria", "aria_log", "mysql", "mysql_upgrade", "ib", "ib_buffer",
 "ddl", "ddl_recovery", "performance"); if(in_array(strtolower($argv[1]), $reserved_names, true)){echo implode(", ", $reserved_names);}' "$user" );
    if [ -n "$check_sysuser" ]; then
        check_result "$E_INVALID" "The user name '$user' is reserved and cannot be used. List of reserved names: $check_sysuser"
        return
    fi
    check_sysuser=$(cut -f 1 -d : /etc/passwd | grep "^$user$" )
    if [ -n "$check_sysuser" ] || [ -e "$USER_DATA" ]; then
        check_result "$E_EXISTS" "user $user exists"
    fi
}

#----------------------------------------------------------#
#                    Verifications                         #
#----------------------------------------------------------#

check_args '3' "$#" 'USER PASSWORD EMAIL [PACKAGE] [NAME] '
is_format_valid 'user' 'email' 'package'
if [ -n "$name" ]; then
    is_format_valid 'name' 
fi

is_user_free "$user"
is_password_valid
is_package_valid

# Perform verification if read-only mode is enabled
check_hestia_demo_mode

#----------------------------------------------------------#
#                       Action                             #
#----------------------------------------------------------#

# Parsing package data
pkg_data=$(cat $HESTIA/data/packages/$package.pkg |egrep -v "TIME|DATE")

# Checking shell
shell_conf=$(echo "$pkg_data" | grep 'SHELL' | cut -f 2 -d \')
shell=$(grep -w "$shell_conf" /etc/shells |head -n1)

# Adding user
/usr/sbin/useradd "$user" -s "$shell" -c "$email" -m -d "$HOMEDIR/$user" -U
check_result $? "user creation failed" "$E_INVALID"

# Adding password
echo "$user:$password" | /usr/sbin/chpasswd

if [ $? -ne 0 ]; then 
    # Delete user on failure
    /usr/sbin/deluser "$user" > /dev/null 2>&1
    echo "Error: Password not accepted due to PAM restrictions"
    exit 2
fi

# Add a general group for normal users created by Hestia
if [ -z "$(grep ^hestia-users: /etc/group)" ]; then
    groupadd --system "hestia-users"
fi

# Add membership to hestia-users group to non-admin users
if [ "$user" = "admin" ]; then
    setfacl -m "g:admin:r-x" "$HOMEDIR/$user"
else
    usermod -a -G "hestia-users" "$user"
    setfacl -m "u:$user:r-x" "$HOMEDIR/$user"
fi
setfacl -m "g:hestia-users:---" "$HOMEDIR/$user"

# Building directory tree
mkdir $HOMEDIR/$user/conf

if [ -n "$WEB_SYSTEM" ]; then
    mkdir $HOMEDIR/$user/conf/web $HOMEDIR/$user/web $HOMEDIR/$user/tmp
    chmod 751 $HOMEDIR/$user/conf/web 
    chmod 700 $HOMEDIR/$user/tmp
    chown $user:$user $HOMEDIR/$user/web $HOMEDIR/$user/tmp
fi

if [ -n "$MAIL_SYSTEM" ]; then
    mkdir $HOMEDIR/$user/conf/mail $HOMEDIR/$user/mail
    chmod 751 $HOMEDIR/$user/mail 
    chmod 755 $HOMEDIR/$user/conf/mail
fi

if [ -n "$DNS_SYSTEM" ]; then
    mkdir $HOMEDIR/$user/conf/dns
    chmod 751 $HOMEDIR/$user/conf/dns
fi 

# Create default writeable folders
mkdir $HOMEDIR/$user/.config \
      $HOMEDIR/$user/.cache \
      $HOMEDIR/$user/.local \
      $HOMEDIR/$user/.composer \
      $HOMEDIR/$user/.vscode-server \
      $HOMEDIR/$user/.ssh \
      $HOMEDIR/$user/.npm

chown $user:$user \
      $HOMEDIR/$user/.config \
      $HOMEDIR/$user/.cache \
      $HOMEDIR/$user/.local \
      $HOMEDIR/$user/.composer \
      $HOMEDIR/$user/.vscode-server \
      $HOMEDIR/$user/.ssh \
      $HOMEDIR/$user/.npm

# Set permissions
chmod a+x $HOMEDIR/$user
chattr +i $HOMEDIR/$user/conf > /dev/null 2>&1

# Adding user dir
mkdir -p  $USER_DATA/ssl $USER_DATA/dns $USER_DATA/mail

# Creating configuration files and pipes
touch $USER_DATA/backup.conf \
    $USER_DATA/history.log \
    $USER_DATA/stats.log \
    $USER_DATA/web.conf \
    $USER_DATA/dns.conf \
    $USER_DATA/mail.conf \
    $USER_DATA/db.conf \
    $USER_DATA/cron.conf

chmod 770 $USER_DATA \
    $USER_DATA/ssl \
    $USER_DATA/dns \
    $USER_DATA/mail

chmod 660 $USER_DATA/backup.conf \
    $USER_DATA/history.log \
    $USER_DATA/stats.log \
    $USER_DATA/web.conf \
    $USER_DATA/dns.conf \
    $USER_DATA/mail.conf \
    $USER_DATA/db.conf \
    $USER_DATA/cron.conf

# Updating queue pipes
echo "$BIN/v-update-user-disk $user" >> $HESTIA/data/queue/disk.pipe
if [ -n "$WEB_SYSTEM" ]; then
    echo "$BIN/v-update-web-domains-traff $user" \
        >> $HESTIA/data/queue/traffic.pipe
    echo "$BIN/v-update-web-domains-disk $user" >> $HESTIA/data/queue/disk.pipe
fi
if [ -n "$MAIL_SYSTEM" ]; then
    echo "$BIN/v-update-mail-domains-disk $user" >> $HESTIA/data/queue/disk.pipe
fi

if [ -n "$DB_SYSTEM" ]; then
    echo "$BIN/v-update-databases-disk $user" >> $HESTIA/data/queue/disk.pipe
fi

# Generating timestamp
time_n_date=$(date +'%T %F')
time=$(echo "$time_n_date" |cut -f 1 -d \ )
date=$(echo "$time_n_date" |cut -f 2 -d \ )

# Filling user config
if [ "$user" != 'admin' ]; then
    ip_avail=$($BIN/v-list-user-ips admin plain |grep -w shared|wc -l)
    u_users=0
else
    ip_avail=0
    u_users=1
fi

echo "NAME='$name'
PACKAGE='$package'
$pkg_data
CONTACT='$email'
CRON_REPORTS='yes'
MD5='$(awk -v user=$user -F : 'user == $1 {print $2}' /etc/shadow)'
RKEY='$(generate_password)'
TWOFA=''
QRCODE=''
PHPCLI=''
ROLE='user'
SUSPENDED='no'
SUSPENDED_USERS='0'
SUSPENDED_WEB='0'
SUSPENDED_DNS='0'
SUSPENDED_MAIL='0'
SUSPENDED_DB='0'
SUSPENDED_CRON='0'
IP_AVAIL='$ip_avail'
IP_OWNED='0'
U_USERS='$u_users'
U_DISK='0'
U_DISK_DIRS='0'
U_DISK_WEB='0'
U_DISK_MAIL='0'
U_DISK_DB='0'
U_BANDWIDTH='0'
U_WEB_DOMAINS='0'
U_WEB_SSL='0'
U_WEB_ALIASES='0'
U_DNS_DOMAINS='0'
U_DNS_RECORDS='0'
U_MAIL_DOMAINS='0'
U_MAIL_DKIM='0'
U_MAIL_ACCOUNTS='0'
U_MAIL_SSL='0'
U_DATABASES='0'
U_CRON_JOBS='0'
U_BACKUPS='0'
LANGUAGE=''
THEME=''
NOTIFICATIONS='no'
PREF_UI_SORT='name'
LOGIN_DISABLED='no'
LOGIN_USE_IPLIST='no'
LOGIN_ALLOW_IPS=''
TIME='$time'
DATE='$date'" > $USER_DATA/user.conf
chmod 660 $USER_DATA/user.conf

# Updating quota
if [ "$DISK_QUOTA" = 'yes' ]; then
    $BIN/v-update-user-quota "$user"
fi

# Updating admin counter
if [ "$user" != 'admin' ]; then
    increase_user_value 'admin' '$U_USERS'
fi

# Run template trigger
if [ -x "$HESTIA/data/packages/$package.sh" ]; then
    $HESTIA/data/packages/$package.sh "$user" "$email" "$name"
fi

# Adding jailed sftp env
$BIN/v-add-user-sftp-jail $user

#----------------------------------------------------------#
#                       Hestia                             #
#----------------------------------------------------------#

# Logging
$BIN/v-log-action "system" "Info" "Users" "User account added (Name: $user)."
$BIN/v-log-action "$user" "Info" "System" "Welcome!"
log_event "$OK" "$ARGUMENTS"

exit


Fixed version (ground truth):
#!/bin/bash
# info: add system user
# options: USER PASSWORD EMAIL [PACKAGE] [NAME] [LASTNAME]
#
# example: v-add-user admin2 P4$$w@rD bgates@aol.com
#
# This function creates new user account.

#----------------------------------------------------------#
#                Variables & Functions                     #
#----------------------------------------------------------#

# Argument definition
user=$1
password=$2; HIDE=2
email=$3
package=${4-default}
name=$5
# Last name has been added for backward compatibility with WHMCS / Blesta VestaCP Plugins
if [ -n "$6" ]; then 
    name="$name $6";
fi
# Includes
# shellcheck source=/etc/hestiacp/hestia.conf
source /etc/hestiacp/hestia.conf
# shellcheck source=/usr/local/hestia/func/main.sh
source $HESTIA/func/main.sh
# load config file
source_conf "$HESTIA/conf/hestia.conf"


is_user_free() {
    # these names may cause issues with MariaDB/MySQL database names and should be reserved:
    # sudo has been added due to Privilege escalation as sudo group has always sudo permission
    check_sysuser=$(php -r '$reserved_names=array("aria", "aria_log", "mysql", "mysql_upgrade", "ib", "ib_buffer",
 "ddl", "ddl_recovery", "performance", "sudo"); if(in_array(strtolower($argv[1]), $reserved_names, true)){echo implode(", ", $reserved_names);}' "$user" );
    if [ -n "$check_sysuser" ]; then
        check_result "$E_INVALID" "The user name '$user' is reserved and cannot be used. List of reserved names: $check_sysuser"
        return
    fi
    check_sysuser=$(cut -f 1 -d : /etc/passwd | grep "^$user$" )
    if [ -n "$check_sysuser" ] || [ -e "$USER_DATA" ]; then
        check_result "$E_EXISTS" "user $user exists"
    fi
}

#----------------------------------------------------------#
#                    Verifications                         #
#----------------------------------------------------------#

check_args '3' "$#" 'USER PASSWORD EMAIL [PACKAGE] [NAME] '
is_format_valid 'user' 'email' 'package'
if [ -n "$name" ]; then
    is_format_valid 'name' 
fi

is_user_free "$user"
is_password_valid
is_package_valid

# Perform verification if read-only mode is enabled
check_hestia_demo_mode

#----------------------------------------------------------#
#                       Action                             #
#----------------------------------------------------------#

# Parsing package data
pkg_data=$(cat $HESTIA/data/packages/$package.pkg |egrep -v "TIME|DATE")

# Checking shell
shell_conf=$(echo "$pkg_data" | grep 'SHELL' | cut -f 2 -d \')
shell=$(grep -w "$shell_conf" /etc/shells |head -n1)

# Adding user
/usr/sbin/useradd "$user" -s "$shell" -c "$email" -m -d "$HOMEDIR/$user" -U
check_result $? "user creation failed" "$E_INVALID"

# Adding password
echo "$user:$password" | /usr/sbin/chpasswd

if [ $? -ne 0 ]; then 
    # Delete user on failure
    /usr/sbin/deluser "$user" > /dev/null 2>&1
    echo "Error: Password not accepted due to PAM restrictions"
    exit 2
fi

# Add a general group for normal users created by Hestia
if [ -z "$(grep ^hestia-users: /etc/group)" ]; then
    groupadd --system "hestia-users"
fi

# Add membership to hestia-users group to non-admin users
if [ "$user" = "admin" ]; then
    setfacl -m "g:admin:r-x" "$HOMEDIR/$user"
else
    usermod -a -G "hestia-users" "$user"
    setfacl -m "u:$user:r-x" "$HOMEDIR/$user"
fi
setfacl -m "g:hestia-users:---" "$HOMEDIR/$user"

# Building directory tree
mkdir $HOMEDIR/$user/conf

if [ -n "$WEB_SYSTEM" ]; then
    mkdir $HOMEDIR/$user/conf/web $HOMEDIR/$user/web $HOMEDIR/$user/tmp
    chmod 751 $HOMEDIR/$user/conf/web 
    chmod 700 $HOMEDIR/$user/tmp
    chown $user:$user $HOMEDIR/$user/web $HOMEDIR/$user/tmp
fi

if [ -n "$MAIL_SYSTEM" ]; then
    mkdir $HOMEDIR/$user/conf/mail $HOMEDIR/$user/mail
    chmod 751 $HOMEDIR/$user/mail 
    chmod 755 $HOMEDIR/$user/conf/mail
fi

if [ -n "$DNS_SYSTEM" ]; then
    mkdir $HOMEDIR/$user/conf/dns
    chmod 751 $HOMEDIR/$user/conf/dns
fi 

# Create default writeable folders
mkdir $HOMEDIR/$user/.config \
      $HOMEDIR/$user/.cache \
      $HOMEDIR/$user/.local \
      $HOMEDIR/$user/.composer \
      $HOMEDIR/$user/.vscode-server \
      $HOMEDIR/$user/.ssh \
      $HOMEDIR/$user/.npm

chown $user:$user \
      $HOMEDIR/$user/.config \
      $HOMEDIR/$user/.cache \
      $HOMEDIR/$user/.local \
      $HOMEDIR/$user/.composer \
      $HOMEDIR/$user/.vscode-server \
      $HOMEDIR/$user/.ssh \
      $HOMEDIR/$user/.npm

# Set permissions
chmod a+x $HOMEDIR/$user
chattr +i $HOMEDIR/$user/conf > /dev/null 2>&1

# Adding user dir
mkdir -p  $USER_DATA/ssl $USER_DATA/dns $USER_DATA/mail

# Creating configuration files and pipes
touch $USER_DATA/backup.conf \
    $USER_DATA/history.log \
    $USER_DATA/stats.log \
    $USER_DATA/web.conf \
    $USER_DATA/dns.conf \
    $USER_DATA/mail.conf \
    $USER_DATA/db.conf \
    $USER_DATA/cron.conf

chmod 770 $USER_DATA \
    $USER_DATA/ssl \
    $USER_DATA/dns \
    $USER_DATA/mail

chmod 660 $USER_DATA/backup.conf \
    $USER_DATA/history.log \
    $USER_DATA/stats.log \
    $USER_DATA/web.conf \
    $USER_DATA/dns.conf \
    $USER_DATA/mail.conf \
    $USER_DATA/db.conf \
    $USER_DATA/cron.conf

# Updating queue pipes
echo "$BIN/v-update-user-disk $user" >> $HESTIA/data/queue/disk.pipe
if [ -n "$WEB_SYSTEM" ]; then
    echo "$BIN/v-update-web-domains-traff $user" \
        >> $HESTIA/data/queue/traffic.pipe
    echo "$BIN/v-update-web-domains-disk $user" >> $HESTIA/data/queue/disk.pipe
fi
if [ -n "$MAIL_SYSTEM" ]; then
    echo "$BIN/v-update-mail-domains-disk $user" >> $HESTIA/data/queue/disk.pipe
fi

if [ -n "$DB_SYSTEM" ]; then
    echo "$BIN/v-update-databases-disk $user" >> $HESTIA/data/queue/disk.pipe
fi

# Generating timestamp
time_n_date=$(date +'%T %F')
time=$(echo "$time_n_date" |cut -f 1 -d \ )
date=$(echo "$time_n_date" |cut -f 2 -d \ )

# Filling user config
if [ "$user" != 'admin' ]; then
    ip_avail=$($BIN/v-list-user-ips admin plain |grep -w shared|wc -l)
    u_users=0
else
    ip_avail=0
    u_users=1
fi

echo "NAME='$name'
PACKAGE='$package'
$pkg_data
CONTACT='$email'
CRON_REPORTS='yes'
MD5='$(awk -v user=$user -F : 'user == $1 {print $2}' /etc/shadow)'
RKEY='$(generate_password)'
TWOFA=''
QRCODE=''
PHPCLI=''
ROLE='user'
SUSPENDED='no'
SUSPENDED_USERS='0'
SUSPENDED_WEB='0'
SUSPENDED_DNS='0'
SUSPENDED_MAIL='0'
SUSPENDED_DB='0'
SUSPENDED_CRON='0'
IP_AVAIL='$ip_avail'
IP_OWNED='0'
U_USERS='$u_users'
U_DISK='0'
U_DISK_DIRS='0'
U_DISK_WEB='0'
U_DISK_MAIL='0'
U_DISK_DB='0'
U_BANDWIDTH='0'
U_WEB_DOMAINS='0'
U_WEB_SSL='0'
U_WEB_ALIASES='0'
U_DNS_DOMAINS='0'
U_DNS_RECORDS='0'
U_MAIL_DOMAINS='0'
U_MAIL_DKIM='0'
U_MAIL_ACCOUNTS='0'
U_MAIL_SSL='0'
U_DATABASES='0'
U_CRON_JOBS='0'
U_BACKUPS='0'
LANGUAGE=''
THEME=''
NOTIFICATIONS='no'
PREF_UI_SORT='name'
LOGIN_DISABLED='no'
LOGIN_USE_IPLIST='no'
LOGIN_ALLOW_IPS=''
TIME='$time'
DATE='$date'" > $USER_DATA/user.conf
chmod 660 $USER_DATA/user.conf

# Updating quota
if [ "$DISK_QUOTA" = 'yes' ]; then
    $BIN/v-update-user-quota "$user"
fi

# Updating admin counter
if [ "$user" != 'admin' ]; then
    increase_user_value 'admin' '$U_USERS'
fi

# Run template trigger
if [ -x "$HESTIA/data/packages/$package.sh" ]; then
    $HESTIA/data/packages/$package.sh "$user" "$email" "$name"
fi

# Adding jailed sftp env
$BIN/v-add-user-sftp-jail $user

#----------------------------------------------------------#
#                       Hestia                             #
#----------------------------------------------------------#

# Logging
$BIN/v-log-action "system" "Info" "Users" "User account added (Name: $user)."
$BIN/v-log-action "$user" "Info" "System" "Welcome!"
log_event "$OK" "$ARGUMENTS"

exit