This post is about a handy tool I developed called seads
, which helps detecting malvertising on search engines. Here is the GitHub repository, if you have ideas or feedback related to this project, feel free to reach out!
The idea for seads
came from the growing number of cases of malicious ads displayed in search engine results and my need for a tool to detect them automatically. Recently, various incidents shows how malvertising has been used for redirecting users to phishing websites and for malware distribution.
Malvertising is a particularly sneaky technique, as it exploits the trust users place in the top search results of by their favorite search engines. A suggestion: to increase your online safety, consider installing tools like uBlock Origin or other reputable ad-blockers. These tools can help mitigate the risks associated with malvertising by blocking potentially harmful ads :)
Introducing seads
seads
is written in GoLang and it uses headless browser pages to navigate to popular search engines, where it retrieves a list of ads displayed in response to user-submitted queries. At the time of this blog post, seads
is able to detect ads on Google, Bing, DuckDuckGo and Yahoo.
In addition, seads
can notify about detected ads via email, Slack or Telegram, it can provide a screenshot to support the evidence of malvertising campaigns, and can be executed using Docker.
Getting started
You can download the binary from the releases section on Github or compile it from source using the following Go installation command:
go install github.com/andpalmier/seads/cmd/seads@latest
Alternatively, you can use Docker to run seads
without affecting your local setup:
docker build -t seads .
docker run -it -v "$(pwd)":/mnt seads -h
Once installed, you need to create a config file, here is an example that you can adjust to your needs:
queries:
- query: "apple"
expected-domains: [apple.com, amazon.com]
- query: "as roma"
expected-domains: []
The config file above will query “ipad” and “as roma” in search engines, the field “expected-domains” is used to specify domains we are expecting to appear in the ads of search engines while searching for the specified keywords. Domains in “expected-domains” will still appear in the output of seads
, but won’t be sent in the notification.
Finally, seads
can be executed with the following flags:
-config string (REQUIRED)
path to config file (default "config.yaml").
-concurrency int
number of concurrent headless browsers (default 4).
-cleanlinks
print clear links in output (links will remain defanged in notifications).
-notify
notify if unexpected domains are found.
-screenshot string
path to store screenshots (if empty, the screenshot feature will be disabled).
In order to receive notifications via email, Slack, or Telegram, you need to configure the config.yaml
file with your credentials and preferences:
mail:
host: SMTP server hostname or IP address (string)
port: SMTP server port, common ones are 25, 465, 587 or 2525 (int)
username: SMTP server username (string)
password: SMTP server password (string)
from: E-mail address that the mail are sent from (string)
recipients: List of recipient e-mails (list of strings)
slack:
token: API Bot token (string)
channels: Channels to send messages to in Cxxxxxxxxxx format(list of strings)
telegram:
token: API Bot token (string)
chatid: Chat IDs or Channel names (list of strings)
Testing it
Here is a real-life example of detecting ads with seads
using the config file pasted above. The tool is executed with:
seads -config config.yaml -screenshot scr -notify
and it produces the following output:
If ads were found, a new folder src
will be created, containing screenshots like the one below:
A screenshot will be taken only if at least one ad is detected, ensuring that screenshots are relevant. Screenshots are named following this format: searchengine-query-timestamp.png
. For example, the screenshot above will be named yahoo-apple-1710278888941790000.png
.
And here is the notification which will be sent to the specified channels:
Here are the "unexpected domains" found during the last execution of seads:
Message creation date: 2024-03-12 22:28:14
* Search engine: Yahoo
Search term: apple
Domain: reparaturpc[.]ch
Full link: www[.]https://reparaturpc[.]ch/de/?msclkid=75c3ce8f8942156ac179ab7f41a03704
* Search engine: Yahoo
Search term: apple
Domain: fust[.]ch
Full link: https://www[.]fust[.]ch/de/marken/apple[.]html?&msclkid=a836011a07061ba4052864eacfe7d0fd&utm_source=bing&utm_medium=cpc&utm_campaign=Bing%20-%20NBrand%20-%20S%20-%20D%20-%20MM%20PC%20Marke%20Apple&utm_term=apple&utm_content=1_Apple%3D2_undefined%C2%A63_Nbrand&gclid=a836011a07061ba4052864eacfe7d0fd&gclsrc=3p[.]ds
* Search engine: Yahoo
Search term: apple
Domain: jobs[.]ch
Full link: https://www[.]jobs[.]ch/en/vacancies/?term=apple&utm_source=bing&utm_medium=search&utm_campaign=wb:jobs|tg:b2c|cn:ww|lg:en|ct:search,nonbrand,company|cd:company|mg:job-application|pd:y|tt:cpc|gt:keyword,nonbrand,company|gd:company&msclkid=17ac4d7d0b0616628f40288dc3e79a46&utm_term=apple&utm_content=gt%3Akeyword,nonbrand,company%7Cgd%3Acompany
* Search engine: Yahoo
Search term: apple
Domain: amazon[.]com
Full link: https://www[.]amazon[.]com/s?k=applwe&adgrpid=1344703557775981&hvadid=84044278562817&hvbmt=be&hvdev=c&hvlocphy=3322&hvnetw=o&hvqmt=e&hvtargid=kwd-84044521042995%3Aloc-175&hydadcr=29387_14610683&tag=mh0b-20&ref=pd_sl_7xha1yy51_e
This message was automatically sent by seads (www.github.com/andpalmier/seads)
Automating executions
It is possible to further leverage the notification feature by automating exectution of seads
. In Linux, we can use cron
to do it, for example, to run seads
daily at 9:00 AM, you can add the following entry to your crontab:
0 9 * * * /path/to/seads -config /path/to/config.yaml -screenshot /path/to/screenshots -notify
For Windows and macOS users, similar scheduling options are available using Task Scheduler and launchd
, respectively.
By setting up a well-configured file, we can automate the execution of seads
and receive notifications whenever it detects an ad from an unexpected domain. For instance, if we’re interested in potential malvertising targeting our company, we simply list our company’s domains in the expected-domains field. This way, seads
will continuously monitor for any ads that don’t match our specified domains, alerting us immediately if it finds any.
Libraries used
For automating the headless browser, I relied on Rod, a powerful library that works with the DevTools Protocol. Rod is very popular for tasks like web scraping and automation, and it is capable of mimicking manual interactions with the browser. You can find additional information about Rod on its GitHub repository and documentation site.
As for notifications, I used the Shoutrrr Notification library, which provides seamless integration for notifications in Go applications. You can find more details about Shoutrrr on its GitHub repository and documentation site.
Limitations
Due to the nature of search engine ads work, a single search might not reveal all the ads. However, by using the concurrency
flag to increase the number of headless browsers working simultaneously - although it may slightly slow down detection - it will ensures a more comprehensive collection of ads.
It’s also worth mentioning that notifications sent have character limits, so messages exceeding this limit won’t be sent. I have encounter this issue while testing seads
, as many search engine ad links may be quite long due to tracking elements. To address this, one approach could be setting up separate config files for different campaigns and running seads
separately for each.
In conclusion, seads
can detect malvertising campaigns in Google, Bing, DuckDuckGo and Yahoo ads by relying on concurrent headless browser pages, which will navigate to the search engines, and display the ads which were found. The tool can be automatically executed with cron
, Windows Scheduler or launchd
, and it can take screenwhots of the ads and send notifications via email, Slack or Telegram.