diff --git a/CHANGELOG b/CHANGELOG
index fa0960b28471d8535a8447e2a21ef1ffd57a0073..39532e88138f5d13c8d48d8eca8afedd6ed90dc8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -118,6 +118,7 @@ v 8.8.5
   - Prevent unauthorized access for projects build traces
   - Forbid scripting for wiki files
   - Only show notes through JSON on confidential issues that the user has access to
+  - Banzai::Filter::UploadLinkFilter use XPath instead CSS expressions
 
 v 8.8.4
   - Fix LDAP-based login for users with 2FA enabled. !4493
diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
index c0f503c9af3a859153d94da6a70fd8570d18da38..45bb66dc99f0ec7a291e0c152d7c3595679ac6f9 100644
--- a/lib/banzai/filter/upload_link_filter.rb
+++ b/lib/banzai/filter/upload_link_filter.rb
@@ -10,11 +10,11 @@ module Banzai
       def call
         return doc unless project
 
-        doc.search('a').each do |el|
+        doc.xpath('descendant-or-self::a[starts-with(@href, "/uploads/")]').each do |el|
           process_link_attr el.attribute('href')
         end
 
-        doc.search('img').each do |el|
+        doc.xpath('descendant-or-self::img[starts-with(@src, "/uploads/")]').each do |el|
           process_link_attr el.attribute('src')
         end
 
@@ -24,12 +24,7 @@ module Banzai
       protected
 
       def process_link_attr(html_attr)
-        return if html_attr.blank?
-
-        uri = html_attr.value
-        if uri.starts_with?("/uploads/")
-          html_attr.value = build_url(uri).to_s
-        end
+        html_attr.value = build_url(html_attr.value).to_s
       end
 
       def build_url(uri)
diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
index b83be54746c9139848bff8d03835a2dd9ec4a008..273d2ed709adf387a16134d1de0787f2915848f3 100644
--- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
@@ -23,6 +23,14 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
     %(<a href="#{path}">#{path}</a>)
   end
 
+  def nested_image(path)
+    %(<div><img src="#{path}" /></div>)
+  end
+
+  def nested_link(path)
+    %(<div><a href="#{path}">#{path}</a></div>)
+  end
+
   let(:project) { create(:project) }
 
   shared_examples :preserve_unchanged do
@@ -47,11 +55,19 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
       doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
       expect(doc.at_css('a')['href']).
         to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+
+      doc = filter(nested_link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+      expect(doc.at_css('a')['href']).
+        to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
     end
 
     it 'rebuilds relative URL for an image' do
-      doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
-      expect(doc.at_css('a')['href']).
+      doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+      expect(doc.at_css('img')['src']).
+        to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
+
+      doc = filter(nested_image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
+      expect(doc.at_css('img')['src']).
         to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
     end