最近iterm2被曝了一个CVE-2019-9535
,据说可以通过cat xxx
达到命令执行的效果。随后我便开始对该漏洞进行分析,走了一趟歪路,不过之后回到正轨,却不知道该漏洞应该如何利用。先把目前分析的结果写一篇文章,如果后续能利用了,再来补充该文章。
iTerm2与Tmux的关系
经过几天的研究,终于搞明白iTerm2和Tmux的关系了。
tmux可以分为客户端与服务端,客户端的指令就是我们平常使用tmux用到的指令,比如:set -g set-titles on
服务端指令一般以百分号%
开头。
此外有固定格式来回应客户端的指令,%begin timestamp number init
开头,%end timestamp number init
结尾,中间的值就是服务端对于客户端的响应请求。
比如:
1 2
| %begin 1571109491 0 0 %end 1571109491 0 0
|
表示让客户端进行初始化,又比如,当客户端像服务端发送指令:show-options -v -g set-titles
,服务端的响应为:
1 2 3
| %begin 1571109491 1 1 on %end 1571109491 1 1
|
我们可以使用命令:tmux -C
来和服务端直接进行通信。
那iTerm2又是如何内置tmux的呢?为啥使用tmux -CC
的时候,iTerm2会新建窗口?
先来看看当我们运行tmux -CC
命令后,产生的现象:
1 2 3 4 5 6 7 8 9
| $ tmux -CC ** tmux mode started **
Command Menu ---------------------------- esc Detach cleanly. X Force-quit tmux mode. L Toggle logging. C Run tmux command.
|
当前终端得到如上所示的输出,然后弹出一个新的iterm2终端。但是在其他终端软件中却不会有这种情况。这就是iTerm2内置的tmux功能。
经过iTerm2源码的审计和调试发现,iTerm2会检测终端上的输出的字符,当检测到\033P1000p
的时候,将会进入tmux模式。iTerm2变成tmux客户端,与服务端进行通信。
可以使用如下命令进行简单的测试:
1 2 3 4 5 6 7 8 9
| $ echo -en "\033P1000p" ** tmux mode started **
Command Menu ---------------------------- esc Detach cleanly. X Force-quit tmux mode. L Toggle logging. C Run tmux command.
|
误入歧途的研究
因为之前看了一篇文章说,使用run 'xxxx'
来达到命令执行的效果,所以我就开始研究如果注入该命令。
审计源码,发现如下几处可能存在命令注入的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| # iTermTmuxStatusBarMonitor.m - (void)handleStatusLeftResponse:(NSString *)response { if (!response) { return; } NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]]; [_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusLeftValueExpansionResponse:)]; }
- (void)handleStatusRightResponse:(NSString *)response { if (!response) { return; } NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]]; [_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusRightValueExpansionResponse:)]; }
# iTermInitialDirectory+Tmux.m - (void)tmuxNewWindowCommandInSession:(NSString *)session recyclingSupported:(BOOL)recyclingSupported scope:(iTermVariableScope *)scope completion:(void (^)(NSString *))completion { NSArray *args = @[ @"new-window", @"-PF '#{window_id}'" ];
if (session) { NSString *targetSessionArg = [NSString stringWithFormat:@"\"%@:+\"", [session stringByEscapingQuotes]]; NSArray *insertionArguments = @[ @"-a", @"-t", targetSessionArg ]; args = [args arrayByAddingObjectsFromArray:insertionArguments]; } [self tmuxCommandByAddingCustomDirectoryWithArgs:args recyclingSupported:recyclingSupported scope:scope completion:completion]; }
# iTermTmuxOptionMonitor.m - (void)updateOnce { if (_haveOutstandingRequest) { DLog(@"Not making a request because one is outstanding"); return; } _haveOutstandingRequest = YES; NSString *command = [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat]; DLog(@"Request option with command %@", command); [self.gateway sendCommand:command responseTarget:self responseSelector:@selector(didFetch:) responseObject:nil flags:kTmuxGatewayCommandShouldTolerateErrors]; }
|
一个一个来说,我们要如何触发上面三处注入。
首先说明一下,我是用的是iTerm版本是Build 3.3.5,使用Xcode对源码进行的调试。
status bar注入点
handleStatusLeftResponse
函数和handleStatusRightResponse
其实代码一样,也就是一个左一个右的区别。对该函数进行回溯:
1 2 3 4 5
| handleStatusLeftResponse -> requestUpdates -> setActive -> _tmuxStatusBarMonitor.active = [iTermProfilePreferences boolForKey:KEY_SHOW_STATUS_BAR inProfile:aDict]; -> _tmuxStatusBarMonitor.active = [iTermProfilePreferences boolForKey:KEY_SHOW_STATUS_BAR inProfile:self.profile];
|
触发该点的注入,首先需要开启KEY_SHOW_STATUS_BAR
配置,没找到通过输出字符串就能控制配置的方法,所以只能手工修改配置,该配置如图所示,选中图中选项:
勾选后,iTerm2客户端就会向tmux服务端发送命令:display-message -p "#{status-left}"
,服务端将会有如下返回:
1 2 3
| %begin 1571109491 4 1 {xxxx} %end 1571109491 4 1
|
接收到返回后,iTerm2客户端会把接受到的字符串拼接到下一个发送到服务端的命令中:
1
| display-message -p "{xxxx}"
|
escapedString
函数只过滤了双引号和反斜杠,所以可以通过\n
之类的符号控制命令。
new-window
第二个存在注入点的函数是tmuxNewWindowCommandInSession
,通过审计追踪发现,该函数是点击事件的函数:
执行命令$ tmux -CC
后,会弹出新窗口,这个时候可以打开Shell -> tmux -> Dashboard
通过点击该+
按钮,表示new window
,可以触发tmuxNewWindowCommandInSession
函数,该window的session_name
将会被注入到该命令中:
1 2 3 4 5 6 7 8 9
| NSArray *args = @[ @"new-window", @"-PF '#{window_id}'" ];
if (session) { NSString *targetSessionArg = [NSString stringWithFormat:@"\"%@:+\"", [session stringByEscapingQuotes]]; NSArray *insertionArguments = @[ @"-a", @"-t", targetSessionArg ]; args = [args arrayByAddingObjectsFromArray:insertionArguments]; }
|
同样也是过滤了双引号和反斜杆,思路也同上。
title string
第三个函数为title string的注入,当服务端向客户端发送初始化指令的时候:
1 2
| %begin 1571109491 0 0 %end 1571109491 0 0
|
iTerm2客户端会进入tmuxInitialCommandDidCompleteSuccessfully
进行一些初始化工作,关键在loadTitleFormat
函数中:
1 2 3 4 5 6 7 8 9 10 11 12
| - (void)loadTitleFormat { [gateway_ sendCommandList:@[ [gateway_ dictionaryForCommand:@"show-options -v -g set-titles" responseTarget:self responseSelector:@selector(handleShowSetTitles:) responseObject:nil flags:0], [gateway_ dictionaryForCommand:@"show-options -v -g set-titles-string" responseTarget:self responseSelector:@selector(handleShowSetTitlesString:) responseObject:nil flags:0] ]]; }
|
该函数将会向服务端发送下面两句命令:
1 2
| show-options -v -g set-titles show-options -v -g set-titles-string
|
相关的回调函数:
1 2 3 4 5 6 7 8 9
| - (void)handleShowSetTitles:(NSString *)result { _shouldSetTitles = [result isEqualToString:@"on"]; [[NSNotificationCenter defaultCenter] postNotificationName:kTmuxControllerDidFetchSetTitlesStringOption object:self]; }
- (void)handleShowSetTitlesString:(NSString *)setTitlesString { _setTitlesString = [setTitlesString copy]; }
|
当set-titles == on
的情况下,会执行下面逻辑:
1 2 3 4
| tmuxDidFetchSetTitlesStringOption -> updateTmuxTitleMonitor -> installTmuxTitleMonitor -> initWithGateway
|
传入initWithGateway
函数的format
参数为set-titles-string
命令的返回值,用户可以控制。
接下来将会触发updateOnce
函数:
obj-c```
_format = set-titles-string
(NSString *)escapedFormat {
return [[_format stringByReplacingOccurrencesOfString:@”\“ withString:@”\\“]
stringByReplacingOccurrencesOfString:@”‘“ withString:@”\‘“];
(void)updateOnce {
……
NSString *command = [NSString stringWithFormat:@”display-message -t ‘%@’ -p ‘%@’”, _target, self.escapedFormat];
这个时候我们就能控制命令的参数了,这次过滤的是单引号和反斜杆,同样思路也同上。
# 回归正途
根据上面的分析,我们发现如果是手动的话,可以有很多方法执行系统命令,但是这个思路正确吗?
我们回头看看最初公布该漏洞的描述和利用视频。总结一下该漏洞被认为是高危的原因:
1. curl/tail就能触发,也就是只要输出相应的字符串(payload)就能触发该漏洞。
2. 连上ssh,也能触发本地的系统命令。
根据这两点,我们回头看看上面的利用思路,除了自己日自己,根本没任何用处。
这个时候研究进度又回到了起点,接下来我按照本地,通过只要输出相应就能执行命令的思路进行思考。
现在已知如下几点:
1. iTerm作为tmux客户端
2. 输出\033P1000p可以激活iTerm客户端
可以得出结论,攻击者应该是作为tmux服务端,这么一看,之前的研究思路完全是错误的。因为`run command`是tmux服务端进行执行的,我们要攻击的是iTerm2,所以攻击的是客户端。
经过研究发现iTerm2 客户端和tmux 服务端通信的方法就是简单的字符输入输出,所以就算我们可以注入iTerm2发给服务端的命令,但是iTerm2并不会执行该命令,只是对该命令进行字符输出。
到此为止,研究陷入僵局......