HTB CWEE common mistakes
HTB CWEE · Understand the traps and how to avoid them.
Spending the first hours fuzzing directories and parameters with large wordlists before manually walking the application as a logged-in user, so the intended business logic and role boundaries are never mapped.
Tooling feels productive and gives visible output, while manual review requires slow reading of requests and UI flows; candidates default to automation under time pressure and mistake a directory tree for an attack surface.
What to do instead
Reserve the first 60-90 minutes for manual exploration with a proxy: register two accounts at different privilege levels, click every feature, and record each request with its parameters, roles, and object identifiers in a notes file. Only then run targeted fuzzing against endpoints you have already seen, and keep a running list of hypotheses ranked by likely impact.
Testing access control only by hiding UI elements or replaying requests as the same user, never re-issuing captured requests from a lower-privileged session or with the identifier swapped.
Candidates confirm the button disappears for a low-privilege user and conclude the endpoint is protected, forgetting that the server must enforce the check independently of the interface.
What to do instead
For every privileged request, capture it in the proxy, then replay it from the low-privilege session and with no session at all. Systematically increment or substitute object identifiers in path, query, body, and JSON fields, and test both horizontal (same role, other user) and vertical (lower role, higher action) movement before marking the endpoint clean.
Treating a session token as opaque and moving on, without decoding it, checking whether logout and password change invalidate it server-side, or testing whether the token is bound to a user or role claim that can be manipulated.
Stateless tokens look like random strings, and candidates assume session handling is out of scope once login works, so they never test fixation, reuse after logout, or claim tampering.
What to do instead
Decode every token and cookie, note the algorithm and claims, and test a fixed matrix: reuse after logout, reuse after password change, token issued to one user replayed against another, and modified claims with signature stripped or algorithm changed. Record which of these the server actually rejects, since a single accepted case is a finding.
Stopping at the first sign of SQL injection, such as a single quote producing an error, and reporting it without confirming exploitability, extracting data, or checking whether a WAF or parameterization blocks the payload in another context.
Error messages feel like proof, and candidates fear wasting time, so they skip the confirmation step that distinguishes a reflected error from an injectable query.
What to do instead
Confirm with boolean and time-based differentials before claiming injection, then demonstrate impact by extracting a value the application would not otherwise show (a version string or a row from another table). Repeat the test in every context the parameter reaches: numeric, string, ORDER BY, and JSON body, since one blocked context does not mean all are.
Assuming server-side template injection and command injection require obvious reflection, and abandoning a parameter because the output did not change, instead of testing arithmetic evaluation, blind timing, or out-of-band callbacks.
Candidates rely on visible output as the success signal and do not have a callback listener ready, so blind variants look like dead ends.
What to do instead
For every parameter that reaches a template or shell, send a benign arithmetic probe and compare rendered output, then a time-delay probe, then an out-of-band probe to a listener you control. Have the listener running before testing begins so blind findings are not lost, and vary the injection context (quoted, unquoted, concatenated) rather than the payload syntax alone.
Testing cross-site scripting by pasting alert(1) into a single reflected field, then declaring the application free of client-side issues without examining DOM sinks, stored contexts, or the cookie attributes that determine real impact.
alert(1) is the memorized payload, and candidates do not trace data from source to sink in JavaScript or consider that a payload may execute only after a second page load.
What to do instead
Map sources (URL, storage, postMessage) to sinks (innerHTML, eval, document.write) in the loaded JavaScript, then test each sink with a context-appropriate payload. For stored findings, confirm execution on a different page or user session, and document the cookie flags and CSP that determine whether session theft is actually possible.
Sending an SSRF payload to an internal address and reporting it on the basis of an error message, without confirming the request left the server, without a controlled listener, and without probing which protocols or redirects bypass the filter.
Different error text for internal versus external addresses is suggestive but not conclusive, and candidates stop once they see a behavioural difference instead of proving reachability.
What to do instead
Point the parameter at a listener you control first to prove the server makes outbound requests, then pivot to internal ranges and cloud metadata endpoints, testing redirects and alternate schemes when a filter blocks direct addresses. Capture the exact response or timing evidence that proves reachability and state clearly what the server could reach.
Uploading a web shell and declaring compromise on upload success alone, without verifying the file is reachable, executable, and survives any renaming, content-type, or extension filtering.
A successful upload response feels like the end of the chain, and candidates do not check whether the stored path is served statically or handed to an interpreter.
What to do instead
After upload, locate the stored file, request it directly, and confirm execution rather than download. If filters block execution, test alternate extensions, content-type manipulation, archive extraction, and path traversal in the filename before concluding the control holds, and record the exact bypass that worked.
Treating a decoded JWT or a GraphQL introspection result as the finding itself, without testing algorithm confusion, key handling, field-level authorization, or whether introspection should be disabled in production.
Decoding a token or dumping a schema produces impressive output quickly, and candidates mistake visibility for vulnerability.
What to do instead
For JWTs, test none and algorithm-confusion variants against a server you can observe, check whether the key is guessable or exposed, and verify claim tampering is rejected. For GraphQL, enumerate queries and mutations, then test each field and mutation as a low-privilege user to find missing object-level checks, and treat introspection exposure as a hardening note rather than the primary finding.
Delivering a report that lists individual bugs with raw request dumps but no reproduction steps, impact statement, or remediation, and no narrative connecting the bugs into the compromise chain that was actually achieved.
Candidates run out of time and dump tool output, assuming the reader will infer severity and steps from the evidence.
What to do instead
Write the report as you go, not at the end. For each finding give a title, affected endpoint, prerequisites, numbered reproduction steps, observed versus expected behaviour, concrete business impact, and a specific fix. Add an executive summary that presents the chain end to end, and keep a remediation section that distinguishes immediate mitigations from structural fixes.