QR code threat links

yanfei

Active Member
Mar 7, 2019
17
3
43
38
Hi,In recent years, there have been more and more spam/phishing emails that send pictures/documents with QR code links. Do we have a protection plan for this type of email? Image content can already be identified through spamassassin 4.0. Can you add the function of identifying and converting QR codes into threat connections and achieving final analysis?
 
I also recently started wondering the same thing. No luck finding any info on this forum, but did manage to find the following SpamAssassin plugin to decode QR codes and filter based on the results.

https://github.com/mxguardian/Mail-SpamAssassin-Plugin-QRCode

I've never added a new plugin to SpamAssassin, so I'm off to break things! If anyone as a basic/generic walkthrough for installing new plugins to SpamAssassin in PMG, I'd love to see them!
 
Hello,

Finally, has anyone been able to install this plugin successfully?
I'm working on it unsuccessfully, and I'm quite new to PMG and not sure how to install a plugin.

Below, what I did to install Mail-SpamAssassin-Plugin-QRCode

  1. Installation of prerequired
    1. cpan Barcode::ZBar
    2. cpan Image::Magick
  2. Copy the QRCode.pm under /usr/share/perl5/Mail/SpamAssassin/Plugin/
  3. Create the configuration file with nano nano /usr/share/spamassassin/40_QRcode.cf with the content:
    Code:
    loadplugin Mail::SpamAssassin::Plugin::QRCode
    
    ifplugin Mail::SpamAssassin::Plugin::QRCode
      qr_code_min_width     100
      qr_code_max_width     0
      qr_code_min_height    100
      qr_code_max_height    0
      qr_code_scan_pdf      0
    endif
    
    uri_detail      HAS_QRCODE_URI   type =~ /^qrcode$/
    describe        HAS_QRCODE_URI   Message contains a URI embedded in a QR code
  4. On the PMG GUI under "spam detector => Custom Scores" I create the rules
    Code:
    # qrcode
    score HAS_QRCODE_URI 30.000
My issue is that I'm not sure the plugin is loading well and if I'm using the correct way to create a rule to announce like spam an email with a QR code.

Any idea or suggestion is welcome.
Thanks a lot
 
Finally, I was able to deploye the Qrcode plugin successfully.

Below, how I did:

  1. Install prerequire
    apt install cpanminus
    apt install libzbar0 libzbar-dev
    cpanm Barcode::ZBar
    apt install imagemagick libimage-magick-perl libmagick++-dev
    cpanm Image::Magick
  2. copy "QRCode.pm" file with wget -O /usr/share/perl5/Mail/SpamAssassin/Plugin/QRCode.pm https://raw.githubusercontent.com/mxguardian/Mail-SpamAssassin-Plugin-QRCode/main/lib/Mail/SpamAssassin/Plugin/QRCode.pm
  3. Active the plugin with nano /etc/mail/spamassassin/init.pre and add:
    Code:
    # QRCode additional custom plugin
    loadplugin Mail::SpamAssassin::Plugin::QRCode
  4. add the plugin configuration with nano /etc/mail/spamassassin/local.cf and add :
    Code:
    #Custome -  check plugin and option.
    ifplugin Mail::SpamAssassin::Plugin::QRCode
      qr_code_min_width     100
      qr_code_max_width     0
      qr_code_min_height    100
      qr_code_max_height    0
      qr_code_scan_pdf      0
    endif
    
    #Custome - Rules definitions for QRCodes plugin
    uri_detail      HAS_QRCODE_URI   type =~ /^qrcode$/
    describe        HAS_QRCODE_URI   Message contains a URI embedded in a QR code
    
    #Custome - score attribution
    score          HAS_QRCODE_URI    30.0

Now the plugin must be functional.
If I try to send myself an email with a QR code inside an email, the spamscore explodes:
Code:
X-SPAM-LEVEL: Spam detection results:  30
    DKIM_SIGNED               0.1 Message has a DKIM or DK signature, not necessarily valid
    DKIM_VALID               -0.1 Message has at least one valid DKIM or DK signature
    DKIM_VALID_AU            -0.1 Message has a valid DKIM or DK signature from author's domain
    DKIM_VALID_EF            -0.1 Message has a valid DKIM or DK signature from envelope-from domain
    DMARC_PASS               -0.1 DMARC pass policy
    HAS_QRCODE_URI             30 Message contains a URI embedded in a QR code
    HTML_IMAGE_ONLY_08          1 HTML: images with 400-800 bytes of words
    HTML_MESSAGE            0.001 HTML included in message
    KAM_INFOUSMEBIZ          0.75 Prevalent use of .info|.us|.me|.me.uk|.biz|xyz|id|rocks|life domains in spam/malware
    RCVD_IN_MSPIKE_H5          -1 Excellent reputation (+5)
    RCVD_IN_MSPIKE_WL       -0.01 Mailspike good senders
    RCVD_IN_VALIDITY_CERTIFIED_BLOCKED  0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked.  See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information.
    RCVD_IN_VALIDITY_RPBL_BLOCKED  0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked.  See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information.
    RCVD_IN_VALIDITY_SAFE_BLOCKED  0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked.  See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information.
    RCVD_IN_ZEN_BLOCKED_OPENDNS  0.001 ADMINISTRATOR NOTICE: The query to zen.spamhaus.org was blocked due to usage of an open resolver. See https://www.spamhaus.org/returnc/pub/
    SPF_HELO_PASS          -0.001 SPF: HELO matches SPF record
    SPF_PASS               -0.001 SPF: sender matches SPF record
    URIBL_BLOCKED           0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked.  See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [virus.com,ik.me]
    URIBL_DBL_BLOCKED_OPENDNS  0.001 ADMINISTRATOR NOTICE: The query to dbl.spamhaus.org was blocked due to usage of an open resolver. See https://www.spamhaus.org/returnc/pub/ [virus.com,ik.me]

I hope this step will help you to add the QrCode plugin to PMG
 
First: Thanks xque for your write up! I wouldn't have been able to get this going without your notes!

Unfortunately, by the time I got a chance to sit down and go through setting this going, I wasn't able to get things going from your notes alone. Whether this was my own ignorance (a *distinct* possibility!) or minor changes in versions of PMG or the underlying OS, or assumptions about which packages were already installed, etc. I don't know.

With that in mind, here is my write up (for our internal documentation) of the installation ind configuration process, and some things I learned along the way. Hopefully they will be helpful to others!

(Edit - typos and this note that the write up is in the next post)
 
Last edited:
(Sorry, didn't realize the forum wouldn't like ".md" files... here is the write up:) I TRIED to get the formatting fixed from my original markdown file, but I'm sure I missed some!)

QR Code Threat Detection on Proxmox Mail Gateway

Summary

Adds QR-code decoding to SpamAssassin on PMG using the community
( https://github.com/mxguardian/Mail-SpamAssassin-Plugin-QRCode )
plugin. Any URL found encoded in a QR code image (attached or inline) is
extracted and registered with SpamAssassin the same way a normal in-body link
is - meaning it automatically flows through existing URIBL/SURBL/domain-reputation
checks, not just a flat static score.

Tested on PMG 9.1.2 (Debian 13.5 "Trixie", SpamAssassin 4.0.2).

What this does and doesn't do

- Decodes QR codes in image attachments (PNG/JPEG etc., via ImageMagick + ZBar)
- Registers the decoded URL via `add_uri_detail_list` - the *same* internal
API SpamAssassin uses for URLs found in message bodies, so it's evaluated by
your existing URIBL/SURBL/KAM domain-reputation rules for free
- Does not decode QR codes in PDFs by default (disabled here -
`qrcode_scan_pdf 0` - since PDF rasterization drags in Ghostscript and is a
separate can of worms; enable only if you specifically need it)
- Does not insert the decoded URL into the visible message body or headers. It's
purely a scoring-pipeline addition. Getting the URL visible in-body would
require a separate MIME-rewriting content filter, but at a quick glance, it
should be possible to get the decoded URL inserted into the headers by adding
a few lines to the plugin..
- Cannot decode a QR code that's a *remote-hosted image link* rather than
actual image bytes in the message (some "insert QR code" tools embed a
<img src="https://..."> reference instead of the image itself - nothing
for the plugin to scan in that case)

Prerequisites

PMG ships as a lean filtering appliance - no compiler, and ImageMagick isn't
installed. Both are needed. Get the following bits installed.

apt update
apt install --no-install-recommends imagemagick libimage-magick-perl \
libzbar0t64 libzbar-dev cpanminus build-essential


Gotcha - Recommends bloat: installing `imagemagick` without
`--no-install-recommends` pulls in a *lot* of irrelevant desktop packages
(GTK3, `adwaita-icon-theme`, `at-spi2`, font packages, X11 libs) that exist
only to support ImageMagick's `display/animate` GUI tools - meaningless on
a headless mail gateway. Always use `--no-install-recommends` here.

Not a gotcha, just how it is: even with `--no-install-recommends`, you'll
still see ~200 packages / ~300MB install. That's *hard* dependencies, not
Recommends - Debian's `imagemagick` package supports essentially every image
format via required delegate libraries (SVG via librsvg2/Cairo/Pango, HEIF,
DjVu, WebP, RAW, WMF, JPEG-XR, etc.). This is normal and not worth fighting;
building ImageMagick from source to strip delegates trades disk space for
losing automatic CVE patching via apt - not a good trade on a mail filter.

Gotcha - no compiler by default: `Barcode::ZBar` (the Perl binding to
libzbar, not packaged for Debian) has to compile from CPAN, and PMG has no
`gcc/make` out of the box. `cpanm Barcode::ZBar` will fail with
`Couldn't find your C compiler` until `build-essential` is installed. Worth
a conscious call: this puts a compiler on an internet-facing filtering
appliance. If that's a concern in your environment, build the module on a
throwaway Debian 13 VM instead and copy the compiled module over rather than
installing `build-essential` on PMG itself. Somewhere in-between, you can always
run `apt purge build-essential` when installation is complete to remove the compiler.

Once the prerequisites are installed, check whether apt's `libimage-magick-perl`
already gives a working `Image::Magick` Perl binding before reaching for cpan:

bash
perl -MImage::Magick -e 'print "ok\n"'

If that prints `ok`, no cpan fallback needed for Image::Magick. Only
`Barcode::ZBar` needs to come from CPAN - it isn't packaged for Debian at all.

cpanm Barcode::ZBar

If it fails, check the actual error rather than guessing:

tail -50 ~/.cpanm/work/*/build.log

Installing the plugin
Now that those pieces are in place, it is time to install the actual
SpamAssassin QRConde plugin:

wget -O /usr/share/perl5/Mail/SpamAssassin/Plugin/QRCode.pm \
https://raw.githubusercontent.com/mxguardian/Mail-SpamAssassin-Plugin-QRCode/main/lib/Mail/SpamAssassin/Plugin/QRCode.pm


Configuration

Gotcha -
don't use `local.cf` or `init.pre`. PMG regenerates these from
its own templates on config sync / upgrades and will silently wipe manual
edits. PMG includes `/etc/mail/spamassassin/custom.cf` by default, and that
file is left alone by PMG's own tooling - put everything there:

cat >> /etc/mail/spamassassin/custom.cf << 'EOF'

# QR code decoding
loadplugin Mail::SpamAssassin::Plugin::QRCode

ifplugin Mail::SpamAssassin::Plugin::QRCode
qrcode_min_width 100
qrcode_min_height 100
qrcode_scan_pdf 0
endif

uri_detail HAS_QRCODE_URI type =~ /^qrcode$/
describe HAS_QRCODE_URI Message contains a URI embedded in a QR code
score HAS_QRCODE_URI 3.0
EOF

Note the config option is `qrcode_min_width` (no underscore after "qr") -
easy typo to make from memory.

Keep the score modest. The real detection value comes from the decoded URL
getting evaluated by your existing URIBL/SURBL/domain-reputation rules, not
from this flat rule alone - a high static score here is a blunt instrument
that will false-positive on the many legitimate QR codes in normal mail
(restaurants, event tickets, marketing, MFA setup, etc.). But it sure can be handy for testing! ;)

Verify before going live

spamassassin --lint -D all 2>&1 | grep -i qrcode

Look for the plugin loading from `@INC`, `HAS_QRCODE_URI` being registered,
and both `finish_parsing_start` and `parsed_metadata` hooks being
implemented - no errors in between. A `Can't locate` error means the `.pm`
landed in the wrong path or isn't in `@INC`.

Deploying

systemctl restart pmg-smtp-filter
systemctl status pmg-smtp-filter --no-pager | head -5

Biggest gotcha of the whole process: `pmgconfig sync --restart 1` does
not reliably restart `pmg-smtp-filter` after a manual `custom.cf` edit.
That command syncs PMG's own database-driven config to its template files
and only restarts if it detects a change to config *it* manages - since
`custom.cf` is deliberately left alone by PMG's tooling (that's why we use
it), editing it doesn't register as a change PMG notices. Confirm the
restart actually happened by checking the `Active:` timestamp in
`systemctl status` - if it's not from moments ago, the plugin is not
actually running yet even though it lints clean. When in doubt, just
`systemctl restart pmg-smtp-filter` directly.

Testing

Send a real test message to yourself with a QR code image (≥100×100px,
PNG or JPEG - SVG won't work, and remote-hosted image links won't work)
encoding some throwaway test URL.

Check the SpamAssassin hit list in the mail log:

grep "SA score" /var/log/mail.log | tail -5

Look for `HAS_QRCODE_URI` in the `hits=(...)` list, ideally alongside a
URIBL/SURBL/KAM hit if your test URL's domain has any reputation signal at
all - that's confirmation the decoded URL is really flowing through normal
link evaluation, not just triggering the static rule.

Quick troubleshooting checklist

If a test message doesn't show `HAS_QRCODE_URI`:

1. Did `pmg-smtp-filter` actually restart since the config change? Check the
`Active:` timestamp, not just that `pmgconfig sync` ran.
2. Is the QR image actually embedded in the message (attached or inline
`cid:`), or is it a remote URL reference the plugin never receives bytes
for?
3. Is the image ≥100×100px?
4. Is it a bitmap format (PNG/JPEG), not SVG?
5. Re-run `spamassassin --lint -D all 2>&1 | grep -i qrcode` to confirm the
plugin and rule are still registered after any config changes.
 
Last edited: