From e5745ba6c448eaca8e1dec031e3571b4de1b78ec Mon Sep 17 00:00:00 2001 From: MAEDA Go Date: Fri, 3 Jul 2026 11:12:10 +0900 Subject: [PATCH] Sanitize NUL bytes in filenames when uploading attachments --- app/controllers/wiki_controller.rb | 2 +- app/models/attachment.rb | 2 +- test/integration/api_test/attachments_test.rb | 17 +++++++++++++++++ test/unit/attachment_test.rb | 4 ++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/controllers/wiki_controller.rb b/app/controllers/wiki_controller.rb index 56e1f710e..3bc196064 100644 --- a/app/controllers/wiki_controller.rb +++ b/app/controllers/wiki_controller.rb @@ -441,7 +441,7 @@ class WikiController < ApplicationController extension = '.txt' # Keep this character set aligned with Attachment#sanitize_filename. # Unlike attachments, do not drop path-like components from wiki titles. - sanitized_title = page.title.tr('\\', '_').gsub(/[\/?%*:|"'<>\n\r]+/, '_') + sanitized_title = page.title.tr('\\', '_').gsub(/[\/?%*:|"'<>\n\r\x00]+/, '_') filename = "#{sanitized_title}#{extension}" dup_count = 0 diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 4d9974dce..26d4053d8 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -581,7 +581,7 @@ class Attachment < ApplicationRecord just_filename = value.gsub(/\A.*(\\|\/)/m, '') # Finally, replace invalid characters with underscore - just_filename.gsub(/[\/?%*:|"'<>\n\r]+/, '_') + just_filename.gsub(/[\/?%*:|"'<>\n\r\x00]+/, '_') end # Returns the subdirectory in which the attachment will be saved diff --git a/test/integration/api_test/attachments_test.rb b/test/integration/api_test/attachments_test.rb index 5b0e8315b..410a891db 100644 --- a/test/integration/api_test/attachments_test.rb +++ b/test/integration/api_test/attachments_test.rb @@ -190,6 +190,23 @@ class Redmine::ApiTest::AttachmentsTest < Redmine::ApiTest::Base assert_match /_test\.txt$/, attachment.diskfile end + test "POST /uploads.json should sanitize NUL in :filename param" do + set_tmp_attachments_directory + assert_difference 'Attachment.count' do + post( + '/uploads.json?filename=hello%00world.txt', + :headers => { + "RAW_POST_DATA" => 'File content', + "CONTENT_TYPE" => 'application/octet-stream' + }.merge(credentials('jsmith')) + ) + assert_response :created + end + + attachment = Attachment.order(id: :desc).first + assert_equal 'hello_world.txt', attachment.filename + end + test "POST /uploads.xml should not accept other content types" do set_tmp_attachments_directory assert_no_difference 'Attachment.count' do diff --git a/test/unit/attachment_test.rb b/test/unit/attachment_test.rb index a7f456a35..79240a6f8 100644 --- a/test/unit/attachment_test.rb +++ b/test/unit/attachment_test.rb @@ -509,11 +509,11 @@ class AttachmentTest < ActiveSupport::TestCase assert( Attachment.update_attachments( attachments, - {2 => {:filename => 'newname?.txt'}} + {2 => {:filename => "new\x00name?.txt"}} ) ) attachment = Attachment.find(2) - assert_equal 'newname_.txt', attachment.filename + assert_equal 'new_name_.txt', attachment.filename end def test_latest_attach -- 2.50.1