Protect CSRF token against BREACH

This commit is contained in:
Jakub Vrana 2013-10-24 19:10:50 -07:00
parent 31abc08df8
commit a564bba261
6 changed files with 28 additions and 11 deletions

View file

@ -1,10 +1,11 @@
<?php <?php
$connection = ''; $connection = '';
$token = $_SESSION["token"]; $has_token = $_SESSION["token"];
if (!$_SESSION["token"]) { if (!$has_token) {
$_SESSION["token"] = rand(1, 1e6); // defense against cross-site request forgery $_SESSION["token"] = rand(1, 1e6); // defense against cross-site request forgery
} }
$token = get_token(); ///< @var string CSRF protection
$permanent = array(); $permanent = array();
if ($_COOKIE["adminer_permanent"]) { if ($_COOKIE["adminer_permanent"]) {
@ -40,7 +41,7 @@ if ($auth) {
} }
} elseif ($_POST["logout"]) { } elseif ($_POST["logout"]) {
if ($token && $_POST["token"] != $token) { if ($has_token && !verify_token()) {
page_header(lang('Logout'), lang('Invalid CSRF token. Send the form again.')); page_header(lang('Logout'), lang('Invalid CSRF token. Send the form again.'));
page_footer("db"); page_footer("db");
exit; exit;
@ -75,13 +76,13 @@ function unset_permanent() {
} }
function auth_error($exception = null) { function auth_error($exception = null) {
global $connection, $adminer, $token; global $connection, $adminer, $has_token;
$session_name = session_name(); $session_name = session_name();
$error = ""; $error = "";
if (!$_COOKIE[$session_name] && $_GET[$session_name] && ini_bool("session.use_only_cookies")) { if (!$_COOKIE[$session_name] && $_GET[$session_name] && ini_bool("session.use_only_cookies")) {
$error = lang('Session support must be enabled.'); $error = lang('Session support must be enabled.');
} elseif (isset($_GET["username"])) { } elseif (isset($_GET["username"])) {
if (($_COOKIE[$session_name] || $_GET[$session_name]) && !$token) { if (($_COOKIE[$session_name] || $_GET[$session_name]) && !$has_token) {
$error = lang('Session expired, please login again.'); $error = lang('Session expired, please login again.');
} else { } else {
$password = get_password(); $password = get_password();
@ -143,14 +144,13 @@ if (is_string($connection) || !$adminer->login($_GET["username"], get_password()
$driver = new Min_Driver($connection); $driver = new Min_Driver($connection);
$token = $_SESSION["token"]; ///< @var string CSRF protection
if ($auth && $_POST["token"]) { if ($auth && $_POST["token"]) {
$_POST["token"] = $token; // reset token after explicit login $_POST["token"] = $token; // reset token after explicit login
} }
$error = ''; ///< @var string $error = ''; ///< @var string
if ($_POST) { if ($_POST) {
if ($_POST["token"] != $token) { if (!verify_token()) {
$ini = "max_input_vars"; $ini = "max_input_vars";
$max_vars = ini_get($ini); $max_vars = ini_get($ini);
if (extension_loaded("suhosin")) { if (extension_loaded("suhosin")) {

View file

@ -25,7 +25,7 @@ if (isset($_GET["file"])) {
include "../adminer/include/functions.inc.php"; include "../adminer/include/functions.inc.php";
global $adminer, $connection, $drivers, $edit_functions, $enum_length, $error, $functions, $grouping, $HTTPS, $inout, $jush, $LANG, $langs, $on_actions, $permanent, $structured_types, $token, $translations, $types, $unsigned, $VERSION; // allows including Adminer inside a function global $adminer, $connection, $drivers, $edit_functions, $enum_length, $error, $functions, $grouping, $HTTPS, $inout, $jush, $LANG, $langs, $on_actions, $permanent, $structured_types, $has_token, $token, $translations, $types, $unsigned, $VERSION; // allows including Adminer inside a function
if (!$_SERVER["REQUEST_URI"]) { // IIS 5 compatibility if (!$_SERVER["REQUEST_URI"]) { // IIS 5 compatibility
$_SERVER["REQUEST_URI"] = $_SERVER["ORIG_PATH_INFO"]; $_SERVER["REQUEST_URI"] = $_SERVER["ORIG_PATH_INFO"];

View file

@ -1124,6 +1124,22 @@ var timeout = setTimeout(function () {
return array_keys($return); return array_keys($return);
} }
/** Generate BREACH resistant CSRF token
* @return string
*/
function get_token() {
$rand = rand(1, 1e6);
return ($rand ^ $_SESSION["token"]) . ":$rand";
}
/** Verify if supplied CSRF token is valid
* @return bool
*/
function verify_token() {
list($token, $rand) = explode(":", $_POST["token"]);
return ($rand ^ $_SESSION["token"]) == $token;
}
// used in compiled version // used in compiled version
function lzw_decompress($binary) { function lzw_decompress($binary) {
// convert binary string to codes // convert binary string to codes

View file

@ -76,11 +76,11 @@ function switch_lang() {
echo "<form action='' method='post'>\n<div id='lang'>"; echo "<form action='' method='post'>\n<div id='lang'>";
echo lang('Language') . ": " . html_select("lang", $langs, $LANG, "this.form.submit();"); echo lang('Language') . ": " . html_select("lang", $langs, $LANG, "this.form.submit();");
echo " <input type='submit' value='" . lang('Use') . "' class='hidden'>\n"; echo " <input type='submit' value='" . lang('Use') . "' class='hidden'>\n";
echo "<input type='hidden' name='token' value='$_SESSION[token]'>\n"; // $token may be empty in auth.inc.php echo "<input type='hidden' name='token' value='" . get_token() . "'>\n"; // $token may be empty in auth.inc.php
echo "</div>\n</form>\n"; echo "</div>\n</form>\n";
} }
if (isset($_POST["lang"]) && $_SESSION["token"] == $_POST["token"]) { // $token and $error not yet available if (isset($_POST["lang"]) && verify_token()) { // $error not yet available
cookie("adminer_lang", $_POST["lang"]); cookie("adminer_lang", $_POST["lang"]);
$_SESSION["lang"] = $_POST["lang"]; // cookies may be disabled $_SESSION["lang"] = $_POST["lang"]; // cookies may be disabled
$_SESSION["translations"] = array(); // used in compiled version $_SESSION["translations"] = array(); // used in compiled version

View file

@ -218,6 +218,7 @@ if (!isset($_GET["import"])) {
echo checkbox("error_stops", 1, ($_POST ? $_POST["error_stops"] : isset($_GET["import"])), lang('Stop on error')) . "\n"; echo checkbox("error_stops", 1, ($_POST ? $_POST["error_stops"] : isset($_GET["import"])), lang('Stop on error')) . "\n";
echo checkbox("only_errors", 1, $_POST["only_errors"], lang('Show only errors')) . "\n"; echo checkbox("only_errors", 1, $_POST["only_errors"], lang('Show only errors')) . "\n";
echo "<input type='hidden' name='token' value='$token'>\n";
if (!isset($_GET["import"]) && $history) { if (!isset($_GET["import"]) && $history) {
print_fieldset("history", lang('History'), $_GET["history"] != ""); print_fieldset("history", lang('History'), $_GET["history"] != "");
@ -231,5 +232,4 @@ if (!isset($_GET["import"]) && $history) {
echo "</div></fieldset>\n"; echo "</div></fieldset>\n";
} }
?> ?>
<input type="hidden" name="token" value="<?php echo $token; ?>">
</form> </form>

View file

@ -15,6 +15,7 @@ Encrypt passwords stored in session by a key stored in cookie
Don't append newlines to uploaded files, bug since Adminer 3.7.0 Don't append newlines to uploaded files, bug since Adminer 3.7.0
Don't display SQL edit form on Ctrl+click on the select query, introduced in Adminer 3.6.4 Don't display SQL edit form on Ctrl+click on the select query, introduced in Adminer 3.6.4
Use MD5 for editing long keys only in supported drivers, bug since Adminer 3.6.4 Use MD5 for editing long keys only in supported drivers, bug since Adminer 3.6.4
Protect CSRF token against BREACH
SQLite: Allow editing primary key SQLite: Allow editing primary key
SQLite: Allow editing foreign keys SQLite: Allow editing foreign keys
PostgreSQL: Fix handling of nextval() default values PostgreSQL: Fix handling of nextval() default values