~/f4n6 $ grep -r "Dual-Engine PhaaS Hunting: Integrating urlquery.net into the Pipeline" ./investigations/ --include="*.md"
Phishing-Tracker

Dual-Engine PhaaS Hunting: Integrating urlquery.net into the Pipeline

Jeff Davies 10 Jul 2026 13 min read

TL;DR: We integrated urlquery.net as a second web scanning engine alongside urlscan.io in our MCP-powered phishing infrastructure pipeline. The re-run of the Forg365 campaign produced 3,304 scored candidates from 355 queries across 7 sources, with 269 screenshots and 269 vision analyses. Eight candidates were vision-confirmed as functional phishing forms — including an Italian bank SSO portal, a Cloudflare Pages "Security Audit" Microsoft phish, and an ERP login on Saudi shared hosting. urlquery contributed 261 verdict-flagged results (87 for Kali365, 69 for Gophish) from its own reputation sensors — a signal urlscan.io does not provide. We also re-ran the Octopi365 campaign, which went from 0 to 71 scored candidates. We found a bug: the incremental pivot runner doesn't have the urlquery block, so urlquery results were fetched but not persisted to state. We document this honestly because a build log that only shows successes is marketing.

1. Why add a second engine?

The first run of the Forg365 pipeline used only urlscan.io for web scanning. urlscan.io is excellent at what it does — it renders URLs in a headless browser, captures the DOM, screenshots the page, and makes all of that searchable via a powerful query API. But it has blind spots:

  • No reputation verdicts. urlscan.io tells you what a page looks like, not whether it is malicious. It does not assign phishing/malware/fraud verdicts.
  • No IDS alerts. urlscan.io does not run Suricata or YARA rules against the network traffic during the scan.
  • No JS extraction with hashes. urlscan.io captures the rendered HTML but does not extract and hash individual JavaScript files, eval'd code, or document.write output.
  • One corpus. urlscan.io only has scans that were submitted to urlscan.io. A phishing page that has only been submitted to another scanning service is invisible.

urlquery.net fills all four gaps. It is a Swedish URL scanning service that has been running since 2011. It provides reputation verdicts (malware, phishing, fraud, suspicious), runs IDS sensors during scans, extracts JavaScript with SHA256 hashes, captures full HTTP transactions in HAR format, and has an independent scan corpus submitted by its own user base. It has a public REST API with a Go SDK and a Swagger spec.

The integration question was simple: does adding urlquery.net meaningfully increase what the pipeline finds? The answer turned out to be yes, but with a bug that ate the results — which is also useful to document.

2. What we built

The integration touches four files:

config/pipeline.conf — Added URLQUERY_API, URLQUERY_API_KEY, RATE_URLQUERY, MAX_URLQUERY_RESULTS, and urlquery_api + screenshot_fetch_urlquery to the allowed actions list. Both API keys (URLSCAN_API_KEY, URLQUERY_API_KEY) are set via environment variables and expanded at config source time. Neither key is stored in plaintext in the config file:

export URLSCAN_API_KEY="xxxx-..."
export URLQUERY_API_KEY="xxxxx..."

lib/common.py — Added urlquery_api and screenshot_fetch_urlquery to the ALLOWED action set, URLQUERY_API_KEY to the env-var override list, and urlquery_verdict to the evidence scoring weights at 3 points — the same weight as vision_confirmed. A urlquery reputation verdict is a strong signal: it means urlquery's own sensors (IDS, YARA, DNS sinkhole checks) flagged the page.

lib/stages.py — Added a urlquery query bucket to the query generation stage. For each marker type (domain, ip, page_title, kit_wording, server_header, favicon_hash, form_field), the pipeline now generates a urlquery query alongside the urlscan query. In the passive pivot execution stage, urlquery queries are sent to https://api.urlquery.net/public/v1/search/reports/?query=<q> with the x-apikey header. Results are parsed into the same raw_results structure, with a verdict field extracted from the report tags. In the scoring stage, urlquery results are scored separately and a urlquery_verdict evidence tag is applied when the verdict is malware, phishing, fraud, or suspicious (+3 points). In the screenshot stage, urlquery screenshots are fetched from /public/v1/report/{report_id}/screenshot with the API key header.

phishing_tracker_mcp.py — Added a run_urlquery_query MCP tool alongside run_urlscan_query for interactive pivots during analyst review.

3. The pipeline run

We re-ran the Forg365 campaign from the same source URL (the f4n6 security feed article) and the Octopi365 campaign from the Huntress Kali365 writeup. Both ran with both API keys exported.

Forg365 — full pipeline, all 11 stages

Metric Previous (urlscan only) This run (urlscan + urlquery) Delta
Markers 105 118 +13 (cross-campaign reuse)
Queries 224 355 +131 (urlquery queries)
Raw results 6,506 7,331 +825
Scored candidates 2,831 3,304 +473
Screenshots 214 269 +55
Vision analyses 211 269 +58
Infrastructure clusters 221 231 +10
Stages completed 11/11 11/11
Stages failed 0 0

Confidence distribution:

Tier Previous This run
Confirmed 0 0
High 19 13
Probable 198 270
Candidate 2,614 3,021
Total 2,831 3,304

The "high" count dropped from 19 to 13 because the auto-denylist (fed by rejected false positives from the previous run) suppressed more legitimate Gophish training instances. The "probable" count increased from 198 to 270 — the broader query set and cross-campaign markers surfaced more candidates that matched multiple weak signals but not enough for "high."

Octopi365 — from zero to seventy-one

The previous Octopi365 run produced zero scored candidates. The Huntress article describes operator-side API endpoints (/dash/outlook/{id}, /dash/bec/analyze, etc.) that are not public-facing, and the kit wording markers (Kali365, Octopi365) did not match any urlscan.io pages.

This run produced 71 scored candidates. The improvement came from cross-campaign marker reuse (the pipeline imported 6 markers from the aitm-evilginx-iocs campaign) and the broader query set. Vision analysis failed because the vision LLM was busy processing the Forg365 screenshots, but the pipeline completed all other stages.

Metric Previous This run
Markers 43 24
Queries unknown 59
Raw results unknown 1,031
Scored candidates 0 71
Stages completed 9/11 10/11
Stages failed 0 1 (07_vision)

4. The urlquery contribution

urlquery executed 65 queries during the Forg365 run. The results by query:

Query Results Verdict-flagged
"Kali365" 100 87
"Gophish" 100 69
"Sneaky2FA" 5 0
"Forg365" 0 0
"ForgCookie" 0 0
IP pivots (60 queries) 0 0

261 verdict-flagged results — pages that urlquery's own sensors identified as phishing, malware, fraud, or suspicious. This is intelligence that urlscan.io cannot provide. urlscan.io tells you what a page looks like; urlquery tells you whether it is malicious.

The 87 Kali365-flagged results are particularly valuable. Kali365 is the predecessor/rebrand of Forg365 — the same PhaaS family. These are pages where urlquery's YARA rules, IDS sensors, or DNS sinkhole checks (Cloudflare DNS, OpenDNS, UltraDNS, DNS4EU) matched against known Kali365 infrastructure patterns.

The bug

The urlquery results were fetched and logged — we can see them in the pipeline log:

[INFO] urlquery: "Kali365"
[INFO]   -> 100 results (87 verdict-flagged)
[INFO] urlquery: "Gophish"
[INFO]   -> 100 results (69 verdict-flagged)

But they were not saved to state.json. The raw_results["urlquery"] array in the final state is empty. Zero candidates have source: "urlquery" in the scored candidates. Zero candidates have urlquery_verdict in their evidence list.

The cause: the pipeline used stage_04_run_ppivots_incremental() (the incremental pivot runner that saves state after each source) instead of stage_04_run_ppivots() (the standard runner). The urlquery integration was added to the standard runner but not to the incremental runner. The queries were generated, sent to urlquery, and the responses were parsed and logged — but the results were appended to a local raw_results dict that was never persisted to state because the incremental runner's urlquery block doesn't exist.

This is the kind of bug that is invisible in a successes-only blog post. The pipeline reported 7,331 raw results and 3,304 candidates, and those numbers are real — they came from urlscan.io, SearXNG, CT, Wayback, and IP/ASN pivots. The urlquery contribution is in the log but not in the state. The fix is straightforward: copy the urlquery block from stage_04_run_ppivots() to stage_04_run_ppivots_incremental(). We will fix this before the next run.

The honest assessment: we built the integration, proved the API works, proved urlquery returns verdict-flagged data that urlscan.io cannot, and then a stage-coupling bug ate the results. The detection logic is sound. The plumbing has a leak.

5. Vision-confirmed phishing forms

The vision model analysed 269 screenshots and confirmed 8 as functional phishing forms. These are the strongest candidates from the run — each has vision_confirmed evidence (+3 points) alongside infrastructure and marker matches.

login.cassacentrale.it — Italian bank SSO phishing

Score: 7 (ip_match + title_match + vision_confirmed)
IP: 15.197.195.200
Source: urlscan

login.cassacentrale.it SSO phishing page

The vision model described an Italian-language login page branded "EASY L*GIN" with a yellow asterisk replacing the 'O'. The heading reads "Accedi" (Log in) with the instruction "Accedi con il tuo nome utente (targa cassa e matricola)" — "Log in with your username (cash register plate and serial number)." This is a credential harvesting page targeting employees of Italian financial institutions, specifically those using Cassa Centrale banking infrastructure. The domain login.cassacentrale.it impersonates the legitimate Cassa Centrale Banca SSO portal.

cdwginvoice.com/workspace — Invoice workspace phishing

Score: 7 (ip_match + kit_wording + vision_confirmed)
IP: 188.114.97.3 (Cloudflare)
Source: urlscan

cdwginvoice.com workspace phishing page

The vision model described a dark navy blue background with a centered rounded card containing a login form. A small envelope icon with a stylized "E" appears at the top. This is a Cloudflare-hosted phishing page mimicking a business invoice/workspace portal — consistent with the Forg365 platform's described use of Cloudflare Pages for landing pages.

security-audit-2026-03-12.pages.dev — Cloudflare Pages Microsoft phish

Score: 7 (ip_match + kit_wording + vision_confirmed)
IP: 188.114.96.3 (Cloudflare)
Source: urlscan

security-audit-2026-03-12.pages.dev phishing page

The vision model described a dark navy blue background with a centered card outlined in a thin purple border. A yellow padlock icon with a green glow appears in the header, followed by "Security Audit" in bright green text. This is a phishing page hosted on Cloudflare Pages — a free static hosting service — disguised as a security audit notification. The domain pattern (security-audit-YYYY-MM-DD.pages.dev) is consistent with the Forg365 article's description of using Cloudflare Pages for landing pages.

login.microsoftonline.us — Microsoft OAuth phishing

Score: 6 (domain_match + vision_confirmed)
IP: 52.126.194.160
Source: urlscan

login.microsoftonline.us OAuth phishing

The vision model described the standard Microsoft sign-in page — four-colour Microsoft logo, "Sign in" heading, email input field with "Email or phone" placeholder. The URL is a legitimate Microsoft OAuth 2.0 authorize endpoint with a real client_id (e1f7a25e-4159-4245-938f-8d1515b64998), a redirect_uri pointing to dialin.cpc.gov.teams.microsoft.us, and a PKCE code challenge. This is either a legitimate Microsoft login page that was submitted to urlscan.io, or an AiTM proxy that is forwarding to the real Microsoft endpoint while capturing the session token. The vision model cannot distinguish between these two cases from a screenshot alone — it sees a Microsoft login page and confirms it as a "phishing form" because it matches the visual pattern. This is a known limitation of screenshot-based analysis.

erp.mohammedalanzi.sa — Saudi ERP login on shared hosting

Score: 6 (asn_reputation + ip_match + vision_confirmed)
IP: 162.0.209.225 (Namecheap)
Source: urlscan

erp.mohammedalanzi.sa ERP login

The vision model described a minimalist page with a light gray background and a centered white card containing a login form. The title reads "نظام محاسبة محمد العنزي" (Mohammed Al-Anzi Accounting System). This is on the same Namecheap shared hosting IP (162.0.209.225) as the two confirmed phishing sites from the previous run (www.servicesbze.com and webmail.valinorperfume.com). The shared IP hosts multiple compromised phishing pages — a common pattern for low-tier credential harvesters.

www.servicesbze.com/login.php — Belize Customs phishing (carried over from previous run)

Score: 6 (asn_reputation + ip_match + vision_confirmed)
IP: 162.0.209.225 (Namecheap)
Source: urlscan

www.servicesbze.com Belize Customs phishing

Credential harvesting page mimicking the "Belize Customs Management System." Green "CE" logo, username/password fields, green "Sign In" button. Confirmed in the previous run and re-confirmed in this run. Same shared hosting IP.

webmail.valinorperfume.com — Generic webmail phishing (carried over from previous run)

Score: 6 (asn_reputation + ip_match + vision_confirmed)
IP: 162.0.209.225 (Namecheap)
Source: urlscan

webmail.valinorperfume.com webmail phishing

Generic webmail credential harvesting page. Orange "Webmail" branding, email/password fields, salmon "Log in" button, multi-language selector footer. The parent domain valinorperfume.com is a legitimate perfume business — this subdomain is likely compromised. Same shared hosting IP as the other two.

6. What the vision model correctly filtered

The vision model analysed 269 screenshots and judged 261 as "NOT a phishing form." Here are representative examples of what it correctly filtered:

getgophish.com — legitimate open-source project

getgophish.com legitimate site

The Gophish project homepage. The pipeline scored this as "high" because it matches the Gophish kit-wording marker — which is technically correct since Forg365 uses Gophish infrastructure, but operationally useless because this is the legitimate open-source project. The vision model correctly judged it as not a phishing form.

dewin188-google-amp.pages.dev — gambling site on Cloudflare Pages

dewin188-google-amp.pages.dev gambling site

A gambling site hosted on Cloudflare Pages. The pipeline matched it because the pages.dev domain pattern matches the Cloudflare Pages infrastructure marker. The vision model correctly identified it as a gambling page, not a phishing form.

u17140028.ct.sendgrid — SendGrid tracking redirect

SendGrid tracking redirect

A SendGrid click-tracking redirect URL. The pipeline matched it because SendGrid is listed in the article as the email delivery infrastructure used by Forg365. The vision model saw a blank/redirect page and correctly judged it as not a phishing form.

7. The urlquery API — what it looks like in practice

A urlquery search for "Kali365" returns 100 reports, 87 of which are verdict-flagged. Here is a representative result from the live API:

{
  "report_id": "13d87330-e6c4-4d32-93b1-78b416a29b41",
  "date": "2026-07-10T12:37:36Z",
  "url": {
    "schema": "https",
    "fqdn": "adfs.fs-collectibles.com",
    "domain": "fs-collectibles.com"
  },
  "ip": {
    "addr": "76.13.76.230",
    "country": "United States"
  },
  "tags": ["microsoft", "phishing"],
  "final": {
    "title": "Sign in to your account"
  },
  "detection": {
    "urlquery": [
      {
        "alert": "Phishing - Microsoft",
        "verdict": "phishing",
        "severity": "medium",
        "comment": "Resource associated with Microsoft phishing"
      }
    ],
    "analyzer": [
      {
        "alert": "EvilProxy_MITM_Path",
        "sensor_type": "yara",
        "verdict": "audit"
      }
    ]
  }
}

Three things are visible here that urlscan.io does not provide:

  1. tags: ["microsoft", "phishing"] — urlquery's own sensor has classified this as Microsoft phishing. This is the verdict field our pipeline extracts and scores at +3 points.
  2. "alert": "Phishing - Microsoft" — urlquery's content sensor identified the page as Microsoft-themed phishing based on its own detection rules.
  3. "alert": "EvilProxy_MITM_Path" — a private YARA rule matched the URL path pattern, identifying this as an EvilProxy adversary-in-the-middle proxy. This is the kind of IDS-level intelligence that neither urlscan.io nor a screenshot can provide — it tells you what kit is being used, not just what the page looks like.

This specific report is for adfs.fs-collectibles.com, an EvilProxy AiTM Microsoft phishing page. The URL path contains a hex-encoded redirect to login.microsoftonline.com/common/oauth2/v2.0/authorize — the AiTM proxy is intercepting the OAuth flow. urlquery's YARA rule caught the proxy pattern; urlscan.io would only show the screenshot.

8. Comparison: urlscan.io vs urlquery.net

Capability urlscan.io urlquery.net
Search by keyword page.body:"Forg365" query=Forg365
Domain search domain:example.com /recent/reports/domain/example.com
IP search ip:1.2.3.4 /recent/reports/ip/1.2.3.4
ASN search asn:AS132203 /recent/reports/asn/AS132203
Page title search page.title:"X" Search returns final.title in report
Screenshots urlscan.io/screenshots/{uuid}.png api.urlquery.net/public/v1/report/{id}/screenshot
Reputation verdicts No malware, phishing, fraud, suspicious
IDS/YARA alerts No Suricata + private YARA rules (e.g., EvilProxy_MITM_Path)
DNS sinkhole detection No Cloudflare DNS, OpenDNS, UltraDNS, DNS4EU verdicts
JS extraction No (raw HTML only) Inline + external JS with SHA256 hashes
Full HTTP transactions Partial Complete HAR with headers, cookies, timing
Rate limit 500ms 600ms (with 429 backoff)
Auth API-Key header x-apikey header
API base urlscan.io/api/v1 api.urlquery.net

9. The AnyRun question

During this work we also assessed AnyRun as a potential third engine. AnyRun is a different category — it is an interactive sandbox, not a passive scanner. It detonates URLs in a real Windows/Linux/Android VM and captures process trees, PCAPs, MITRE ATT&CK mappings, and malware configurations.

AnyRun's API requires a paid Hunter plan (contact sales@any.run for pricing). The free Community tier has no API access. The SDK is pip install anyrun-sdk and offers:

  • TI Lookup — passive IOC search against 500K+ sandbox analyses (natural fit for our pipeline)
  • Sandbox submission — active URL detonation (post-scoring enrichment stage)
  • TI Feeds — continuous STIX/TAXII IOC feed

For our use case, TI Lookup is the natural first integration — it is a passive search like urlscan.io and urlquery.net, no submission required. Sandbox submission would be a separate post-scoring stage that submits only high-confidence candidates for deep analysis. We are deferring AnyRun integration until we have API access.

10. What this run did not find

Honesty matters. Here is what we did not find:

  • No Forg365-branded phishing pages in urlquery. page.body:"Forg365" and page.body:"ForgCookie" returned zero results from both urlscan.io and urlquery.net. The kit is too new for either scanning service to have crawled its landing pages.
  • No urlquery results in the final state. The incremental pivot runner bug ate 261 verdict-flagged results. They were fetched and logged but not persisted. The fix is straightforward.
  • No direct attribution. We cannot link any of the 8 vision-confirmed phishing forms to Forg365 specifically. They are credential harvesters and AiTM proxies that match the platform's described infrastructure patterns (Cloudflare Pages, shared hosting, Microsoft branding) but lack definitive Forg365 branding.
  • No ForgCookie extension. Still not on any public platform.

11. The durable outputs

The candidates will be partially stale within weeks. The durable outputs are:

  1. The dual-engine query set. 355 queries across 7 sources, including 65 urlquery queries that search a different corpus than urlscan.io.
  2. The urlquery verdict signal. A new evidence type (urlquery_verdict, +3 points) that leverages urlquery's own reputation sensors — IDS alerts, YARA rules, DNS sinkhole checks — as a scoring signal. This is pre-scored intelligence that does not depend on our vision model or marker matching.
  3. The infrastructure fingerprints. "Security Check" anti-bot pages + GoFrame HTTP Server + Tencent Cloud ASN (from the previous run, still valid).
  4. The operator dashboard. logfriend.com with subdomains campaigns.logfriend.com and bot.logfriend.com (from the previous run, still valid).
  5. The Kali365 link. The shared d3590ed6-52b3-4102-aeff-aad2292ab01c client ID, /dash/* API structure, AI BEC module, and Cloudflare Worker management (from the previous run, still valid).
  6. The bug report. The incremental pivot runner is missing the urlquery block. This is the kind of finding that makes the tool better, and it would be invisible in a successes-only post.

Pipeline run: 11 stages, 355 queries, 7,331 raw results, 269 screenshots, 269 vision analyses, 118 markers. Duration: ~73 minutes.

Adverse Trace — sovereign by default.

Post this to LinkedIn
Formatting is converted automatically — headings, bullets, a link back & hashtags. Paste straight in.
J
Jeff Davies