Sekurak - hacking fortinet


        

in fortigate product (a product related to security!). I'm trying to show the problem hoping that maybe you (among others) can learn a bit to write better (more secure) shell scripts.

A bit about...

me

I worked a bit with many shells in different versions on many platforms. To name a few: ksh (korn shell), bash (bourne again shell), at&t sh, zsh, ash, dash. Linux, BSD, Android (I already mentioned linux), iOS (and BSD), some unix-like OS'es, hp-ux, minix, aix. Running in posix and non-posix mode.

fortigate

I have not worked with them much but from what I heard - they offer great (but a bit expensive) products of many kinds.

sekurak

Great trainings, great and professional materials about security topics. If you have some budget for trainings then this is a good option.

The script

Sekurak asked about this script:

#!/bin/s

unalias cd 2> /dev/null

cd /

VERSION=`/bsc/campusMgr/bin/getPlatformVersion`
if [ "$VERSION" == "0" ]
then
?? ?echo "This script is not supported on this version of firmware"
?? ?exit;
fi

/usr/bin/unzip -o /bsc/campusMgr/config/upload.applianceKey        

The vulnerability explained

When decompressing an archive (zip, tar or other) you should make sure where you decompress it to. If you are lucky - there would be a sub-directory created with the content. Worse when your current directory would be filled with thousands of small files. Here problem is even worse (and we are not yet touching any hardcore hacking techniques): the decompressed files could have some path (sometimes absolute).

If the script is executed with root context, no advanced security (ex. SELinux) and in the root directory then we can prepare a special zip file to take control over the system by extracting files to any place in the filesystem.

How to test

I suggest that you run 2 terminals. First one to run the fortigate, second to access terminal on it for tests.

docker run --rm --name forti -it fortinet/fortimanager
# "docker" This runs docker
# "run" and docker "runs" an image
# "--rm" after docker is stopped it should clean after itself
# "--name forti" to call it "forti" so we can easily access it
# "-it" run it with interactive mode so you can try to log in
# "fortinet/fortimanager" is the image from fortigate that we are running        

This runs but asks us for the password... and I have no idea what is it. Let's start second docker command to play with the docker container:

docker exec -u0 -it forti /bin/sh
# "exec" to execute a command within the running container
# "-u0" to run as root :)
# "-it forti" to run it in our already running container
# "/bin/sh" is the command (shell) we would like to start        

And let's start by checking what is the version of our shell

sh-5.0# /bin/sh --versio
GNU bash, version 5.0.11(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>        

Problems with the script

Once we know what is in the FortiOS, we know the script and can play with it... let's see what issues can we find.

#!/bin/sh        

The `#!` at the beginning is called a magic number or a shebang. Before executing any command OS must first check what type of the executable it is. If it is a script - it must be executed within something else. Shebang tells to execute `/bin/sh /path/to/the/script.sh` in our case. Sometimes system doesn't know where `sh` is - this is why you provide a full path (but only when you are sure that the file is there). This is rather OK.

unalias cd > /dev/null        

Command "unalias" removes any aliasses defined in the system just in case if anything was "hiding" the original command "cd". If there is any output (ex. an warning or an error) then it is printed - but we are sending this to nowhere (/dev/null). This should be OK.

VERSION=`/bsc/campusMgr/bin/getPlatformVersion`        

We are defining a new variable called `VERSION`. While it might be better to define this variable using `typeset`, we cannot be sure that script is executed within bash. This is so far a valid code.

The backticks are executing a command `/bsc/campusMgr/bin/getPlatformVersion`. I am not running this on any actual vulnerable machine thus I cannot tell what it does or what is the result here. There are multiple problems with that part already:

  • We are not checking whether the command exists
  • If the command failed - there is no handling for that
  • The command can contain whatever - and would be executed within current context (if executed as root then it runs as root)

But we can assume that we know where it runs and that the binary is there and what it returns.

if [ "$VERSION" == "0" ]        

Multiple issues:

  • Keyword `if` is OK - but you never know where it comes from. Some shells have it as a built-in, some execute an external command for it. Still not an issue. Let's assume we are sure what the `if` is here.
  • The `[` can be anything. Again an unknown thing. Often this is an built-in command (part of the shell), a binary executable (something like `[.exe`) or an alias for `test` command. Let's assume that it runs in bash.
  • Bash has a better option: `[[`. Original shell had an issue with params unfolding (next bullet about it). The `[[` should be safe though. Use of `[` is not OK.
  • The `[ "$VERSION"` would be unfolded, that is rewritten with the value of `VERSION` as a text. Assume that `VERSION="1=0 || rm -rf /"` and then text would be rewritten to....

[ 1 = 0 || rm -rf / ]        

  • Unfolded command might execute something from the value of `VERSION`. If villain can inject something malicious into the `VERSION` then it is simply executed. For that reason you should always avoid `eval` and use `[[` instead of `[` when available.
  • The `"$VERSION"` is partially good, as the value is enclosed within quotes. It is usually better to also enclose the variable name with curly brackets. There can be a separate article about it - for now just remember to use curly brackets in shell variable references.
  • Finally `==` is not a proper string comparison. This is bash regexp match. You can put an "regexp" and compare values that way. Sometimes it is useful to check if ex. `"${variable}" == /dev/*/loop/*`. Here it is not an issue. Also note that some languages have special meaning for similar comparisons (ex. javascript has even `===`).

[ "$VERSION" == "0" ]        

  • The check above is OK (except the problems already listed). Actually the value `"$VERSION"` would be expanded and compared to a regexp which is a string with character "0". Even if the argument on the left would be a number - it would be converted to a string.

then
  echo "This script is not supported on this versin of firmware"
  exit;
fi        

  • When producing error message output - it is usually better to print it to "standard error". This way if any other command tries to read your output - it can be sure that the output is read, not an error message. This is bad.
  • `exit` with no arguments stops current command and returns some status code to the calling process. The status code is the same as previous command - and it was `echo` which likely finished with a success. As a result the command will exit with "0" status code indicating a successful execution. This is bad.
  • Tailing semicolon is an overhead, `fi` is just to close the block. This is OK.

/usr/bin/unzip -o /bsc/campusMgr/config/upload.applianceKey        

  • This calls the `unzip` command. I guess that in this case it is an `unzip` coming from busybox. That shouldn't be an issue.
  • The option `-o` means "overwrite without asking". This is an issue - at least if you are not 100% sure what is in the decompressed file.
  • Finally `/bsc/campusMgr/config/upload.applianceKey` is something that can be missing. Normally you chould check it. If it exists, is non-empty and is readable by current user. Probably not a big issue here.

Preparing an exploit

There is a group of techniques called `tarbomb`. It is related to "tar archives" that could contain full paths and whatever data.

  • A tar archive can contain full or relative path so it can overwrite files in your system.
  • A tar with full (or relative) paths can extract fiels into places it should not do it (ex. to create a new file `/etc/init.d/doSomethingBadAsRootOnStartup`)

In our case this is a zip - it should be more secure.

Let's try to prepare somethign malicious.

sh-5.0# cd /tmp
sh-5.0# mkdir -p tarbomb/etc
sh-5.0# cd /tmp/tarbomb
sh-5.0# echo 'root:$1$TX57bFeO$KkuKrRHWnaa9s46IEuCmi/:19411:0:99999:7::' > ./etc/shadow
sh-5.0# echo 'root:x:0:0:root:/root:/bin/sh' > ./etc/passwd
sh-5.0# zip /tmp/malicious.zip ./etc/passwd ./etc/shadow

sh-5.0# cd /
sh-5.0# unzip /tmp/malicious.zip        

  • We have prepared a malicious tarbomb :)
  • In fact nothing terrible happened - the `/etc/passwd` is not a file - it is a link. We have received an error:

sh-5.0# unzip -o /tmp/malicious.zip
Archive:? /tmp/malicious.zip
unzip: 'etc/passwd' exists but is not a regular file        

Let's make it easier

sh-5.0# rm /etc/passwd
sh-5.0# unzip -o /tmp/malicious.zip
Archive:? /tmp/malicious.zip
? inflating: etc/passw
? inflating: etc/shadow        

Now we have replaced passwords file with our own. The value injected is an MD5 for password `pass`. Kinda. In fact it is still not working... But this is not a hacking instruction but a training material to protect from hacking.

If you have read it all - thanks for your time and patience!

Maciej Waku?a

IT enthusiast... ;)

2 年

And you can find articles about the vulnerability itself on ex. https://www.horizon3.ai/fortinet-fortinac-cve-2022-39952-deep-dive-and-iocs/ (while I tried to focus on security, not hacking the fortinet)

回复

要查看或添加评论,请登录

Maciej Waku?a的更多文章

  • Lista moich tematów do zg??bienia

    Lista moich tematów do zg??bienia

    To taka niekończ?ca si? lista "TODO": Dystrybucja wiadomo?ci Apache ActiveMQ artemis Apache pulsar RabbitMQ message…

  • CODEIUM WindSurf

    CODEIUM WindSurf

    This article would describe my experience with WindSurf so far. Why I love Codeium and WindSurf (and Microsoft a bit…

  • Phishing, o tym jak przest?pcy wp?ywaj? na popraw? bezpieczeństwa w sieci...

    Phishing, o tym jak przest?pcy wp?ywaj? na popraw? bezpieczeństwa w sieci...

    TL;DR: Przest?pcy wymy?laj? nowe sztuczki a my si? przed nimi bronimy buduj?c coraz bezpieczniejszy internet. Wst?p…

  • AMD RX 6700 and "own gh copilot"

    AMD RX 6700 and "own gh copilot"

    Github copilot is very good - but also expensive. I wanted to run a local LLM with VSCode on my graphic card but so far…

  • Optional (JAVA) vs NULL (a billion-dollar blunder)

    Optional (JAVA) vs NULL (a billion-dollar blunder)

    Origin of nothing Imagine a tiny box in your computer code. This box can hold a value, like a number or some text.

  • binary, 2GL, 3GL, 4GL, LLM?

    binary, 2GL, 3GL, 4GL, LLM?

    Introduction This article explores the evolution of programming languages, categorized by generations. Understanding…

  • Should we trust AI? Short answer: No.

    Should we trust AI? Short answer: No.

    This morning (before my younger kid got up) I have asked Google Gemini, OpenAI copilot, Preplexity and Phind (all have…

    1 条评论
  • Common Pitfalls in Agile Transformation

    Common Pitfalls in Agile Transformation

    Introduction Agile methodologies have gained popularity due to their flexibility, adaptability, and focus on delivering…

    3 条评论
  • Logs - What, Why and How

    Logs - What, Why and How

    This article would give you some hints how to write better logs. Initially I wanted to write it as an internal company…

    7 条评论
  • About today's (2023-05-09) github outage :)

    About today's (2023-05-09) github outage :)

    Introduction A colleague asked me if I know what to do about the error with github: github error: failed to push some…

社区洞察