|
3 | 3 | try { |
4 | 4 |
|
5 | 5 | if (isset($_GET['modal']) && $_GET['modal'] == 'external') { |
| 6 | + |
| 7 | + // External image zoom is only used by the operator chat interface. Requiring a |
| 8 | + // logged in user prevents this anonymous view from being abused to reflect an |
| 9 | + // attacker supplied URL into an <img src>, which could otherwise force victims' |
| 10 | + // browsers to request arbitrary/internal hosts (client side SSRF). |
| 11 | + if (!erLhcoreClassUser::instance()->isLogged()) { |
| 12 | + http_response_code(403); |
| 13 | + exit; |
| 14 | + } |
| 15 | + |
| 16 | + // Only http/https images are allowed. This drops gopher/ftp/mailto/... and any |
| 17 | + // other scheme that erLhcoreClassBBCode::esc_url() would otherwise let through. |
| 18 | + $src = erLhcoreClassChatValidator::sanitizeUrl(isset($_GET['src']) ? $_GET['src'] : ''); |
| 19 | + |
| 20 | + if ($src === '') { |
| 21 | + http_response_code(400); |
| 22 | + exit; |
| 23 | + } |
| 24 | + |
| 25 | + // Only allow URLs pointing to an external (public) host. Resolve the host and |
| 26 | + // reject loopback/private/reserved destinations so this view cannot be used to |
| 27 | + // make a visitor's browser probe internal services (e.g. 169.254.169.254). |
| 28 | + $srcHost = parse_url($src, PHP_URL_HOST); |
| 29 | + |
| 30 | + if ($srcHost === null || $srcHost === false || $srcHost === '' || strtolower($srcHost) === 'localhost') { |
| 31 | + http_response_code(403); |
| 32 | + exit; |
| 33 | + } |
| 34 | + |
| 35 | + // Literal IPs are validated as-is; hostnames are resolved first |
| 36 | + if (filter_var($srcHost, FILTER_VALIDATE_IP) !== false) { |
| 37 | + $resolvedIp = $srcHost; |
| 38 | + } else { |
| 39 | + $resolvedIp = gethostbyname($srcHost); |
| 40 | + |
| 41 | + if ($resolvedIp === $srcHost || filter_var($resolvedIp, FILTER_VALIDATE_IP) === false) { |
| 42 | + // Host could not be resolved, do not reflect it |
| 43 | + http_response_code(403); |
| 44 | + exit; |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + if (filter_var($resolvedIp, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) { |
| 49 | + http_response_code(403); |
| 50 | + exit; |
| 51 | + } |
| 52 | + |
6 | 53 | $tpl = erLhcoreClassTemplate::getInstance('lhchat/zoomimage.tpl.php'); |
7 | 54 | $tpl->set('externalImage', true); |
8 | | - $tpl->set('fileImage', $_GET['src']); |
| 55 | + $tpl->set('fileImage', $src); |
9 | 56 | echo $tpl->fetch(); |
10 | 57 | exit; |
11 | 58 | } |
|
0 commit comments