Skip to content

Commit dc9c981

Browse files
authored
Merge pull request #2042 from Automattic/feat-2017-complete-adversarial-campaigns
Complete supported WordPress adversarial campaigns
2 parents 9add873 + caa0192 commit dc9c981

11 files changed

Lines changed: 340 additions & 27 deletions

File tree

examples/recipes/wordpress-adversarial-vulnerable.json

Lines changed: 159 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
"inputs": {
99
"mounts": [
1010
{
11-
"source": "../../tests/fixtures/wordpress-adversarial/vulnerable-plugin",
12-
"target": "/wordpress/wp-content/plugins/wp-codebox-adversarial-vulnerable",
11+
"source": "../../tests/fixtures/wordpress-adversarial/vulnerable-plugin/vulnerable-plugin.php",
12+
"target": "/wordpress/wp-content/mu-plugins/wp-codebox-adversarial-vulnerable.php",
13+
"type": "file",
1314
"mode": "readonly",
1415
"metadata": { "kind": "fixture", "disposableOnly": true }
1516
},
@@ -25,7 +26,7 @@
2526
"steps": [
2627
{
2728
"command": "wordpress.run-php",
28-
"args": ["code=require_once ABSPATH . 'wp-admin/includes/plugin.php'; activate_plugin('wp-codebox-adversarial-vulnerable/vulnerable-plugin.php'); switch_theme('wp-codebox-adversarial-vulnerable'); delete_option('wp_codebox_vulnerable_authorization'); delete_option('wp_codebox_vulnerable_partial_state');"]
29+
"args": ["code=switch_theme('wp-codebox-adversarial-vulnerable'); foreach (array('wp_codebox_vulnerable_authorization', 'wp_codebox_vulnerable_partial_state', 'wp_codebox_vulnerable_ajax', 'wp_codebox_vulnerable_xmlrpc', 'wp_codebox_vulnerable_injection', 'wp_codebox_vulnerable_race_count', 'wp_codebox_adversarial_http_faults', 'wp_codebox_adversarial_http_fault_counts') as $option) { delete_option($option); }"]
2930
}
3031
]
3132
},
@@ -86,6 +87,161 @@
8687
"shrinking": { "enabled": true },
8788
"replay": { "selection": "findings" },
8889
"metadata": { "fixtureDefect": "state-corruption", "network": "deny" }
90+
},
91+
{
92+
"schema": "wp-codebox/adversarial-recipe-campaign/v1",
93+
"id": "wordpress-vulnerable-ajax",
94+
"seed": "wordpress-vulnerable-ajax-v1",
95+
"corpus": [
96+
{ "id": "unauthenticated-ajax", "actions": [{ "type": "ajax-save", "input": { "value": "fixture-value" } }], "signals": ["transport:ajax"] }
97+
],
98+
"caseTemplates": [
99+
{
100+
"id": "ajax-save",
101+
"phases": {
102+
"action": [{ "command": "wordpress.browser-actions", "args": ["steps-json=[{\"kind\":\"navigate\",\"url\":\"/\",\"waitFor\":\"load\"},{\"kind\":\"evaluate\",\"expression\":\"const response = await fetch('/wp-admin/admin-ajax.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=wp_codebox_vulnerable_save&value=fixture-value' }); return response.ok\",\"assert\":true}]", "capture=steps,network", "timeout=30s"] }],
103+
"assert": [{ "command": "wordpress.run-php", "args": ["code=if (get_option('wp_codebox_vulnerable_ajax', null) !== null) { throw new RuntimeException('ajax-nonce-bypass'); }"] }],
104+
"teardown": [{ "command": "wordpress.run-php", "args": ["code=delete_option('wp_codebox_vulnerable_ajax');"] }]
105+
}
106+
}
107+
],
108+
"mutators": ["scalar"],
109+
"oracles": [{ "id": "runtime-status", "severity": "critical", "description": "Unauthenticated AJAX writes without a nonce must fail closed." }],
110+
"concurrency": 1,
111+
"budgets": { "maxCases": 1, "maxActionsPerCase": 2, "maxInputBytes": 4096, "maxCaseTimeMs": 15000, "maxWallTimeMs": 120000, "maxArtifactBytes": 10485760 },
112+
"resetPolicy": { "mode": "none" },
113+
"requiredCapabilities": ["adversarial-campaign", "artifact-export", "bounded-evidence", "replay", "command:wordpress.browser-actions", "command:wordpress.run-php"],
114+
"novelty": { "retainSignals": true },
115+
"shrinking": { "enabled": false },
116+
"replay": { "selection": "findings" },
117+
"metadata": { "fixtureDefect": "ajax-nonce-bypass", "transportFidelity": "exact", "network": "deny" }
118+
},
119+
{
120+
"schema": "wp-codebox/adversarial-recipe-campaign/v1",
121+
"id": "wordpress-vulnerable-xmlrpc",
122+
"seed": "wordpress-vulnerable-xmlrpc-v1",
123+
"corpus": [
124+
{ "id": "unauthenticated-xmlrpc", "actions": [{ "type": "xmlrpc-save", "input": {} }], "signals": ["transport:xmlrpc"] }
125+
],
126+
"caseTemplates": [
127+
{
128+
"id": "xmlrpc-save",
129+
"phases": {
130+
"action": [{ "command": "wordpress.browser-actions", "args": ["steps-json=[{\"kind\":\"navigate\",\"url\":\"/\",\"waitFor\":\"load\"},{\"kind\":\"evaluate\",\"expression\":\"const response = await fetch('/xmlrpc.php', { method: 'POST', headers: { 'Content-Type': 'text/xml' }, body: '<?xml version=\\\"1.0\\\"?><methodCall><methodName>wpCodebox.vulnerable</methodName><params><param><value><string>fixture-value</string></value></param></params></methodCall>' }); return response.ok\",\"assert\":true}]", "capture=steps,network", "timeout=30s"] }],
131+
"assert": [{ "command": "wordpress.run-php", "args": ["code=if (get_option('wp_codebox_vulnerable_xmlrpc', null) !== null) { throw new RuntimeException('xmlrpc-authorization-bypass'); }"] }],
132+
"teardown": [{ "command": "wordpress.run-php", "args": ["code=delete_option('wp_codebox_vulnerable_xmlrpc');"] }]
133+
}
134+
}
135+
],
136+
"mutators": ["scalar"],
137+
"oracles": [{ "id": "runtime-status", "severity": "critical", "description": "Unauthenticated XML-RPC writes must fail closed." }],
138+
"concurrency": 1,
139+
"budgets": { "maxCases": 1, "maxActionsPerCase": 2, "maxInputBytes": 4096, "maxCaseTimeMs": 15000, "maxWallTimeMs": 120000, "maxArtifactBytes": 10485760 },
140+
"resetPolicy": { "mode": "none" },
141+
"requiredCapabilities": ["adversarial-campaign", "artifact-export", "bounded-evidence", "replay", "command:wordpress.browser-actions", "command:wordpress.run-php"],
142+
"novelty": { "retainSignals": true },
143+
"shrinking": { "enabled": false },
144+
"replay": { "selection": "findings" },
145+
"metadata": { "fixtureDefect": "xmlrpc-authorization-bypass", "transportFidelity": "exact", "network": "deny" }
146+
},
147+
{
148+
"schema": "wp-codebox/adversarial-recipe-campaign/v1",
149+
"id": "wordpress-vulnerable-injection",
150+
"seed": "wordpress-vulnerable-injection-v1",
151+
"corpus": [
152+
{ "id": "rest-code-injection", "actions": [{ "type": "injection-rest", "input": { "expression": "update_option('wp_codebox_vulnerable_injection', 'executed')" } }], "signals": ["route:injection"] }
153+
],
154+
"caseTemplates": [
155+
{
156+
"id": "injection-rest",
157+
"phases": {
158+
"action": [{ "command": "wordpress.run-php", "args": ["code=$request = new WP_REST_Request('POST', '/wp-codebox-vulnerable/v1/injection'); $request->set_body_params(json_decode(base64_decode('{{action.inputBase64}}'), true)); rest_do_request($request); if (get_option('wp_codebox_vulnerable_injection', null) !== null) { throw new RuntimeException('code-execution-indicator'); }"] }],
159+
"assert": [{ "command": "wordpress.run-php", "args": ["code=if (get_option('wp_codebox_vulnerable_injection', null) !== null) { throw new RuntimeException('code-execution-indicator'); }"] }],
160+
"teardown": [{ "command": "wordpress.run-php", "args": ["code=delete_option('wp_codebox_vulnerable_injection');"] }]
161+
}
162+
}
163+
],
164+
"mutators": ["sequence"],
165+
"oracles": [{ "id": "runtime-status", "severity": "critical", "description": "Mutated REST input must not execute server-side code." }],
166+
"concurrency": 1,
167+
"budgets": { "maxCases": 2, "maxActionsPerCase": 3, "maxInputBytes": 4096, "maxCaseTimeMs": 15000, "maxWallTimeMs": 120000, "maxArtifactBytes": 10485760 },
168+
"resetPolicy": { "mode": "checkpoint-per-case", "checkpointName": "wordpress-vulnerable-injection-baseline" },
169+
"requiredCapabilities": ["adversarial-campaign", "artifact-export", "bounded-evidence", "replay", "command:wordpress.rest-request", "command:wordpress.run-php"],
170+
"novelty": { "retainSignals": true },
171+
"shrinking": { "enabled": true },
172+
"replay": { "selection": "findings" },
173+
"metadata": { "fixtureDefect": "injection", "network": "deny" }
174+
},
175+
{
176+
"schema": "wp-codebox/adversarial-recipe-campaign/v1",
177+
"id": "wordpress-vulnerable-external-service",
178+
"seed": "wordpress-vulnerable-external-service-v1",
179+
"corpus": [
180+
{ "id": "failure-then-recovery", "actions": [{ "type": "external-service-cycle", "input": {} }], "signals": ["transport:failure", "transport:recovery"] }
181+
],
182+
"caseTemplates": [
183+
{
184+
"id": "external-service-cycle",
185+
"phases": {
186+
"setup": [{ "command": "wordpress.run-php", "args": ["code=delete_option('wp_codebox_adversarial_http_fault_counts'); update_option('wp_codebox_adversarial_http_faults', array('schema' => 'wp-codebox/transport-fault-model/v1', 'seed' => 'failure-recovery', 'rules' => array(array('id' => 'fixture-cycle', 'sequence' => array(array('timeoutMs' => 10), array('status' => 403, 'body' => '{\"authorized\":false}'))))), false);"] }],
187+
"action": [{ "command": "wordpress.run-php", "args": ["code=$request = new WP_REST_Request('POST', '/wp-codebox-vulnerable/v1/external-service'); $failed = rest_do_request($request); $recovered = rest_do_request($request); $failed_data = (array) $failed->get_data(); $recovered_data = (array) $recovered->get_data(); if (!empty($recovered_data['authorized'])) { throw new RuntimeException('external-service-recovery-failed'); } if (!empty($failed_data['authorized'])) { throw new RuntimeException('external-service-fail-open'); }"] }],
188+
"teardown": [{ "command": "wordpress.run-php", "args": ["code=delete_option('wp_codebox_adversarial_http_faults'); delete_option('wp_codebox_adversarial_http_fault_counts');"] }]
189+
}
190+
}
191+
],
192+
"mutators": ["sequence"],
193+
"oracles": [{ "id": "runtime-status", "severity": "high", "description": "Dependency failure must fail closed and the next healthy response must recover." }],
194+
"concurrency": 1,
195+
"budgets": { "maxCases": 1, "maxActionsPerCase": 2, "maxInputBytes": 4096, "maxCaseTimeMs": 15000, "maxWallTimeMs": 120000, "maxArtifactBytes": 10485760 },
196+
"resetPolicy": { "mode": "checkpoint-per-case", "checkpointName": "wordpress-vulnerable-external-baseline" },
197+
"requiredCapabilities": ["adversarial-campaign", "artifact-export", "bounded-evidence", "replay", "command:wordpress.run-php"],
198+
"optionalCapabilities": ["transport-faults"],
199+
"novelty": { "retainSignals": true },
200+
"shrinking": { "enabled": true },
201+
"replay": { "selection": "findings" },
202+
"metadata": { "fixtureDefect": "external-service-fail-open", "network": "deny", "recoveryVerified": true }
203+
},
204+
{
205+
"schema": "wp-codebox/adversarial-recipe-campaign/v1",
206+
"id": "wordpress-vulnerable-ui-duplicate",
207+
"seed": "wordpress-vulnerable-ui-duplicate-v1",
208+
"corpus": [
209+
{ "id": "admin-duplicate-effect", "actions": [{ "type": "browser-journey", "input": [{ "kind": "navigate", "url": "/wp-admin/tools.php?page=wp-codebox-vulnerable", "waitFor": "load" }, { "kind": "click", "selector": ".wp-codebox-vulnerable-save" }, { "kind": "evaluate", "expression": "document.querySelector('.wp-codebox-vulnerable-count').value", "assert": "1" }, { "kind": "capture" }, { "kind": "capture" }, { "kind": "capture" }] }], "signals": ["browser:duplicate-effect"] }
210+
],
211+
"caseTemplates": [
212+
{ "id": "browser-journey", "phases": { "action": [{ "command": "wordpress.browser-actions", "args": ["steps-json={{action.input}}", "auth=wordpress-admin", "auth-user-id=1", "capture=steps,html,screenshot,dom-snapshot", "timeout=30s"] }] } }
213+
],
214+
"mutators": ["sequence"],
215+
"oracles": [{ "id": "runtime-status", "severity": "high", "description": "One UI action must produce at most one effect." }],
216+
"concurrency": 1,
217+
"budgets": { "maxCases": 1, "maxActionsPerCase": 2, "maxInputBytes": 16384, "maxCaseTimeMs": 45000, "maxWallTimeMs": 120000, "maxArtifactBytes": 20971520 },
218+
"resetPolicy": { "mode": "checkpoint-per-case", "checkpointName": "wordpress-vulnerable-ui-duplicate-baseline" },
219+
"requiredCapabilities": ["adversarial-campaign", "artifact-export", "bounded-evidence", "replay", "command:wordpress.browser-actions"],
220+
"novelty": { "retainSignals": true },
221+
"shrinking": { "enabled": true },
222+
"replay": { "selection": "findings" },
223+
"metadata": { "fixtureDefect": "duplicated-ui-effect", "network": "deny", "browserJourney": true }
224+
},
225+
{
226+
"schema": "wp-codebox/adversarial-recipe-campaign/v1",
227+
"id": "wordpress-vulnerable-ui-loading",
228+
"seed": "wordpress-vulnerable-ui-loading-v1",
229+
"corpus": [
230+
{ "id": "admin-stuck-loading", "actions": [{ "type": "browser-journey", "input": [{ "kind": "navigate", "url": "/wp-admin/tools.php?page=wp-codebox-vulnerable", "waitFor": "load" }, { "kind": "click", "selector": ".wp-codebox-vulnerable-save" }, { "kind": "evaluate", "expression": "document.querySelector('.spinner').classList.contains('is-active')", "assert": false }, { "kind": "capture" }, { "kind": "capture" }, { "kind": "capture" }] }], "signals": ["browser:stuck-loading"] }
231+
],
232+
"caseTemplates": [
233+
{ "id": "browser-journey", "phases": { "action": [{ "command": "wordpress.browser-actions", "args": ["steps-json={{action.input}}", "auth=wordpress-admin", "auth-user-id=1", "capture=steps,html,screenshot,dom-snapshot", "timeout=30s"] }] } }
234+
],
235+
"mutators": ["sequence"],
236+
"oracles": [{ "id": "runtime-status", "severity": "high", "description": "Loading state must clear after the UI action completes." }],
237+
"concurrency": 1,
238+
"budgets": { "maxCases": 1, "maxActionsPerCase": 2, "maxInputBytes": 16384, "maxCaseTimeMs": 45000, "maxWallTimeMs": 120000, "maxArtifactBytes": 20971520 },
239+
"resetPolicy": { "mode": "checkpoint-per-case", "checkpointName": "wordpress-vulnerable-ui-loading-baseline" },
240+
"requiredCapabilities": ["adversarial-campaign", "artifact-export", "bounded-evidence", "replay", "command:wordpress.browser-actions"],
241+
"novelty": { "retainSignals": true },
242+
"shrinking": { "enabled": true },
243+
"replay": { "selection": "findings" },
244+
"metadata": { "fixtureDefect": "stuck-loading-state", "network": "deny", "browserJourney": true }
89245
}
90246
],
91247
"artifacts": {

0 commit comments

Comments
 (0)