// HackTheBox  ·  writeup

Holmes 2025 1: The Card

HackTheBox Easy grepcatcurlPython
Threat Intelligence

Scenario

Players will use Threat Intelligence services to link together a series of cyber crime activities.

Executive Summary

An attacker probed a honeypot web application with a custom, branded User-Agent, got past the WAF and planted a PHP web shell, then pulled a SQL database dump out over HTTP a few days later. A short marker string recurred across the whole intrusion: in the User-Agent, in the names of the files they created and in the beacon identifier. Searching the CTI platforms for that tag tied the activity to a cluster of campaigns attributed to one actor, all using the same malware. Pivoting on that sample’s hash led to its C2 address, the file it drops for persistence, and a scanner profile of the C2 host that exposed its ports, its owner and a taunting service banner.

Artifacts

Timestamp note: The logs carry dates in 2025-05; none of the questions need a timestamp answer, but the order (first contact, web shell, dump) is what makes the story.

Initial Access

Q1 — Analyze the provided logs and identify what is the first User-Agent used by the attacker against Nicole Vale's honeypot.

Read access.log from the top. The earliest requests are the attacker’s first contact (2025-05-01 08:23:12) and their User-Agent is hand-built, not a browser or scanner default: a tool name, a slash, a short token, a hyphen and a second name. That fits the format mask exactly, and the short token is the marker that returns in Q4.

cat access.log
Click flag to reveal Lilnunc/4A4D - SpecterEye

Persistence

Q2 — It appears the threat actor deployed a web shell after bypassing the WAF. What is the file name?

application.log and waf.log agree on the moment of the bypass: a new PHP file is created at 2025-05-15 11:25. Its name carries the same token as the User-Agent from Q1, which ties the file to the same actor.

cat application.log waf.log

Tip: waf.log has an ERROR line with a garbled field. The bytes are already replaced with U+FFFD in the file, so there is nothing to recover with a hex dump. application.log has the filename in clear.

Click to reveal answer temp_4A4D.php

Exfiltration

Q3 — The threat actor also managed to exfiltrate some data. What is the name of the database that was exfiltrated?

On 2025-05-18 access.log shows the attacker downloading a SQL dump over plain HTTP. The filename follows the same naming convention as the web shell, with the format mask ********_****_****.*** fitting a database_dump_<token>.sql name.

cat access.log
Click flag to reveal database_dump_4A4D.sql

Command and Control

Q4 — During the attack, a seemingly meaningless string seems to be recurring. Which one is it?

Compare Q1 to Q3: the same four-character token sits in the User-Agent, the web shell name and the dump name, and it is also the beacon identifier in the logs. A string that shows up in a header, in filenames and in a beacon is a deliberate actor tag, and it is the search term for everything that follows.

cat access.log application.log waf.log
Click to reveal answer 4A4D

Threat Intelligence

Q5 — OmniYard-3 (formerly Scotland Yard) has granted you access to its CTI platform. Browse to the first IP:port address and count how many campaigns appear to be linked to the honeypot attack.

OmniYard-3 is a single-page app, so fetch the page and then its script bundles. The Intel Graph data is a STIX-style set of entities and relationships hardcoded in the bundle. Filter the relationships for campaigns with an attributed-to link to the actor identity (identity--threat-actor-jm): five campaigns come back (bio-breach, civic-disruption, neural-storm, quantum-heist, transport-chaos).

curl -s http://<omniyard-host>:<port>/ | grep -o 'src="[^"]*\.js"'
curl -s http://<omniyard-host>:<port>/<bundle>.js | grep -o 'attributed-to[^}]*'

Hint: There is no API to query. If the page looks empty to curl, the data is in the bundle, not behind an endpoint.

Click flag to reveal 5
Q6 — How many tools and malware in total are linked to the previously identified campaigns?

From Q5 take the five campaign IDs and list their uses relationships. They resolve to five malware entities and four tools, because transport-chaos has no tool attached, for nine in total.

curl -s http://<omniyard-host>:<port>/<bundle>.js | grep -o '"relationship_type":"uses"[^}]*'
Click to reveal answer 9
Q7 — It appears that the threat actor has always used the same malware in their campaigns. What is its SHA-256 hash?

Of the malware entities from Q6, one is linked to every campaign. Its SHA-256 indicator appears five times in the graph data, once per campaign, which is the signal that it is the shared implant. Pull the 64-hex string out of the bundle with grep -o.

curl -s http://<omniyard-host>:<port>/<bundle>.js | grep -oE '[0-9a-f]{64}' | sort | uniq -c | sort -rn
Click flag to reveal 7477c4f5e6d7c8b9a0f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d17477
Q8 — Browse to the second IP:port address and use the CogWork Security Platform to look for the hash and locate the IP address to which the malware connects. (Credentials: nvale/CogworkBurning!)

CogWork is the same kind of SPA. Search its bundles for the Q7 hash and read the scan entry for it. The network activity section lists the C2 address the sample calls out to.

curl -s http://<cogwork-host>:<port>/<bundle>.js | grep -o '7477c4f5e6d7c8b9[^}]*'
Click to reveal answer 74.77.74.77
Q9 — What is the full path of the file that the malware created to ensure its persistence on systems?

The same scan entry holds the behaviorAnalysis block. Under fileOperations, the create action names the script the implant drops to survive reboots. The 4a4d in the filename is the actor tag from Q4 again.

curl -s http://<cogwork-host>:<port>/<bundle>.js | grep -o 'fileOperations[^]]*'
Click flag to reveal /opt/lilnunc/implant/4a4d_persistence.sh
Q10 — Browse to the third IP:port address and use the CogNet Scanner Platform to discover additional details about the TA's infrastructure. How many open ports does the server have?

On CogNet, find the host entry for the C2 address from Q8 and count the elements of its ports array. The bundle is one minified line, so a regex with a bounded wildcard hangs; slice it in Python with str.index from the IP to the end of the array instead.

python3 -I -c "import sys;s=open(sys.argv[1]).read();i=s.index('74.77.74.77');print(s[i:i+1500])" cognet_bundle.js
Click to reveal answer 11
Q11 — Which organization does the previously identified IP belong to?

The same host entry carries an organization field. It is the hosting provider that owns the address, not the actor’s own name.

python3 -I -c "import sys,re;s=open(sys.argv[1]).read();i=s.index('74.77.74.77');print(re.findall(r'organization[^,]*',s[i:i+3000]))" cognet_bundle.js
Click flag to reveal SenseShield MSP
Q12 — One of the exposed services displays a banner containing a cryptic message. What is it?

Step through the services in the Q10 ports array. Most banners are product strings; the one on port 7477 is a sentence. The long format mask is the hint that you are looking for a quote with an attribution after the hyphen.

python3 -I -c "import sys;s=open(sys.argv[1]).read();i=s.index('7477',s.index('74.77.74.77'));print(s[i:i+400])" cognet_bundle.js

Gotcha: Copy the banner exactly, including the apostrophe in “He’s” and the spacing around the final hyphen. The attribution after the hyphen is part of the banner.

Click to reveal answer He's a ghost I carry, not to haunt me, but to hold me together - NULLINC REVENGE

Attack Summary

PhaseAction
Initial AccessFirst requests to the honeypot with a branded User-Agent (2025-05-01)
Initial AccessWAF bypassed
PersistencePHP web shell created on the server (2025-05-15)
ExfiltrationSQL database dump downloaded over HTTP (2025-05-18)
Command and ControlMarker string reused in the User-Agent, filenames and beacon identifier
Command and ControlShared implant calls out to a single C2 host and drops a persistence script on victims

IOCs

TypeValue
User-AgentLilnunc/4A4D - SpecterEye
File (web shell)temp_4A4D.php
File (exfiltrated)database_dump_4A4D.sql
Marker string4A4D
SHA-256 (implant)7477c4f5e6d7c8b9a0f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9e0d17477
IP (C2)74.77.74[.]77
File (persistence)/opt/lilnunc/implant/4a4d_persistence.sh

MITRE ATT&CK

TechniqueIDDescription
Exploit Public-Facing ApplicationT1190Attack on the honeypot web app that got past the WAF
Server Software Component: Web ShellT1505.003PHP web shell dropped on the server
Exfiltration Over C2 ChannelT1041Database dump pulled back over HTTP
Application Layer Protocol: Web ProtocolsT1071.001Implant network activity to the C2 address reported by the sandbox

Defender Takeaways

A WAF bypass was followed by a file write, and that write was visible. The web shell creation shows in both application.log and waf.log. Alert on new executable files appearing in web-served directories, especially names such as temp_*.php.

Custom User-Agents are cheap, high-signal detections. The attacker’s first request already carried a branded string. Unusual or hand-built User-Agents are worth a watchlist and a pivot into the other logs.

Database dumps should never be web-reachable. The exfiltrated file was fetched with a plain HTTP request. Keep dumps out of the document root and alert on downloads of .sql files.

Reused marker strings link an intrusion to a wider actor. The same token in the User-Agent, filenames and beacon is what let the CTI platforms connect this honeypot hit to five campaigns and one shared implant. Extract such strings as indicators early, then pivot from them to hashes, C2 addresses and persistence paths for hunting.

Conclusion

The attacker went from first probe to web shell to database dump over about two and a half weeks, and tagged everything with one marker string. That tag is the weakness: it ties every artifact to five campaigns, one implant and one C2 host, and it is the thread that turns a honeypot log into an attribution.