好的,所以我是開發世界中的 Junior 之前的任何人,也許是一個想要的人?但是我仍然有一些服務器為一些客戶和我自己運行實時站點。其中一個服務器有一些可疑的活動,所以每當訪問具有關鍵資料/腳本訪問權限的頁面或執行資料庫查詢時,我都會啟動日志跟蹤。
該站點是我的第一個專案之一,使用 PHP 7.4 構建。托管由 Bluehost 提供。
日志記錄當前用戶名(登錄 cookie 的值)、查詢/頁面摘要、IP 地址和時間戳。注冊大約一周后,我發現了一些令人不安的結果。
服務器上大約四分之一的流量來自無法識別的 IP 地址,其中大多數試圖訪問頁面,但驗證失敗(檢查登錄 cookie 是否已設定以及是否具有一定長度)并被重定向到登錄頁面。但是,多個 IP 地址(記錄到日志中,登錄 cookie 值為空字串)以某種方式繞過了這一點,并在頁面上執行了更改資料庫的敏感腳本(應該只對管理員可用)。
攻擊者是否找到了阻止身份驗證腳本運行的方法?登錄時,我使用準備好的陳述句,并且在單獨的查詢中驗證用戶名和密碼,希望避免 SQL 注入。即使攻擊者能夠創建一個具有正確名稱的 cookie(無論是通過猜測還是 XSS 抓取),它也被存盤為空字串的值,并且仍然無法通過長度要求驗證檢查。(對嗎?)
此外,使用過時的 Google API 庫(訪問客戶端 google 日歷)存在一個明顯的漏洞,其中 GuzzleHTTP 導致了各種漏洞(以下串列來自 github 的dependabot):
- 港口變更應視為原產地變更
- 更改原點時未清除 CURLOPT_HTTPAUTH 選項
- 無法在主機更改或 HTTP 降級時剝離 Cookie 標頭
- 修復 HTTP 降級時無法去除授權標頭
- Guzzle 中的跨域 cookie 泄漏
- HTTP 代理標頭漏洞
- guzzlehttp/psr7 中的輸入驗證不正確
我更新了庫,阻止了相關的 IP 范圍,并更改了登錄 cookie 名稱。但在過去一周,另外兩個 IP 地址出現了問題。我完全不知道這是如何發生的,我非常感謝有關如何繼續進行故障排除的任何指導。
更新:這個問題太模糊了,所以我會在這里發布一些更詳細的資訊除了登錄頁面之外的每個頁面都呼叫一個頭檔案
示例-page.php:
<?PHP
include '../secure.db.conn.php'; //db connection
$note = 'accessed client list'
$log = $conn->query("INSERT INTO admin_log (user, note, ip) VALUES ('" . $_COOKIE['login'] . "', '$note', '$ipAddress')");
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Client List</title>
<?php include "header.php"; ?>
<link rel="stylesheet" href="./css/style.css">
</head>
頭檔案.php
<!-- Bootstrap core CSS & JS dependencies-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev nYRRuWlolflfl" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
<!-- Favicons -->
<?php include "nav.php"; ?>
導航.php
<?php
include dirname(__DIR__) . "/config.php"; //
include dirname(__DIR__) . "/master/siteSearch.php";
//user validation
if (!isset($_COOKIE["login"]) || strlen($_COOKIE["login"]) <= 3) {
$URL = $rootURL . "/login/";
if (headers_sent()) {
echo ("<script>location.href='$URL'</script>");
} else {
header("Location: $URL");
}
exit;
}
?>
<!-- Nav menu -->
<script> //There's a site search in navbar
$(document).ready(function() {
$('#site_search').on('input', function() {
var query = $(this).val()
$.ajax({
url: "<?php echo $rootURL ?>/siteSearch.php",
method: "post",
data: {
query: query
},
success: function(data) {
$('#site_search_results').html(data);
}
})
})
/*When google Oauth token expires, the server deletes it.
This check helps ensure the user gets a new token in a timely manner*/
$(document).ready(function() {
var isToken = <?php echo (file_exists(dirname(__DIR__) . '/token.json') ? 1 : 0); ?>;
if (isToken == 0) {
$.ajax({
url: "<?php echo $rootURL ?>/oauth/oauth.php",
data: {
tokes: isToken
},
method: "post",
success: function(data) {
$('#primaryNav-Modaldetail').html(data)
$('#primaryNav-Modal').modal("show")
}
})
}
})
The login page code may be useful too (I did not abstract anything fearing that I may omit a seemingly inaccuous- though actually vulnerable- line of code login.php
// Check if the user is already logged in, if yes then redirect him to welcome page
if(isset($_COOKIE['masterLogin']) && strlen($_COOKIE['masterLogin']) >= 3){
header("location: welcome.php");
exit;
}
// Include config file
require_once "secure.db.conn.php";
// Define variables and initialize with empty values
$username = $password = "";
$username_err = $password_err = $login_err = "";
// Processing form data when form is submitted
if($_SERVER["REQUEST_METHOD"] == "POST"){
// Check if username is empty
if(empty(trim($_POST["username"]))){
$username_err = "Please enter username.";
} else{
$username = trim($_POST["username"]);
}
// Check if password is empty
if(empty(trim($_POST["password"]))){
$password_err = "Please enter your password.";
} else{
$password = trim($_POST["password"]);
}
// Validate credentials
if(empty($username_err) && empty($password_err)){
// Prepare a select statement
$sql = "SELECT id, username, password FROM master WHERE username = :username";
if($stmt = $conn->prepare($sql)){
// Bind variables to the prepared statement as parameters
$stmt->bindParam(":username", $param_username, PDO::PARAM_STR);
// Set parameters
$param_username = trim($_POST["username"]);
// Attempt to execute the prepared statement
if($stmt->execute()){
// Check if username exists, if yes then verify password
if($stmt->rowCount() == 1){
if($row = $stmt->fetch()){
$id = $row["id"];
$username = $row["username"];
$hashed_password = $row["password"];
if(password_verify($password, $hashed_password)){
// Password is correct, so start a new session
session_start();
if(!empty($_POST["remember"])) {
setcookie ("login",$username,time() (5*24*60*60), '/');
} else {
setcookie ("login",$username,time() (4*60*60), '/');
}
// Redirect user to welcome page
header("location: welcome.php");
} else{
// Password is not valid, display a generic error message
$login_err = "Invalid username or password.";
}
}
} else{
// Username doesn't exist, display a generic error message
$login_err = "Invalid username or password.";
}
} else{
echo "Oops! Something went wrong. Please try again later.";
}
// Close statement
unset($stmt);
}
}
// Close connection
unset($conn);
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.88.1">
<title>Sign in</title>
<!-- Bootstrap core CSS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev nYRRuWlolflfl" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
<!-- Favicons (ommitted for Stack Overflow) -->
</head>
<body class="text-center">
<?php
if(!empty($login_err)){
echo '<div >' . $login_err . '</div>';
}
?>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
<div class="form-group">
<label>Username</label>
<input type="text" name="username" class="form-control <?php echo (!empty($username_err)) ? 'is-invalid' : ''; ?>" value="<?php echo $username; ?>">
<span class="invalid-feedback"><?php echo $username_err; ?></span>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" class="form-control <?php echo (!empty($password_err)) ? 'is-invalid' : ''; ?>">
<span class="invalid-feedback"><?php echo $password_err; ?></span>
</div>
<input type="checkbox" value="remember-me" name="remember" id="remember" <?php if(isset($_COOKIE["remember_login"])) { ?> checked <?php } ?>/> Remember me
</label>
<div class="form-group">
<input type="submit" class="btn btn-primary" value="Login">
</form>
</div>
</body>
</html>
uj5u.com熱心網友回復:
Your complete user validation seems to consist of this single statement:
if (!isset($_COOKIE["login"]) || strlen($_COOKIE["login"]) <= 3) {
<user is not valid, redirect to login>
}
<user is valid>
All this code does is check whether there is a cookie named "login" and whether the content is longer than 3 characters.
That is NOT a proper validation of a logged in user. Anybody can create such a cookie and can access the protected part of your site.
A cookie should contain something that proves that the user is really the one that just logged in. Suppose an user logged in with an username and password, and we put a "token" in the cookie, to somehow prove it is the same user.
Such a token can be a random generated string, for instance with random_bytes(), which is stored in the database alongside the users credentials, at the moment someone logs in. Now you have a cookie, and a row in your database, both containing an unique random string.
To validate whether an user has indeed already logged in, all you have to do is compare the two. Yes, this require a lookup in the data, but if you also remember the row id of the user in the cookie this can be very quick.
Does any of this make any sense?
Using tokens like this is not perfect. Cookies can still be stolen, but since any cookie, made like this, is only valid as long as the login session lasts, it will be very hard to spoof it. It will certainly be harder than simply making a cookie with the right name and more than 3 characters.
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/511456.html
上一篇:如何在Firefox中使用受HTTPS客戶端證書保護的api
下一篇:Javascript重定向漏洞
