ctf-writeups-page
  • 🚩teebow1e's CTF write-ups
  • Challenges I created
    • Page 1
  • 2023
    • NahamCon CTF
      • Museum
      • Obligatory
      • Star Wars
      • Hidden Figures
    • DownUnderCTF 2023
      • misc challenges
Powered by GitBook
On this page
  • My First C Program!
  • SimpleFTPServer
  • baby ruby
  • Rougeful Helper
  1. 2023
  2. DownUnderCTF 2023

misc challenges

PreviousDownUnderCTF 2023

Last updated 1 year ago

My First C Program!

After having solved this challenge, I learned that the syntax used in this C file is actually a real programming language.

Once you read the readme, it will all make sense why the syntax is so weird.

Flag:

DUCTF{I_D0nT_Th1nk_th15_1s_R34L_C} - yes I think so

SimpleFTPServer

Enumeration

Connecting using the netcat command, I was greeted with the text 220 vsFTPd (v2.3.4) ready..., which turn out to be red-herring.

Entering gibberish returns error, however the error indicates that the server is running Python.

Although I am unable to login using the USER and PASS command, I soon realized they are all red-herring since I can list the directory with LIST and change directory with CWD.

Using RETR to retrieve a file on flag.txt, I received a fake flag:

and accessing pwn give us access to the source code of the app:

#!/usr/bin/env python3
# Adapted from: https://gist.github.com/ZoeS17/467387af22de19c028f0430dcfc5ada8#file-ftpserver-py-L83
# FTP spec comments borrowed from Wikipedia

import os,time,operator,sys
allow_delete = False
local_ip = '0.0.0.0'
local_port = 1337
currdir = os.path.abspath('.')
ENCODING = "utf-8"

f = open("/chal/flag.txt", "r")
FLAG = f.read()
f.close()

f = open("/chal/pwn", "r")
SOURCE_CODE = f.read()
f.close()

class FTPServerThread():
    def __init__(self):
        self.basewd=currdir
        self.cwd=self.basewd
        self.rest=False
        self.pasv_mode=False
        self.mode='A'

    def run(self):
        sys.stdout.buffer.write('220 vsFTPd (v2.3.4) ready...\r\n'.encode(ENCODING)) # Red Herring

        while True:
            sys.stdout.flush()
            recv=sys.stdin.buffer.readline()
            if not recv: break
            else:
                lst = recv.decode(ENCODING).strip().split(" ", 1)
                if len(lst) < 2:
                    cmd, args = lst[0], ""
                else:
                    cmd, args = lst
                try:
                    func=operator.attrgetter(cmd)(self)
                    msg = func(args)
                    sys.stdout.buffer.write(f'{msg}'.encode(ENCODING))
                except Exception as e:
                    sys.stdout.buffer.write(f'500 Sorry. {e}\r\n'.encode(ENCODING))

    def SYST(self,args):
        '''
        Return system type.
        '''
        return '215 UNIX Type: L8\r\n'

    def OPTS(self,args):
        '''
        RFC 2389        Select options for a feature (for example OPTS UTF8 ON).
        '''
        if args.upper()=='UTF8 ON':
            return '200 OK.\r\n'
        else:
            return '451 Sorry.\r\n'

    def USER(self,args):
        '''
        Authentication username.
        '''
        return '331 OK.\r\n'

    def PASS(self,args):
        '''
        Authentication password.
        '''
        # return '230 OK.\r\n'
        return '530 Incorrect.\r\n' # Red Herring

    def QUIT(self,args):
        '''
        Disconnect.
        '''
        sys.stdout.buffer.write('221 Goodbye.\r\n'.encode(ENCODING))
        exit()

    def NOOP(self,args):
        '''
        No operation (dummy packet; used mostly on keepalives).
        '''
        return '200 OK.\r\n'

    def TYPE(self,args):
        '''
        Sets the transfer mode
        '''
        if args == "A":
            self.mode=args
            return '200 ASCII mode.\r\n'
        if args == "I":
            self.mode=args
            return '200 Binary mode.\r\n'
        return '504 Command not implemented for that parameter.\r\n'

    def CDUP(self,args):
        '''
        Change to Parent Directory.
        '''
        if not os.path.samefile(self.cwd,self.basewd):
            #learn from stackoverflow
            self.cwd=os.path.abspath(os.path.join(self.cwd,'..'))
        return '200 OK.\r\n'

    def PWD(self,args):
        '''
        Print working directory. Returns the current directory of the host.
        '''
        cwd=os.path.relpath(self.cwd,self.basewd)
        if cwd=='.':
            cwd='/'
        else:
            cwd='/'+cwd
        return '257 \"%s\"\r\n' % cwd

    def CWD(self,args):
        '''
        RFC 697         Change working directory.
        '''
        chwd=args
        if chwd=='/':
            self.cwd=self.basewd
        elif chwd[0]=='/':
            self.cwd=os.path.join(self.basewd,chwd[1:])
        else:
            self.cwd=os.path.join(self.cwd,chwd)
        return '250 OK.\r\n'

    def PORT(self,args):
        '''
        Specifies an address and port to which the server should connect.
        '''
        return '501 Syntax error in parameters or argument.\r\n'

    def PASV(self,args):
        '''
        Enter passive mode.
        '''
        return '227 Entering Passive Mode (%s,%u,%u).\r\n' % ("ip_here", "port_here", "port_here") # Hint that this isn't a real server

    def LIST(self,args): # Red Herring, you see the flag.txt but get it
        sys.stdout.buffer.write(b'150 Here comes the directory listing.\r\n')
        for t in os.listdir(self.cwd):
            k=self.toListItem(os.path.join(self.cwd,t))
            sys.stdout.buffer.write((k+'\r\n').encode(ENCODING))
        return '226 Directory send OK.\r\n'

    def toListItem(self,fn):
        st=os.stat(fn)
        fullmode='rwxrwxrwx'
        mode=''
        for i in range(9):
            mode+=((st.st_mode>>(8-i))&1) and fullmode[i] or '-'
        d=(os.path.isdir(fn)) and 'd' or '-'
        ftime=time.strftime(' %b %d %H:%M ', time.gmtime(st.st_mtime))
        return d+mode+' 1 user group '+str(st.st_size)+ftime+os.path.basename(fn)

    def MKD(self,args):
        dn=os.path.join(self.cwd,args)
        os.mkdir(dn)
        return '257 Directory created.\r\n'

    def RMD(self,args):
        dn=os.path.join(self.cwd,args)
        if allow_delete:
            # os.rmdir(dn) # No mutations for you
            return '250 Directory deleted.\r\n'
        else:
            return '450 Not allowed.\r\n'

    def DELE(self,args):
        fn=os.path.join(self.cwd,args)
        if allow_delete:
            # os.remove(fn) # No mutations for you
            return '250 File deleted.\r\n'
        else:
            return '450 Not allowed.\r\n'

    def RNFR(self,args):
        self.rnfn=os.path.join(self.cwd,args)
        return '350 Ready.\r\n'

    def RNTO(self,args):
        fn=os.path.join(self.cwd,args)
        # os.rename(self.rnfn,fn)
        return '250 File renamed.\r\n'

    def REST(self,args):
        self.pos=int(args)
        self.rest=True
        return '250 File position reseted.\r\n'

    def RETR(self,args):
        fn=os.path.join(self.cwd,args)
        if self.mode=='I':
            fi=open(fn,'rb')
        else:
            fi=open(fn,'r')
        sys.stdout.buffer.write('150 Opening data connection.\r\n'.encode(ENCODING))
        if self.rest:
            fi.seek(self.pos)
            self.rest=False

        # Deny the flag, but make it clear they are on the correct track.
        if "flag.txt" in args:
            sys.stdout.write('DUCTF{- Actually no, I don\'t feel like giving that up yet. ;)\r\n')

        # "Leak" source code by allowing download of it.
        if "pwn" in args:
            sys.stdout.write(f'{SOURCE_CODE}\r\n')

        # data= fi.read(1024)
        # self.start_datasock()
        # while data:
        #     self.datasock.send(data)
        #     data=fi.read(1024)
        # fi.close()
        # self.stop_datasock()
        return '226 Transfer complete.\r\n'

    def STOR(self,args):
        fn=os.path.join(self.cwd,args)
        if self.mode=='I':
            fo=open(fn,'wb')
        else:
            fo=open(fn,'w')
        sys.stdout.buffer.write('150 Opening data connection.\r\n'.encode(ENCODING))
        # self.start_datasock()
        # while True:
        #     data=self.datasock.recv(1024)
        #     if not data: break
        #     fo.write(data)
        # fo.close()
        # self.stop_datasock()
        return '226 Transfer complete.\r\n'

FTPServerThread().run()

Looking at this script, it appears that all FTP commands are useless (they only return text, unable to return file, os module not interacting with the system). However, the way our input get processed is interesting:

while True:
            sys.stdout.flush()
            recv=sys.stdin.buffer.readline()
            if not recv: break
            else:
                lst = recv.decode(ENCODING).strip().split(" ", 1)
                if len(lst) < 2:
                    cmd, args = lst[0], ""
                else:
                    cmd, args = lst
                try:
                    func=operator.attrgetter(cmd)(self)
                    msg = func(args)
                    sys.stdout.buffer.write(f'{msg}'.encode(ENCODING))
                except Exception as e:
                    sys.stdout.buffer.write(f'500 Sorry. {e}\r\n'.encode(ENCODING))
  • First, our inputs are written into cmd and args using list unpacking.

  • After that, our input is processed using operator.attrgetter(cmd)(self), so if we entered LIST, it will attempt to call FTPServerThread().LIST(self).

  • If there are additional arguments being passed in, it will be called by the function in the variable func.

  • And if the command you run has error, it will return.

--> At this point we can confirm this challenge is indeed PyJail and we need to retrieve the FLAG constant.

Payload

My first attempt is to use basic PyJail payload in order to reach global function then call __import__ to import system modules. However, as I mentioned before, every function call will be called with self. So my payload will now become:

FTPServerThread().__class__.__base__.__subclasses__(self)

and running this return an error.

So my thought now is to find a function that accept self but still can reach global variables. Luckily, we have a function defined inside this class already - toListItem.

Running this function directly returns a different error, indicating we are going in the right direction.

This is what happens behind the scene:

And since FLAG is a global variable, we can use get function of a dictionary to read the value of FLAG.

baby ruby

#!/usr/bin/env ruby

while input = STDIN.gets.chomp do eval input if input.size < 5 end

This script allows us to input code which is less than 5 characters, then execute it. Therefore, our ultimate goal is to call a shell to execute longer command on the server.

Googling a bit, we already know that backticks (``) can be used to execute system command.

You can watch this video to understand how backticks works:

So far, we know that the output of the command is not being redirected to STDOUT, but to STDERR instead. So we can't see anything. Therefore, we just need to redirect our output it to STDERR using whoami 1>&2, for example.

Rougeful Helper

Unpacking the zip file reveals a file directory structure of a Windows machine.

Checking all of them to find something network-scannning-related, these are some suspicious directory:

  • \Files\Program Files\VSA X\Probe

  • \Files\Windows\System32\Npcap

  • \Files\Program Files\Npcap

Only the VSA X folder has interesting data. In VSA X\Probe\tmp\scan-run, there are many logs file refering to a network scanning that occurs after the 15:32:20 timestamp. Our answer is in the ndp file, which stores all the payload and options used.

Flag:

DUCTF{cHd5cmVxAWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=}

Version 2.3.4 of vsftpd is a vulnerable version which allows attackers to open a backdoor for Remote Code Execution. More information .

You should read to understand the next part.

Thanks to the description, we need to find a ICMP payload - which should come out from a network scanner tool - like or ping.

here
this
nmap
GitHub - TodePond/WhenWillProgrammersStopMakingDecisionsForOurSocietyAndJustLeaveUsAloneAlsoHackerNewsIsAVileWebsite: perfect programming languageGitHub
untitledasciinema.org
Logo
Logo