Skip to content

Commit d730803

Browse files
committed
Fix SCP path traversal issue
This commit fixes an issue where a malicious SCP server could trick an SCP client into writing files outside of a requested target directory. Thanks go to Jaden Furtado for reporting this issue and providing detailed reproduction steps. Note: While this fix should prevent writing outside a target directory, the SCP protocol is inherently insecure and may still be able to overwrite arbitrary files within the target directory, even if they don't match the requested source files to copy. To avoid this issue, it is strongly recommended that SFTP be used in place of SCP.
1 parent 3d515ba commit d730803

2 files changed

Lines changed: 18 additions & 0 deletions

File tree

asyncssh/scp.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ def _parse_cd_args(args: bytes) -> Tuple[int, int, bytes]:
136136

137137
try:
138138
permissions, size, name = args.split(None, 2)
139+
140+
if b'/' in name or b'\\' in name or name == b'..':
141+
raise _scp_error(SFTPBadMessage, 'Invalid filename')
142+
139143
return int(permissions, 8), int(size), name
140144
except ValueError:
141145
raise _scp_error(SFTPBadMessage,

tests/test_sftp.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5737,6 +5737,9 @@ async def _handle_client(process):
57375737

57385738
if command.endswith('get_connection_lost'):
57395739
pass
5740+
elif command.endswith('get_invalid_filename_response'):
5741+
await process.stdin.read(1)
5742+
process.stdout.write('C0644 0 ../src\n')
57405743
elif command.endswith('get_dir_no_recurse'):
57415744
await process.stdin.read(1)
57425745
process.stdout.write('D0755 0 src\n')
@@ -5772,6 +5775,17 @@ async def _handle_client(process):
57725775

57735776
return await cls.create_server(process_factory=_handle_client)
57745777

5778+
@asynctest
5779+
async def test_get_invalid_filename_response(self):
5780+
"""Test receiving directory when recurse wasn't requested"""
5781+
5782+
try:
5783+
with self.assertRaises((SFTPBadMessage, SFTPConnectionLost)):
5784+
await scp((self._scp_server, 'get_invalid_filename_response'),
5785+
'dst')
5786+
finally:
5787+
remove('dst')
5788+
57755789
@asynctest
57765790
async def test_get_directory_without_recurse(self):
57775791
"""Test receiving directory when recurse wasn't requested"""

0 commit comments

Comments
 (0)