Machine / Machines

SmartHire

Status: COMPLETE. Raw flags and reusable secrets are stored only in <local workspace><TARGET>-SmartHire/loot/. 1. Recon found only SSH and HTTP. HTTP redirected to smarthire.htb; vhost fuzzing discovered models.smarthire.htb. 2. models.smarthire.htb exposed...

MediumPublished 2026-04-23Sanitized local writeup

Scenario

SmartHire attack path

Status: COMPLETE. Raw flags and reusable secrets are stored only in -SmartHire/loot/. 1. Recon found only SSH and HTTP. HTTP redirected to smarthire.htb; vhost fuzzing discovered models.smarthire.htb. 2. models.smarthire.htb exposed...

Objective

Machine walkthrough focused on Machines evidence, validation, and reusable operator lessons.

SmartHire sanitized attack graph

Walkthrough flow

01

Verify target IP and /etc/hosts if machine respawned.

02

Recreate a normal app account and MLflow default...

03

Use notes/walkthrough evidence to reproduce; do not...

Source coverage

High source coverage

Status: complete. This article is generated from 7 sanitized Markdown sources and keeps raw flags, credentials, keys, cookies, and reusable secrets out of the rendered blog.

100% coverage
Evidence verdict

High confidence: the page is reconstructed from a primary walkthrough plus multiple supporting notes or evidence sources. Treat the chain as source-backed, while still checking the listed source files for sensitive values.

  • <TARGET>-SmartHire/walkthrough.md
  • HTB/<TARGET>-SmartHire/notes.md
  • HTB/<TARGET>-SmartHire/memory-summary.md
  • HTB/<TARGET>-SmartHire/session-resume.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/machine__<TARGET>-SmartHire__notes.md.c4c01223f4.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/machine__<TARGET>-SmartHire__session-resume.md.14811b658d.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/machine__<TARGET>-SmartHire__notes.md.14aab64937.md

Technical Walkthrough

SmartHire — Walkthrough

Status: COMPLETE. Raw flags and reusable secrets are stored only in <local workspace><TARGET>-SmartHire/loot/.

Chain

  1. Recon found only SSH and HTTP. HTTP redirected to smarthire.htb; vhost fuzzing discovered models.smarthire.htb.
  2. models.smarthire.htb exposed MLflow 2.14.1 behind Basic Auth. A default credential succeeded; value stored in loot/mlflow-basic-auth.json.
  3. The SmartHire app allowed normal registration/login and exposed /dashboard, /model_info, /upload_hiring_data, and /predict.
  4. /model_info revealed an app-derived MLflow registered model name. Authenticated MLflow API access allowed registering model versions under that name.
  5. A benign model registration proved SmartHire consumed the MLflow registry. A callback model proved command execution as svcweb when /predict loaded the registered model.
  6. A reverse-shell model gave a shell as svcweb; user.txt was captured to loot/user.txt.
  7. sudo -l showed svcweb could run /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py * as root.
  8. The script imports plugin directories with site.addsitedir(). /opt/tools/mlflow_ctl/plugins/dev was group-writable by devs, and svcweb belonged to devs.
  9. A temporary .pth hook in the dev plugin directory executed as root via the allowed sudo command, copied root proof/flag to /tmp, and was removed immediately.
  10. root.txt was captured to loot/root.txt; MLflow model versions and the temporary .pth hook were cleaned up.

Key Evidence

  • Nmap: <local workspace><TARGET>-SmartHire/nmap/initial, <local workspace><TARGET>-SmartHire/nmap/allports
  • Vhost discovery: <local workspace><TARGET>-SmartHire/enum/ffuf-vhosts-filtered.json
  • MLflow version/API: <local workspace><TARGET>-SmartHire/enum/mlflow-api-summary.json
  • App endpoints: <local workspace><TARGET>-SmartHire/enum/auth-js-endpoints.json
  • Callback proof: <local workspace><TARGET>-SmartHire/enum/proof-callback-review.txt
  • Foothold shell log: <local workspace><TARGET>-SmartHire/enum/tmux-shell.log
  • Sudo/privesc script inspection: <local workspace><TARGET>-SmartHire/enum/mlflowctl-inspect.txt
  • Root proof and cleanup evidence: <local workspace><TARGET>-SmartHire/enum/root-privesc-exec.txt, <local workspace><TARGET>-SmartHire/enum/mlflow-cleanup-summary.json

Source-Backed Dossier

The sections below are merged from companion Markdown notes for the same case. They are rendered after sanitization so the article stays precise without publishing raw flags, credentials, or target-specific secrets.

Notes

Target: SmartHire

Active IP: <TARGET>

Prior IP(s): <TARGET> (stale; prior target state showed filtered ports only)

Difficulty: Medium

OS: Linux

Created by: retrib3

Pwnbox: <TARGET> (profex0r), hostname htb-pbtmg2qrop

Attacker/VPN IP: <TARGET>

Workspace Local: <local workspace><TARGET>-SmartHire

Workspace Pwnbox: /home/profex0r/<TARGET>-SmartHire

Started: 2026-05-27

Completion State: COMPLETE

Evidence Ledger

Timestamp (UTC)Command / ActionOutput FileFindingConfidenceNext Action
2026-05-27T00:00:00ZWorkspace initializationtarget-state.jsonNew active workspace created for <TARGET>; old prior IP treated as staleHIGHVerify Pwnbox/VPN/target reachability
2026-05-27T22:43:36ZPwnbox connectivity and target route/pingenum/connectivity-baseline.txtPwnbox SSH works; attacker/VPN IP <TARGET>; route via tun0; target ping responsiveHIGHBaseline recon
2026-05-27T22:44:30Znmap -sC -sVnmap/initial22/tcp OpenSSH 8.9p1 Ubuntu; 80/tcp nginx 1.18.0 redirects to smarthire.htbHIGHAdd hostname and enumerate web
2026-05-27T22:44:35ZFull TCP scannmap/allportsOnly 22 and 80 openHIGHTarget HTTP thoroughly
2026-05-27T22:47:00ZDirectory fuzzenum/ffuf-summary.txtMain routes: /register, /login, /logout, /dashboard, /predictHIGHAuthenticated app enumeration
2026-05-27T22:48:20ZVhost fuzzenum/ffuf-vhosts-filtered.jsonDiscovered models.smarthire.htb, MLflow Basic Auth realm; /health publicHIGHResearch/test MLflow
2026-05-27T22:47:30ZTargeted UDP scannmap/udp-targetedNo actionable UDP serviceMEDIUMContinue TCP/web path
2026-05-27T22:55:00ZRegister/login using live form fieldsenum/web-auth-register-login.txt; secret ref loot/web-account.jsonNormal app account works; /dashboard and /predict authenticatedHIGHInspect endpoints
2026-05-27T22:57:28ZMLflow Basic Auth checksenum/mlflow-default-cred-check.txt; secret ref loot/mlflow-basic-auth.jsonA default MLflow admin credential succeeded; values stored only in lootHIGHEnumerate MLflow API
2026-05-27T22:59:00ZAuthenticated MLflow API enumerationenum/mlflow-api-summary.json, enum/mlflow-api-_version.txtMLflow 2.14.1; default experiment exists; registry write reachableHIGHValidate app MLflow consumption
2026-05-27T23:00:00ZBenign registered modelenum/benign-mlflow-register-summary.json, enum/post-benign-model_info.txtApp-derived registered model name consumed by /model_info; /predict reaches model load/prediction pathHIGHControlled callback proof
2026-05-27T23:04:35ZMLflow model-load callback proofenum/proof-callback-review.txt; token ref loot/model-load-proof-token.txtTarget callback received; identity was svcweb with mlflowweb and devs groupsHIGHReverse shell model payload
2026-05-27T23:07:30ZReverse-shell MLflow model → /predictenum/mlflow-revshell-register-summary.json, enum/tmux-shell.logReverse shell connected as svcwebHIGHFoothold baseline and user flag
2026-05-27T23:08:30Zfind /home -name user.txt; nc transferloot/user.txt, enum/user-transfer.logLive user flag captured and verified 32-hex format; raw flag kept only in lootHIGHPrivilege escalation
2026-05-27T23:10:00ZLinux baseline and sudo checkenum/tmux-shell.log, enum/mlflowctl-inspect.txtsvcweb may run /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py * as root without passwordHIGHInspect sudo script/import path
2026-05-27T23:12:00ZSudo script inspectionenum/mlflowctl-inspect.txtmlflowctl.py uses site.addsitedir() for plugin dirs; /opt/tools/mlflow_ctl/plugins/dev is group-writable by devs; svcweb is in devsHIGHUse temporary .pth hook with cleanup
2026-05-27T23:16:43ZTemporary .pth hook + allowed sudo commandenum/root-privesc-exec.txt, loot/root.txtRoot execution proven; live root flag captured and verified 32-hex format; temporary hook removedHIGHCleanup MLflow artifacts
2026-05-27T23:18:00ZMLflow cleanupenum/mlflow-cleanup-summary.jsonCreated model versions and registered models removedHIGHFinalize docs

LightRAG Checkpoints

Timestamp (UTC)QueryRetrieved RefsTagLive ValidationAccepted/Rejected
2026-05-27T22:44:00ZHTB Linux Medium SmartHire retrib3 prior sanitized patterns startup advisory onlyenum/lightrag-startup.txtMISSINGNo live claims acceptedRejected for execution guidance; continued live recon
2026-05-27T22:56:00ZHTB Linux Medium nginx Flask app authenticated predict route MLflow Basic auth models vhost health public SmartHire attack pathenum/lightrag-after-baseline.txtMISSINGNo live claims acceptedRejected for execution guidance; used live evidence and external docs

Findings

  • TCP: only 22/tcp OpenSSH and 80/tcp nginx exposed.
  • HTTP redirects bare IP to smarthire.htb.
  • Vhost fuzzing found models.smarthire.htb, an MLflow service behind Basic Auth.
  • SmartHire app supports normal user registration and uses app-derived MLflow registered model names.
  • MLflow default credential worked and allowed model registry writes.
  • SmartHire /predict loads the latest MLflow model for the account/company model name, enabling model-load code execution.
  • Foothold user: svcweb; groups include mlflowweb and devs.
  • Privilege escalation: sudo-allowed Python script adds group-writable plugin directory to site, allowing temporary .pth execution as root.

Phase 2 Research

  • Research file: enum/research-mlflow-websearch.txt (advisory; deleted/recreated only if needed, not required for proof).
  • MLflow Basic Auth/default credential risk was validated live by successful authenticated access.
  • MLflow model artifact/deserialization and model registry abuse classes were treated as advisory until live validation proved the app consumed a registered model and loaded it during prediction.
  • The .pth privesc vector was verified directly from the sudo script and filesystem permissions, not external research.

Phase 3 Synthesis / Ranked Hypotheses

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1MLflow default credential → register model under app-derived name → SmartHire /predict loads model → command execution as app userMLflow auth succeeded; registry write worked; /model_info and callback proof confirmed app load pathNone after callback/reverse shellCompletedHighConfirmed
2SmartHire /upload_hiring_data CSV parser/training abuseAuthenticated upload endpoint existsServer-side parser weakness not neededNot pursued after H1 successLowClosed
3MLflow path traversal / artifact readMLflow reachable; research showed path traversal classExact vulnerable endpoint/file proof not neededNot pursued after H1 successLowClosed

Completion State

COMPLETE

Memory Summary

Platform: HackTheBox machine

Type: Linux Medium

Status: solved from live target

Reusable Lessons

  • Vhost fuzzing on a simple nginx/Flask app found an MLflow service behind Basic Auth on a subdomain.
  • MLflow default/auth weakness plus SmartHire's app-specific registered model lookup formed the foothold chain.
  • Before exploit, validate model-registry consumption with /model_info and a benign model/callback; do not trust public MLflow CVE claims until the app actually loads the model.
  • MLflow pyfunc/model-load deserialization can execute before prediction fully succeeds, so an application error after model load can still mean command execution occurred.
  • For privesc, a sudo-allowed Python script that uses site.addsitedir() on group-writable plugin directories is exploitable via temporary .pth code execution.

Evidence Workspace

<local workspace><TARGET>-SmartHire

No raw flags or reusable credentials are included here. Ingest only after user approval.

Session Resume

Completion state: COMPLETE

Access

  • Foothold obtained as svcweb through SmartHire MLflow model-load execution.
  • Root execution proven through temporary .pth plugin hook via allowed sudo command.
  • User flag: loot/user.txt
  • Root flag: loot/root.txt

Cleanup

  • Temporary MLflow registered model versions removed. Evidence: enum/mlflow-cleanup-summary.json.
  • Temporary sudo .pth hook removed. Evidence: enum/root-privesc-exec.txt.
  • Active tmux/listeners stopped at end of session.

Active Sessions/Tunnels/Tickets

None expected after cleanup.

If resuming

  1. Verify target IP and /etc/hosts if machine respawned.
  2. Recreate a normal app account and MLflow default credential proof if needed.
  3. Use notes/walkthrough evidence to reproduce; do not rely on stale model registry objects because cleanup removed them.

Notes

Target: SmartHire

Active IP: <TARGET>

Prior IP(s): <TARGET> (stale; prior target state showed filtered ports only)

Difficulty: Medium

OS: Linux

Created by: retrib3

Pwnbox: <TARGET> (<<secret redacted>>), hostname htb-pbtmg2qrop

Attacker/VPN IP: <TARGET>

Workspace Local: <local workspace><TARGET>-SmartHire

Workspace Pwnbox: /home/<<secret redacted>>/<TARGET>-SmartHire

Started: 2026-05-27

Completion State: COMPLETE

Operating Notes

  • Source of truth is live target <TARGET>.
  • Older workspace <local workspace><TARGET>-SmartHire is advisory stale state only.
  • Raw flags, credentials, tokens, and reusable secrets stay in loot/ only and are referenced by path.
  • Medium flow completed: baseline recon, mandatory research, synthesis, foothold, privesc, cleanup.

Evidence Ledger

Timestamp (UTC)Command / ActionOutput FileFindingConfidenceNext Action
2026-05-27T00:00:00ZWorkspace initializationtarget-state.jsonNew active workspace created for <TARGET>; old prior IP treated as staleHIGHVerify Pwnbox/VPN/target reachability
2026-05-27T22:43:36ZPwnbox connectivity and target route/pingenum/connectivity-baseline.txtPwnbox SSH works; attacker/VPN IP <TARGET>; route via tun0; target ping responsiveHIGHBaseline recon
2026-05-27T22:44:30Znmap -sC -sVnmap/initial22/tcp OpenSSH 8.9p1 Ubuntu; 80/tcp nginx 1.18.0 redirects to smarthire.htbHIGHAdd hostname and enumerate web
2026-05-27T22:44:35ZFull TCP scannmap/allportsOnly 22 and 80 openHIGHTarget HTTP thoroughly
2026-05-27T22:47:00ZDirectory fuzzenum/ffuf-summary.txtMain routes: /register, /login, /logout, /dashboard, /predictHIGHAuthenticated app enumeration
2026-05-27T22:48:20ZVhost fuzzenum/ffuf-vhosts-filtered.jsonDiscovered models.smarthire.htb, MLflow Basic Auth realm; /health publicHIGHResearch/test MLflow
2026-05-27T22:47:30ZTargeted UDP scannmap/udp-targetedNo actionable UDP serviceMEDIUMContinue TCP/web path
2026-05-27T22: <REDACTED>
2026-05-27T22: <REDACTED>
2026-05-27T22:59:00ZAuthenticated MLflow API enumerationenum/mlflow-api-summary.json, enum/mlflow-api-_version.txtMLflow 2.14.1; default experiment exists; registry write reachableHIGHValidate app MLflow consumption
2026-05-27T23:00:00ZBenign registered modelenum/benign-mlflow-register-summary.json, enum/post-benign-model_info.txtApp-derived registered model name consumed by /model_info; /predict reaches model load/prediction pathHIGHControlled callback proof
2026-05-27T23: <REDACTED>
2026-05-27T23: <REDACTED>, enum/tmux-shell.logReverse shell connected as svcwebHIGHFoothold baseline and user flag
2026-05-27T23: <REDACTED>, enum/user-transfer.logLive user flag captured and verified 32-hex format; raw flag kept only in lootHIGHPrivilege escalation
2026-05-27T23: <REDACTED>, enum/mlflowctl-inspect.txtsvcweb may run /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py * as root without passwordHIGHInspect sudo script/import path
2026-05-27T23:12:00ZSudo script inspectionenum/mlflowctl-inspect.txtmlflowctl.py uses site.addsitedir() for plugin dirs; /opt/tools/mlflow_ctl/plugins/dev is group-writable by devs; svcweb is in devsHIGHUse temporary .pth hook with cleanup
2026-05-27T23: <REDACTED>, loot/root.txtRoot execution proven; live root flag captured and verified 32-hex format; temporary hook removedHIGHCleanup MLflow artifacts
2026-05-27T23:18:00ZMLflow cleanupenum/mlflow-cleanup-summary.jsonCreated model versions and registered models removedHIGHFinalize docs

LightRAG Checkpoints

Timestamp (UTC)QueryRetrieved RefsTagLive ValidationAccepted/Rejected
2026-05-27T22:44:00ZHTB Linux Medium SmartHire retrib3 prior sanitized patterns startup advisory onlyenum/lightrag-startup.txtMISSINGNo live claims acceptedRejected for execution guidance; continued live recon
2026-05-27T22:56:00ZHTB Linux Medium nginx Flask app authenticated predict route MLflow Basic auth models vhost health public SmartHire attack pathenum/lightrag-after-baseline.txtMISSINGNo live claims acceptedRejected for execution guidance; used live evidence and external docs

Findings

  • TCP: only 22/tcp OpenSSH and 80/tcp nginx exposed.
  • HTTP redirects bare IP to smarthire.htb.
  • Vhost fuzzing found models.smarthire.htb, an MLflow service behind Basic Auth.
  • SmartHire app supports normal user registration and uses app-derived MLflow registered model names.
  • MLflow default credential worked and allowed model registry writes.
  • SmartHire /predict loads the latest MLflow model for the account/company model name, enabling model-load code execution.
  • Foothold user: svcweb; groups include mlflowweb and devs.
  • Privilege escalation: sudo-allowed Python script adds group-writable plugin directory to site, allowing temporary .pth execution as root.

Phase 2 Research

  • Research file: enum/research-mlflow-websearch.txt (advisory; deleted/recreated only if needed, not required for proof).
  • MLflow Basic Auth/default credential risk was validated live by successful authenticated access.
  • MLflow model artifact/deserialization and model registry abuse classes were treated as advisory until live validation proved the app consumed a registered model and loaded it during prediction.
  • The .pth privesc vector was verified directly from the sudo script and filesystem permissions, not external research.

Phase 3 Synthesis / Ranked Hypotheses

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1MLflow default credential → register model under app-derived name → SmartHire /predict loads model → command execution as app userMLflow auth succeeded; registry write worked; /model_info and callback proof confirmed app load pathNone after callback/reverse shellCompletedHighConfirmed
2SmartHire /upload_hiring_data CSV parser/training abuseAuthenticated upload endpoint existsServer-side parser weakness not neededNot pursued after H1 successLowClosed
3MLflow path traversal / artifact readMLflow reachable; research showed path traversal classExact vulnerable endpoint/file proof not neededNot pursued after H1 successLowClosed

Completion State

COMPLETE

Session Resume

Completion state: COMPLETE

Access

  • Foothold obtained as svcweb through SmartHire MLflow model-load execution.
  • Root execution proven through temporary .pth plugin hook via allowed sudo command.
  • User flag: <REDACTED>
  • Root flag: <REDACTED>

Cleanup

  • Temporary MLflow registered model versions removed. Evidence: enum/mlflow-cleanup-summary.json.
  • Temporary sudo .pth hook removed. Evidence: enum/root-privesc-exec.txt.
  • Active tmux/listeners stopped at end of session.

Active Sessions/Tunnels/Tickets

None expected after cleanup.

If resuming

  1. Verify target IP and /etc/hosts if machine respawned.
  2. Recreate a normal app account and MLflow default credential proof if needed.
  3. Use notes/walkthrough evidence to reproduce; do not rely on stale model registry objects because cleanup removed them.

Notes

Target: SmartHire

IP: <TARGET>

Difficulty: Medium

OS: Linux

Pwnbox: <TARGET> (<<secret redacted>>)

VPN/Attacker IP: <TARGET>

Started: 2026-05-25

Completion State: <secret redacted>

Evidence Ledger

TimestampCommandOutput FileFindingConfidenceNext Action
08:33nmap -Pn -p 22,80,443All filtered, host not liveHIGHWait for spawn
08:35nmap -Pn -sC -sV -p 22,80,443,8080,8443,3000,5000,9090All 8 ports filteredHIGHConfirm machine spawned on HTB

Findings

(Pending target availability)