PHP原生实现远程图片下载与防盗链绕过

Hazel
Hazel
发布时间:2026-10-14 | 4人浏览过
原创

在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)

参数说明:

  1. $gurl:目标远程图片的完整 URL。
  2. $rfurl:伪造的来路地址(Referer)。若目标站点启用防盗链,传入此值可模拟从首页或其他允许referer的页面访问。
  3. $filename:本地保存的文件名(建议使用相对路径,避免权限问题)。
  4. $gcookie:可选的 Cookie 字符串。若不传,函数会自动尝试从 $rfurl 获取 Cookie。
  5. $JumpCount:内部递归参数,用于限制重定向次数,防止死循环。
  6. $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);
?>

注意事项与优化建议

虽然上述代码能解决特定的防盗链需求,但请注意以下几点:

  1. 性能损耗:逐字节读取(fgetc)在处理大图片时效率极低。若需下载较大大文件,建议改用 fread($m_fp, 4096) 块读取,并在判断 $i >= $contentLength 时累加读取长度而非单次字节计数。
  2. HTTPS支持:当前代码仅硬编码了端口 80,无法支持 HTTPS。如需支持 SSL,应使用 ssl:// 前缀配合端口 443,并使用 OpenSSL 扩展封装套接字,或使用更高级的 cURL 库。
  3. 安全性风险:直接执行用户可控的 URL 下载可能带来 SSRF(服务器端请求伪造)风险或本地文件覆盖漏洞。在生产环境中,务必对 $filename 做严格的白名单或路径校验,防止写入到敏感目录。

以上就是基于PHP原生Socket接口实现远程图片下载与防盗链绕过的详细内容,更多关于PHP网络编程的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表