Skip to content
Snippets Groups Projects

Resolve "Increase readability of Proxy URLs"

Merged Dominik Hebeler requested to merge 22-increase-readability-of-proxy-urls into master
Files
3
@@ -6,149 +6,91 @@ use App\CssDocument;
use App\HtmlDocument;
use Cache;
use finfo;
use Log;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
use URL;
use Illuminate\Support\Facades\Redis;
use App\Console\Commands\RequestFetcher;
use App\Models\HttpParser;
use Carbon\Carbon;
class ProxyController extends Controller
{
const PROXY_CACHE = 5; # Cache duration in minutes
const PROXYLINKVALIDHOURS = 1;
public function proxyPage(Request $request, $password, $url)
public function proxyPage(Request $request)
{
$targetUrl = str_replace("<<SLASH>>", "/", $url);
$targetUrl = str_rot13(base64_decode($targetUrl));
if (strpos($targetUrl, URL::to('/')) === 0) {
return redirect($targetUrl);
}
// Password already got checked by the middleware:
$newPW = md5(env('PROXY_PASSWORD') . date('dmy'));
# Check For URL-Parameters that don't belong to the Proxy but to the URL that needs to be proxied
$params = $request->except(['enableJS', 'enableCookies']);
if (sizeof($params) > 0) {
# There are Params that need to be passed to the page
# Most of the times this happens due to forms that are submitted on a proxied page
# Let's redirect to the correct URI
$proxyParams = $request->except(array_keys($params));
$redirProxyUrl = $targetUrl;
$redirParams = [];
if (strpos($redirProxyUrl, "?") === false) {
$redirProxyUrl .= "?";
if(!$request->filled("url") || !$request->filled("password")){
if (env("APP_ENV", "") !== "production") {
return view("development");
} else {
# There are already Params for this site which need to get updated
$tmpParams = substr($redirProxyUrl, strpos($redirProxyUrl, "?") + 1);
$tmpParams = explode("&", $tmpParams);
foreach ($tmpParams as $param) {
$tmp = explode("=", $param);
if (sizeof($tmp) === 2) {
$redirParams[$tmp[0]] = $tmp[1];
}
}
}
foreach ($params as $key => $value) {
$redirParams[$key] = $value;
return redirect("https://metager.de");
}
foreach ($redirParams as $key => $value) {
$redirProxyUrl .= $key . "=" . urlencode($value) . "&";
}
$redirProxyUrl = rtrim($redirProxyUrl, "&");
$pw = md5(env('PROXY_PASSWORD') . $redirProxyUrl);
$redirProxyUrl = base64_encode(str_rot13($redirProxyUrl));
$redirProxyUrl = urlencode(str_replace("/", "<<SLASH>>", $redirProxyUrl));
$proxyParams['url'] = $redirProxyUrl;
$proxyParams['password'] = $pw;
$newLink = action('ProxyController@proxyPage', $proxyParams);
return redirect($newLink);
}
$this->writeLog($targetUrl, $request->ip());
$toggles = "111000A";
$targetUrl = $request->input("url", "https://metager.de");
$password = $request->input("password", "");
# Script Toggle Url:
$params = $request->all();
$scriptsEnabled = false;
if ($request->has('enableJS')) {
$scriptsEnabled = true;
array_forget($params, 'enableJS');
} else {
$toggles[1] = "0";
$params['enableJS'] = "true";
// Check Password
if(!self::checkPassword($targetUrl, null, $password)){
abort(400, "Invalid Request");
}
$params['password'] = $password;
$params['url'] = $url;
$scriptUrl = action('ProxyController@proxyPage', $params);
//$scriptUrl = "javascript:alert('Diese Funktion wurde auf Grund von technischen Problemen vorerst deaktiviert. Sie können JavaScript wieder aktivieren sobald diese behoben wurden.');";
# Cookie Toggle Url:
$params = $request->all();
$cookiesEnabled = false;
if ($request->has('enableCookies')) {
$cookiesEnabled = true;
array_forget($params, 'enableCookies');
} else {
$toggles[0] = "0";
$params['enableCookies'] = "true";
// Deny Loading internal URLs and check if URL syntax is correct
$host = parse_url($targetUrl, PHP_URL_HOST);
$selfHost = $request->getHttpHost();
// The target URL couldn't be parsed. This is probably a malformed URL
if($host === false){
abort(404, "Invalid Request");
}
$params['password'] = $password;
$params['url'] = $url;
$cookieUrl = action('ProxyController@proxyPage', $params);
$settings = "u0";
if ($cookiesEnabled && !$scriptsEnabled) {
$settings = "O0";
} elseif (!$cookiesEnabled && $scriptsEnabled) {
$settings = "e0";
} elseif ($cookiesEnabled && $scriptsEnabled) {
$settings = "80";
// The URL to load itself is a URL to our proxy
// We will just redirect to that URL
if ($host === $selfHost) {
return redirect($targetUrl);
}
$key = md5($request->ip() . microtime(true));
$urlToProxy = $this->proxifyUrl($targetUrl, $newPW, $key, false);
$this->writeLog($targetUrl, $request->ip());
$urlToProxy = self::generateProxyUrl($targetUrl);
return view('ProxyPage')
->with('key', $key)
->with('iframeUrl', $urlToProxy)
->with('scriptsEnabled', $scriptsEnabled)
->with('scriptUrl', $scriptUrl)
->with('cookiesEnabled', $cookiesEnabled)
->with('cookieUrl', $cookieUrl)
->with('targetUrl', $targetUrl);
}
public function proxy(Request $request, $password, $id, $url)
public function proxy(Request $request)
{
$supportedContentTypes = [
'text/html',
];
if(!$request->filled("url") || !$request->filled("password") || !$request->filled("valid-until")){
abort(400, "Invalid Request");
}
$targetUrl = $request->input("url", "https://metager.de");
$password = $request->input("password", "");
$validUntil = $request->input("valid-until", "");
// Check Password
if(!self::checkPassword($targetUrl, $validUntil, $password)){
abort(400, "Invalid Request");
}
$targetUrl = str_replace("<<SLASH>>", "/", $url);
$targetUrl = str_rot13(base64_decode($targetUrl));
try {
$path = parse_url($targetUrl)["path"];
} catch (\Exception $e) {
$path = "";
$validUntil = Carbon::createFromFormat("d-m-Y H:i:s P", $validUntil);
} catch (InvalidFormatException $e) {
abort(400, "Invalid Request");
}
if ($validUntil->isBefore(Carbon::now()->setTimezone("UTC"))) {
abort(400, "Invalid Request");
}
// Deny Loading internal URLs and check if URL syntax is correct
$host = parse_url($targetUrl, PHP_URL_HOST);
$selfHost = $request->getHttpHost();
// The target URL couldn't be parsed. This is probably a malformed URL
// The URL to load itself is a URL to our proxy
if($host === false || $host === $selfHost){
abort(404, "Invalid Request");
}
$this->password = $password;
// Hash Value under which a possible cached file would've been stored
$hash = md5($targetUrl);
$result = [];
@@ -182,6 +124,10 @@ class ProxyController extends Controller
$answer = Cache::get($hash);
}
if(stripos($targetUrl, ".svg") !== FALSE){
$test = "test";
}
if (!empty($answer["error"])) {
if ($answer["error"] === CURLE_ABORTED_BY_CALLBACK) {
// File Downloads aren't working anymore within an IFrame.
@@ -195,8 +141,8 @@ class ProxyController extends Controller
}
}
if ($result === null) {
return $this->streamFile($targetUrl);
if ($answer === null) {
abort(400, "Couldn't fetch response");
} else {
$httpcode = $answer["http-code"];
extract(parse_url($targetUrl));
@@ -267,18 +213,8 @@ class ProxyController extends Controller
$answer["headers"]["content-disposition"] = "attachment; filename=$name";
}
// no break
case 'image/png':
case 'image/jpeg':
case 'image/gif':
case 'application/font-woff':
case 'application/x-font-woff':
case 'application/x-empty':
case 'font/woff2':
case 'image/svg+xml':
case 'application/octet-stream':
case 'text/plain':
case 'image/x-icon':
case 'font/eot':
case 'image/png': $toggles = "111000A";
case 'image/vnd.microsoft.icon':
case 'application/vnd.ms-fontobject':
case 'application/x-font-ttf':
@@ -286,6 +222,9 @@ class ProxyController extends Controller
case 'application/zip':
case 'binary/octet-stream':
case 'application/vnd.android.package-archive':
case 'image/svg+xml':
case 'font/woff':
case 'font/woff2':
# Nothing to do with Images: Just return them
break;
case 'text/css':
@@ -296,6 +235,7 @@ class ProxyController extends Controller
break;
default:
# We have no Parser for this one. Let's respond:
Log::error("Couldn't find parser for content type " . $contentType . " on URL " . $targetUrl);
abort(500, $contentType . " " . $targetUrl);
break;
}
@@ -308,100 +248,85 @@ class ProxyController extends Controller
->withHeaders($answer["headers"]);
}
public function streamFile(Request $request, $password, $id, $url)
{
/**
* Forms of proxied webpages might aswell Post to this URL
* Those need to be denied
*/
$check = md5(env('PROXY_PASSWORD') . date('dmy') . $request->ip());
if (!$request->filled("force-download") && $request->input("check", "") === $check) {
abort(405);
}
$targetUrl = str_replace("<<SLASH>>", "/", $url);
$targetUrl = str_rot13(base64_decode($targetUrl));
if (strpos($targetUrl, URL::to('/')) === 0) {
return redirect($targetUrl);
}
return $this->streamResponse($targetUrl);
/**
* This function generates a URL to a proxied page
* including the proxy header.
*/
public static function generateProxyWrapperUrl($url){
$password = self::generatePassword($url, null);
$parameters = [
"url" => $url,
"password" => $password,
];
return route('proxy-wrapper-page', $parameters);
}
private function streamResponse($url)
{
$headers = get_headers($url, 1);
/**
* This function generates a URL to a proxied page
* excluding the proxy header.
*/
public static function generateProxyUrl($url){
$filename = basename($url);
$validUntil = self::generateValidUntilDate();
$password = self::generatePassword($url, $validUntil);
# From the headers we need to remove the first Element since it's the status code:
$status = $headers[0];
$status = intval(preg_split("/\s+/si", $status)[1]);
array_forget($headers, 0);
$parameters = [
"url" => $url,
"valid-until" => $validUntil,
"password" => $password,
];
# Add the Filename if it's not set:
if (!isset($headers["Content-Disposition"])) {
$headers["Content-Disposition"] = "inline; filename=\"" . $filename . "\"";
} elseif (preg_match("/filename=\"{0,1}(.*?)(\"|\s|$)/", $headers["Content-Disposition"], $matches)) {
$filename = $matches[1];
}
return route('proxy', $parameters);
}
/**
* This function generates a Date/Time String which is used by our Download Controller
* to check if a download link is valid.
* This Date/Time is used in the password hash, too to make sure it is not altered
*/
private static function generateValidUntilDate()
{
$validUntil = Carbon::now()->setTimezone("UTC");
$validUntil->addHours(self::PROXYLINKVALIDHOURS);
return response()->streamDownload(function () use ($url) {
# We are gonna stream a large file
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 4096);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 50000);
curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, 5);
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $data) {
echo($data);
});
curL_exec($ch);
curl_close($ch);
}, $filename, $headers);
return $validUntil->format("d-m-Y H:i:s P");
}
public function proxifyUrl($url, $password = null, $key, $topLevel)
/**
* This function generates the password for Download Links
* The password is an hmac with the proxy password.
* Algo is SHA256
* Data is $url . $validUntil or just $url when it should not expire
*/
private static function generatePassword($url, $validUntil)
{
// Only convert valid URLs
$url = trim($url);
if (strpos($url, "http") !== 0 || strpos($url, URL::to('/')) === 0) {
return $url;
}
$data = $url;
if (!$password) {
$password = urlencode(\Request::route('password'));
if(!empty($validUntil)){
$data .= $validUntil;
}
$urlToProxy = base64_encode(str_rot13($url));
$urlToProxy = str_replace("/", "<<SLASH>>", $urlToProxy);
$urlToProxy = urlencode($urlToProxy);
if ($topLevel) {
$params = \Request::all();
if (!is_string($data) || strlen($data) === 0) {
return null;
}
return hash_hmac("sha256", $data, env("PROXY_PASSWORD", "unsecure_password"));
}
# Password
$pw = md5(env('PROXY_PASSWORD') . $url);
$urlToProxy = base64_encode(str_rot13($url));
$urlToProxy = urlencode(str_replace("/", "<<SLASH>>", $urlToProxy));
private static function checkPassword($url, $validUntil, $password)
{
$data = $url;
# Params
$params['password'] = $pw;
$params['url'] = $urlToProxy;
$iframeUrl = action('ProxyController@proxyPage', $params);
} else {
$params = \Request::all();
$params['password'] = $password;
$params['url'] = $urlToProxy;
$params["id"] = $key;
$iframeUrl = action('ProxyController@proxy', $params);
if(!empty($validUntil)){
$data .= $validUntil;
}
return $iframeUrl;
if (!is_string($data) || strlen($data) === 0) {
return false;
}
$excpectedHash = hash_hmac("sha256", $data, env("PROXY_PASSWORD", "unsecure_password"));
return hash_equals($excpectedHash, $password);
}
private function writeLog($targetUrl, $ip)
Loading