PHP原生实现远程图片下载与防盗链绕过
在PHP开发中,下载远程资源通常首选 cURL 或 file_get_contents,但在需要伪装 HTTP 头部(如绕过防盗链)且扩展受限的情况下,可直接使用 Socket 接口进行原始通信。
核心原理与方法说明
第1步:理解参数与功能模块
该方案由三个核心函数组成,分别负责获取 Cookie、解析 URL 以及执行带防盗链功能的下载操作:
DownImageKeep:主流程函数。负责建立连接、发送带伪造Referer的 HTTP GET 请求、处理重定向(自动最多3次),并将二进制流写入本地文件。RefurlCookie:辅助函数。向指定源页面发起轻量级 HTTP 请求,提取响应头中的Set-Cookie字段,以便在主请求中携带会话状态。GetHostInfo:工具函数。剥离 URL 中的协议前缀,将域名(Host)和路径(Query)分离,便于构造 HTTP 报文。
第2步:调用方式与关键参数
主要函数 DownImageKeep 的签名如下:
function DownImageKeep($gurl, $rfurl, $filename, $gcookie="", $JumpCount=0, $maxtime=30)参数说明:
$gurl:目标远程图片的完整 URL。$rfurl:伪造的来路地址(Referer)。若目标站点启用防盗链,传入此值可模拟从首页或其他允许referer的页面访问。$filename:本地保存的文件名(建议使用相对路径,避免权限问题)。$gcookie:可选的 Cookie 字符串。若不传,函数会自动尝试从$rfurl获取 Cookie。$JumpCount:内部递归参数,用于限制重定向次数,防止死循环。$maxtime:单次下载的超时秒数,默认30秒。
第3步:代码实现细节
以下是经过整理的完整 PHP 代码。代码使用了原生的 fsockopen 和 fwrite/fread 系列函数,完全手工拼接 HTTP/1.1 协议头。
<?php
/**
* 下载远程图片,支持伪造 Referer 和 Cookie 以绕过防盗链
* @param string $gurl 图片地址
* @param string $rfurl 来路地址 (Referer)
* @param string $filename 保存文件名
* @param string $gcookie 伪造的Cookie
* @param int $JumpCount 跳转计数
* @param int $maxtime 最大等待时间(秒)
* @return bool
*/
function DownImageKeep($gurl, $rfurl, $filename, $gcookie="", $JumpCount=0, $maxtime=30)
{
$urlinfos = GetHostInfo($gurl);
$ghost = trim($urlinfos['host']);
if($ghost=='') return FALSE;
$gquery = $urlinfos['query'];
// 如果没有提供Cookie,尝试从ReferUrl获取
if($gcookie=="" && !empty($rfurl)) {
$gcookie = RefurlCookie($rfurl);
}
// 构造标准 HTTP/1.1 请求头
$sessionQuery = "GET $gquery HTTP/1.1\r\n";
$sessionQuery .= "Host: $ghost\r\n";
$sessionQuery .= "Referer: $rfurl\r\n";
$sessionQuery .= "Accept: */*\r\n";
$sessionQuery .= "User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)\r\n";
if($gcookie!="" && !preg_match("/[\r\n]/", $gcookie)) {
$sessionQuery .= "Cookie: " . trim($gcookie) . "\r\n";
}
$sessionQuery .= "Connection: Keep-Alive\r\n\r\n";
// 建立TCP连接
$errno = "";
$errstr = "";
$m_fp = fsockopen($ghost, 80, $errno, $errstr, 10);
if (!$m_fp) {
return FALSE;
}
fwrite($m_fp, $sessionQuery);
// 读取响应头
$m_httphead = Array();
$httpstas = explode(" ", fgets($m_fp, 256));
$m_httphead["http-edition"] = trim($httpstas[0]);
$m_httphead["http-state"] = trim($httpstas[1]);
$lnum = 0;
while(!feof($m_fp)) {
$line = trim(fgets($m_fp, 256));
if ($line == "" || $lnum > 100) break;
// 简单的手动解析Header键值对
$hkey = "";
$hvalue = "";
$v = 0;
for($i=0; $i<strlen($line); $i++) {
if($v==1) { $hvalue .= $line[$i]; }
if($line[$i]==":") { $v = 1; }
if($v==0) { $hkey .= $line[$i]; }
}
$hkey = trim($hkey);
if($hkey!="") {
$m_httphead[strtolower($hkey)] = trim($hvalue);
}
$lnum++;
}
// 处理3xx重定向
if(preg_match("/^3/", $m_httphead["http-state"])) {
if(isset($m_httphead["location"]) && $JumpCount < 3) {
$JumpCount++;
return DownImageKeep($gurl, $rfurl, $filename, $gcookie, $JumpCount);
} else {
fclose($m_fp);
return FALSE;
}
}
// 非200 OK则失败
if(!preg_match("/^2/", $m_httphead["http-state"])) {
fclose($m_fp);
return FALSE;
}
// 获取内容长度并保存文件
$contentLength = isset($m_httphead['content-length']) ? $m_httphead['content-length'] : 0;
$fp = fopen($filename, "w") or die("写入文件:{$filename} 失败!");
$i = 0;
$okdata = "";
$starttime = time();
// 按字节读取Body数据
while(!feof($m_fp)) {
$okdata .= fgetc($m_fp);
$i++;
// 超时检查
if(time()-$starttime > $maxtime) break;
// 达到预期大小检查
if($contentLength > 0 && $i >= $contentLength) break;
}
if($okdata != "") {
fwrite($fp, $okdata);
}
fclose($fp);
// 如果没下载到数据,删除空文件
if($okdata=="") {
@unlink($filename);
fclose($m_fp);
return FALSE;
}
fclose($m_fp);
return TRUE;
}
/**
* 获取ReferUrl返回的Cookie
*/
function RefurlCookie($gurl)
{
global $gcookie, $lastRfurl;
$gurl = trim($gurl);
if(!empty($gcookie) && $lastRfurl==$gurl) return $gcookie;
if(trim($gurl)=='') return '';
$urlinfos = GetHostInfo($gurl);
$ghost = $urlinfos['host'];
$gquery = $urlinfos['query'];
$sessionQuery = "GET $gquery HTTP/1.1\r\n";
$sessionQuery .= "Host: $ghost\r\n";
$sessionQuery .= "Accept: */*\r\n";
$sessionQuery .= "User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)\r\n";
$sessionQuery .= "Connection: Close\r\n\r\n";
$errno = "";
$errstr = "";
// 注意:此处原代码有die(),实际使用建议改为错误日志记录
$m_fp = fsockopen($ghost, 80, $errno, $errstr, 10);
if(!$m_fp) return '';
fwrite($m_fp, $sessionQuery);
$gcookie = "";
while(!feof($m_fp)) {
$line = trim(fgets($m_fp, 256));
if ($line == "") break;
if(preg_match("/^Set-Cookie:/i", $line)) {
// 清理多余的换行符等
$gcookie = trim(substr($line, 12));
break;
}
}
fclose($m_fp);
return $gcookie;
}
/**
* 辅助:分离URL的Host和Path
*/
function GetHostInfo($gurl)
{
$gurl = preg_replace("/^http:\/\//i", "", trim($gurl));
$garr['host'] = preg_replace("/\/.*$/i", "", $gurl);
$path_part = preg_replace("/^([^\/]*)(\/.*?)$/is", "$2", $gurl);
$garr['query'] = "/" . (empty($path_part) ? "" : $path_part);
return $garr;
}
// 调用示例
// DownImageKeep("http://example.com/image.jpg", "http://example.com", "local_image.jpg", "", 0, 30);
?>注意事项与优化建议
虽然上述代码能解决特定的防盗链需求,但请注意以下几点:
- 性能损耗:逐字节读取(
fgetc)在处理大图片时效率极低。若需下载较大大文件,建议改用fread($m_fp, 4096)块读取,并在判断$i >= $contentLength时累加读取长度而非单次字节计数。 - HTTPS支持:当前代码仅硬编码了端口 80,无法支持 HTTPS。如需支持 SSL,应使用
ssl://前缀配合端口 443,并使用 OpenSSL 扩展封装套接字,或使用更高级的 cURL 库。 - 安全性风险:直接执行用户可控的 URL 下载可能带来 SSRF(服务器端请求伪造)风险或本地文件覆盖漏洞。在生产环境中,务必对
$filename做严格的白名单或路径校验,防止写入到敏感目录。
以上就是基于PHP原生Socket接口实现远程图片下载与防盗链绕过的详细内容,更多关于PHP网络编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













