Skip to content

[WIP] Rework the linked server usage#1271

Draft
Dfte wants to merge 3 commits into
Pennyw0rth:mainfrom
Dfte:add_list_linked_server
Draft

[WIP] Rework the linked server usage#1271
Dfte wants to merge 3 commits into
Pennyw0rth:mainfrom
Dfte:add_list_linked_server

Conversation

@Dfte

@Dfte Dfte commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Description

So far NXC MSSQL can list linked servers via the enum_links module which output is the following:

image

The module is great because it allows listing both the local login, the remote login and the target server.

From there a user could use modules *_links to execute code on the linked server. Let's say the user wants to get the version of the SQL database via the following request:

SELECT @@VERSION;

If using the *_links module, the module will wrap it that way:

EXEC('SELECT @@VERSION;') AT [TARGET_LINKED_SERVER];

Whil it works, I believe we shouldn't have a module to use this feature as it clearly limitates what we can do with it. For example, it is not yet possible to do a simple SQL query via a linked server. Why ? Because modules only allow running xpcmdshell via a linked server. It's not a proper way of handling this amazing feature so here is my proposal.

First, note this is a crash test PR (hence WIP). The idea is to provide a working solution and estimate whether or not we should deploy it globally. To handle this issue, I added the following function:

def _wrap_linked(self, query):
        if not getattr(self.args, "on_linked_server", None):
            return query

        # Enable RPC out for complexe queries
        # Here we should check the value, back it up
        # enable then restore
        check_rpc_out_query = f"""
        EXEC sp_serveroption @server = N'{self.args.on_linked_server}', @optname = 'rpc out', @optvalue = 'true';
        """
        self.conn.sql_query(check_rpc_out_query)

        # Second we need to check that the actual user can rely on the linked server link
        check_user_has_access_query = f"""
        SELECT l.remote_name, l.uses_self_credential
        FROM sys.linked_logins l
        JOIN sys.servers s ON s.server_id = l.server_id
        WHERE s.name = N'{self.args.on_linked_server}'
        AND (l.local_principal_id = 0 OR l.local_principal_id = USER_ID(SYSTEM_USER));
        """
        rows = self.conn.sql_query(check_user_has_access_query)
        if not rows or not rows[0].get("remote_name"):
            self.logger.fail(f"No valid login mapping found for {self.username} on linked server '{self.args.on_linked_server}'")

        escaped = query.replace("'", "''")
        wrapped_query = f"EXEC('{escaped}') AT [{self.args.on_linked_server}];"
        print(wrapped_query)
        return wrapped_query

It does a few things:

  • Checks whether RPC OUT is enabled on the local server to the linked server. Indeed, using EXEC requires being able to have RPC coming out from the source DB. I didn't implement the check/enable/restore code because we don't need to have that yet. This PR Add oleexec, clrexec, jobexec and assembly execution fileless for MSSQL #1268 shows the idea ;
  • Checks if the current user can use a linked server link (because it's not always the case) ;
  • Wraps the initial query and executes it.

For testing purpose I have added the following --list-linked-servers option:

nxc mssql 192.168.56.72 -u Administrator -p Defte@WF   --list-linked-servers

Whose output is the following:

image
  • Linked server = the target SQL server ;
  • local login = the local users that have the right to use that linked server link ;
  • Remote login = the remote user that will run the query thourgh the linked server link ;
  • Use self = whether we rely on the same context (ie: whiteflag/administrator in that case).

From the output we understand that, being connected on SRV22 as WHITEFLAG/administrator, I can run queries as SA on DC25. Using the --on-linked-server, I can now select where to run the query:

nxc mssql 192.168.56.72 -u Administrator -p Defte@WF  -q "SELECT @@VERSION;" --on-linked-server DC25
image

And here without the option, we have a different output:

image

Because:

  • DC25 has got a 2025 MSSQL DB installed ;
  • SRV22 has got a 2022 MSSQL DB installed.

Now about the wrapping, we simply have the following modification to replicate everywher we use self.conn.sql_query:

raw_output = self.conn.sql_query(self._wrap_linked(self.args.query))

Doing so we will be able to eliminate 4 modules that were great at the time but not necessary anymore. This will also allow using linked servers on SQLEXEC methods which is why I believe we should rework that part.

Type of change

Insert an "x" inside the brackets for relevant items (do not delete options)

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Deprecation of feature or functionality
  • This change requires a documentation update
  • This requires a third party update (such as Impacket, Dploot, lsassy, etc)
  • This PR was created with the assistance of AI (list what type of assistance, tool(s)/model(s) in the description)

Setup guide for the review

More on that later.

Screenshots (if appropriate):

Checklist:

Insert an "x" inside the brackets for completed and relevant items (do not delete options)

  • I have ran Ruff against my changes (poetry: poetry run ruff check ., use --fix to automatically fix what it can)
  • I have added or updated the tests/e2e_commands.txt file if necessary (new modules or features are required to be added to the e2e tests)
  • If reliant on changes of third party dependencies, such as Impacket, dploot, lsassy, etc, I have linked the relevant PRs in those projects
  • I have linked relevant sources that describes the added technique (blog posts, documentation, etc)
  • I have performed a self-review of my own code (not an AI review)
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (PR here: https://github.com/Pennyw0rth/NetExec-Wiki)

@Dfte

Dfte commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Will have to merge that as well, wrapping both linked-server and impersonation will be absolutely powerful #1271

@Dfte
Dfte marked this pull request as draft June 8, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant