f1rst_
Overview
HuntPanel: a rented phishing kit with an operator on the socket

HuntPanel: a rented phishing kit with an operator on the socket

16 min read

Introduction

HuntPanel is a phishing kit that affiliates rent and deploy on their own domains. A deployment is a page cloned from a real brand site, a collection form, a fake identity-provider screen, and a client that holds a socket open to an operator console. The operator watches the session and pushes the next screen by command, which is what separates this family from kits that post a form and redirect.

urlscan.io holds 245 captures of HuntPanel landing pages on 134 hosts, resolving to 108 registrable domains, the earliest on April 10th, 2024 and the most recent on October 7th, 2026. The targets in that set are mostly recruitment and retail banking: Meta, Spotify, General Motors, PUMA, Adidas, Tangerine, Scotiabank, PayPal and Yahoo.

The deployment analysed here is recruit.meetingoperations.com, a General Motors careers clone captured three times between August and October 2026. urlscan’s classifier scores all three 100 and files them as phishing.

This post covers the victim flow, the kit’s configuration, the operator command channel, the infrastructure and the detection opportunities. A second part covers the kit’s origin, its version history and its tenant fleet.

Key findings

  • 245 landing page captures on 134 hosts, resolving to 108 registrable domains, between April 2024 and October 2026.

  • The kit is a versioned product rather than a private tool. Its console carries a build constant, viteHPVer, that runs from 1.67.2 in October 2024 to 1.82 in July 2026, and it ships a help screen written for customers. The console itself, its versions and that help screen are the subject of a second part, which traces the kit’s origin.

  • Landing pages are cloned from the legitimate site with the Save Page WE browser extension, which leaves savepage-version and data-savepage markers in the HTML.

  • Page titles mix Latin and Cyrillic characters, so a title search in plain ASCII misses the page and an exact-match filter sees a different string than a person does.

  • The operator channel runs on Socket.IO at path /fake. Six commands cover the victim’s next screen, including prompts for SMS, WhatsApp and authenticator codes.

  • The project field names each campaign after an ad platform and a brand, so FacebookGeneralMotors2 and GoogleGeneralMotors2 are one target bought through two channels.

  • Eight of twenty sampled configurations still point at 127.0.0.1, the kit’s development default. Those deployments take a submission and then fail.

  • The same codebase appears in Phish Report’s analysis of September 13th, 2023, which documented it under the codename RedLungfish with the same configuration variables, the same panel services and the same cloning method. That history is the second part of this write-up.

Attack flow

Figure 1. The relay model this kit is built for: the victim's browser reaches only the phishing host, the operator console drives both sides over the socket, and contact with the real identity service happens from the operator's side

The victim’s browser reaches the phishing host and nothing else. The analysed capture makes 128 requests, 101 of them back to that host, and none to a Google or Facebook host. What the victim types leaves over the socket at path /fake as fishLoginData, and every screen that follows is a page the kit serves itself.

The other half of the diagram, the operator relaying credentials and one-time codes to the real service, is an assessment rather than something this dataset shows. That traffic starts from the operator’s machine, not the victim’s browser, so no capture here contains it. Three things in the kit support the reading: the panel keeps a bank-sessions route for the sessions it collects, the client ships provider screens that only make sense while a live service is answering, and the six commands exist so an operator can read a prompt and send back the screen that matches it.

Analysis of a live deployment

Figure 2. The landing page as urlscan captured it, built from General Motors' own careers site

The title tag is a homoglyph string: GМ Jоbs | Gеnеrаl Моtоrs Саrееrs uses Cyrillic М (U+041C), о, е, а and С in place of their Latin lookalikes. The document also records how it was built: savepage-version 33.9 identifies the Save Page WE extension, and 324 data-savepage attributes mark the resources the extension inlined.

The capture makes 128 requests, 101 of them back to the phishing host. The only POST bodies are the hCaptcha verification and an analytics beacon. That is not evidence about where the credentials end up: urlscan records HTTP requests and not socket frames, and the socket’s destination is a domain of the operator’s own. What the capture does show is that the browser has no submission path of its own.

How the figures below were produced

Figures 2 and 3 are browser sessions that urlscan ran against live tenants, and they are fully styled. Figures 4 and 5 are the kit’s own markup, taken from the DOM urlscan stored for this capture and rendered offline in a browser with every network request blocked, so nothing in them contacts the phishing infrastructure. Those two appear in browser default styling, for one reason: urlscan’s response store does not hold the kit’s stylesheets. It returns 404 for hp-styles/style.css, hp-style-custom.css and the provider document’s css/style.css on every tenant checked, four distinct hashes of style.css included, so the styling is absent from the public capture record. The only way to reproduce it was to request those files from the tenant itself, which is also the one step in this analysis that the operator can see.

The provenance matters because the result looks like Google’s own pages. Every file in figures 4 and 5 came from the tenant and returned HTTP 200, the render logged every request it attempted, the clone’s markup references no Google host, and the fonts are inlined as base64 data URIs inside the stylesheets the kit serves, with the original fonts.gstatic.com URLs commented out beside them. The design is Google’s, the bytes are the operator’s.

Brand replicas across the family

Figure 3. Four tenants of the same kit: Meta, Spotify, Tangerine and PayPal, captured by urlscan's browser on the dates shown

The same kit serves each affiliate whatever brand page they rent it for. The four captures above are urlscan’s own browser sessions against four different tenants, so the layout, fonts and imagery are the kit’s, not a reconstruction. The campaigns behind them appear in the operator’s configuration as Facebook, GoogleSpotify, Tangerine and PayPal.

Collection screens

The page contains three forms: a job search that submits to /en/jobs/, a popup wrapper holding an hCaptcha widget, and #login_form, whose class hp-login-email-pass belongs to the kit rather than to the cloned site.

Figure 4. Left: the collection form with its 30-entry position list. Right: the captcha step, presented as a named support agent

The collection form is hidden until the visitor clicks, and it asks for a full name, an email address, a mobile number, a location, a position from a 30-entry list, and years of experience. Its submit button is the entry to the provider flow: this tenant offers “Continue with Facebook” here, while the kit’s Google variant offers Google on the same step. The captcha popup introduces a support persona, “Sandra Williams, metaSupport Pro”, which is the framing the operator uses to keep the victim engaged before the provider screens appear. Field values are read by id and sent over the socket, and none of the fields carries a name attribute, so no ordinary form submission to a collection endpoint occurs.

Provider screens

Figure 5. The kit's Google document: the password step, the verification-method choice, the device prompt and the SMS code request

The landing page’s first frame points at ./google/index.html, a second document served from the same host. It is not a single credential form. It reproduces Google’s sign-in and account recovery flow, screen by screen, and it carries an answer for every path Google’s own interface offers:

  • the password step, titled Welcome, with “Show password” and “Forgot password?”;
  • Verify it's you, offering a code from the Google Authenticator app, a code by SMS to a masked number, or tapping “Yes” on a device where the recovery email is signed in;
  • a device prompt that waits for that tap;
  • 2-Step Verification, which asks for the phone number on the account before it will send a code.

The markup keeps Google’s own class names (TcuCfd NQ5OL form-pages, kY6ve, jsname and jscontroller attributes) and the document ships Google’s stylesheets as local files, so the copy is pixel-accurate against the brand it imitates. The kit also contains a Facebook variant of the same document, which is the one this tenant serves on the landing page, and the wider family contains campaigns for both. The Google flow is the one analysed here, because it walks a victim through account recovery rather than stopping at a credential prompt.

The screens are not decoration. They are the reason the operator can keep a session alive: when a victim hesitates at Verify it's you, the operator sends SHOW AUTH APP, SHOW PHONE or SHOW SECRET and the next screen arrives, matching the method the victim recognises from their own account.

Configuration and command channel

Each deployment reads its settings from a 403-byte hp-scripts/config.js. The GM deployment carries:

const backUrl = "https://api.hp-live-silver.live"; // the socket backend
const apiKey = "<redacted>"; // third-party geolocation
const apiUrl = `https://api.ipdata.co?api-key=${apiKey}`;
const project = `FacebookGeneralMotors2`; // campaign: channel + brand
const banLink = `https://hr.mediacareerssocial-schedule.com/meet-expert`;
const projectType = "bank"; // vertical

backUrl points at infrastructure the operator controls rather than at the phishing host, and the socket path is /fake rather than the library default, so a filter tuned to /socket.io/ alone would not match it. The /google/ copy of the kit names its campaign GoogleGeneralMotors2.

Over that socket the panel pushes commands, each one a screen for the victim:

CommandVictim-visible effect
SHOW WRONG LOGIN/PASSan authentication error, to make the victim retype the password
SHOW SECRETthe fake Google screen, to move the victim to a second provider
SHOW PHONEa phone number prompt
SHOW AUTH APPan authenticator code prompt
SHOW PAGE OTHER1a redirect to /meeting.html, the “schedule a call” page
BANa redirect to /service.html or to the decoy link, for suspected scanners

Data travels the other way as fishLoginData and fishAction, and the kit’s element list enumerates the screens it can render: login-page, choose-page, another-device, email-page, wh-page for WhatsApp and sms-page. A helper file in the same directory carries a Luhn check for card numbers.

Two implementation details describe the intended audience. Redirects wait at least two seconds after a login attempt and only the first one is honoured, because an instant redirect or a second redirect looks automated to a scanner and broken to a victim. The client’s JavaScript also reads a private property on navigator before doing anything else, a check a normal browser passes and a scripted one may not.

Campaign structure

Every deployment keeps its settings in a 403-byte config.js, which makes the operator’s own bookkeeping readable at scale. Reading that file out of 28 sampled deployments produced 20 parsed configurations, 11 distinct campaign names, two verticals and five socket backends.

Each field records how a campaign was bought and where it reports:

fieldwhat it recordswhat the sample shows
projectcampaign name, built from the acquisition channel and the brand, with a revision numberFacebook 4, GoogleGeneralMotors2 3, FacebookSpotify 3, GoogleSpotify 2, PayPal 2, then one each of GoogleNike, FacebookPuma, FacebookMOODYS, WebDe, ZKB and StripeDKK
projectTypethe vertical a campaign is filed underbank 19, cc 1
backUrlthe socket backend that deployment reports to127.0.0.1 8, hp-live-silver 5, hp-live-palladium 4, platinumhuntingpanel 2, hp-silver-vps 1
banLinkwhere a visitor judged to be a scanner is senta careers-themed decoy, an empty value, or, in one deployment, the real paypal.com
apiKeythe third-party geolocation accounttwo keys across all twenty deployments

Three readings follow from the table. Campaign names are channel-specific and versioned, so FacebookGeneralMotors2 and GoogleGeneralMotors2 are the same brand bought through two ad networks, and the trailing digit means the second General Motors build replaced a first. The projectType field needs care: a General Motors careers campaign is filed under bank, so the value may label the panel module or the kind of session being collected rather than the brand’s industry, and the panel’s separate bank-sessions and card-sessions routes support that reading. And eight of the twenty deployments still point at http://127.0.0.1:3333, the kit’s development default: those pages take a submission and then fail silently while the socket looks for a server that is not there.

The geolocation key is the one value in that file that behaves like an operator identifier. Two keys cover every deployment sampled, so the same third-party account pays for the traffic checks behind all of them. The domains are the opposite, unique per tenant. Tenant domains follow one naming template: an HR word in front of a candidate word.

recruit.meetingoperations.com careers.candidate-support.com
schedule.candidatecoordination.com meet.candidateaccessspotify.com
recruit.moodyscareersapply.com recruit.metacareersglobal.com

Counting vocabulary over the 102 hosts of the scraped careers layer, which is a subset of the 134 hosts above, career appears in 53, recruit in 40, candidate in 25 and a brand name in 21. Where a suitable domain was not available, the kit lands on something that already exists, including compromised small business sites and free hosting.

Scale and infrastructure

Figure 6. The family in four views: captures per month, the brand behind each capture, the campaign names read from the sampled configurations, and the backend each of those configurations points at

The chart sets the shape of the operation. Captures run from April 2024 to October 2026, with August 2025 the busiest month at 31 captures, followed by May 2025 at 9 and January 2026 at 7. Those peaks are consistent with campaign bursts, but this dataset records what scanners submitted, not what the operator scheduled.

The family covers 245 captures on 134 hosts, resolving to 108 registrable domains, on 122 distinct capture days between April 10th, 2024 and October 7th, 2026. Domain age at scan time has a median of 211 days, and 76 captures hit a domain younger than a month.

Infrastructure is cheap and consistent. Cloudflare answers 180 of the 245 captures. Self-hosted nginx answers 55, across five versions between 1.18 and 1.30, which is the operator running their own boxes beside the CDN. Three captures answer on Apache and six returned no server header. Thirteen of the 134 hosts sit on free platforms, twelve of them on Cloudflare Pages and one on GitHub Pages. The TLD mix is led by .com on 91 captures and .uk on 18, with a long tail of throwaway endings.

Those counts are defensible rather than convenient, and the reason is worth recording. Four candidate fingerprints were tried: the page title Verification Required returns more than 10,000 captures because a sibling template uses the same phrase, the directory prefix hp-scripts/ returns 494 captures including an Ohio food bank and a Texas hospital system, the socket wiring file returns 149, and the shared careers script’s body hash returns 149 with no unrelated hosts in the set. The measurement uses the kit’s exact file names and the SHA-256 of five of its files, and an independent fingerprint of the same family agreed with it within three percent on both hosts and domains.

Coverage limits apply. Captures are scanner-driven, so 245 is what feeds and researchers submitted rather than a census of the operation; total caps at 10,000 per query; and twenty of the 28 sampled deployments returned a readable configuration, so the campaign list is a sample rather than an inventory.

Detection opportunities

  • Requests for /hp-scripts/, /hp-script/, /hp-custom-script/ or /google/hp-script/, and for init-socket-on-func.js.
  • A second document on the same host that loads /google/css/custom-style.css?v=google-device-actions-12, or markup carrying Google’s form-pages containers alongside jsname and jscontroller attributes on a non-Google domain. That is the provider clone.
  • A socket connection, WebSocket or polling, to a different domain than the page, on path /fake.
  • savepage-version in the page source, or a large number of data-savepage attributes.
  • Page titles that mix Latin and Cyrillic characters. Compare Unicode codepoints rather than lowercased strings.
  • A page whose configuration names a project and a projectType and whose backUrl is 127.0.0.1. That is this kit, even when the panel is unreachable.
  • /meeting.html and /service.html as in-page redirects, and a banLink value pointing at a careers-themed decoy page.
  • On the operator side, panel hosts answer on /login and /bank-sessions with a HuntPanel Ver 2.0 title and a Hunting panel 1.82 login card.
IndicatorValue
Kit script directory/hp-scripts/, /hp-custom-script/, /hp-styles/
Provider clone/google/index.html, /google/css/custom-style.css?v=google-device-actions-12
Socket path/fake, transports websocket, polling
Panel titleHuntPanel Ver 2.0
Panel login cardHunting panel 1.82
Panel build constantviteHPVer 1.67.2 to 1.82
Saved-page markerssavepage-version 33.9, 324 data-savepage attributes
Panel favicon hashsha256 48ac2b7827156514cb8a46798f6c2b9a14c3846f9391efc5863a80b4d77786bd

To hunt this family in urlscan, these four queries are the ones that held up, and hp-scripts/ on its own is the one that did not:

filename:"init-socket-on-func.js"
filename:"hp-script/hp-script.js" OR filename:"google/hp-script/hp-script.js"
hash:"ca8c3faa7107974dc683c769b87f1e4c0bf902aa855fbf5c37baca909bfe5d0b"
page.title.keyword:"HuntPanel Ver 2.0"

For file scanning, a rule that keys on the saved page together with the kit’s own directories:

rule huntpanel_landing_page {
meta:
description = "Landing page saved with Save Page WE and shipped with the HuntPanel client kit"
strings:
$saved = "savepage-version" ascii
$kit_scripts = "/hp-scripts/" ascii
$kit_custom = "/hp-custom-script/" ascii
$kit_provider = "/google/hp-script/" ascii
condition:
$saved and any of ($kit_*)
}

Every indicator above is something the kit carries rather than something a tenant rents. Domains are deliberately absent from the list: this operator replaces a tenant domain in an afternoon and rotates socket backends on the same schedule, so a blocklist entry against a hostname is stale before it is deployed. What survives a redeployment is the kit’s own code, the paths it serves, and the strings its console prints. The tenant analysed in this post appears once, as the subject of the analysis, and nowhere as an indicator.