From 529ad70e11d04fc1639edd1061ad6e015b904224 Mon Sep 17 00:00:00 2001 From: MasonLiu <2857911564@qq.com> Date: Sat, 30 May 2026 10:20:22 +0800 Subject: [PATCH] =?UTF-8?q?V1.0=E6=AD=A3=E5=BC=8F=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .htaccess | 0 README.md | 150 +++++++++++++- assets/css/style.css | 399 ++++++++++++++++++++++++++++---------- assets/db/sechub.db | Bin 0 -> 28672 bytes assets/imgs/favicon.ico | Bin 0 -> 38700 bytes assets/json/plugin.json | 20 -- assets/json/poc.json | 20 -- assets/json/template.json | 10 + assets/json/tool.json | 25 --- db.php | 353 +++++++++++++++++++++++++++++++++ index.php | 268 +++++++++++++++++++------ nginx.htaccess | 0 search.php | 44 +++++ 13 files changed, 1052 insertions(+), 237 deletions(-) delete mode 100644 .htaccess create mode 100644 assets/db/sechub.db create mode 100644 assets/imgs/favicon.ico delete mode 100644 assets/json/plugin.json delete mode 100644 assets/json/poc.json create mode 100644 assets/json/template.json delete mode 100644 assets/json/tool.json create mode 100644 db.php delete mode 100644 nginx.htaccess create mode 100644 search.php diff --git a/.htaccess b/.htaccess deleted file mode 100644 index e69de29..0000000 diff --git a/README.md b/README.md index abafdef..6543557 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,148 @@ -## SecHub 网安工具集 +# SecHub - 网络安全工具集 -### 技术栈 +![SecHub](assets/imgs/favicon.ico) -PHP: 网页实现 -Json: 存储工具集 +## 项目简介 -### 部署 +SecHub 是一个面向网络安全领域的工具集合系统,提供多种网络安全工具的集中管理。该项目采用 JSON 存储工具信息,通过 PHP 网页界面实现工具的展示与访问,为安全研究人员和从业者提供一站式的工具导航平台。 -直接复制本项目放置于服务器上即可 \ No newline at end of file +## 功能特性 + +- 🛠️ **工具分类管理**:按类别组织网络安全工具,便于查找和使用 +- 🔍 **智能搜索**:支持全局搜索和栏目内搜索,快速定位所需工具 +- 🌓 **主题切换**:支持白天/黑夜模式切换,保护视力 +- 📱 **响应式设计**:适配各种屏幕尺寸,移动端友好 +- 💾 **数据持久化**:使用 SQLite 数据库存储工具信息,提高查询效率 +- 🔄 **自动同步**:JSON 数据变更时自动同步到数据库 +- 📥 **数据导出**:支持下载原始 JSON 数据文件 + +## 技术栈 + +- **后端**: PHP +- **数据库**: SQLite +- **前端**: HTML, CSS, JavaScript +- **数据存储**: JSON 文件 +- **架构**: JSON 驱动架构 + +## 项目结构 + +``` +SecHub/ +├── assets/ +│ ├── css/ +│ │ └── style.css # 样式文件 +│ ├── db/ +│ │ └── sechub.db # SQLite 数据库 +│ ├── imgs/ +│ │ ├── beian.png # 备案图标 +│ │ └── favicon.ico # 网站图标 +│ └── json/ +│ └── template.json # json模板 +│ +├── db.php # 数据库管理类 +├── index.php # 主页面 +└── search.php # 搜索 API +``` + +## 部署方式 + +### 环境要求 + +- PHP 7.0+ +- PDO SQLite 扩展 +- Web 服务器(Apache/Nginx) + +### 快速部署 + +1. 克隆或下载本项目 +2. 将项目文件放置于 Web 服务器根目录 +3. 确保 `assets/db/` 目录具有写入权限 +4. 访问 `index.php` 即可使用 + +### 权限设置 + +确保以下目录具有写入权限: +```bash +chmod 755 assets/db/ +``` + +## 使用说明 + +### 添加新工具 + +在对应的 JSON 文件中添加工具信息,格式如下: + +```json +[ + { + "section": "安全工具" + }, + { + "name": "工具名称", + "url": "https://example.com", + "description": "工具描述" + } +] +``` + +### 搜索功能 + +- **全局搜索**:在顶部搜索框输入关键词,实时显示所有栏目中的匹配结果 +- **栏目搜索**:在每个栏目内的搜索框输入关键词,仅在该栏目中搜索 + +### 主题切换 + +点击右上角的月亮/太阳图标切换白天/黑夜模式,系统会记住您的选择。 + +## 数据格式 + +每个 JSON 文件包含一个数组,第一个元素定义栏目名称,后续元素为具体的工具信息: + +```json +[ + { + "section": "栏目名称" + }, + { + "name": "工具名称", + "url": "工具链接", + "description": "工具描述" + } +] +``` + +## 核心功能说明 + +### 数据库同步机制 + +系统采用智能同步机制,仅在 JSON 文件修改时间超过 5 分钟时才更新数据库,避免频繁读写影响性能。 + +### 搜索算法 + +支持对工具名称、描述和 URL 进行模糊搜索,提供实时的搜索结果反馈。 + +### 响应式设计 + +采用现代 CSS Grid 布局,自动适配不同屏幕尺寸,提供良好的移动端体验。 + + +## 许可证 + +本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情 + +## 联系方式 + +- 项目主页: [https://git.masonliu.com/MasonLiu/SecHub](https://git.masonliu.com/MasonLiu/SecHub) +- 作者: MasonLiu +- 邮箱: [您的邮箱地址] + +## 备案信息 + +**示例网站:**[sehub.masonliu.com](https://sechub.masonliu.com) + +- ICP 备案号: 蜀ICP备2026025173号 +- 公安备案号: 川公网安备51162302000285号 + +--- + +© 2026 SecHub - Gaming Master Cybersecurity | MasonLiu \ No newline at end of file diff --git a/assets/css/style.css b/assets/css/style.css index 4a440ca..0c32de1 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -5,64 +5,162 @@ } body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background: #f5f5f5; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); min-height: 100vh; padding: 20px; - transition: background-color 0.3s ease, color 0.3s ease; + padding-bottom: 40px; + transition: all 0.3s ease; + color: #2d3748; } body.dark-mode { - background: #1a1a1a; - color: #e0e0e0; + background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%); + color: #e2e8f0; } .container { max-width: 1400px; margin: 0 auto; + padding-bottom: 20px; } header { text-align: center; - color: #333; - margin-bottom: 30px; - padding: 20px 0; + margin-bottom: 40px; + padding: 30px 20px; position: relative; - transition: color 0.3s ease; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(10px); + border-radius: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07); + transition: all 0.3s ease; + z-index: 10; } body.dark-mode header { - color: #e0e0e0; + background: rgba(26, 32, 44, 0.9); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); } header h1 { - font-size: 2rem; - margin-bottom: 8px; - color: #2c3e50; + font-size: 2.2rem; + margin-bottom: 10px; + color: #2b6cb0; + font-weight: 700; + letter-spacing: -0.5px; transition: color 0.3s ease; } body.dark-mode header h1 { - color: #ecf0f1; + color: #63b3ed; } header p { font-size: 1rem; - color: #666; + color: #718096; transition: color 0.3s ease; } body.dark-mode header p { - color: #b0b0b0; + color: #a0aec0; +} + +/* 搜索框样式 */ +.search-container { + position: relative; + max-width: 600px; + margin: 20px auto 0; +} + +.search-input { + width: 100%; + padding: 12px 20px; + font-size: 1rem; + border: 2px solid #e2e8f0; + border-radius: 12px; + background: white; + transition: all 0.3s ease; + outline: none; +} + +.search-input:focus { + border-color: #4299e1; + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1); +} + +body.dark-mode .search-input { + background: #2d3748; + border-color: #4a5568; + color: #e2e8f0; +} + +body.dark-mode .search-input:focus { + border-color: #63b3ed; + box-shadow: 0 0 0 3px rgba(99, 179, 237, 0.1); +} + +.search-results { + position: absolute; + top: 100%; + left: 0; + right: 0; + margin-top: 8px; + background: white; + border-radius: 12px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); + max-height: 70vh; + overflow-y: auto; + z-index: 9999; + display: none; + padding: 15px; + border: 2px solid #e2e8f0; +} + +body.dark-mode .search-results { + background: #2d3748; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); + border-color: #4a5568; +} + +/* body.search-active { + padding-bottom: 400px; +} */ + + +.search-result-section { + margin-bottom: 20px; +} + +.search-result-section:last-child { + margin-bottom: 0; +} + +.search-result-title { + font-size: 1rem; + color: #4a5568; + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 2px solid #e2e8f0; +} + +body.dark-mode .search-result-title { + color: #a0aec0; + border-bottom-color: #4a5568; +} + +.no-results { + text-align: center; + color: #a0aec0; + padding: 20px; } .theme-toggle { position: absolute; right: 20px; - top: 50%; - transform: translateY(-50%); - background: #fff; - border: 2px solid #e0e0e0; + top: 20px; + background: white; + border: 2px solid #e2e8f0; border-radius: 50%; width: 45px; height: 45px; @@ -72,22 +170,22 @@ body.dark-mode header p { justify-content: center; font-size: 1.3rem; transition: all 0.3s ease; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } .theme-toggle:hover { - transform: translateY(-50%) scale(1.1); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); - border-color: #3498db; + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border-color: #4299e1; } body.dark-mode .theme-toggle { - background: #2c2c2c; - border-color: #444; + background: #2d3748; + border-color: #4a5568; } body.dark-mode .theme-toggle:hover { - border-color: #f39c12; + border-color: #63b3ed; } .theme-tooltip { @@ -132,48 +230,133 @@ body.dark-mode .theme-toggle:hover { } .section { - margin-bottom: 30px; -} - -.section-title { - color: #2c3e50; - font-size: 1.4rem; - margin-bottom: 15px; - padding: 10px 15px; - border-left: 4px solid #3498db; - background: #fff; - border-radius: 4px; + margin-bottom: 35px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 16px; + padding: 25px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07); transition: all 0.3s ease; } +body.dark-mode .section { + background: rgba(26, 32, 44, 0.95); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; + gap: 15px; +} + +.section-title-wrapper { + display: flex; + align-items: center; + gap: 12px; +} + +.section-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.section-title { + color: #2b6cb0; + font-size: 1.5rem; + font-weight: 600; + margin: 0; + transition: color 0.3s ease; +} + body.dark-mode .section-title { - color: #ecf0f1; - background: #2c2c2c; - border-left-color: #f39c12; + color: #63b3ed; +} + +.section-search { + padding: 8px 15px; + font-size: 0.9rem; + border: 2px solid #e2e8f0; + border-radius: 8px; + background: white; + transition: all 0.3s ease; + outline: none; + min-width: 200px; +} + +.download-btn { + padding: 6px 14px; + font-size: 0.85rem; + border: 2px solid #4299e1; + border-radius: 8px; + background: white; + color: #4299e1; + cursor: pointer; + transition: all 0.3s ease; + outline: none; + font-weight: 500; + white-space: nowrap; +} + +.download-btn:hover { + background: #4299e1; + color: white; + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(66, 153, 225, 0.3); +} + +body.dark-mode .download-btn { + background: #2d3748; + border-color: #63b3ed; + color: #63b3ed; +} + +body.dark-mode .download-btn:hover { + background: #63b3ed; + color: #2d3748; +} + +.section-search:focus { + border-color: #4299e1; + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1); +} + +body.dark-mode .section-search { + background: #2d3748; + border-color: #4a5568; + color: #e2e8f0; +} + +body.dark-mode .section-search:focus { + border-color: #63b3ed; + box-shadow: 0 0 0 3px rgba(99, 179, 237, 0.1); } .cards-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 15px; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 18px; } .card { background: white; - border-radius: 8px; - padding: 18px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); - transition: all 0.3s ease; + border-radius: 12px; + padding: 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; position: relative; overflow: hidden; - border: 1px solid #e0e0e0; + border: 1px solid #e2e8f0; } body.dark-mode .card { - background: #2c2c2c; - border-color: #444; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + background: #2d3748; + border-color: #4a5568; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } .card::before { @@ -182,26 +365,26 @@ body.dark-mode .card { top: 0; left: 0; width: 100%; - height: 3px; - background: linear-gradient(90deg, #3498db, #2980b9); + height: 4px; + background: linear-gradient(90deg, #4299e1, #667eea); transform: scaleX(0); transform-origin: left; transition: transform 0.3s ease; } body.dark-mode .card::before { - background: linear-gradient(90deg, #f39c12, #e67e22); + background: linear-gradient(90deg, #63b3ed, #90cdf4); } .card:hover { - transform: translateY(-3px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); - border-color: #3498db; + transform: translateY(-4px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); + border-color: #4299e1; } body.dark-mode .card:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); - border-color: #f39c12; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); + border-color: #63b3ed; } .card:hover::before { @@ -213,85 +396,88 @@ body.dark-mode .card:hover { } .card-title { - font-size: 1.1rem; - color: #2c3e50; - margin-bottom: 6px; + font-size: 1.15rem; + color: #2d3748; + margin-bottom: 8px; font-weight: 600; transition: color 0.3s ease; } body.dark-mode .card-title { - color: #ecf0f1; + color: #e2e8f0; } .card-link { display: inline-block; - color: #3498db; + color: #4299e1; text-decoration: none; font-size: 0.85rem; word-break: break-all; transition: color 0.2s ease; + font-weight: 500; } body.dark-mode .card-link { - color: #f39c12; + color: #63b3ed; } .card-link:hover { - color: #2980b9; + color: #2b6cb0; text-decoration: underline; } body.dark-mode .card-link:hover { - color: #e67e22; + color: #90cdf4; } .card-description { - color: #666; - line-height: 1.5; + color: #718096; + line-height: 1.6; font-size: 0.9rem; transition: color 0.3s ease; } body.dark-mode .card-description { - color: #b0b0b0; + color: #a0aec0; } .empty-state { text-align: center; - color: #999; - padding: 30px; - background: #fff; - border-radius: 8px; - border: 1px dashed #ddd; + color: #a0aec0; + padding: 40px; + background: rgba(255, 255, 255, 0.5); + border-radius: 12px; + border: 2px dashed #cbd5e0; transition: all 0.3s ease; } body.dark-mode .empty-state { - background: #2c2c2c; - border-color: #444; - color: #888; + background: rgba(45, 55, 72, 0.5); + border-color: #4a5568; + color: #718096; } .empty-state p { - font-size: 0.95rem; + font-size: 1rem; } footer { text-align: center; - color: #999; - margin-top: 40px; - padding: 25px 20px; + color: #718096; + margin-top: 50px; + padding: 30px 20px; font-size: 0.9rem; - background: #fff; - border-top: 1px solid #e0e0e0; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(10px); + border-radius: 16px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07); transition: all 0.3s ease; } body.dark-mode footer { - background: #2c2c2c; - border-top-color: #444; - color: #888; + background: rgba(26, 32, 44, 0.9); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); + color: #a0aec0; } .footer-content { @@ -301,12 +487,12 @@ body.dark-mode footer { .copyright { margin-bottom: 10px; - color: #666; + color: #4a5568; transition: color 0.3s ease; } body.dark-mode .copyright { - color: #b0b0b0; + color: #a0aec0; } .beian-info { @@ -319,7 +505,7 @@ body.dark-mode .copyright { } .beian-link { - color: #999; + color: #718096; text-decoration: none; transition: color 0.2s ease; display: inline-flex; @@ -328,25 +514,25 @@ body.dark-mode .copyright { } body.dark-mode .beian-link { - color: #888; + color: #a0aec0; } .beian-link:hover { - color: #3498db; + color: #4299e1; } body.dark-mode .beian-link:hover { - color: #f39c12; + color: #63b3ed; } .beian-divider { - color: #ddd; + color: #cbd5e0; user-select: none; transition: color 0.3s ease; } body.dark-mode .beian-divider { - color: #555; + color: #4a5568; } .beian-icon { @@ -355,11 +541,11 @@ body.dark-mode .beian-divider { @media (max-width: 768px) { header h1 { - font-size: 1.6rem; + font-size: 1.8rem; } .section-title { - font-size: 1.2rem; + font-size: 1.3rem; } .cards-grid { @@ -367,7 +553,7 @@ body.dark-mode .beian-divider { } .card { - padding: 15px; + padding: 18px; } .theme-toggle { @@ -375,6 +561,7 @@ body.dark-mode .beian-divider { height: 40px; font-size: 1.1rem; right: 10px; + top: 10px; } .theme-tooltip { @@ -383,6 +570,15 @@ body.dark-mode .beian-divider { bottom: -32px; } + .section-header { + flex-direction: column; + align-items: flex-start; + } + + .section-search { + width: 100%; + } + footer { padding: 20px 15px; font-size: 0.85rem; @@ -404,7 +600,7 @@ body.dark-mode .beian-divider { } header h1 { - font-size: 1.4rem; + font-size: 1.5rem; } header p { @@ -412,12 +608,11 @@ body.dark-mode .beian-divider { } .section-title { - font-size: 1.1rem; - padding: 8px 12px; + font-size: 1.2rem; } .card-title { - font-size: 1rem; + font-size: 1.05rem; } footer { diff --git a/assets/db/sechub.db b/assets/db/sechub.db new file mode 100644 index 0000000000000000000000000000000000000000..54ff8ba287d8025f013bf47dc4301c69354060eb GIT binary patch literal 28672 zcmeI5|4$QV9LMiyX#r_FLrBM*^A={1NGVcWBHMI;w}l9Z&~6%&HKjfAwDjur>hza| zI_np{SQXF>5hZHG;R^1H5!g}?!{TaaNUIa@bTBJc2=jh*e_EW633+)oxY&N$ZZFSx z1jgrv|6+GSznt}rG_{ldtj5*gr0QHPEg;sd6t+5R8k^H%!$IHbW0!hddcu`$v=$VQ z(X}etAm@c2Lkd_ZK(4DgCdpiRozYrSLPm>Ku7^Tgz#Tds@Vfn6d&-7Bg>}lV7eHNY z?*Qv(mWHgWseX@(UT$csYTZk6JL==Yf%vywQ6UWKitj+g6i+AloqtI7jV$uu#Yek z95$Wl8DS|kpMw=9B!C2v01`j~NB{{S0VIF~kN^^RJqSeJ&Celrtn;`W4k>nVcKW`3 z{_3#YeODgq7AIojgIi+s!Iw-M)x5jzpdbW8)pmP3D;x~Bm3z4kyN}}!GrW&E%KGh4 z%ht>U1STXvR-EdW`$wh8%k!Nv>DTVL@d0RI?&P2}bXPooX8y{l%8JSlZ53N>jtZ(` zOLf)O>dLANMUl7HfV|>e`&W=x_XTaEj|Kf4=z3nFz_Z@Npvmq%%1A?<(xnNgFU`cH zUxwwulORgG*hBAamiq65sM+a0ajGwajtoT+XI?I`!xoOLUqM?_*z0Fl%E@#D{cuMy zltEyS=MKSLQXd=gaz`2dxILi8*n~ukeDjgmGc|wbp4c@Ck~Qtp*)b^=lSW6RtB*lA zFnu=DBlV95;A2@hR&$d6_CnwBk3I_o>xvDsVgG22^0D1^MA{B!ZKs|23DAm01`j~NB{{S0VIF~kN^@u0!RP}Ac5xy zY}Xly4TTQ9L%FUlS1!PnO!8y)ZQ5LiL+{ksl8a#qL{;_Hs*kgb#QFk=hYNKz9M;Lz zmZD3Oix;A6Nh48Qq(rMPL}(4D+2qPgZM>gcywFA-^hsy=jj;S}nS~W5B!C2v01`j~ zNB{{S0VIF~kN^@u0!ZMMAz;#NP#^WHw*++gy7kIT0CgkFtSin1dsp{Kmter2_OL^fCP{L5s{~>I literal 0 HcmV?d00001 diff --git a/assets/imgs/favicon.ico b/assets/imgs/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..edab23335eaaa20dd24680d1fb718bef94e5a7d5 GIT binary patch literal 38700 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 literal 0 HcmV?d00001 diff --git a/assets/json/plugin.json b/assets/json/plugin.json deleted file mode 100644 index 6384c95..0000000 --- a/assets/json/plugin.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "section": "插件/非独立小工具" - }, - { - "name": "Nuclei Templates", - "url": "https://github.com/projectdiscovery/nuclei-templates", - "description": "社区驱动的漏洞扫描模板集合,支持多种协议和技术栈" - }, - { - "name": "CVE-Exploit", - "url": "https://github.com/trickest/cve", - "description": "最新CVE漏洞的POC和EXP收集仓库" - }, - { - "name": "Web POC", - "url": "https://github.com/dorkerdevil/Web-Pentest", - "description": "Web应用渗透测试POC脚本合集" - } -] \ No newline at end of file diff --git a/assets/json/poc.json b/assets/json/poc.json deleted file mode 100644 index d456356..0000000 --- a/assets/json/poc.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "section": "POC/EXP" - }, - { - "name": "Nuclei Templates", - "url": "https://github.com/projectdiscovery/nuclei-templates", - "description": "社区驱动的漏洞扫描模板集合,支持多种协议和技术栈" - }, - { - "name": "CVE-Exploit", - "url": "https://github.com/trickest/cve", - "description": "最新CVE漏洞的POC和EXP收集仓库" - }, - { - "name": "Web POC", - "url": "https://github.com/dorkerdevil/Web-Pentest", - "description": "Web应用渗透测试POC脚本合集" - } -] \ No newline at end of file diff --git a/assets/json/template.json b/assets/json/template.json new file mode 100644 index 0000000..a7ad07e --- /dev/null +++ b/assets/json/template.json @@ -0,0 +1,10 @@ +[ + { + "section": "栏目名称" + }, + { + "name": "工具名称", + "url": "工具链接", + "description": "工具描述" + } +] \ No newline at end of file diff --git a/assets/json/tool.json b/assets/json/tool.json deleted file mode 100644 index 020309d..0000000 --- a/assets/json/tool.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "section": "安全工具" - }, - { - "name": "Burp Suite", - "url": "https://portswigger.net/burp", - "description": "专业的Web安全测试工具集,包含代理、扫描器等多种功能模块" - }, - { - "name": "SQLMap", - "url": "https://github.com/sqlmapproject/sqlmap", - "description": "自动化SQL注入检测和利用工具,支持多种数据库类型" - }, - { - "name": "Dirsearch", - "url": "https://github.com/maurosoria/dirsearch", - "description": "高性能Web目录和文件枚举工具,支持多线程和多种输出格式" - }, - { - "name": "Nmap", - "url": "https://nmap.org/", - "description": "网络发现和安全审计工具,支持端口扫描、服务检测等功能" - } -] \ No newline at end of file diff --git a/db.php b/db.php new file mode 100644 index 0000000..369da77 --- /dev/null +++ b/db.php @@ -0,0 +1,353 @@ +dbPath = $dbPath; + $this->jsonDir = $jsonDir; + $this->initDatabase(); + } + + /** + * 初始化数据库连接 + */ + private function initDatabase() { + try { + $this->db = new PDO('sqlite:' . $this->dbPath); + $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + + // 创建同步日志表 + $this->createSyncLogTable(); + } catch (PDOException $e) { + error_log("数据库连接失败: " . $e->getMessage()); + throw $e; + } + } + + /** + * 创建同步日志表 + */ + private function createSyncLogTable() { + $sql = "CREATE TABLE IF NOT EXISTS json_sync_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + json_filename TEXT UNIQUE NOT NULL, + table_name TEXT NOT NULL, + last_sync_time DATETIME NOT NULL, + json_file_mtime INTEGER NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )"; + + $this->db->exec($sql); + } + + /** + * 检查并同步JSON数据到数据库 + */ + public function syncJsonToDatabase() { + $jsonFiles = glob($this->jsonDir . '*.json'); + + foreach ($jsonFiles as $file) { + $this->syncSingleFile($file); + } + } + + /** + * 同步单个JSON文件到数据库 + */ + private function syncSingleFile($filePath) { + $filename = basename($filePath); + $tableName = pathinfo($filename, PATHINFO_FILENAME); + + // 检查是否需要更新 + if (!$this->shouldUpdate($filePath, $tableName)) { + return; + } + + // 读取JSON数据 + $data = $this->loadJsonData($filePath); + if (empty($data)) { + return; + } + + // 创建或更新表 + $this->createTable($tableName, $data[0]); + + // 清空旧数据 + $this->clearTable($tableName); + + // 插入新数据(跳过第一个section项) + $items = array_slice($data, 1); + foreach ($items as $item) { + $this->insertItem($tableName, $item, $data[0]['section'] ?? $tableName); + } + + // 更新同步日志 + $this->updateSyncLog($filename, $tableName); + } + + /** + * 更新同步日志 + */ + private function updateSyncLog($filename, $tableName) { + $jsonFile = $this->jsonDir . $filename; + $jsonModified = filemtime($jsonFile); + $syncTime = date('Y-m-d H:i:s'); + + // 检查是否已存在记录 + $sql = "SELECT id FROM json_sync_log WHERE json_filename = :filename"; + $stmt = $this->db->prepare($sql); + $stmt->execute([':filename' => $filename]); + $exists = $stmt->fetch(); + + if ($exists) { + // 更新现有记录 + $sql = "UPDATE json_sync_log + SET table_name = :table_name, + last_sync_time = :sync_time, + json_file_mtime = :mtime + WHERE json_filename = :filename"; + } else { + // 插入新记录 + $sql = "INSERT INTO json_sync_log (json_filename, table_name, last_sync_time, json_file_mtime) + VALUES (:filename, :table_name, :sync_time, :mtime)"; + } + + $stmt = $this->db->prepare($sql); + $stmt->execute([ + ':filename' => $filename, + ':table_name' => $tableName, + ':sync_time' => $syncTime, + ':mtime' => $jsonModified + ]); + } + + /** + * 判断是否需要更新 + */ + private function shouldUpdate($jsonFile, $tableName) { + $filename = basename($jsonFile); + $jsonModified = filemtime($jsonFile); + + // 查询该JSON文件的同步记录 + $sql = "SELECT * FROM json_sync_log WHERE json_filename = :filename"; + $stmt = $this->db->prepare($sql); + $stmt->execute([':filename' => $filename]); + $log = $stmt->fetch(); + + // 如果没有同步记录,需要更新 + if (!$log) { + return true; + } + + // 计算时间差(秒) + $timeDiff = $jsonModified - $log['json_file_mtime']; + + // 如果JSON文件修改时间比记录的晚至少5分钟(300秒),则需要更新 + if ($timeDiff >= 300) { + return true; + } + + // 否则不需要更新 + return false; + } + + + /** + * 加载JSON数据 + */ + private function loadJsonData($filePath) { + if (!file_exists($filePath)) { + return []; + } + + $content = file_get_contents($filePath); + if ($content === false) { + return []; + } + + $data = json_decode($content, true); + if (json_last_error() !== JSON_ERROR_NONE) { + error_log("JSON解析错误: " . json_last_error_msg()); + return []; + } + + return is_array($data) ? $data : []; + } + + /** + * 创建数据表 + */ + private function createTable($tableName, $firstItem) { + // 清理表名,只保留字母、数字和下划线 + $tableName = preg_replace('/[^a-zA-Z0-9_]/', '_', $tableName); + + $sql = "CREATE TABLE IF NOT EXISTS {$tableName} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + section TEXT NOT NULL, + name TEXT NOT NULL, + url TEXT, + description TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + )"; + + $this->db->exec($sql); + } + + /** + * 清空表数据 + */ + private function clearTable($tableName) { + $tableName = preg_replace('/[^a-zA-Z0-9_]/', '_', $tableName); + $this->db->exec("DELETE FROM {$tableName}"); + } + + /** + * 插入数据项 + */ + private function insertItem($tableName, $item, $section) { + $tableName = preg_replace('/[^a-zA-Z0-9_]/', '_', $tableName); + + $sql = "INSERT INTO {$tableName} (section, name, url, description) + VALUES (:section, :name, :url, :description)"; + + $stmt = $this->db->prepare($sql); + $stmt->execute([ + ':section' => $section, + ':name' => $item['name'] ?? '', + ':url' => $item['url'] ?? '', + ':description' => $item['description'] ?? '' + ]); + } + + /** + * 全局搜索 + */ + public function globalSearch($keyword) { + if (empty($keyword)) { + return []; + } + + $tables = $this->getAllTables(); + $results = []; + + foreach ($tables as $table) { + $tableName = $table['name']; + $sql = "SELECT *, '{$tableName}' as source_table FROM {$tableName} + WHERE name LIKE :keyword + OR description LIKE :keyword + OR url LIKE :keyword + ORDER BY name"; + + $stmt = $this->db->prepare($sql); + $stmt->execute([':keyword' => '%' . $keyword . '%']); + $items = $stmt->fetchAll(); + + if (!empty($items)) { + $results[$tableName] = [ + 'section' => $items[0]['section'] ?? $tableName, + 'items' => $items + ]; + } + } + + return $results; + } + + /** + * 按栏目搜索 + */ + public function searchBySection($section, $keyword) { + if (empty($keyword)) { + return []; + } + + $tables = $this->getAllTables(); + $results = []; + + foreach ($tables as $table) { + $tableName = $table['name']; + $sql = "SELECT * FROM {$tableName} + WHERE section = :section + AND (name LIKE :keyword + OR description LIKE :keyword + OR url LIKE :keyword) + ORDER BY name"; + + $stmt = $this->db->prepare($sql); + $stmt->execute([ + ':section' => $section, + ':keyword' => '%' . $keyword . '%' + ]); + $items = $stmt->fetchAll(); + + if (!empty($items)) { + $results[] = $items; + } + } + + return array_merge(...$results); + } + + /** + * 获取所有栏目配置 + */ + public function getSectionsConfig() { + $tables = $this->getAllTables(); + $sections = []; + + foreach ($tables as $table) { + $tableName = $table['name']; + $sql = "SELECT DISTINCT section FROM {$tableName} LIMIT 1"; + $stmt = $this->db->query($sql); + $row = $stmt->fetch(); + + if ($row) { + $sections[$tableName] = [ + 'title' => $row['section'], + 'table' => $tableName + ]; + } + } + + return $sections; + } + + /** + * 获取指定栏目的所有项目 + */ + public function getItemsBySection($tableName) { + $tableName = preg_replace('/[^a-zA-Z0-9_]/', '_', $tableName); + $sql = "SELECT * FROM {$tableName} ORDER BY name"; + $stmt = $this->db->query($sql); + return $stmt->fetchAll(); + } + + /** + * 获取所有表名 + */ + private function getAllTables() { + $sql = "SELECT name FROM sqlite_master + WHERE type='table' + AND name NOT LIKE 'sqlite_%' + AND name != 'json_sync_log' + ORDER BY name"; + $stmt = $this->db->query($sql); + return $stmt->fetchAll(); + } + + /** + * 关闭数据库连接 + */ + public function close() { + $this->db = null; + } +} \ No newline at end of file diff --git a/index.php b/index.php index d166f61..c132862 100644 --- a/index.php +++ b/index.php @@ -3,63 +3,18 @@ * SecHub - 网络安全工具导航页 */ -// 定义JSON文件路径 +// 定义路径 $jsonDir = __DIR__ . '/assets/json/'; +$dbDir = __DIR__ . '/assets/db/'; +$dbPath = $dbDir . 'sechub.db'; -/** - * 读取JSON文件并返回数据 - * @param string $filePath JSON文件路径 - * @return array 解析后的数据数组 - */ -function loadJsonData($filePath) { - if (!file_exists($filePath)) { - return []; - } - - $content = file_get_contents($filePath); - if ($content === false) { - return []; - } - - $data = json_decode($content, true); - if (json_last_error() !== JSON_ERROR_NONE) { - error_log("JSON解析错误: " . json_last_error_msg()); - return []; - } - - return is_array($data) ? $data : []; +// 确保数据库目录存在 +if (!is_dir($dbDir)) { + mkdir($dbDir, 0755, true); } -/** - * 获取所有JSON文件并自动识别栏目配置 - * @param string $jsonDir JSON目录路径 - * @return array 栏目配置数组 - */ -function getSectionsConfig($jsonDir) { - $sections = []; - $jsonFiles = glob($jsonDir . '*.json'); - - foreach ($jsonFiles as $file) { - $filename = basename($file); - $data = loadJsonData($file); - - if (!empty($data) && isset($data[0]['section'])) { - $sections[$filename] = [ - 'title' => $data[0]['section'], - 'file' => $filename, - 'items' => array_slice($data, 1) - ]; - } else { - $sections[$filename] = [ - 'title' => pathinfo($filename, PATHINFO_FILENAME), - 'file' => $filename, - 'items' => $data - ]; - } - } - - return $sections; -} +// 引入数据库类 +require_once __DIR__ . '/db.php'; /** * 渲染卡片HTML @@ -81,8 +36,17 @@ function renderCard($item) { "; } -// 自动获取栏目配置 -$sections = getSectionsConfig($jsonDir); +// 初始化数据库并同步数据 +try { + $database = new SecHubDatabase($dbPath, $jsonDir); + $database->syncJsonToDatabase(); + + // 获取栏目配置 + $sections = $database->getSectionsConfig(); +} catch (Exception $e) { + error_log("数据库初始化失败: " . $e->getMessage()); + $sections = []; +} ?> @@ -90,6 +54,7 @@ $sections = getSectionsConfig($jsonDir); + SecHub - 网络安全工具集 @@ -98,28 +63,57 @@ $sections = getSectionsConfig($jsonDir);

SecHub 网安工具集

一站式网络安全工具与资源导航平台

+ + +
+ +
+
+
- $config): ?> + $config): ?> getItemsBySection($key); + // 获取对应的JSON文件名 + $jsonFile = $key . '.json'; ?> -
-

- -

+
+
+
+

+ +

+ + +
+ + +
-

暂无数据,请在 中添加项目

+

暂无数据

-
+
@@ -145,6 +139,152 @@ $sections = getSectionsConfig($jsonDir);
- + \ No newline at end of file diff --git a/nginx.htaccess b/nginx.htaccess deleted file mode 100644 index e69de29..0000000 diff --git a/search.php b/search.php new file mode 100644 index 0000000..83c3bfa --- /dev/null +++ b/search.php @@ -0,0 +1,44 @@ +globalSearch($keyword); + echo json_encode($results, JSON_UNESCAPED_UNICODE); + } elseif ($action === 'section') { + // 栏目搜索 + $section = $_GET['section'] ?? ''; + $results = $database->searchBySection($section, $keyword); + echo json_encode($results, JSON_UNESCAPED_UNICODE); + } else { + echo json_encode([]); + } + + $database->close(); +} catch (Exception $e) { + error_log("搜索失败: " . $e->getMessage()); + echo json_encode(['error' => '搜索失败']); +} \ No newline at end of file