最近在复盘 SQL注入 相关的技术,看到一个利用文件EXIF信息进行注入的技巧非常有意思。这个攻击的核心在于,后端程序可能会将检测到的文件EXIF信息直接拼接到SQL语句中,从而导致注入漏洞。
本文将从一个实际的CTF题目出发,解析攻击原理,并深入到PHP底层源码,通过调试来验证整个利用链。
什么是EXIF?
在深入探讨这个技巧之前,我们先简单了解一下什么是EXIF。
EXIF是可交换图像文件格式的缩写,它是为数码相机照片专门设定的,能够记录照片的属性信息和拍摄时的数据。例如,当你用手机或相机拍照时,EXIF会记录下光圈大小、焦距、拍摄时间、设备型号等一系列属性。
从一道CTF题目说起
这里我们以一道模拟的CTF题目为例。题目环境是一个简单的文件上传后台。
打开上传页面,是一个非常基础的文件上传表单。

经过测试发现,上传的任何文件都不会被过滤,且每次上传的文件名都不同。这通常意味着后端使用了随机数生成MD5值作为新文件名。查看文件列表时,我们能看到每个文件对应的“filetype”。

观察filetype字段,其内容格式非常眼熟,类似于Linux系统中 file 命令的输出结果。查询 PHP 手册可以发现,finfo 对象及其 finfo_file() 函数正是用来获取此类文件类型信息的。

因此,可以合理猜测,filetype 字段的数据来源于 finfo_file() 函数的返回结果,并且该结果被直接拼接到了 数据库 的INSERT语句中,从而构成了SQL注入点。
那么问题来了:如何控制 finfo_file() 函数的返回结果呢?
利用EXIF信息构造注入
在Linux中,file 命令可以查看文件信息,如下图所示,其输出与题目中的 filetype 字段高度相似。

我们可以使用 exiftool 工具来修改图片的EXIF信息(如Comment评论字段),从而“污染” file 命令的输出。
假设后端SQL语句大致如下(实战中需要不断测试和猜测):
insert into columns('字段1','字段2','字段3') value('值1','值2','值3')
那么我们可以构造注入Payload为:
123\"');select if(1,sleep(5),sleep(5));--+
使用 exiftool 执行以下命令,将Payload写入图片的Comment字段:
exiftool -overwrite_original -comment="123\"');select if(1,sleep(5),sleep(5));--+" avatar.jpg
再次使用 file 命令查看图片,可以看到我们的注入语句已经成功嵌入到了文件信息中。

此时上传这张图片,如果页面响应出现明显延迟,就证明时间盲注成功。后续利用 into outfile 写入Webshell等操作就属于常规操作,此处不再赘述。
那么,究竟是哪个函数如此“诚实”地将EXIF信息返回,并导致了注入呢?让我们深入题目源码一探究竟。
源码分析:漏洞根源
我们直接查看核心的上传处理代码。为了突出重点,以下仅展示关键部分:
$filename = md5(md5(rand(1,10000))).".zip";
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
$filepath = "upload/".$filename;
$sql = "INSERT INTO file(filename,filepath,filetype) VALUES ('".$filename."','".$filepath."','".$filetype."');";
- 第一行:使用双重MD5哈希的随机数作为新文件名,验证了我们之前的猜测。
- 第二行:漏洞核心。使用
finfo::file() 方法获取上传临时文件的类型信息。
- 第四行:存在SQL注入的SQL语句,
$filetype 被未经任何过滤直接拼接。
列文件列表的代码逻辑很简单,就是从数据库读取并展示,不再详述。
所以,整个注入链的罪魁祸首就是这一行:
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
finfo::file() 方法在PHP手册中的描述相对简单。为了彻底理解其行为,我们需要深入PHP底层进行跟踪和调试。
深入底层:跟踪 finfo::file
finfo::file 方法在PHP源码的 ext/fileinfo/fileinfo.c 中定义。finfo 类主要包含以下几个方法:
class finfo
{
/** @alias finfo_open */
public function __construct(int $flags = FILEINFO_NONE, ?string $magic_database = null) {}
/**
* @param resource|null $context
* @return string|false
* @alias finfo_file
*/
public function file(string $filename, int $flags = FILEINFO_NONE, $context = null) {}
/** @alias finfo_buffer */
public function buffer(string $string, int $flags = FILEINFO_NONE, $context = null) {}
/** @alias finfo_set_flags */
public function set_flags(int $flags) {}
}
我们跟进 finfo::file 的实际实现 finfo_file 函数。通过调试,可以定位到关键的执行流程。
首先,在 ext/fileinfo/fileinfo.c 中,函数会调用 php_stream_open_wrapper_ex 打开文件流。

随后,进入 magic_stream 函数,并进一步调用 file_or_stream 函数进行处理。

在 file_or_stream 函数的最后,其返回值由 file_getbuffer 函数决定。

跟踪进入 file_getbuffer 函数,此时观察数据结构,可以清晰看到 ms->o.buf 中已经包含了从图片文件中读取到的EXIF信息,其中就有我们植入的恶意注释内容。

至此,整个流程就非常清晰了:finfo::file() 方法会调用底层的libmagic库对文件进行检测,该库会读取包括EXIF注释在内的各种文件元数据,并将其作为文件类型描述信息的一部分返回。当后端开发者未经处理直接将此信息拼接进SQL语句时,便导致了注入漏洞。
总结与思考
这个利用图片EXIF信息进行SQL注入的技巧,虽然看似冷门,却揭示了安全攻防中一个重要的原则:任何来自用户可控输入的数据,在进入关键逻辑(如数据库查询)前都必须进行严格的校验和过滤,无论这个数据是来自表单、URL还是看似“无害”的文件元数据。
对于开发者而言,防范此类漏洞的关键在于:
- 对
finfo_file() 等函数返回的内容进行过滤或转义。
- 使用参数化查询(预编译语句)来执行数据库操作,从根本上杜绝SQL拼接。
对于安全研究者来说,每深入理解一个攻击技巧,就意味着对系统脆弱点的认知又拓宽了一分。在云栈社区,我们鼓励这种深度探究的精神,因为只有透彻理解攻击,才能构建更有效的防御。