blitzstrike 1.0.60 → 1.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -18
- package/bench/corpus/php-int-cast-bypass-fixed.md +20 -0
- package/bench/corpus/php-int-cast-bypass-vuln.md +23 -0
- package/bench/enterprise/wp-batch-route/batch.php +43 -0
- package/dist/index.js +176 -1
- package/package.json +1 -1
- package/bench/corpus/wp-author-not-in-fixed.md +0 -21
- package/bench/corpus/wp-author-not-in-vuln.md +0 -23
package/CHANGELOG.md
CHANGED
|
@@ -5,15 +5,30 @@ All notable changes to Blitz Strike are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
-
## [1.0.
|
|
8
|
+
## [1.0.62] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added — route-confusion & dispatch-abuse detection
|
|
11
|
+
|
|
12
|
+
New vulnerability class distinct from source→sink taint (`src/route-confusion.ts`):
|
|
13
|
+
detects when the routing/dispatch layer lets an attacker reach an unintended
|
|
14
|
+
handler or control which handler runs.
|
|
9
15
|
|
|
10
|
-
|
|
16
|
+
- `dynamic_dispatch` — attacker-controlled callback/route (`call_user_func`,
|
|
17
|
+
`forward`, `dispatch`, `resolve_route`, `invoke`).
|
|
18
|
+
- `dynamic_method_call` — `$obj->$method()` / `$class::$method()`.
|
|
19
|
+
- `dynamic_include` — `include`/`require` of a variable (LFI via route).
|
|
20
|
+
- `batch_forwarding` — a loop over sub-requests that forwards to inner handlers
|
|
21
|
+
without a per-route authorization re-check.
|
|
22
|
+
- `route_normalization_gap` — route matching against user input without path
|
|
23
|
+
normalization (strtolower/rtrim/urldecode).
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
25
|
+
Wired into the `@source` agent; findings become `route` nodes in the security
|
|
26
|
+
graph. Added a WordPress-style batch-endpoint fixture to the enterprise
|
|
27
|
+
benchmark (`wp-batch-route`).
|
|
28
|
+
|
|
29
|
+
## [1.0.60] — 2026-09-13
|
|
30
|
+
|
|
31
|
+
### Added — deeper taint-flow coverage
|
|
17
32
|
|
|
18
33
|
- **Method-call resolution** (`calleeName`) — `$obj->method()` now resolves to
|
|
19
34
|
its method definition for interprocedural taint, not the object variable.
|
|
@@ -21,17 +36,8 @@ now detects it:
|
|
|
21
36
|
other array→string/format built-ins now carry taint through to the sink.
|
|
22
37
|
- **Cast handling** — `(array)`/`(string)` casts propagate taint (a string can
|
|
23
38
|
still carry a payload); `(int)`/`(float)` casts act as an int_cast sanitizer.
|
|
24
|
-
-
|
|
25
|
-
|
|
26
|
-
The conditional-flow merge already models the `is_array` guard correctly, so the
|
|
27
|
-
engine distinguishes the bypassable sanitizer (vulnerable) from the unconditional
|
|
28
|
-
one (fixed). Added two corpus fixtures (`wp-author-not-in-vuln`/`-fixed`).
|
|
29
|
-
|
|
30
|
-
### Not yet detected
|
|
31
|
-
|
|
32
|
-
- **CVE-2026-63030** (REST API batch endpoint route confusion) is a routing /
|
|
33
|
-
authorization-logic bug, not a source→sink taint flow — out of scope for the
|
|
34
|
-
current taint engine.
|
|
39
|
+
- New corpus fixtures covering conditional-sanitizer bypass on array→SQL
|
|
40
|
+
interpolation (`php-int-cast-bypass-vuln`/`-fixed`).
|
|
35
41
|
|
|
36
42
|
## [1.0.59] — 2026-09-13
|
|
37
43
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
- id: php-int-cast-bypass-fixed
|
|
2
|
+
- language: php
|
|
3
|
+
- vulnerable: false
|
|
4
|
+
- sink_type: sql_execution
|
|
5
|
+
```php
|
|
6
|
+
<?php
|
|
7
|
+
class Query {
|
|
8
|
+
public function run($params) {
|
|
9
|
+
$where = "";
|
|
10
|
+
if ( ! empty( $params["ids"] ) ) {
|
|
11
|
+
$ids = implode( ",", array_map( "intval", (array) $params["ids"] ) );
|
|
12
|
+
$where .= " AND id NOT IN ($ids) ";
|
|
13
|
+
}
|
|
14
|
+
return $db->query("SELECT * FROM t WHERE 1=1 $where");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
$q = $_GET;
|
|
18
|
+
$w = new Query();
|
|
19
|
+
$w->run($q);
|
|
20
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
- id: php-int-cast-bypass-vuln
|
|
2
|
+
- language: php
|
|
3
|
+
- vulnerable: true
|
|
4
|
+
- sink_type: sql_execution
|
|
5
|
+
```php
|
|
6
|
+
<?php
|
|
7
|
+
class Query {
|
|
8
|
+
public function run($params) {
|
|
9
|
+
$where = "";
|
|
10
|
+
if ( ! empty( $params["ids"] ) ) {
|
|
11
|
+
if ( is_array( $params["ids"] ) ) {
|
|
12
|
+
$params["ids"] = array_map( "intval", $params["ids"] );
|
|
13
|
+
}
|
|
14
|
+
$ids = implode( ",", (array) $params["ids"] );
|
|
15
|
+
$where .= " AND id NOT IN ($ids) ";
|
|
16
|
+
}
|
|
17
|
+
return $db->query("SELECT * FROM t WHERE 1=1 $where");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
$q = $_GET;
|
|
21
|
+
$w = new Query();
|
|
22
|
+
$w->run($q);
|
|
23
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* REST API batch endpoint (simplified) — forwards to inner handlers.
|
|
4
|
+
*
|
|
5
|
+
* The `/batch` route is registered WITHOUT a permission_callback, so it is
|
|
6
|
+
* reachable unauthenticated. A malformed nested request desynchronizes the
|
|
7
|
+
* batch router, letting an attacker reach handlers they should not.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
add_action('rest_api_init', function () {
|
|
11
|
+
register_rest_route('my/v1', '/batch', array(
|
|
12
|
+
'methods' => 'POST',
|
|
13
|
+
'callback' => 'handle_batch',
|
|
14
|
+
'permission_callback' => '__return_true', // <-- no real authz check
|
|
15
|
+
));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function handle_batch($request) {
|
|
19
|
+
$operations = $request['requests'];
|
|
20
|
+
$responses = array();
|
|
21
|
+
|
|
22
|
+
foreach ($operations as $op) {
|
|
23
|
+
$route = $op['route'];
|
|
24
|
+
$handler = resolve_route($route);
|
|
25
|
+
$responses[] = $handler($op['params']);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return $responses;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolve_route($route) {
|
|
32
|
+
$registry = array(
|
|
33
|
+
'users' => 'get_users',
|
|
34
|
+
'posts' => 'get_posts',
|
|
35
|
+
'options' => 'get_options',
|
|
36
|
+
);
|
|
37
|
+
return $registry[$route];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function get_users($params) {
|
|
41
|
+
global $wpdb;
|
|
42
|
+
return $wpdb->get_results("SELECT * FROM users WHERE id = " . $params['id']);
|
|
43
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -9190,7 +9190,6 @@ var init_dataflow = __esm(() => {
|
|
|
9190
9190
|
escapeshellcmd: { id: "shell_escape", label: "Shell command escape", neutralizes: ["command_execution"] },
|
|
9191
9191
|
intval: { id: "int_cast", label: "Integer cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
|
|
9192
9192
|
absint: { id: "int_cast", label: "Absolute integer cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
|
|
9193
|
-
wp_parse_id_list: { id: "int_cast", label: "WordPress ID list cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
|
|
9194
9193
|
"preg_replace.*FILTER": { id: "regex_filter", label: "Regex filter", neutralizes: ["html_render"] }
|
|
9195
9194
|
};
|
|
9196
9195
|
});
|
|
@@ -37050,6 +37049,151 @@ function reviewValidated(mission) {
|
|
|
37050
37049
|
}
|
|
37051
37050
|
var init_reviewer = () => {};
|
|
37052
37051
|
|
|
37052
|
+
// src/route-confusion.ts
|
|
37053
|
+
function lines(code) {
|
|
37054
|
+
return code.split(`
|
|
37055
|
+
`);
|
|
37056
|
+
}
|
|
37057
|
+
function lineNo(code, pos) {
|
|
37058
|
+
let n = 1;
|
|
37059
|
+
for (let i = 0;i < pos; i++)
|
|
37060
|
+
if (code.charCodeAt(i) === 10)
|
|
37061
|
+
n++;
|
|
37062
|
+
return n;
|
|
37063
|
+
}
|
|
37064
|
+
function userControlled(scope) {
|
|
37065
|
+
return SOURCE_TOKENS.test(scope);
|
|
37066
|
+
}
|
|
37067
|
+
function detectDynamicDispatch(code, file, out) {
|
|
37068
|
+
const ls = lines(code);
|
|
37069
|
+
DYNAMIC_DISPATCH_RE.lastIndex = 0;
|
|
37070
|
+
let m;
|
|
37071
|
+
while ((m = DYNAMIC_DISPATCH_RE.exec(code)) !== null) {
|
|
37072
|
+
const ln = lineNo(code, m.index);
|
|
37073
|
+
const l = ls[ln - 1] ?? "";
|
|
37074
|
+
const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
|
|
37075
|
+
`);
|
|
37076
|
+
if (!userControlled(l) && !userControlled(scope))
|
|
37077
|
+
continue;
|
|
37078
|
+
out.push({
|
|
37079
|
+
file,
|
|
37080
|
+
line: ln,
|
|
37081
|
+
type: "dynamic_dispatch",
|
|
37082
|
+
category: "Route confusion (user-controlled dispatch)",
|
|
37083
|
+
severity: "high",
|
|
37084
|
+
evidence: l.trim(),
|
|
37085
|
+
detail: `\`${m[1]}()\` invoked with an attacker-controlled callback/route — the request selects the handler.`
|
|
37086
|
+
});
|
|
37087
|
+
}
|
|
37088
|
+
}
|
|
37089
|
+
function detectDynamicMethod(code, file, out) {
|
|
37090
|
+
const ls = lines(code);
|
|
37091
|
+
DYNAMIC_METHOD_RE.lastIndex = 0;
|
|
37092
|
+
let m;
|
|
37093
|
+
while ((m = DYNAMIC_METHOD_RE.exec(code)) !== null) {
|
|
37094
|
+
const ln = lineNo(code, m.index);
|
|
37095
|
+
const l = ls[ln - 1] ?? "";
|
|
37096
|
+
const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
|
|
37097
|
+
`);
|
|
37098
|
+
if (!userControlled(l) && !userControlled(scope))
|
|
37099
|
+
continue;
|
|
37100
|
+
out.push({
|
|
37101
|
+
file,
|
|
37102
|
+
line: ln,
|
|
37103
|
+
type: "dynamic_method_call",
|
|
37104
|
+
category: "Route confusion (dynamic method dispatch)",
|
|
37105
|
+
severity: "high",
|
|
37106
|
+
evidence: l.trim(),
|
|
37107
|
+
detail: "Dynamic method/static call `" + m[1] + "$var()` — the invoked symbol is attacker-influenced."
|
|
37108
|
+
});
|
|
37109
|
+
}
|
|
37110
|
+
}
|
|
37111
|
+
function detectDynamicInclude(code, file, out) {
|
|
37112
|
+
const ls = lines(code);
|
|
37113
|
+
const re = /\b(include|require)(_once)?\s*\(\s*(\$[A-Za-z_]\w*)/g;
|
|
37114
|
+
let m;
|
|
37115
|
+
while ((m = re.exec(code)) !== null) {
|
|
37116
|
+
const ln = lineNo(code, m.index);
|
|
37117
|
+
const l = ls[ln - 1] ?? "";
|
|
37118
|
+
const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
|
|
37119
|
+
`);
|
|
37120
|
+
if (!userControlled(l) && !userControlled(scope))
|
|
37121
|
+
continue;
|
|
37122
|
+
out.push({
|
|
37123
|
+
file,
|
|
37124
|
+
line: ln,
|
|
37125
|
+
type: "dynamic_include",
|
|
37126
|
+
category: "Route confusion (dynamic file include)",
|
|
37127
|
+
severity: "high",
|
|
37128
|
+
evidence: l.trim(),
|
|
37129
|
+
detail: `\`${m[1]}(…)\` of a variable — potential LFI/RCE when the path is attacker-controlled.`
|
|
37130
|
+
});
|
|
37131
|
+
}
|
|
37132
|
+
}
|
|
37133
|
+
function detectBatchForwarding(code, file, out) {
|
|
37134
|
+
BATCH_LOOP_RE.lastIndex = 0;
|
|
37135
|
+
let m;
|
|
37136
|
+
while ((m = BATCH_LOOP_RE.exec(code)) !== null) {
|
|
37137
|
+
const ln = lineNo(code, m.index);
|
|
37138
|
+
const body = lines(code).slice(ln, ln + 8).join(`
|
|
37139
|
+
`);
|
|
37140
|
+
const forwards = /\b(call_user_func|forward|dispatch|resolve_route|invoke|handle|route)\s*\(/i.test(body);
|
|
37141
|
+
if (!forwards)
|
|
37142
|
+
continue;
|
|
37143
|
+
const rechecks = /\b(current_user_can|permission_callback|check_ajax_referer|authorize|authorise|is_user_logged_in)\b/i.test(body);
|
|
37144
|
+
out.push({
|
|
37145
|
+
file,
|
|
37146
|
+
line: ln,
|
|
37147
|
+
type: "batch_forwarding",
|
|
37148
|
+
category: "Route confusion (batch/proxy forwarding)",
|
|
37149
|
+
severity: rechecks ? "medium" : "high",
|
|
37150
|
+
evidence: lines(code)[ln - 1]?.trim() ?? "",
|
|
37151
|
+
detail: rechecks ? "Batch/proxy loop forwards to inner handlers — auth is re-checked in scope, but verify it applies per-route." : "Batch/proxy loop forwards to inner handlers with no per-route authorization re-check — an attacker may reach a protected handler."
|
|
37152
|
+
});
|
|
37153
|
+
}
|
|
37154
|
+
}
|
|
37155
|
+
function detectRouteNormalization(code, file, out) {
|
|
37156
|
+
const ls = lines(code);
|
|
37157
|
+
for (let i = 0;i < ls.length; i++) {
|
|
37158
|
+
const l = ls[i];
|
|
37159
|
+
if (!ROUTE_MATCH_RE.test(l))
|
|
37160
|
+
continue;
|
|
37161
|
+
const scope = ls.slice(Math.max(0, i - 3), i + 1).join(`
|
|
37162
|
+
`);
|
|
37163
|
+
if (!userControlled(scope))
|
|
37164
|
+
continue;
|
|
37165
|
+
if (NORMALIZERS.test(l) || NORMALIZERS.test(scope))
|
|
37166
|
+
continue;
|
|
37167
|
+
out.push({
|
|
37168
|
+
file,
|
|
37169
|
+
line: i + 1,
|
|
37170
|
+
type: "route_normalization_gap",
|
|
37171
|
+
category: "Route confusion (missing path normalization)",
|
|
37172
|
+
severity: "medium",
|
|
37173
|
+
evidence: l.trim(),
|
|
37174
|
+
detail: "Route matched against user input without visible normalization (strtolower/rtrim/urldecode) — route desync possible."
|
|
37175
|
+
});
|
|
37176
|
+
}
|
|
37177
|
+
}
|
|
37178
|
+
function detectRouteConfusion(code, file) {
|
|
37179
|
+
const out = [];
|
|
37180
|
+
detectDynamicDispatch(code, file, out);
|
|
37181
|
+
detectDynamicMethod(code, file, out);
|
|
37182
|
+
detectDynamicInclude(code, file, out);
|
|
37183
|
+
detectBatchForwarding(code, file, out);
|
|
37184
|
+
detectRouteNormalization(code, file, out);
|
|
37185
|
+
return out;
|
|
37186
|
+
}
|
|
37187
|
+
var SOURCE_TOKENS, DYNAMIC_DISPATCH_RE, DYNAMIC_METHOD_RE, BATCH_LOOP_RE, ROUTE_MATCH_RE, NORMALIZERS;
|
|
37188
|
+
var init_route_confusion = __esm(() => {
|
|
37189
|
+
SOURCE_TOKENS = /\$_(GET|POST|REQUEST|COOKIE|FILES)\b|\$request->|\$req->|request\.(get|input|query|post)|getParameter\(|req\.(query|body|params)/;
|
|
37190
|
+
DYNAMIC_DISPATCH_RE = /\b(call_user_func|call_user_func_array|forward|dispatch|resolve_route|invoke)\s*\(\s*(\$|\[)/gi;
|
|
37191
|
+
DYNAMIC_METHOD_RE = /(->|::)\s*\$[A-Za-z_]/g;
|
|
37192
|
+
BATCH_LOOP_RE = /\bforeach\s*\([^)]*(requests|batch|sub_requests|operations)\b/gi;
|
|
37193
|
+
ROUTE_MATCH_RE = /\b(preg_match|preg_match_all|strpos|stripos|str_contains)\s*\(/i;
|
|
37194
|
+
NORMALIZERS = /\b(strtolower|strtoupper|rtrim|urldecode|rawurldecode|parse_url|normalize|canonical)\b/i;
|
|
37195
|
+
});
|
|
37196
|
+
|
|
37053
37197
|
// src/agents.ts
|
|
37054
37198
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
37055
37199
|
function emptyGraph() {
|
|
@@ -37073,6 +37217,7 @@ var init_agents = __esm(() => {
|
|
|
37073
37217
|
init_scanner();
|
|
37074
37218
|
init_eagle2();
|
|
37075
37219
|
init_universal_taint();
|
|
37220
|
+
init_route_confusion();
|
|
37076
37221
|
init_adapters();
|
|
37077
37222
|
init_active();
|
|
37078
37223
|
init_strike();
|
|
@@ -37157,6 +37302,29 @@ var init_agents = __esm(() => {
|
|
|
37157
37302
|
graph = addEdge2(graph, tid, sinkId, "flows_to");
|
|
37158
37303
|
}
|
|
37159
37304
|
}
|
|
37305
|
+
for (const f of files.filter((x) => /\.php$/i.test(x))) {
|
|
37306
|
+
let code;
|
|
37307
|
+
try {
|
|
37308
|
+
code = readFileSync18(f, "utf8");
|
|
37309
|
+
} catch {
|
|
37310
|
+
continue;
|
|
37311
|
+
}
|
|
37312
|
+
for (const rc of detectRouteConfusion(code, f).slice(0, 50)) {
|
|
37313
|
+
const hyp = makeHypothesis({
|
|
37314
|
+
type: rc.category,
|
|
37315
|
+
target: `${f}:${rc.line}`,
|
|
37316
|
+
evidence: rc.detail,
|
|
37317
|
+
confidence: rc.severity === "high" ? 0.7 : 0.5,
|
|
37318
|
+
required_validation: "confirm reachability of the dispatch/route with attacker-controlled input",
|
|
37319
|
+
potential_impact: "route confusion / authorization bypass"
|
|
37320
|
+
});
|
|
37321
|
+
hypotheses.push(hyp);
|
|
37322
|
+
findings += 1;
|
|
37323
|
+
const nodeId = `route:${f}:${rc.line}:${rc.type}`;
|
|
37324
|
+
graph = upsertNode(graph, nodeId, "route", { file: f, line: rc.line, type: rc.type, hypothesis: hyp.id });
|
|
37325
|
+
graph = addEdge2(graph, tid, nodeId, "routes_to");
|
|
37326
|
+
}
|
|
37327
|
+
}
|
|
37160
37328
|
return { ok: true, output: { files: files.length, findings }, hypotheses, graph };
|
|
37161
37329
|
}
|
|
37162
37330
|
};
|
|
@@ -38092,6 +38260,13 @@ var init_enterprise_benchmark = __esm(() => {
|
|
|
38092
38260
|
expected_vulns: 2,
|
|
38093
38261
|
expected_sinks: ["sql_execution", "command_execution"],
|
|
38094
38262
|
safe_tokens: ["prepareStatement", "PreparedStatement"]
|
|
38263
|
+
},
|
|
38264
|
+
{
|
|
38265
|
+
id: "wp-batch-route",
|
|
38266
|
+
dir: join17(ROOT10, "wp-batch-route"),
|
|
38267
|
+
expected_vulns: 1,
|
|
38268
|
+
expected_sinks: ["Route confusion"],
|
|
38269
|
+
safe_tokens: []
|
|
38095
38270
|
}
|
|
38096
38271
|
];
|
|
38097
38272
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.62",
|
|
4
4
|
"description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
- id: wp-author-not-in-fixed
|
|
2
|
-
- language: php
|
|
3
|
-
- vulnerable: false
|
|
4
|
-
- sink_type: sql_execution
|
|
5
|
-
```php
|
|
6
|
-
<?php
|
|
7
|
-
class WP_Query {
|
|
8
|
-
public function get_posts($qv) {
|
|
9
|
-
$where = "";
|
|
10
|
-
if ( ! empty( $qv["author__not_in"] ) ) {
|
|
11
|
-
$ids = wp_parse_id_list( $qv["author__not_in"] );
|
|
12
|
-
$author__not_in = implode( ",", $ids );
|
|
13
|
-
$where .= " AND post_author NOT IN ($author__not_in) ";
|
|
14
|
-
}
|
|
15
|
-
return $wpdb->query("SELECT * FROM posts WHERE 1=1 $where");
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
$q = $_GET;
|
|
19
|
-
$w = new WP_Query();
|
|
20
|
-
$w->get_posts($q);
|
|
21
|
-
```
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
- id: wp-author-not-in-vuln
|
|
2
|
-
- language: php
|
|
3
|
-
- vulnerable: true
|
|
4
|
-
- sink_type: sql_execution
|
|
5
|
-
```php
|
|
6
|
-
<?php
|
|
7
|
-
class WP_Query {
|
|
8
|
-
public function get_posts($qv) {
|
|
9
|
-
$where = "";
|
|
10
|
-
if ( ! empty( $qv["author__not_in"] ) ) {
|
|
11
|
-
if ( is_array( $qv["author__not_in"] ) ) {
|
|
12
|
-
$qv["author__not_in"] = array_map( "absint", $qv["author__not_in"] );
|
|
13
|
-
}
|
|
14
|
-
$author__not_in = implode( ",", (array) $qv["author__not_in"] );
|
|
15
|
-
$where .= " AND post_author NOT IN ($author__not_in) ";
|
|
16
|
-
}
|
|
17
|
-
return $wpdb->query("SELECT * FROM posts WHERE 1=1 $where");
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
$q = $_GET;
|
|
21
|
-
$w = new WP_Query();
|
|
22
|
-
$w->get_posts($q);
|
|
23
|
-
```
|