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                       // exclude sources of potential FPs
 42                       and .domain.root_domain not in ("atlassian.net")
 43                )
 44            )
 45          )
 46        )
 47        and (
 48          (
 49            (
 50              strings.icontains(beta.parse_exif(.).creator, 'HeadlessChrome')
 51              or strings.icontains(beta.parse_exif(.).creator, 'Chromium')
 52            )
 53            and strings.icontains(beta.parse_exif(.).producer, 'Skia/PDF')
 54          )
 55          or (
 56            any(beta.parse_exif(.).fields,
 57                .key == "Creator"
 58                and (.value == "" or strings.istarts_with(.value, 'wkhtmltopdf'))
 59            )
 60            and any(beta.parse_exif(.).fields,
 61                    .key == "Title"
 62                    and (
 63                      .value == ""
 64                      // company handbook
 65                      or .value in ('Company HandBook')
 66                      // appraisal themes
 67                      or strings.icontains(.value,
 68                                           'Employee Performance Appraisal'
 69                      )
 70                    )
 71            )
 72            and strings.istarts_with(beta.parse_exif(.).producer, 'QT ')
 73          )
 74        )
 75    )
 76    // or within an attached EML
 77    or any(filter(attachments,
 78                  .content_type == "message/rfc822" or .file_extension == "eml"
 79           ),
 80           any(filter(file.parse_eml(.).attachments, .file_type == "pdf"),
 81               (
 82                 // table of contents detection
 83                 (
 84                   any(file.explode(.),
 85                       strings.contains(.scan.ocr.raw, 'TABLE OF CONTEN')
 86                   )
 87                   // the Table of contents can be on another page
 88                   and any(file.explode(.),
 89                           regex.icontains(.scan.ocr.raw,
 90                                           '(?:[\r\n]|^)+(?:\s*1\s*(?:\.|:))?\s*Introduction'
 91                           )
 92                           or strings.icontains(.scan.ocr.raw, 'marked in red')
 93                   )
 94                 )
 95                 or (
 96                   any(file.explode(.),
 97                       any(.scan.strings.strings,
 98                           // heading of sections within observed documents
 99                           any([
100                                 'Employee Acknowledgement',
101                                 'Document Summary',
102                                 'appraisal overview',
103                                 'accessing full appraisal',
104                               ],
105                               .. =~ .
106                           )
107                       )
108                       // or links to free subdomain host
109                       or any(.scan.url.urls,
110                              .domain.root_domain in $free_subdomain_hosts
111                              and .domain.subdomain is not null
112                       )
113                   )
114                 )
115               )
116               and (
117                 (
118                   (
119                     strings.icontains(beta.parse_exif(.).creator,
120                                       'HeadlessChrome'
121                     )
122                     or strings.icontains(beta.parse_exif(.).creator, 'Chromium')
123                   )
124                   and strings.icontains(beta.parse_exif(.).producer, 'Skia/PDF')
125                 )
126                 or (
127                   any(beta.parse_exif(.).fields,
128                       .key == "Creator"
129                       and (
130                         .value == ""
131                         or strings.istarts_with(.value, 'wkhtmltopdf')
132                       )
133                   )
134                   and any(beta.parse_exif(.).fields,
135                           .key == "Title"
136                           and (
137                             .value == ""
138                             // company handbook
139                             or .value in ('Company HandBook')
140                             // appraisal themes
141                             or strings.icontains(.value,
142                                                  'Employee Performance Appraisal'
143                             )
144                           )
145                   )
146                   and strings.istarts_with(beta.parse_exif(.).producer, 'QT ')
147                 )
148               )
149           )
150    )
151  )  
152
153attack_types:
154  - "Credential Phishing"
155tactics_and_techniques:
156  - "Evasion"
157  - "PDF"
158detection_methods:
159  - "Content analysis"
160  - "Exif analysis"
161  - "File analysis"
162  - "Optical Character Recognition"
163id: "8f3108d7-e224-5bb0-81f4-e4f8506cfed3"
to-top