Merge pull request #6562 from KarlVogel/host_sanitize
authorJo-Philipp Wich <jo@mein.io>
Thu, 16 Aug 2018 13:44:28 +0000 (15:44 +0200)
committerGitHub <noreply@github.com>
Thu, 16 Aug 2018 13:44:28 +0000 (15:44 +0200)
ddns-scripts: sanitize host charset and shell escape characters

1  2 
net/ddns-scripts/files/dynamic_dns_functions.sh
net/ddns-scripts/files/dynamic_dns_updater.sh

index 7128807a4ae2f6c0667ff172c77f1e2976c3924c,4ad9e60ed612e416c97a7d310a92fd8c4a8c43a7..e6706f4c603b5e6b102d7ed2b8ef100b4506bca1
@@@ -21,7 -21,7 +21,7 @@@
  . /lib/functions/network.sh
  
  # GLOBAL VARIABLES #
 -VERSION="2.7.7-2"
 +VERSION="2.7.8-1"
  SECTION_ID=""         # hold config's section name
  VERBOSE=0             # default mode is log to console, but easily changed with parameter
  MYPROG=$(basename $0) # my program call name
@@@ -63,6 -63,12 +63,12 @@@ IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\
  # IPv6       ( ( 0-9a-f  1-4char ":") min 1x) ( ( 0-9a-f  1-4char   )optional) ( (":" 0-9a-f 1-4char  ) min 1x)
  IPV6_REGEX="\(\([0-9A-Fa-f]\{1,4\}:\)\{1,\}\)\(\([0-9A-Fa-f]\{1,4\}\)\{0,1\}\)\(\(:[0-9A-Fa-f]\{1,4\}\)\{1,\}\)"
  
+ # characters that are dangerous to pass to a shell command line
+ SHELL_ESCAPE="[\"\'\`\$\!();><{}?|\[\]\*\\\\]"
+ # dns character set
+ DNS_CHARSET="[@a-zA-Z0-9._-]"
  # detect if called by ddns-lucihelper.sh script, disable retrys (empty variable == false)
  LUCI_HELPER=$(printf %s "$MYPROG" | grep -i "luci")
  
@@@ -224,7 -230,7 +230,7 @@@ stop_daemon_for_all_ddns_sections() 
  #     value +10 will exit the scripts
  # $2..n       text to report
  write_log() {
 -      local __LEVEL __EXIT __CMD __MSG
 +      local __LEVEL __EXIT __CMD __MSG __MSE
        local __TIME=$(date +%H%M%S)
        [ $1 -ge 10 ] && {
                __LEVEL=$(($1-10))
        [ $VERBOSE -gt 0 -o $__EXIT -gt 0 ] && echo -e "$__MSG"
        # write to logfile
        if [ ${use_logfile:-1} -eq 1 -o $VERBOSE -gt 1 ]; then
 -              [ -n "$password" ] && __MSG=$( printf "%s" "$__MSG" | sed -e "s/$password/*password*/g" )
 -              [ -n "$URL_PASS" ] && __MSG=$( printf "%s" "$__MSG" | sed -e "s/$URL_PASS/*URL_PASS*/g" )
 +              if [ -n "$password" ]; then
 +                      # url encode __MSG, password already done
 +                      urlencode __MSE "$__MSG"
 +                      # replace encoded password inside encoded message
 +                      # and url decode (newline was encoded as %00)
 +                      __MSG=$( echo -e "$__MSE" \
 +                              | sed -e "s/$URL_PASS/***PW***/g" \
 +                              | sed -e "s/+/ /g; s/%00/\n/g; s/%/\\\\x/g" | xargs -0 printf "%b" )
 +              fi
                printf "%s\n" "$__MSG" >> $LOGFILE
                # VERBOSE > 1 then NO loop so NO truncate log to $ddns_loglines lines
                [ $VERBOSE -gt 1 ] || sed -i -e :a -e '$q;N;'$ddns_loglines',$D;ba' $LOGFILE
@@@ -474,6 -473,27 +480,27 @@@ timeout() 
        return $status
  }
  
+ # sanitize a variable
+ # $1  variable name
+ # $2  allowed shell pattern
+ # $3  disallowed shell pattern
+ sanitize_variable() {
+       local __VAR=$1
+       eval __VALUE=\$$__VAR
+       local __ALLOWED=$2
+       local __REJECT=$3
+       # removing all allowed should give empty string
+       if [ -n "$__ALLOWED" ]; then
+               [ -z "${__VALUE//$__ALLOWED}" ] || write_log 12 "sanitize on $__VAR found characters outside allowed subset"
+       fi
+       # removing rejected pattern should give the same string as the input
+       if [ -n "$__REJECT" ]; then
+               [ "$__VALUE" = "${__VALUE//$__REJECT}" ] || write_log 12 "sanitize on $__VAR found rejected characters"
+       fi
+ }
  # verify given host and port is connectable
  # $1  Host/IP to verify
  # $2  Port to verify
@@@ -515,7 -535,10 +542,10 @@@ verify_host_port() 
                        __RUNPROG="$NSLOOKUP $__HOST >$DATFILE 2>$ERRFILE"
                fi
                write_log 7 "#> $__RUNPROG"
-               eval $__RUNPROG
+               (
+                       set -o noglob
+                       eval $__RUNPROG
+               )
                __ERR=$?
                # command error
                [ $__ERR -gt 0 ] && {
        if [ -n "$__NCEXT" ]; then      # BusyBox nc compiled with extensions (timeout support)
                __RUNPROG="$__NC -w 1 $__IP $__PORT </dev/null >$DATFILE 2>$ERRFILE"
                write_log 7 "#> $__RUNPROG"
-               eval $__RUNPROG
+               (
+                       set -o noglob
+                       eval $__RUNPROG
+               )
                __ERR=$?
                [ $__ERR -eq 0 ] && return 0
                write_log 3 "Connect error - BusyBox nc (netcat) Error '$__ERR'"
        else            # nc compiled without extensions (no timeout support)
                __RUNPROG="timeout 2 -- $__NC $__IP $__PORT </dev/null >$DATFILE 2>$ERRFILE"
                write_log 7 "#> $__RUNPROG"
-               eval $__RUNPROG
+               (
+                       set -o noglob
+                       eval $__RUNPROG
+               )
                __ERR=$?
                [ $__ERR -eq 0 ] && return 0
                write_log 3 "Connect error - BusyBox nc (netcat) timeout Error '$__ERR'"
@@@ -696,7 -725,7 +732,7 @@@ do_transfer() 
                        local __BINDIP
                        # set correct program to detect IP
                        [ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" || __RUNPROG="network_get_ipaddr6"
-                       eval "$__RUNPROG __BINDIP $bind_network" || \
+                       ( set -o noglob ; eval "$__RUNPROG __BINDIP $bind_network" ) || \
                                write_log 13 "Can not detect local IP using '$__RUNPROG $bind_network' - Error: '$?'"
                        write_log 7 "Force communication via IP '$__BINDIP'"
                        __PROG="$__PROG --bind-address=$__BINDIP"
  
        while : ; do
                write_log 7 "#> $__RUNPROG"
-               eval $__RUNPROG                 # DO transfer
+               (
+                       set -o noglob
+                       eval $__RUNPROG                 # DO transfer
+               )
                __ERR=$?                        # save error code
                [ $__ERR -eq 0 ] && return 0    # no error leave
                [ -n "$LUCI_HELPER" ] && return 1       # no retry if called by LuCI helper script
@@@ -884,7 -916,7 +923,7 @@@ send_update() 
  
                do_transfer "$__URL" || return 1
  
 -              write_log 7 "DDNS Provider answered:\n$(cat $DATFILE)"
 +              write_log 7 "DDNS Provider answered:${N}$(cat $DATFILE)"
  
                [ -z "$UPD_ANSWER" ] && return 0        # not set then ignore
  
@@@ -907,7 -939,7 +946,7 @@@ get_local_ip () 
                        network_flush_cache     # force re-read data from ubus
                        [ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" \
                                            || __RUNPROG="network_get_ipaddr6"
-                       eval "$__RUNPROG __DATA $ip_network" || \
+                       ( set -o noglob ; eval "$__RUNPROG __DATA $ip_network" ) || \
                                write_log 13 "Can not detect local IP using $__RUNPROG '$ip_network' - Error: '$?'"
                        [ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected on network '$ip_network'"
                elif [ -n "$ip_interface" ]; then
                        [ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected on interface '$ip_interface'"
                elif [ -n "$ip_script" ]; then
                        write_log 7 "#> $ip_script >$DATFILE 2>$ERRFILE"
-                       eval $ip_script >$DATFILE 2>$ERRFILE
+                       (
+                               set -o noglob
+                               eval $ip_script >$DATFILE 2>$ERRFILE
+                       )
                        __ERR=$?
                        if [ $__ERR -eq 0 ]; then
                                __DATA=$(cat $DATFILE)
@@@ -1131,7 -1166,10 +1173,10 @@@ get_registered_ip() 
  
        while : ; do
                write_log 7 "#> $__RUNPROG"
-               eval $__RUNPROG
+               (
+                       set -o noglob
+                       eval $__RUNPROG
+               )
                __ERR=$?
                if [ $__ERR -ne 0 ]; then
                        write_log 3 "$__PROG error: '$__ERR'"
@@@ -1204,17 -1242,17 +1249,17 @@@ trap_handler() 
  
        case $1 in
                 0)     if [ $__ERR -eq 0 ]; then
 -                              write_log 5 "PID '$$' exit normal at $(eval $DATE_PROG)\n"
 +                              write_log 5 "PID '$$' exit normal at $(eval $DATE_PROG)${N}"
                        else
 -                              write_log 4 "PID '$$' exit WITH ERROR '$__ERR' at $(eval $DATE_PROG)\n"
 +                              write_log 4 "PID '$$' exit WITH ERROR '$__ERR' at $(eval $DATE_PROG)${N}"
                        fi ;;
                 1)     write_log 6 "PID '$$' received 'SIGHUP' at $(eval $DATE_PROG)"
                        # reload config via starting the script again
                        /usr/lib/ddns/dynamic_dns_updater.sh -v "0" -S "$__SECTIONID" -- start || true
                        exit 0 ;;       # and leave this one
 -               2)     write_log 5 "PID '$$' terminated by 'SIGINT' at $(eval $DATE_PROG)\n";;
 -               3)     write_log 5 "PID '$$' terminated by 'SIGQUIT' at $(eval $DATE_PROG)\n";;
 -              15)     write_log 5 "PID '$$' terminated by 'SIGTERM' at $(eval $DATE_PROG)\n";;
 +               2)     write_log 5 "PID '$$' terminated by 'SIGINT' at $(eval $DATE_PROG)${N}";;
 +               3)     write_log 5 "PID '$$' terminated by 'SIGQUIT' at $(eval $DATE_PROG)${N}";;
 +              15)     write_log 5 "PID '$$' terminated by 'SIGTERM' at $(eval $DATE_PROG)${N}";;
                 *)     write_log 13 "Unhandled signal '$1' in 'trap_handler()'";;
        esac
  
index b84e82920b6a473eded95249d0e49b3b2fca6e45,5c73c12ff137d16a09edd04e13c01857b74a6e2f..2076c0d9245e2c05600529f74006360cf1ab2f63
@@@ -195,13 -195,6 +195,13 @@@ ERR_LAST=$?      # save return code - equal 
  [ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 1 ] && ip_url="http://checkipv6.dyndns.com"
  [ "$ip_source" = "interface" -a -z "$ip_interface" ] && ip_interface="eth1"
  
 +# url encode username (might be email or something like this)
 +# and password (might have special chars for security reason)
 +# and optional parameter "param_enc"
 +[ -n "$username" ] && urlencode URL_USER "$username"
 +[ -n "$password" ] && urlencode URL_PASS "$password"
 +[ -n "$param_enc" ] && urlencode URL_PENC "$param_enc"
 +
  # SECTION_ID does not exists
  [ $ERR_LAST -ne 0 ] && {
        [ $VERBOSE -le 1 ] && VERBOSE=2         # force console out and logfile output
        write_log  7 "************ ************** ************** **************"
        write_log  5 "PID '$$' started at $(eval $DATE_PROG)"
        write_log  7 "ddns version  : $VERSION"
 -      write_log  7 "uci configuration:\n$(uci -q show ddns | grep '=service' | sort)"
 +      write_log  7 "uci configuration:${N}$(uci -q show ddns | grep '=service' | sort)"
        write_log 14 "Service section '$SECTION_ID' not defined"
  }
  
  write_log 7 "************ ************** ************** **************"
  write_log 5 "PID '$$' started at $(eval $DATE_PROG)"
  write_log 7 "ddns version  : $VERSION"
 -write_log 7 "uci configuration:\n$(uci -q show ddns.$SECTION_ID | sort)"
 +write_log 7 "uci configuration:${N}$(uci -q show ddns.$SECTION_ID | sort)"
  # write_log 7 "ddns version  : $(opkg list-installed ddns-scripts | cut -d ' ' -f 3)"
  case $VERBOSE in
        0) write_log  7 "verbose mode  : 0 - run normal, NO console output";;
@@@ -247,6 -240,15 +247,15 @@@ esa
  # without lookup host and possibly other required options we can do nothing for you
  [ -z "$lookup_host" ] && write_log 14 "Service section not configured correctly! Missing 'lookup_host'"
  
+ # verify validity of variables
+ [ -n "$lookup_host" ] && sanitize_variable lookup_host "$DNS_CHARSET" ""
+ [ -n "$dns_server" ] && sanitize_variable dns_server "$DNS_CHARSET" ""
+ [ -n "$domain" ] && sanitize_variable domain "$DNS_CHARSET" ""
+ # Filter shell escape characters, if these are required in the URL, they
+ # can still be passed url encoded
+ [ -n "$param_opt" ] && sanitize_variable param_opt "" "$SHELL_ESCAPE"
  [ -n "$update_url" ] && {
        # only check if update_url is given, update_scripts have to check themselves
        [ -z "$domain" ] && $(echo "$update_url" | grep "\[DOMAIN\]" >/dev/null 2>&1) && \
                write_log 14 "Service section not configured correctly! Missing 'param_opt'"
  }
  
 -# url encode username (might be email or something like this)
 -# and password (might have special chars for security reason)
 -# and optional parameter "param_enc"
 -[ -n "$username" ] && urlencode URL_USER "$username"
 -[ -n "$password" ] && urlencode URL_PASS "$password"
 -[ -n "$param_enc" ] && urlencode URL_PENC "$param_enc"
 -
  # verify ip_source 'script' if script is configured and executable
  if [ "$ip_source" = "script" ]; then
        set -- $ip_script       #handling script with parameters, we need a trick