Attachment: Suspicious PDF created with headless browser

Detects PDF documents containing a table of contents that were generated using HeadlessChrome, Chromium with Skia/PDF, or QT with empty metadata fields - common characteristics of automated malicious document creation.

Sublime rule (View on GitHub)

  1name: "Attachment: Suspicious PDF created with headless browser"
  2description: "Detects PDF documents containing a table of contents that were generated using HeadlessChrome, Chromium with Skia/PDF, or QT with empty metadata fields - common characteristics of automated malicious document creation."
  3type: "rule"
  4severity: "high"
  5source: |
  6  type.inbound
  7  and (
  8    // directly attached PDF
  9    any(filter(attachments, .file_type == "pdf"),
 10        (
 11          // table of contents detection
 12          (
 13            any(file.explode(.),
 14                strings.contains(.scan.ocr.raw, 'TABLE OF CONTEN')
 15            )
 16            // the Table of contents can be on another page
 17            and any(file.explode(.),
 18                    regex.icontains(.scan.ocr.raw,
 19                                    '(?:[\r\n]|^)+(?:\s*1\s*(?:\.|:))?\s*Introduction'
 20                    )
 21                    or strings.icontains(.scan.ocr.raw, 'marked in red')
 22            )
 23          )
 24          or (
 25            any(file.explode(.),
 26                any(.scan.strings.strings,
 27                    // heading of sections within observed documents
 28                    any([
 29                          'Employee Acknowledgement',
 30                          'Document Summary',
 31                          'appraisal overview',
 32                          'accessing full appraisal',
 33                        ],
 34                        .. =~ .
 35                    )
 36                )
 37                // or links to free subdomain host
 38                or any(.scan.url.urls,
 39                       .domain.root_domain in $free_subdomain_hosts
 40                       and .domain.subdomain is not null
 41                )
 42            )
 43          )
 44        )
 45        and (
 46          (
 47            (
 48              strings.icontains(beta.parse_exif(.).creator, 'HeadlessChrome')
 49              or strings.icontains(beta.parse_exif(.).creator, 'Chromium')
 50            )
 51            and strings.icontains(beta.parse_exif(.).producer, 'Skia/PDF')
 52          )
 53          or (
 54            any(beta.parse_exif(.).fields,
 55                .key == "Creator"
 56                and (.value == "" or strings.istarts_with(.value, 'wkhtmltopdf'))
 57            )
 58            and any(beta.parse_exif(.).fields,
 59                    .key == "Title"
 60                    and (
 61                      .value == ""
 62                      // company handbook
 63                      or .value in ('Company HandBook')
 64                      // appraisal themes
 65                      or strings.icontains(.value,
 66                                           'Employee Performance Appraisal'
 67                      )
 68                    )
 69            )
 70            and strings.istarts_with(beta.parse_exif(.).producer, 'QT ')
 71          )
 72        )
 73    )
 74    // or within an attached EML
 75    or any(filter(attachments,
 76                  .content_type == "message/rfc822" or .file_extension == "eml"
 77           ),
 78           any(filter(file.parse_eml(.).attachments, .file_type == "pdf"),
 79               (
 80                 // table of contents detection
 81                 (
 82                   any(file.explode(.),
 83                       strings.contains(.scan.ocr.raw, 'TABLE OF CONTEN')
 84                   )
 85                   // the Table of contents can be on another page
 86                   and any(file.explode(.),
 87                           regex.icontains(.scan.ocr.raw,
 88                                           '(?:[\r\n]|^)+(?:\s*1\s*(?:\.|:))?\s*Introduction'
 89                           )
 90                           or strings.icontains(.scan.ocr.raw, 'marked in red')
 91                   )
 92                 )
 93                 or (
 94                   any(file.explode(.),
 95                       any(.scan.strings.strings,
 96                           // heading of sections within observed documents
 97                           any([
 98                                 'Employee Acknowledgement',
 99                                 'Document Summary',
100                                 'appraisal overview',
101                                 'accessing full appraisal',
102                               ],
103                               .. =~ .
104                           )
105                       )
106                       // or links to free subdomain host
107                       or any(.scan.url.urls,
108                              .domain.root_domain in $free_subdomain_hosts
109                              and .domain.subdomain is not null
110                       )
111                   )
112                 )
113               )
114               and (
115                 (
116                   (
117                     strings.icontains(beta.parse_exif(.).creator,
118                                       'HeadlessChrome'
119                     )
120                     or strings.icontains(beta.parse_exif(.).creator, 'Chromium')
121                   )
122                   and strings.icontains(beta.parse_exif(.).producer, 'Skia/PDF')
123                 )
124                 or (
125                   any(beta.parse_exif(.).fields,
126                       .key == "Creator"
127                       and (
128                         .value == ""
129                         or strings.istarts_with(.value, 'wkhtmltopdf')
130                       )
131                   )
132                   and any(beta.parse_exif(.).fields,
133                           .key == "Title"
134                           and (
135                             .value == ""
136                             // company handbook
137                             or .value in ('Company HandBook')
138                             // appraisal themes
139                             or strings.icontains(.value,
140                                                  'Employee Performance Appraisal'
141                             )
142                           )
143                   )
144                   and strings.istarts_with(beta.parse_exif(.).producer, 'QT ')
145                 )
146               )
147           )
148    )
149  )  
150
151attack_types:
152  - "Credential Phishing"
153tactics_and_techniques:
154  - "Evasion"
155  - "PDF"
156detection_methods:
157  - "Content analysis"
158  - "Exif analysis"
159  - "File analysis"
160  - "Optical Character Recognition"
161id: "8f3108d7-e224-5bb0-81f4-e4f8506cfed3"
to-top