AI-Assisted Penetration Testing in Practice: From SQL Injection to Custom Tooling

AI-Assisted Penetration Testing in Practice: From SQL Injection to Custom Tooling

In a previous post, we discussed how AI-assisted analysis has become a useful tool in offensive security engagements. This article demonstrates that methodology in practice, showing how it accelerated our work during a recent web application and API penetration test. The combination of AI assistance and human expertise enabled identification of an obscure SQL injection vulnerability and comprehensive testing within a compressed timeframe.


Scope Overview

Testing was conducted across two target systems: an internal web application used by administrative support staff and an API gateway requiring cryptographic request signing. The engagement was conducted under expedited conditions following a change to the client's go-live date, with high-severity findings communicated within the first three days to allow for proactive remediation.

Target Environment

Initial reconnaissance revealed that the application was built on GoAdmin, an open-source Go framework for creating admin panels, identified through characteristic asset references in the HTML source. While the client's implementation repository was private and inaccessible, the underlying GoAdmin repository was publicly available, enabling identification of framework-level vulnerabilities that may be inherited by the application.

The API gateway used ECDSA request signing to ensure request authenticity and provide protection against message tampering. However, no tooling existed to generate valid signatures dynamically for each request, effectively hindering automated testing approaches such as fuzzing with Burp Intruder.

Note: For client confidentiality, all code examples and screenshots reference the public GoAdmin framework repository. A local GoAdmin instance was deployed using the demo table data provided by the developer. The exception is the custom Burp plugin output, which has been redacted to protect sensitive information.

Where AI Assisted During Testing

Before diving into the main technical analysis, the table below firstly summarises the key areas where AI assistance complemented human-led actions.

Activity Human-Led AI-Assisted
Initial Reconnaissance • Identifying GoAdmin framework from HTML source
• Enumerating application URL paths
-
Source Code Analysis • Deciding to pull the public repository
• Grepping for interesting keywords in code
• Flagging relevant files for review
• Code-assisted/dynamic testing
• Tracing execution flow across multiple files
• Mapping URL parameters to SQL query construction
• Rapidly understanding the application's architecture
Vulnerability Identification • Recognising SortField vs SortType inconsistency
• Identifying stored and reflected XSS vulnerabilities
• Identifying insecure CSRF token implementation
• Identifying unsanitised parameters within complex Go structs
• Identifying SQL injection (SQLi)
• Flagging potential logic flaws in session management
Exploitation & PoC • Manual SQLi attempts and error message interpretation
• Recognising GoAdmin cookie extraction as an attack path
• Suggesting payload syntax refinements
• Constructing XSS payloads exploiting CSRF weakness
• Constructing Python scripts to exploit SQLi
Tool Development • Articulating API signing requirements • Building Burp ECDSA signing plugin

After manually identifying a stored Cross-Site Scripting (XSS) vulnerability and a misconfigured CSRF implementation, AI was used to construct functional payloads that demonstrated contextual impact. By using AI to offload the iterative JavaScript development process, an effective privilege escalation attack was created without diverting time away from security analysis.

The API gateway's request signing process necessitated custom tooling for thorough input validation analysis. Although tool development time was not factored into the engagement scope, an AI-generated Burp ECDSA signing plugin that automatically generated a signature for every unique request offset this drawback. This enabled wider payload coverage, ultimately confirming that the client had implemented effective input validation controls for this component.

Generating ECDSA signatures for dynamic payloads in Burp

Multiple vulnerabilities were identified within the client's application, including XSS and CSRF issues. However, the SQL injection finding best exemplified various human-AI workflows in practice. The remainder of this post focuses exclusively on this vulnerability, with a detailed walkthrough that covers:

  • AI-augmented source code analysis
  • Vulnerability identification and manual validation
  • Exploitation to extract session identifiers
  • Remediation patch development and verification

Prompting Our Way to SQL Injection

Despite not having access to the client's actual application repository, the underlying GoAdmin framework served as a basis for identifying potentially vulnerable code. We then inferred through dynamic testing whether the client's application had inherited any discovered flaws, or conversely, whether any customisations negated the affected code paths.

For demonstration, the screenshots depicted below are from a local GoAdmin instance.

Tracing User Input

Fortunately, the developers of GoAdmin published an architectural overview of its various Go modules. The plugin modules were of interest since they documented the application's routing and controller architecture, providing insight into how specific HTTP request handlers processed input. This approach is typical when analysing MVC architectures, as the controller layer dictates how user requests are routed through the application.

The GoAdmin application

After downloading the repository, the analysis shifted to the plugins/admin/controller/ directory. Observing the /admin/info/{table} URL pattern in the application prompted an AI-assisted review of show.go and plugins.go controllers to understand how routes were managed. This identified plugins/admin/controller/show.go as the route handler for /admin/info/{table} list view requests.

With a potential entry point identified, keywords such as fmt.Sprintfdb.Query, and db.Exec were searched across the controller and database modules. While fmt.Sprintf calls constructing SQL fragments are not inherently vulnerable, they become exploitable when user-controlled input is interpolated without validation, as this circumvents the database driver's parameterisation safeguards.

Tracing user input to SQL construction in the Go modules

The grep results returned multiple hits for fmt.Sprintf, with most relating to innocuous functionality such as logging statements, template rendering and general string formatting. However, we narrowed our focus to lines 419 and 557 in plugins/admin/modules/table/default.go. This module handled data table operations and warranted further inspection after observing that the queryCmd variables directly interpolated the params.SortField and params.SortType parameters. More notably, these lines resided within a function aptly named getDataFromDatabase.

To confirm these variables were user-controllable, we returned to show.go and used AI to map the data flow:

  • "Trace the flow of _sort and _sort_type from a HTTP request through to query construction. Do they undergo any validation before reaching the fmt.Sprintf call?"

This identified ShowInfo on line 37 as the responsible handler and confirmed that parameter.GetParam() accepted ctx.Request.URL as its first argument:

params := parameter.GetParam(ctx.Request.URL, panel.GetInfo().DefaultPageSize,
    panel.GetInfo().SortField, panel.GetInfo().GetSort())

Reviewing plugins/admin/modules/parameter/parameter.go confirmed that the framework mapped these URL keys directly into the Parameters struct. Specifically, lines 86–87 used a getDefault helper to ingest the values from the parsed URL:

sortField := getDefault(values, Sort, primaryKey)
sortType := getDefault(values, SortType, defaultSortType)

As suspected, the __sort and __sort_type parameters represented a direct entry point for user input into the application logic. More interestingly, at this point both parameters appeared to be parsed across all /admin/info/{table} endpoints, including those where sorting was never intended to be user-accessible.

AI highlighting UI bypass via URL sorting parameters

As shown above, AI analysis initially took the investigation down a short rabbit hole by suggesting the vulnerability might exist in the __sort parameter based on the framework's documented sortable field metadata. However, dynamic testing confirmed this injection point to be a false positive in the client's application.

Note: The reachability of the __sort_type parameter depended on the specific table configuration. If a table utilised a GetDataFn or getDataFun implementation, the data flow branched away before reaching the vulnerable code. During the engagement, certain custom endpoints were not vulnerable because they relied on these specific functions rather than the default getDataFromDatabase handler.

Confirming the Vulnerability

The previous trace established that these URL sorting parameters were inherited functionality that had been overlooked and unknowingly exposed. With dynamic testing eliminating __sort as vulnerable, the remaining __sort_type parameter became the focus. The analysis returned to getDataFromDatabase() in plugins/admin/modules/table/default.go to confirm if the flaw translated into a viable injection attack.

Within getDataFromDatabase(), the queryCmd variable at lines 557–561 represented the relevant code path for standard paginated list views. The use of fmt.Sprintf for query construction here was also identified in a near-identical pattern at line 419 within getAllDataFromDatabase(), indicating a systemic issue across the module:

queryCmd = fmt.Sprintf(queryStatement, allFields, tb.Info.Table, joins, wheres, groupBy,
    tb.Info.Table, params.SortField, params.SortType)

The queryStatement definition below was noted for reference, as it would influence payload construction. Since the injection point resided within an ORDER BY clause, standard boolean-based and stacked query payloads would be syntactically invalid.

// %s means: fields, table, join table, wheres, group by, order by field, order by type
queryStatement = "select %s from " + placeholder + "%s %s %s order by " + placeholder + "." + placeholder + " %s LIMIT ? OFFSET ?"

Prompting AI to review the parameter validation logic explained why our earlier injection attacks on params.SortField (__sort) were unsuccessful. This parameter was found to be validated against an allowlist of legitimate database column names at lines 507-509. If a user provided an invalid value, the application safely defaulted to the primary key:

Inconsistent parameter validation identified with AI assistance

However, as the logic progressed to the queryCmd query construction at lines 557–561, no equivalent validation was performed on params.SortType (the __sort_type URL query parameter).

To independently verify AI's assessment that validation was insufficient, we traced back to parameter.GetParam() within parameter.go where URL parsing was initially performed. At line 97, input sanitisation was applied to SortType; however, it was ultimately ineffective due to a logic flaw. As shown in the snippet below, despite correctly validating whether the SortType value was asc or desc, the function failed to store the sanitised value, resulting in the original sortType variable being passed directly to the query construction logic.

// parameter.go snippet
sortType := getDefault(values, SortType, defaultSortType) // [1] Value assigned here
// ...
for key, value := range values {
    if !modules.InArray(keys, key) && len(value) > 0 && value[0] != "" {
        if key == SortType {
            if value[0] != sortTypeDesc && value[0] != sortTypeAsc {
                fields[key] = []string{sortTypeDesc} // [2] Validation only affects the 'fields' map
            }
        }
    }
}

Having confirmed the untrusted SortType parameter was interpolated into the SQL query via fmt.Sprintf, testing proceeded by manually appending both sorting parameters to an arbitrary /admin/info/{table} endpoint. While __sort required a valid column name, __sort_type was populated with the following payload that leveraged MySQL's sleep() function. If successful, this would introduce an artificial 5-second delay in the injected subquery, confirming the vulnerability.

__sort=id&__sort_type=ASC%2C(SELECT+1+FROM+(SELECT+SLEEP(5))a)
Successful time-based SQLi attack

A resulting query time of 5010.935ms validated the time-based technique and confirmed arbitrary SQL execution. However, an error-based SQLi would enable significantly faster data extraction. To test for this, an updated payload leveraging extractvalue() was attempted. If successful, this would force an XML parsing error, causing the database to return sensitive information, such as the current user, directly in the output:

__sort=id&__sort_type=ASC,(SELECT%201%20FROM%20(SELECT%20extractvalue(1,concat(0x7e,(SELECT%20user()))))a)
Successful error-based SQLi attack

This second payload confirmed that not only was the injection exploitable via time-based techniques, but sensitive information could be directly exfiltrated through the application's verbose error responses.

As a side observation, the lack of output encoding on this parameter also resulted in a reflected XSS vulnerability. While not the primary focus of this analysis, it confirmed a broader pattern of improper input handling across the framework:

__sort=id&__sort_type=ASC%3cscript%3ealert('Hello')%3c%2fscript%3e
A Reflected XSS

Note: In our GoAdmin demo environment, only the default Admin and Operators roles were configured. The client's application was configured with RBAC permissions that granted a low-privileged user legitimate access to affected tables. This intended access provided a pathway for exploitation.

AI-Assisted Post-Exploitation

To recap the analysis: the __sort_type parameter was parsed across all /admin/info/{table} endpoints, regardless of whether sorting controls were visible in the user interface. While parameter.go attempted to validate the parameter against an allowlist of ASC and DESC values, a variable assignment error rendered this check ineffective. The unsanitised value was then passed directly into queryCmd construction via fmt.Sprintf, resulting in SQL injection within an ORDER BY clause.

With arbitrary SQL execution confirmed through both time-based and error-based techniques, the focus shifted to identifying exploitable data. Despite manual testing validating both injection techniques in our demo environment, sqlmap's automated detection failed against the client's application. This discrepancy was suspected to be related to upstream controls, likely a WAF filtering sqlmap's high request volume.

As a workaround, AI-generated Python scripts were used to automate credential extraction via the error-based SQLi, as shown below against our demo application:

Extracting environment and credential information via SQLi

In contrast to our demo application, although bcrypt was also used for password hashing, the client's database was (securely) configured with minimal user privileges. As a result, further OS-level compromise attempts were prevented.

An AI-assisted review of the modules/auth/session.go code revealed a more efficient attack path. The application stored the SID session identifier in the goadmin_session table to authenticate requests. By querying this table directly via the SQLi attack, all active session cookies could be extracted:

Extracting all active user session cookies via SQLi

Using this refined approach, an attacker could then hijack any active session by replacing their go_admin_session cookie value with an extracted SID. This negated the need to perform computationally intensive attacks to achieve account compromise, as demonstrated below:

Session hijacking using extracted SID value

Remediation

The standard recommendation for preventing injection attacks is to sanitise input, however the specifics of implementation are typically left to developers. As previously discussed during the vulnerability analysis, the GetParam function in parameter.go attempted to validate SortType values but stored the result in the wrong variable.

Rather than refactoring the parameter parsing logic, a more robust approach is to enforce a strict allowlist before parameters reach the query construction logic. As shown in the code snippet below, a validateSortType() function was implemented in default.go to check user-supplied values against an allowlist of ASC and DESC only. By placing this check at the start of the affected getAllDataFromDatabase and getDataFromDatabase functions, untrusted input is sanitised before reaching any fmt.Sprintf operations, mitigating the SQL injection risk.

// Updated default.go snippet
func validateSortType(sortType string) string {
    normalised := strings.ToUpper(strings.TrimSpace(sortType))
    if normalised == "DESC" {
        return "DESC"
    }
    return "ASC" // Defaults to ASC for anything else
}

// [1] Patching the first vulnerable function
func (tb *DefaultTable) getAllDataFromDatabase(params parameter.Parameters) (PanelInfo, error) {
    params.SortType = validateSortType(params.SortType)
    // ...
}

// [2] Patching the second vulnerable function
func (tb *DefaultTable) getDataFromDatabase(ctx *context.Context, params parameter.Parameters) (PanelInfo, error) {
    params.SortType = validateSortType(params.SortType)
    // ...
}

We verified the changes by launching a patched instance of the application and observing that the previous injection attacks were ineffective:

SQLi successfully remediated

Conclusion

This engagement illustrated AI's value across multiple aspects of penetration testing: identifying SQL injection through rapid source code analysis, generating contextually relevant XSS exploitation payloads, and developing custom tooling for ECDSA-signed API testing. In hindsight, discovery of the SQL injection would have been unlikely without access to the GoAdmin source code and impractical without AI assistance to navigate it efficiently. Similarly, developing custom tooling would typically have consumed significant engagement time.

While AI significantly accelerated the analysis, it was most effective when guided by human intuition. Occasionally, AI identified false positives requiring manual validation, but excelled at time-consuming tasks such as code comprehension, payload construction, and tool development. However, strategic decisions around investigative direction and attack prioritisation remain human responsibilities.

Responsible Disclosure

We acknowledge the GoAdmin maintainers' contribution to the open-source community and the effort required to build and maintain such frameworks. Currently, the GoAdmin project appears to be unmaintained, with no commits since April 2024 and limited community activity. We contacted the maintainers, providing technical details and our proposed remediation patch. As of publication, we have not received a response.

Timeline:

  • March 2026: Vulnerabilities identified during client engagement
  • April 3, 2026: GoAdmin maintainers contacted with technical details and proposed patch
  • April 9, 2026: Follow-up contact attempt made (no response received)
  • May 5, 2026: Public disclosure

Given the project's apparent end-of-life status and the time elapsed, we are publishing these findings to inform organisations currently using GoAdmin-based applications of these risks. Organisations using GoAdmin should assess whether the framework aligns with their long-term security and maintenance requirements.

Read more