php之web开发

目标

实现一款具有常用大部分功能的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时不要展开实体引用。实体引用是以&开头的字符序列,比如&amp;
表示&,&lt;表示<等。
设置这个选项后,解析器不会将实体引用展开为相应的字符。
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";
}
}
}

序列化:

<?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(); }
}

}

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、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>

结果

<?php

echo 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)跨域访问。

预检请求通常在以下情况下发起:

  1. 使用非简单请求方法:当浏览器在跨域情况下使用非简单请求方法(如PUT、DELETE、自定义头部等)时,它会发起预检请求来验证服务器是否允许这种请求。

  2. 自定义请求头部:如果请求中包含了自定义的HTTP头部,浏览器可能会发起预检请求来确保服务器允许这些自定义头部。

以下是浏览器发起预检请求(OPTIONS请求)的一般流程:

  1. 浏览器检测到跨域请求,检查请求是否满足“简单请求”的条件,如果满足则直接发送实际请求。

  2. 如果请求不满足“简单请求”的条件,浏览器会自动发送一个OPTIONS请求,请求的头部中会包含一些预检信息,如请求方法、请求头部等。

  3. 服务器收到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头部允许跨域访问,浏览器会继续发送实际请求,如果不允许,则浏览器会拒绝发送实际请

求,遵循同源策略。

请求类型:

  1. PUT:

    用于向服务器提交数据,修改服务器上的资源。

  2. DELETE:

    用于请求服务器删除指定的资源。

  3. PATCH:

    用于对服务器上的资源进行部分更新。

  4. CONNECT:

    用于建立网络连接,通常在代理服务器上使用。

  5. OPTIONS:

    用于请求有关服务器支持的请求方法和HTTP头部信息的信息。

  6. 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>