CVE-2023-46730 (PHP, published 2023-11-07T18:15Z)
Description: value: Group-Office is an enterprise CRM and groupware tool. In affected versions there is full Server-Side Request Forgery (SSRF) vulnerability in the /api/upload.php endpoint. The /api/upload.php endpoint does not filter URLs which allows a malicious user to cause the server to make resource requests to untrusted domains. Note that protocols like file:// can also be used to access the server disk. The request result (on success) can then be retrieved using /api/download.php. This issue has been addressed in versions 6.8.15, 6.7.54, and 6.6.177. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-918 \u2014 Server-Side Request Forgery (SSRF)
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
Repository: https://github.com/Intermesh/groupoffice
Files changed:
www/api/upload.php
www/go/core/model/Settings.php
www/go/core/views/extjs3/form/ImageField.js
www/go/core/webclient/Extjs3.php
Commit message: Fix security issue GHSA-vw6c-h82w-mvfv


Vulnerable code:
	if (isset($_GET['url'])) {
		$tmpFile = \go\core\fs\File::tempFile('tmp');
		try {
			$httpClient = new Client();
			$response = $httpClient->download($_GET['url'], $tmpFile);
			$blob = Blob::fromTmp($tmpFile);
			$blob->name = $response['name'];
			$blob->type = $response['type'];
		} catch(\Exception $e) {
			throw new \Exception("Failed to download from given URL " .  $_GET['url']);
		}

		//raise max_execution_time for calculating hash of large files
		ini_set('max_execution_time', 300);

		$filename = Request::get()->getHeader('X-File-Name');
		$filename = Request::headerDecode($filename);
		$tmpFile = \go\core\fs\File::tempFile($filename);

		$input = fopen('php://input', "r");
		$fp = $tmpFile->open("w+");
		while ($data = fread($input, 4096)) { // 4kb at the time
			fwrite($fp, $data);
		}
		fclose($fp);
		fclose($input);

		if (\go\core\jmap\Capabilities::get()->maxSizeUpload && $tmpFile->getSize() > \go\core\jmap\Capabilities::get()->maxSizeUpload) {
			$tmpFile->delete();
			Response::get()->setStatus(413);
			Response::get()->output([
				"error" => "File exceeds maximum size of " . \go\core\jmap\Capabilities::get()->maxSizeUpload . " bytes"
			]);
			exit();
		}

		$blob = Blob::fromTmp($tmpFile);
		$blob->name = $filename;
		if (Request::get()->getHeader('X-File-LastModified') == null) {
			$blob->modifiedAt = new \go\core\util\DateTime();
		} else {
			$blob->modifiedAt = new \go\core\util\DateTime('@' . Request::get()->getHeader('X-File-LastModified'));
		}

	 * 
	 * @var string 
			}, {
				iconCls: 'ic-link',
				text: t("From URL"),
				handler: function () {
					Ext.MessageBox.prompt(t("Set Image From URL"), t("Enter URL"), function(btn, url) {

						
						if(btn != "ok" || !url) {
							return;
						}


						Ext.Ajax.request({
							url: go.User.uploadUrl + "?url=" + encodeURIComponent(url),
							method: "GET",
							success: function(response) {
								data = Ext.decode(response.responseText);
								this.setValue(data.blobId);								
							},
							scope: this
						});

					}, this);
				},
				scope: this

Fixed code:
	//raise max_execution_time for calculating hash of large files
	ini_set('max_execution_time', 300);
	$filename = Request::get()->getHeader('X-File-Name');
	$filename = Request::headerDecode($filename);
	$tmpFile = \go\core\fs\File::tempFile($filename);
	$input = fopen('php://input', "r");
	$fp = $tmpFile->open("w+");
	while ($data = fread($input, 4096)) { // 4kb at the time
		fwrite($fp, $data);
	}
	fclose($fp);
	fclose($input);

	if (\go\core\jmap\Capabilities::get()->maxSizeUpload && $tmpFile->getSize() > \go\core\jmap\Capabilities::get()->maxSizeUpload) {
		$tmpFile->delete();
		Response::get()->setStatus(413);
		Response::get()->output([
			"error" => "File exceeds maximum size of " . \go\core\jmap\Capabilities::get()->maxSizeUpload . " bytes"
		]);
		exit();
	}
	$blob = Blob::fromTmp($tmpFile);
	$blob->name = $filename;
	if (Request::get()->getHeader('X-File-LastModified') == null) {
		$blob->modifiedAt = new \go\core\util\DateTime();
		$blob->modifiedAt = new \go\core\util\DateTime('@' . Request::get()->getHeader('X-File-LastModified'));
	 *
	 * Alternatively to generate a URL based on the request you can use:
	 *
	 * @example
	 * ```
	 * Extjs3::get()->getBaseUrl();
	 * ```
	 *
	 * @var string
	 * eg. https://groupoffice.example.com/groupoffice/
	 *