调用VirusTotal API 2.0始终返回资源未在扫描队列的问题
看起来你遇到的核心问题是调用VirusTotal API上传文件后,始终无法获取扫描状态,返回"The requested resource is not among the finished, queued or pending scans",而且手动上传正常,说明API密钥和文件本身没问题,问题大概率出在PHP代码的文件上传逻辑上。结合你的代码和VirusTotal公开API的特性,我整理了几个关键修复点:
1. 修复PHP文件上传的CURL语法(最可能的根本原因)
在PHP 5.5及以上版本中,curl的@语法用于文件上传已经被废弃,默认会被当作普通字符串处理,而不是文件上传。你的AdrianTNT脚本中用了$post['file'] = '@'.$fileToScan;,这会导致VirusTotal无法正确接收文件,自然返回资源不存在的错误。
修改方式:使用CURLFile代替旧的@语法
替换AdrianTNT脚本中的上传代码部分:
// 原代码(错误) $post['file'] = '@'.$fileToScan; // 修改为: $post['file'] = new CURLFile(realpath($fileToScan));
如果你的PHP版本低于5.5(不推荐),可以手动开启CURLOPT_SAFE_UPLOAD为false,但优先推荐使用CURLFile:
curl_setopt($ch, CURLOPT_SAFE_UPLOAD, false);
2. 确保文件路径正确且PHP有读取权限
不管是临时上传文件还是移动后的文件,都要确保:
- 文件路径是绝对路径(用
realpath()验证) - PHP进程对该文件有读取权限(比如WAMP环境下,apache用户需要能访问
C:/WAMP/www/server/files/目录)
可以在调用扫描函数前添加调试代码:
$filePath = realpath($tmpdir.$fileName); if (!file_exists($filePath) || !is_readable($filePath)) { error_log("文件不存在或无法读取:".$filePath); exit; }
3. 处理API速率限制(公开API的硬限制)
VirusTotal公开API有严格的速率限制:
- 每分钟最多4个请求
- 每天最多500个请求
如果你的测试过程中频繁调用API,可能会被临时限制,导致请求被静默拒绝。可以在上传后添加延迟,或者在查询结果时加入重试逻辑(带指数退避):
// 上传文件后,等待一段时间再查询结果(比如10秒) sleep(10); // 然后调用查询接口
4. 调试完整的API响应
你目前只输出了部分响应内容,建议把整个API返回的JSON都打印出来,看看有没有隐藏的错误信息:
error_log("完整API响应:".$api_reply); $api_reply_array = json_decode($api_reply, true); error_log("响应数组:".print_r($api_reply_array, true));
比如可能返回文件大小超过限制(公开API最大支持32MB,超过需要先获取上传URL),或者请求头不正确。
5. 修复IzzyOnDroid类的调用逻辑
如果你用的是IzzySoft的virustotal.class.php,确保checkFile()方法内部正确处理了文件上传。可以查看该类的源码,确认它是否使用了正确的CURL文件上传方式,如果没有,手动修改类中的上传逻辑,替换为CURLFile。
6. 正确处理扫描状态的等待逻辑
即使文件成功上传到VirusTotal,公开API的扫描也需要排队,不会立刻返回结果。你需要循环查询扫描状态,直到返回response_code=1(有结果)或response_code=-2(排队中):
// 示例:循环查询扫描结果,最多重试5次 $maxRetries = 5; $retryCount = 0; $scanId = $api_reply_array['scan_id']; // 上传成功后获取的scan_id while ($retryCount < $maxRetries) { $reportUrl = "https://www.virustotal.com/vtapi/v2/file/report?apikey={$virustotal_api_key}&resource={$scanId}"; $reportReply = file_get_contents($reportUrl); $reportArray = json_decode($reportReply, true); if ($reportArray['response_code'] == 1) { // 有结果,处理 positives break; } elseif ($reportArray['response_code'] == -2) { // 排队中,等待后重试 sleep(15); $retryCount++; } else { // 其他错误,退出循环 break; } }
验证测试
修改后,先用一个小的、未知的无毒文件测试,比如新建一个空白文本文件,上传后观察API响应:
- 上传成功应该返回
response_code=1,并带有scan_id - 查询时,第一次可能返回
-2(排队),几次重试后返回1(结果)
内容的提问来源于stack exchange,提问作者Joe

