Last active
July 18, 2019 03:32
-
-
Save retanoj/04a120381d9ae5ff3a183b02114adccf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class AntiSSRF | |
{ | |
private $timeout; | |
private $limit; | |
function __construct() | |
{ | |
$this->timeout = 5; //默认5s超时 | |
$this->limit = 3; //默认跳转3次 | |
} | |
function setTimeout($var) | |
{ | |
$this->timeout = $var; | |
} | |
function setJmpLimit($var) | |
{ | |
$this->limit = $var; | |
} | |
/** | |
* @param string url地址 | |
* @return array 'status'状态码 'location'当'status'为30X时为才存在,为跳转的URL, 'host'当请求为200时才有的变量 | |
*/ | |
private function getURLInfo($url) | |
{ | |
$ch = curl_init($url); | |
curl_setopt($ch, CURLOPT_HEADER, TRUE); | |
curl_setopt($ch, CURLOPT_NOBODY, TRUE); | |
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); | |
$result = curl_exec($ch); | |
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
$ret = array(); | |
$match = array(); | |
$ret['status'] = intval($status); | |
if ($ret['status'] >= 300 && $ret['status'] < 400) { | |
preg_match("#location: ([^\s]*)#i", $result, $match); | |
if (substr($match[1], 0, 4) === 'http') { | |
$ret['location'] = $match[1]; | |
} else { | |
$ret['location'] = $url . $match[1]; | |
} | |
} | |
if ($ret['status'] == 200) { | |
$ret['host'] = $url; | |
} | |
curl_close($ch); | |
return $ret; | |
} | |
/** | |
* @param string url地址 | |
* @return string 最终请求的IP地址,如失败则返回false | |
*/ | |
private function getRealIP($url) | |
{ | |
// 首次判断host的IP性质 | |
$url_host = @parse_url($url)['host']; | |
if (empty($url_host)) { | |
return false; | |
} | |
$url_ip = gethostbyname($url_host); | |
if ($this->isInnerIP($url_ip)) { | |
return $url_ip; | |
} | |
$count = 0; | |
$info = $this->getURLInfo($url); | |
while ($count < $this->limit - 1 && $info['status'] >= 300 && $info['status'] < 400) { | |
$count++; | |
$info = $this->getURLInfo($info['location']); | |
} | |
if ($info['status'] >= 300 || $info['status'] < 200) { //大于$limit 次跳转 或 最后一次请求出错 | |
return false; | |
} | |
// 判断请求后的host的IP性质 | |
$host = @parse_url($info['host'])['host']; | |
if ($host == $url_host) { | |
return $url_ip; | |
} | |
$ip = gethostbyname($host); | |
return $ip; | |
} | |
/** | |
* @param string IP地址 | |
* @return bool 是否为内网地址,是返回true,否返回false | |
*/ | |
private function isInnerIP($ip_arg) | |
{ | |
$ip = ip2long($ip_arg); | |
return ip2long('127.0.0.0') >> 24 === $ip >> 24 or \ | |
ip2long('10.0.0.0') >> 24 === $ip >> 24 or \ | |
ip2long('172.16.0.0') >> 20 === $ip >> 20 or \ | |
ip2long('192.168.0.0') >> 16 === $ip >> 16; | |
} | |
/** | |
* @param string url地址 | |
* @return bool 是否为合法的请求地址 | |
*/ | |
public function checkSafeUrl($url) | |
{ | |
if (!preg_match('#^https?://[^\\\\]*/.*#', $url)) { | |
return false; | |
} | |
$ip = $this->getRealIP($url); | |
if (!$ip) { | |
return false; | |
} | |
if ($this->isInnerIP($ip)) { | |
return false; | |
} | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment