Repository navigation
[MySQL] az mysql: Fix Windows executable-search hijacking - #34190
ravive9199 wants to merge 2 commits into
Conversation
…ible-server deployment commands
|
Thank you for your contribution ravive9199! We will review the pull request and get back to you soon. |
There was a problem hiding this comment.
🟡 Changes recommended
CWD exclusion remains bypassable through filesystem aliases or executable symlinks.
1 open finding
What changed in this PR
Secures MySQL deployment subprocesses against Windows executable-search hijacking.
Changes:
- Adds safe executable resolution for
gitandgh. - Applies resolution to both subprocess helpers.
- Adds Windows-focused regression tests.
| File | Description |
|---|---|
mysql/_util.py |
Adds executable resolution and secured launch wrappers. |
mysql/custom.py |
Uses the secured wrapper and preserves installation errors. |
mysql/tests/latest/test_mysql_util.py |
Tests PATH filtering and subprocess behavior. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
| if os.path.normcase(os.path.realpath(directory)) == current_directory: | ||
| continue | ||
| candidate = os.path.join(directory, name) | ||
| if os.path.isfile(candidate) and os.access(candidate, os.X_OK): | ||
| return os.path.abspath(candidate) |
|
Please fix CI issues |
Fixed CI issues. Could you please rerun the pipeline? as I am not authorized to do so. |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
…ible-server deployment commands
@microsoft-github-policy-service agree company="Microsoft" |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
az mysql: Fix Windows executable-search hijacking
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |

🤖 PR Validation — ️✔️ All clear
Related command
Description
Fixes Windows executable-search hijacking in MySQL flexible-server deployment commands.
Previously, MySQL commands launched git and gh using bare executable names. Windows could select a same-named executable from the current project directory instead of the installed tool, allowing untrusted project content to run with the CLI user’s privileges.
• Added MySQL-local executable resolution that launches the absolute PATH-selected executable.
• Excluded empty/relative PATH entries and entries resolving to the current directory.
• Preserved native .exe lookup without implicit .bat/.cmd fallback.
• Applied resolution to both subprocess helpers, covering Git operations, GitHub authentication, workflows, and secret operations.
• Preserved the GitHub CLI installation message when no eligible executable is found.
• Kept explicit executable paths and Linux/macOS lookup behavior unchanged.
Changes are limited to MySQL’s _util.py, custom.py, and test_mysql_util.py.
ICM: https://portal.microsofticm.com/imp/v5/incidents/details/31000000711818/summary
Testing Guide
Automated: All 17 MySQL regression tests passed on Windows using Python 3.14. Coverage includes current-directory substitution, missing dependencies, PATH handling, argument preservation, both output modes, and GitHub workflow launches.
Manual reproduction: Created a harmless text file named git.exe in a temporary project directory, kept legitimate Git on PATH, and invoked the pre-fix MySQL helper from that directory. The launch failed with WinError 216, consistent with Windows attempting to launch the invalid planted file.
The following comparisons were provided for manual verification; their results have not yet been recorded:
Scenario1: Pre-fix, planted git.exe. Expected result: Invalid-executable error.
Scenario2: Before fix, plant renamed. Expected result: Git version printed.
Scenario3: After fix, plant present. Expected result: Git version printed; Arguments contains the absolute installed Git path
Set-Location 'C:\Users\vvaadakattu\workspace\test\azure-cli'
$repo = (Get-Location).Path
$python = 'C:\Program Files\Microsoft SDKs\Azure\CLI2\python.exe'
$gitExe = (Get-Command git.exe -ErrorAction Stop).Source
$gitDir = Split-Path $gitExe
$gitExe
Confirm the displayed Git path belongs to your legitimate Git installation,
not a file inside a downloaded project. Stop if it does not.
$scratch = Join-Path $env:TEMP ('mysql-path-test-' + [guid]::NewGuid())
New-Item -ItemType Directory -Path $scratch | Out-Null
Set-Content -LiteralPath (Join-Path $scratch 'git.exe') -Value 'Deliberately invalid executable'
Define the test function (paste the entire following line):
function Test-MySqlGit { param([string]$SourceRoot) & $python -c "import sys, os; sys.path[:0]=[os.path.join(sys.argv[1],'src',p) for p in ('azure-cli-core','azure-cli','azure-cli-telemetry')]; from azure.cli.command_modules.mysql import _util as u; os.chdir(sys.argv[2]); os.environ['PATH']=sys.argv[3]; print('Source:',u.file,flush=True); print('Working directory:',os.getcwd(),flush=True); result=u.run_cmd(['git','--version'],capture_output=True,check=True); print('Arguments:',result.args); print(result.stdout.decode().strip())" $SourceRoot $scratch $gitDir }
Keep the fix stashed for this scenario.
Test-MySqlGit $repo
Expected:
or WinError 193. Git does not print its version.
The traceback is expected. Windows attempts to launch the invalid planted
file instead of the installed Git. This demonstrates attempted executable
selection, not successful execution of attacker code.
Keep the fix stashed for this scenario too.
Rename-Item -LiteralPath (Join-Path $scratch 'git.exe') -NewName 'git.exe.disabled'
Test-MySqlGit $repo
Expected:
Arguments: ['git', '--version']
git version ...
Without the planted filename, Windows finds legitimate Git on PATH.
Together, scenarios 1 and 2 isolate the planted filename as the cause.
Find the stash containing the MySQL fix:
git stash list
If it is the latest entry, apply it with the following command. Otherwise,
replace stash@{0} with the correct entry from the list:
git stash apply 'stash@{0}'
Apply keeps the stash as a backup. If Git reports any conflicts or errors,
stop and resolve them before proceeding. Confirm the fix was restored:
git diff -- src\azure-cli\azure\cli\command_modules\mysql_util.py src\azure-cli\azure\cli\command_modules\mysql\custom.py
Restore the planted filename and rerun the same test:
Rename-Item -LiteralPath (Join-Path $scratch 'git.exe.disabled') -NewName 'git.exe'
Test-MySqlGit $repo
Expected:
Example (the installation path and Git version may differ):
Arguments: ['C:\Program Files\Git\cmd\git.exe', '--version']
git version ...
The fixed MySQL helper selects the installed executable before launching it,
so Windows does not perform a second bare-name search in the project folder.
This checklist is used to make sure that common guidelines for a pull request are followed.
The PR title and description has followed the guideline in Submitting Pull Requests.
I adhere to the Command Guidelines.
I adhere to the Error Handling Guidelines.