Skip to content

Commit b3fa786

Browse files
author
Daniel Neto
committed
fix: enhance security by implementing CSRF protection and using prepared statements for database operations
GHSA-2f9h-23f7-8gcx
1 parent 2075fac commit b3fa786

File tree

4 files changed

+127
-66
lines changed

4 files changed

+127
-66
lines changed

install/.htaccess

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Defense-in-depth: prevent directory listing of the install directory.
2+
# Access control for checkConfiguration.php is enforced in PHP via a
3+
# session CSRF token (generated by index.php), so the endpoint works
4+
# correctly for remote installs while still blocking blind POST attacks.
5+
Options -Indexes

install/checkConfiguration.php

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44
exit;
55
}
66

7+
// CWE-306: Require a valid session CSRF token generated by install/index.php.
8+
// This prevents any direct (blind) POST from a remote attacker because the
9+
// token is only accessible to whoever loaded the install wizard in a browser.
10+
// CLI usage (php install/install.php) is exempt — it is already protected by
11+
// the isCommandLineInterface() guard at the top of install.php.
12+
if (php_sapi_name() !== 'cli') {
13+
if (session_status() === PHP_SESSION_NONE) {
14+
session_start();
15+
}
16+
$_installToken = isset($_SESSION['install_csrf_token']) ? $_SESSION['install_csrf_token'] : '';
17+
if (empty($_installToken) ||
18+
empty($_POST['install_csrf_token']) ||
19+
!hash_equals($_installToken, $_POST['install_csrf_token'])) {
20+
header('Content-Type: application/json');
21+
echo json_encode(['error' => 'Invalid or missing install token. Please reload the install page and try again.']);
22+
exit;
23+
}
24+
}
25+
726

827
$installationVersion = "25.0";
928

@@ -117,12 +136,22 @@
117136
}
118137

119138
error_log("Installation: ".__LINE__);
120-
$sql = "INSERT INTO users (id, user, email, password, created, modified, isAdmin) VALUES (1, 'admin', '" . $_POST['contactEmail'] . "', '" . md5($_POST['systemAdminPass']) . "', now(), now(), true)";
121-
122-
try {
123-
$mysqli->query($sql);
124-
} catch (Exception $exc) {
125-
$obj->error = "Error deleting user: " . $mysqli->error;
139+
// CWE-89: Use a prepared statement to prevent SQL injection via contactEmail.
140+
// Also use the application's standard password hash (md5+whirlpool+sha1) so
141+
// the account works with the normal login flow instead of the legacy md5-only
142+
// fallback path.
143+
$hashedPass = md5(hash("whirlpool", sha1($_POST['systemAdminPass'])));
144+
$stmt = $mysqli->prepare("INSERT INTO users (id, user, email, password, created, modified, isAdmin) VALUES (1, 'admin', ?, ?, now(), now(), true)");
145+
if ($stmt) {
146+
$stmt->bind_param("ss", $_POST['contactEmail'], $hashedPass);
147+
$stmt->execute();
148+
if ($stmt->error) {
149+
$obj->error = "Error inserting admin user: " . $stmt->error;
150+
echo json_encode($obj);
151+
}
152+
$stmt->close();
153+
} else {
154+
$obj->error = "Error preparing user insert: " . $mysqli->error;
126155
echo json_encode($obj);
127156
}
128157

@@ -160,9 +189,13 @@
160189
$encoder = "{$_POST['webSiteRootURL']}Encoder/";
161190
}
162191

192+
$safeWebSiteTitle = $mysqli->real_escape_string($_POST['webSiteTitle']);
193+
$safeMainLanguage = $mysqli->real_escape_string($_POST['mainLanguage']);
194+
$safeContactEmail = $mysqli->real_escape_string($_POST['contactEmail']);
195+
$safeEncoder = $mysqli->real_escape_string($encoder);
163196
$sql = "INSERT INTO configurations (id, video_resolution, users_id, version, webSiteTitle, language, contactEmail, encoderURL, created, modified) "
164197
. " VALUES "
165-
. " (1, '858:480', 1,'{$installationVersion}', '{$_POST['webSiteTitle']}', '{$_POST['mainLanguage']}', '{$_POST['contactEmail']}', '{$encoder}', now(), now())";
198+
. " (1, '858:480', 1,'{$installationVersion}', '{$safeWebSiteTitle}', '{$safeMainLanguage}', '{$safeContactEmail}', '{$safeEncoder}', now(), now())";
166199
try {
167200
$mysqli->query($sql);
168201
} catch (Exception $exc) {

install/index.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
require_once '../objects/functions.php';
33
require_once '../locale/function.php';
44
//var_dump($_SERVER);exit;
5+
6+
// Generate a one-time CSRF token for the install form.
7+
// checkConfiguration.php validates this token so that direct POST requests
8+
// (without first loading this page) are rejected.
9+
if (session_status() === PHP_SESSION_NONE) {
10+
session_start();
11+
}
12+
if (empty($_SESSION['install_csrf_token'])) {
13+
$_SESSION['install_csrf_token'] = bin2hex(random_bytes(32));
14+
}
15+
$_installCsrfToken = $_SESSION['install_csrf_token'];
516
?>
617
<!DOCTYPE html>
718
<html lang="en">
@@ -443,7 +454,8 @@
443454
mainLanguage: mainLanguage,
444455
systemAdminPass: systemAdminPass,
445456
contactEmail: contactEmail,
446-
createTables: createTables
457+
createTables: createTables,
458+
install_csrf_token: '<?php echo htmlspecialchars($_installCsrfToken, ENT_QUOTES, "UTF-8"); ?>'
447459
},
448460
type: 'post',
449461
success: function (response) {

install/install.php

Lines changed: 69 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,69 @@
1-
<?php
2-
require_once '../objects/functions.php';
3-
if (!isCommandLineInterface()) {
4-
die('Command Line only');
5-
}
6-
if (file_exists("../videos/configuration.php")) {
7-
die("Can not create configuration again: " . json_encode($_SERVER));
8-
}
9-
10-
11-
$databaseUser = "youphptube";
12-
$databasePass = "youphptube";
13-
if (version_compare(phpversion(), '7.2', '<')) {
14-
$databaseUser = "root";
15-
}
16-
17-
$webSiteRootURL = @$argv[1];
18-
$webSiteRootURL = preg_replace("/[^0-9a-z._\/:-]/i", "", trim($webSiteRootURL));
19-
$databaseUser = empty($argv[2]) ? $databaseUser : $argv[2];
20-
$databasePass = empty($argv[3]) ? $databasePass : $argv[3];
21-
$systemAdminPass = empty($argv[4]) ? "123" : $argv[4];
22-
$contactEmail = empty($argv[5]) ? "undefined@youremail.com" : $argv[5];
23-
if (!filter_var($webSiteRootURL, FILTER_VALIDATE_URL)) {
24-
if (!empty($webSiteRootURL)) {
25-
echo "Invalid Site URL ({$webSiteRootURL})\n";
26-
}
27-
echo "Enter Site URL\n";
28-
@ob_flush();
29-
$webSiteRootURL = trim(readline(""));
30-
if (!filter_var($webSiteRootURL, FILTER_VALIDATE_URL)) {
31-
die("Invalid Site URL ({$webSiteRootURL})\n");
32-
}
33-
}
34-
35-
$webSiteRootURL = rtrim($webSiteRootURL, '/') . '/';
36-
37-
$_POST['systemRootPath'] = str_replace("install", "", getcwd());
38-
if (!is_dir($_POST['systemRootPath'])) {
39-
$_POST['systemRootPath'] = "/var/www/html/YouPHPTube/";
40-
if (!is_dir($_POST['systemRootPath'])) {
41-
$_POST['systemRootPath'] = "/var/www/html/AVideo/";
42-
}
43-
}
44-
45-
46-
$_POST['databaseHost'] = "localhost";
47-
$_POST['databaseUser'] = $databaseUser;
48-
$_POST['databasePass'] = $databasePass;
49-
$_POST['databasePort'] = "3306";
50-
$_POST['databaseName'] = "AVideo_". preg_replace("/[^0-9a-z]/i", "", parse_url($webSiteRootURL, PHP_URL_HOST));
51-
$_POST['createTables'] = 2;
52-
$_POST['contactEmail'] = $contactEmail;
53-
$_POST['systemAdminPass'] = $systemAdminPass;
54-
$_POST['mainLanguage'] = "en";
55-
$_POST['webSiteTitle'] = "AVideo";
56-
$_POST['webSiteRootURL'] = $webSiteRootURL;
57-
58-
include './checkConfiguration.php';
1+
<?php
2+
require_once '../objects/functions.php';
3+
if (!isCommandLineInterface()) {
4+
die('Command Line only');
5+
}
6+
if (file_exists("../videos/configuration.php")) {
7+
die("Can not create configuration again: " . json_encode($_SERVER));
8+
}
9+
10+
11+
$databaseUser = "youphptube";
12+
$databasePass = "youphptube";
13+
if (version_compare(phpversion(), '7.2', '<')) {
14+
$databaseUser = "root";
15+
}
16+
17+
$webSiteRootURL = @$argv[1];
18+
$webSiteRootURL = preg_replace("/[^0-9a-z._\/:-]/i", "", trim($webSiteRootURL));
19+
$databaseUser = empty($argv[2]) ? $databaseUser : $argv[2];
20+
$databasePass = empty($argv[3]) ? $databasePass : $argv[3];
21+
if (empty($argv[4])) {
22+
echo "Enter admin password (leave blank to generate a random one): ";
23+
@ob_flush();
24+
$systemAdminPass = trim(readline(""));
25+
if (empty($systemAdminPass)) {
26+
$systemAdminPass = bin2hex(random_bytes(8));
27+
echo "Generated admin password: {$systemAdminPass}\n";
28+
echo "IMPORTANT: Save this password now, it will not be shown again.\n";
29+
}
30+
} else {
31+
$systemAdminPass = $argv[4];
32+
}
33+
$contactEmail = empty($argv[5]) ? "undefined@youremail.com" : $argv[5];
34+
if (!filter_var($webSiteRootURL, FILTER_VALIDATE_URL)) {
35+
if (!empty($webSiteRootURL)) {
36+
echo "Invalid Site URL ({$webSiteRootURL})\n";
37+
}
38+
echo "Enter Site URL\n";
39+
@ob_flush();
40+
$webSiteRootURL = trim(readline(""));
41+
if (!filter_var($webSiteRootURL, FILTER_VALIDATE_URL)) {
42+
die("Invalid Site URL ({$webSiteRootURL})\n");
43+
}
44+
}
45+
46+
$webSiteRootURL = rtrim($webSiteRootURL, '/') . '/';
47+
48+
$_POST['systemRootPath'] = str_replace("install", "", getcwd());
49+
if (!is_dir($_POST['systemRootPath'])) {
50+
$_POST['systemRootPath'] = "/var/www/html/YouPHPTube/";
51+
if (!is_dir($_POST['systemRootPath'])) {
52+
$_POST['systemRootPath'] = "/var/www/html/AVideo/";
53+
}
54+
}
55+
56+
57+
$_POST['databaseHost'] = "localhost";
58+
$_POST['databaseUser'] = $databaseUser;
59+
$_POST['databasePass'] = $databasePass;
60+
$_POST['databasePort'] = "3306";
61+
$_POST['databaseName'] = "AVideo_". preg_replace("/[^0-9a-z]/i", "", parse_url($webSiteRootURL, PHP_URL_HOST));
62+
$_POST['createTables'] = 2;
63+
$_POST['contactEmail'] = $contactEmail;
64+
$_POST['systemAdminPass'] = $systemAdminPass;
65+
$_POST['mainLanguage'] = "en";
66+
$_POST['webSiteTitle'] = "AVideo";
67+
$_POST['webSiteRootURL'] = $webSiteRootURL;
68+
69+
include './checkConfiguration.php';

0 commit comments

Comments
 (0)