|
| 1 | +## Path traversal in Ruby's Tempfile and mktmpdir on Windows |
| 2 | + |
| 3 | +### The bug |
| 4 | + |
| 5 | +While playing around Ruby we noticed that Ruby's Tempfile/mktmpdir were allowing `\` in Filename and extension which immediately made us wonder what happens when we do this on windows as the file separator on Windows is also `\`. To our surprise, it was working, we had a path traversal in Tempfile on Windows. But to our knowledge while reading source of Node, generally languages have file separator based on the OS you're running the language on. So we wondered what is Ruby doing to identify file sepaator here. It didn't work on `File.basename` which made it clear that Tempfile is doing something differet. |
| 6 | + |
| 7 | +### CVE-2018-6914 Patch analysis |
| 8 | + |
| 9 | +A similar issue was previously reported by @oooooo_q for Linux, where `/` could be used which made path traversal possible on both Linux and Windows. We looked at the patch of this bug, which was relatively straight forward, delete `/` (File::SEPARATOR) and `\` (File::ALT_SEPARATOR, set on windows only) from the input before creating the file. |
| 10 | + |
| 11 | +```patch |
| 12 | +--- lib/tmpdir.rb |
| 13 | ++++ lib/tmpdir.rb |
| 14 | +@@ -114,10 +114,12 @@ def create(basename, tmpdir=nil, max_try: nil, **opts) |
| 15 | + end |
| 16 | + n = nil |
| 17 | + prefix, suffix = basename |
| 18 | ++ prefix = prefix.delete("#{File::SEPARATOR}#{File::ALT_SEPARATOR}") |
| 19 | + prefix = (String.try_convert(prefix) or |
| 20 | + raise ArgumentError, "unexpected prefix: #{prefix.inspect}") |
| 21 | + suffix &&= (String.try_convert(suffix) or |
| 22 | + raise ArgumentError, "unexpected suffix: #{suffix.inspect}") |
| 23 | ++ suffix &&= suffix.delete("#{File::SEPARATOR}#{File::ALT_SEPARATOR}") |
| 24 | + begin |
| 25 | +``` |
| 26 | + |
| 27 | +We run the above patch |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +The patch here works as expected, where's the problem? why is the `\` not removed from the input then? |
| 32 | + |
| 33 | +### Any changes that can regress this? |
| 34 | + |
| 35 | +We opened the master branch and went through the code and found the code has recently changed, the change basically adds more characters and uses a constant now for this chars |
| 36 | + |
| 37 | +```patch |
| 38 | +--- |
| 39 | + lib/tmpdir.rb | 6 ++++-- |
| 40 | + 1 file changed, 4 insertions(+), 2 deletions(-) |
| 41 | + |
| 42 | +diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb |
| 43 | +index 87e53a8..05e74eb 100644 |
| 44 | +--- a/lib/tmpdir.rb |
| 45 | ++++ b/lib/tmpdir.rb |
| 46 | +@@ -112,6 +112,8 @@ def tmpdir |
| 47 | + Dir.tmpdir |
| 48 | + end |
| 49 | + |
| 50 | ++ UNUSABLE_CHARS = [File::SEPARATOR, File::ALT_SEPARATOR, File::PATH_SEPARATOR, ":"].uniq.join("").freeze |
| 51 | ++ |
| 52 | + def create(basename, tmpdir=nil, max_try: nil, **opts) |
| 53 | + if $SAFE > 0 and tmpdir.tainted? |
| 54 | + tmpdir = '/tmp' |
| 55 | +@@ -123,10 +125,10 @@ def create(basename, tmpdir=nil, max_try: nil, **opts) |
| 56 | + prefix, suffix = basename |
| 57 | + prefix = (String.try_convert(prefix) or |
| 58 | + raise ArgumentError, "unexpected prefix: #{prefix.inspect}") |
| 59 | +- prefix = prefix.delete("#{File::SEPARATOR}#{File::ALT_SEPARATOR}") |
| 60 | ++ prefix = prefix.delete(UNUSABLE_CHARS) |
| 61 | + suffix &&= (String.try_convert(suffix) or |
| 62 | + raise ArgumentError, "unexpected suffix: #{suffix.inspect}") |
| 63 | +- suffix &&= suffix.delete("#{File::SEPARATOR}#{File::ALT_SEPARATOR}") |
| 64 | ++ suffix &&= suffix.delete(UNUSABLE_CHARS) |
| 65 | + |
| 66 | +``` |
| 67 | + |
| 68 | +Commit - https://github.com/ruby/tmpdir/commit/5a70e9c27d29ebcb7a04a6c00400219ac62ec0af |
| 69 | + |
| 70 | +### What's wrong? why `\` is not deleted? |
| 71 | + |
| 72 | +The change were basically same to us, until we tried them by ourself. |
| 73 | + |
| 74 | + |
| 75 | + |
| 76 | +Whoops, `.delete` expects `\` to be escaped, Was it intentional to write delete this way? or It's wrongly used here? |
| 77 | + |
| 78 | +Ruby fixed it by changing the way delete has been used here - https://github.com/ruby/tmpdir/commit/adf294bc2d10cd223aa3ca488079ec313032c07b |
| 79 | +HackerOne Report - https://hackerone.com/reports/1131465 |
| 80 | + |
| 81 | +That's all, Thanks for reading. |
0 commit comments