0

All right, folks, you may have seen this infamous quirk to get hold of those values:

query=`echo $QUERY_STRING | sed "s/=/='/g; s/&/';/g; s/$/'/"`
eval $query

If the query string is host=example.com&port=80 it works just fine and you get the values in bash variables host and port. However, you may know that a cleverly crafted query string will cause an arbitrary command to be executed on the server side. I'm looking for a secure replacement or an alternative not using eval. After some research I dug up these alternatives:

read host port <<< $(echo "$QUERY_STRING" | tr '=&' '  ' | cut -d ' ' -f 2,4)
echo $host
echo $port

and

if [[ $QUERY_STRING =~ ^host=([^&]*)\&port=(.*)$ ]]
then
    echo ${BASH_REMATCH[1]}
    echo ${BASH_REMATCH[2]}
else
    echo no match, sorry
fi

Unfortunately these two alternatives only work if the pars come in the order host,port. But they could come in the opposite order. There could also be more than 2 pars, and any order is possible and allowed. So how do you propose to get the values into the appropriate bash vars? Can the above methods be amended? Remember that with n pars there are n! possible orders. With 2 pars there are only 2, but with 3 pars there are already 3! = 6.

I returned to the first method. Can it be made safe to run eval? Can you transform $QUERY_STRING with sed in a way that makes it safe to do eval $query ?

EDIT: Note that this question differs from the other one referred to and is not a duplicate. The emphasis here is on using eval in a safe way. That is not answered in the other thread.

Henrik4
  • 464
  • 3
  • 13
  • @LéaGris See the edit above. – Henrik4 Sep 28 '19 at 14:38
  • There is no such thing as a safe use of `eval`, especially when processing foreign tainted data as query strings. Also the other question and its answers already address the use and dangers of `eval` to parse a CGI query string. – Léa Gris Sep 28 '19 at 15:11
  • @LéaGris No safe use of eval? Prove it! The other thread doesn't address the issue, neither proves nor disproves. – Henrik4 Sep 28 '19 at 17:17

2 Answers2

3

This method is safe. It does not eval or execute the QUERY_STRING. It uses string manipulation to break up the string into pieces:

QUERY_STRING='host=example.com&port=80'

declare -a pairs
IFS='&' read -ra pairs <<<"$QUERY_STRING"

declare -A values
for pair in "${pairs[@]}"; do
    IFS='=' read -r key value <<<"$pair"
    values["$key"]="$value"
done

echo do something with "${values[host]}" and "${values[port]}"

URL "percent decoding" left as an exercise.

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
1

You must avoid executing strings at all time when they come from untrusted sources. Therefore I would strongly suggest never to use eval in Bash do something with a string.

To be really save, I think I would echo the string into a file, use grep to retrieve parts of the string and remove the file afterwards. Always use a directory out of the web root.

#! /bin/bash

MYFILE=$(mktemp)
QUERY_STRING='host=example.com&port=80&host=lepmaxe.moc&port=80'

echo "${QUERY_STRING}" > ${MYFILE}
TMP_ARR=($(grep -Eo '(host|port)[^&]*' ${MYFILE}))

[ ${#TMP_ARR} -gt 0 ]          || exit 1
[ $((${#TMP_ARR} % 2)) -eq 0 ] || exit 1

declare -A ARRAY;
for ((i = 0; i < ${#TMP_ARR[@]}; i+=2)); do
    tmp=$(echo ${TMP_ARR[@]:$((i)):2})
    port=$(echo $tmp | sed -r 's/.*port=([^ ]*).*/\1/')
    host=$(echo $tmp | sed -r 's/.*host=([^ ]*).*/\1/')
    ARRAY[$host]=$port
done

for i in ${!ARRAY[@]}; do
        echo "$i = ${ARRAY[$i]}"
done

rm ${MYFILE}
exit 0

This produces:

lepmaxe.moc = 80
example.com = 80
Bayou
  • 3,293
  • 1
  • 9
  • 22