05 - Structured Logging for Exploit Development
As exploit PoCs grow beyond linear execution, visibility becomes critical. This article introduces structured logging as a practical tool for understanding exploit behavior, auditing execution stages,
With our PoC code structured to allow retries, branching, and stages, control flow has become non-linear. Any failures that occur, even if anticipated, may not be immediately understandable. Print statements can fail to scale if unexpected errors occur like network timeouts, payload parsing issues, or even if the target crashes. Logging, on the other hand, is built to capture response details, stack traces, timestamps.
Logging allows us to save a persistent record of a PoC’s execution to a file, the terminal, or both. Correctly configured, it provides an audit trail that print statements don’t scale to support. Correctly configured, logging provides an audit trail easier than with print statements. On the other hand, we're not shipping logs to a syslog server. This is not enterprise observability; it’s exploit observability. The idea is to allow a record of each stage and any issues that might have been encountered . Reconstructing the chain of events becomes much easier this way.
At the ground level, what does this look like? Consider what information is best shown as output. Logging messages should indicate success or failure clearly. You may desire messages for each stage and have clear delimitation of each stage boundary. Logs, at least for the labs and exam, will likely be saved to disk providing a diary of the execution stages when the code ran. For the labs, exam and beyond, configuring a logging system shouldn't place an undue burden on how much code you have to write. Your system should be easy to stand-up. Python has a standard logging module that I ultimately ended up using and extending for my own purposes.
With this philosophy in mind, logging seemed to fit two purposes:
During execution, allow an understanding of what the exploit is doing
After execution, provide a diary of what the exploit did Conceptually, this allows tracing progression of our exploit code. As such, someone other than yourself should be able to grasp the flow of how the application was exploited along with any possible issues encountered. In a professional situation, a history like this could provide valuable information to the developer(s).
Logging systems traditionally indicate a level of severity if triggered. Popular levels include DEBUG, INFO, WARN, and ERROR. To make logs readable during execution and useful afterward, messages need consistent meaning.
ERROR
Operation failed, but system continues running
WARN
Non-fatal issue, but may lead to a problem in the future
INFO
Message indicating normal operation
DEBUG
Diagnostic output used for troubleshooting and development
In exploit development, these levels are not about system health but about state transitions and decision points.
For the purposes of the PoC, we might adjust these and add an additional level, SUCCESS. A SUCCESS message marks a meaningful state transition. We're not looking to align these definitions with traditional error logging for services or servers on continuously running systems. The goal is to provide an auditable record that allows someone to replay, at a high level, what the PoC did and where it deviated from expectations.
import logging
logger = logging.getLogger('poc')
logger.setLevel(logging.INFO)
# Console (stdout)
ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter('%(message)s'))
logger.addHandler(ch)
# File
fh = logging.FileHandler('poc.log')
fh.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
logger.addHandler(fh)
logger.info("SQLi payload sent: ' OR 1=1--")
This approach is perfectly valid, but I found it didn’t give me the visual or semantic cues I wanted during live exploitation. I wanted a clear differentiation of the logging levels during execution and for review. I needed something for the exam which would have timestamps and a clear indication of what was transpiring as the script ran. To this end, I created what I called OffsecLogger. It's a class that I created just for these PoCs. As you can see, I didn't have to add external libraries.
So when added to the PoC, throughout the script, there will be something like the following to use it.
And one might see the following as a snippet from a PoC. If this was a terminal, the output would have some color to it, but I wasn't going to create a fake PoC and insert an image for you to see.
I made the output just enough for me and anyone else to discern the stages and see relevant information. You want a summary of the exploit in the logs. It should tell a story for others or your future self, this also includes the exam grader.
At this point, we have a way to reason about exploit execution as a sequence of stages and a way to observe what happens at each stage. Control flow tells us what should happen, and logging tells us what actually happened.
The next practical problem is that many real-world exploits don’t just send requests, they need to receive data, serve payloads, or wait for the target to call back. XSS delivery, token exfiltration, magic-link capture, and staged payload downloads all require lightweight, temporary infrastructure that lives alongside the PoC.
In the next article, we’ll build a minimal, one-shot web server designed specifically for exploit delivery and callback handling: something disposable, observable, and tightly scoped to a single run of the exploit.
Last updated