mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss] phase 21: Track M.3 — ScheduledJob + GraphQLResolver + WebSocket + Middleware + Migration
This commit is contained in:
parent
00b0fbaea9
commit
f9bd51c024
84 changed files with 5898 additions and 40 deletions
9
tests/dynamic_fixtures/graphql_resolver/apollo/benign.js
Normal file
9
tests/dynamic_fixtures/graphql_resolver/apollo/benign.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Phase 21 — Apollo resolver benign control.
|
||||
const _NYX_ADAPTER_MARKER = "require('@apollo/server')";
|
||||
|
||||
function resolveUser(parent, args, ctx) {
|
||||
const id = String(args.id || '').replace(/[^A-Za-z0-9_-]/g, '');
|
||||
return { id, name: 'user-' + id };
|
||||
}
|
||||
|
||||
module.exports = { resolveUser };
|
||||
14
tests/dynamic_fixtures/graphql_resolver/apollo/vuln.js
Normal file
14
tests/dynamic_fixtures/graphql_resolver/apollo/vuln.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Phase 21 (Track M.3) — Apollo GraphQL resolver vuln fixture.
|
||||
//
|
||||
// `resolveUser(parent, args)` is a resolver from an Apollo schema that
|
||||
// splices `args.id` into a SQL query via raw string concatenation —
|
||||
// classic GraphQL → SQLi shape.
|
||||
const _NYX_ADAPTER_MARKER = "require('@apollo/server')";
|
||||
|
||||
function resolveUser(parent, args, ctx) {
|
||||
// SINK: tainted args.id concatenated into SQL.
|
||||
const query = "SELECT * FROM users WHERE id = '" + args.id + "'";
|
||||
return { id: args.id, name: 'user-' + args.id, _query: query };
|
||||
}
|
||||
|
||||
module.exports = { resolveUser };
|
||||
15
tests/dynamic_fixtures/graphql_resolver/gqlgen/benign.go
Normal file
15
tests/dynamic_fixtures/graphql_resolver/gqlgen/benign.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Phase 21 — gqlgen benign control.
|
||||
package benign
|
||||
|
||||
// import "github.com/99designs/gqlgen/graphql"
|
||||
|
||||
import "regexp"
|
||||
|
||||
var idAllow = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
|
||||
|
||||
func ResolveUser(id string) (string, error) {
|
||||
if !idAllow.MatchString(id) {
|
||||
return "", nil
|
||||
}
|
||||
return "user-" + id, nil
|
||||
}
|
||||
23
tests/dynamic_fixtures/graphql_resolver/gqlgen/vuln.go
Normal file
23
tests/dynamic_fixtures/graphql_resolver/gqlgen/vuln.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Phase 21 (Track M.3) — gqlgen GraphQL resolver vuln fixture.
|
||||
//
|
||||
// `resolveUser(ctx, id)` is a gqlgen resolver (substring marker only —
|
||||
// the real gqlgen runtime is not on the workdir's go.mod). The
|
||||
// resolver splices the id into a shell command via os/exec.
|
||||
package vuln
|
||||
|
||||
// import "github.com/99designs/gqlgen/graphql"
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// type queryResolver struct{}
|
||||
|
||||
func ResolveUser(id string) (string, error) {
|
||||
// SINK: tainted id concatenated into shell command.
|
||||
out, err := exec.Command("/bin/sh", "-c", "echo lookup-"+id).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
"""Phase 21 — Graphene resolver benign control."""
|
||||
import re
|
||||
|
||||
_NYX_ADAPTER_MARKER = "import graphene"
|
||||
|
||||
|
||||
def resolve_user(self, info, id):
|
||||
safe = re.sub(r"[^A-Za-z0-9_-]", "", str(id))
|
||||
return "user-" + safe
|
||||
15
tests/dynamic_fixtures/graphql_resolver/graphene/vuln.py
Normal file
15
tests/dynamic_fixtures/graphql_resolver/graphene/vuln.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
"""Phase 21 (Track M.3) — Graphene resolver vuln fixture.
|
||||
|
||||
`resolve_user(self, info, id)` is a Graphene query resolver that
|
||||
splices the tainted `id` into a shell command via `os.system`.
|
||||
"""
|
||||
import os
|
||||
|
||||
_NYX_ADAPTER_MARKER = "import graphene"
|
||||
_NYX_OBJECT_TYPE_MARKER = "class Query(graphene.ObjectType):"
|
||||
|
||||
|
||||
def resolve_user(self, info, id):
|
||||
# SINK: tainted id concatenated into shell command.
|
||||
os.system("echo lookup-" + str(id))
|
||||
return "user-" + str(id)
|
||||
10
tests/dynamic_fixtures/graphql_resolver/juniper/benign.rs
Normal file
10
tests/dynamic_fixtures/graphql_resolver/juniper/benign.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//! Phase 21 — Juniper resolver benign control.
|
||||
// use juniper::graphql_object;
|
||||
|
||||
pub fn resolve_user(id: &str) -> String {
|
||||
let safe: String = id
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_alphanumeric() || *c == '_' || *c == '-')
|
||||
.collect();
|
||||
format!("user-{}", safe)
|
||||
}
|
||||
15
tests/dynamic_fixtures/graphql_resolver/juniper/vuln.rs
Normal file
15
tests/dynamic_fixtures/graphql_resolver/juniper/vuln.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
//! Phase 21 (Track M.3) — Juniper GraphQL resolver vuln fixture.
|
||||
//!
|
||||
//! `resolve_user(id)` is a Juniper resolver (substring marker only —
|
||||
//! the real `juniper` crate is not on the workdir's Cargo.toml). The
|
||||
//! resolver builds a SQL query via raw string concat — classic
|
||||
//! GraphQL → SQLi shape.
|
||||
|
||||
// use juniper::graphql_object;
|
||||
|
||||
pub fn resolve_user(id: &str) -> String {
|
||||
// SINK: tainted id concatenated into SQL.
|
||||
let query = format!("SELECT * FROM users WHERE id = '{}'", id);
|
||||
let _ = query;
|
||||
format!("user-{}", id)
|
||||
}
|
||||
9
tests/dynamic_fixtures/graphql_resolver/relay/benign.js
Normal file
9
tests/dynamic_fixtures/graphql_resolver/relay/benign.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Phase 21 — graphql-relay benign control.
|
||||
const _NYX_ADAPTER_MARKER = "require('graphql-relay')";
|
||||
|
||||
function resolveNode(parent, args) {
|
||||
const id = String(args.id || '').replace(/[^A-Za-z0-9_-]/g, '');
|
||||
return { id };
|
||||
}
|
||||
|
||||
module.exports = { resolveNode };
|
||||
10
tests/dynamic_fixtures/graphql_resolver/relay/vuln.js
Normal file
10
tests/dynamic_fixtures/graphql_resolver/relay/vuln.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Phase 21 (Track M.3) — graphql-relay vuln fixture.
|
||||
const _NYX_ADAPTER_MARKER = "require('graphql-relay')";
|
||||
|
||||
function resolveNode(parent, args, ctx, info) {
|
||||
// SINK: tainted globalId interpolated into SQL.
|
||||
const sql = "SELECT * FROM nodes WHERE id = '" + args.id + "'";
|
||||
return { id: args.id, _sql: sql };
|
||||
}
|
||||
|
||||
module.exports = { resolveNode };
|
||||
18
tests/dynamic_fixtures/middleware/django/benign.py
Normal file
18
tests/dynamic_fixtures/middleware/django/benign.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
"""Phase 21 — Django middleware benign control."""
|
||||
import os
|
||||
import shlex
|
||||
|
||||
_NYX_ADAPTER_MARKER = "from django.utils.deprecation import MiddlewareMixin"
|
||||
|
||||
|
||||
class AuditMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
os.system("echo " + shlex.quote(str(request.body)))
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
def audit(get_response):
|
||||
return AuditMiddleware(get_response)
|
||||
23
tests/dynamic_fixtures/middleware/django/vuln.py
Normal file
23
tests/dynamic_fixtures/middleware/django/vuln.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""Phase 21 (Track M.3) — Django middleware vuln fixture.
|
||||
|
||||
`AuditMiddleware.__call__(request)` splices `request.body` into a shell
|
||||
command via `os.system`.
|
||||
"""
|
||||
import os
|
||||
|
||||
_NYX_ADAPTER_MARKER = "from django.utils.deprecation import MiddlewareMixin"
|
||||
|
||||
|
||||
class AuditMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
# SINK: tainted request body concatenated into shell command.
|
||||
os.system("echo " + str(request.body))
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
# Module-level alias for the harness to resolve `audit` directly.
|
||||
def audit(get_response):
|
||||
return AuditMiddleware(get_response)
|
||||
11
tests/dynamic_fixtures/middleware/express/benign.js
Normal file
11
tests/dynamic_fixtures/middleware/express/benign.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Phase 21 — Express middleware benign control.
|
||||
const _NYX_ADAPTER_MARKER = "require('express')";
|
||||
|
||||
function audit(req, res, next) {
|
||||
const body = String(req.body || '');
|
||||
if (body.length > 1024) return res.end('too large');
|
||||
if (typeof next === 'function') next();
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
module.exports = { audit };
|
||||
17
tests/dynamic_fixtures/middleware/express/vuln.js
Normal file
17
tests/dynamic_fixtures/middleware/express/vuln.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Phase 21 (Track M.3) — Express middleware vuln fixture.
|
||||
//
|
||||
// `audit(req, res, next)` is mounted via `app.use(audit)`. It splices
|
||||
// the request body into a shell command via `execSync`.
|
||||
const _NYX_ADAPTER_MARKER = "require('express')";
|
||||
const _NYX_REGISTER_MARKER = "app.use(audit)";
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function audit(req, res, next) {
|
||||
// SINK: tainted req.body concatenated into shell command.
|
||||
const out = execSync('echo ' + String(req.body || '')).toString();
|
||||
if (typeof next === 'function') next();
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports = { audit };
|
||||
11
tests/dynamic_fixtures/middleware/laravel/benign.php
Normal file
11
tests/dynamic_fixtures/middleware/laravel/benign.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
// Phase 21 — Laravel middleware benign control.
|
||||
// use Illuminate\\Http\\Request;
|
||||
|
||||
class Audit {
|
||||
public function handle($request, $next) {
|
||||
$body = is_object($request) && isset($request->body) ? (string)$request->body : (string)$request;
|
||||
shell_exec("echo " . escapeshellarg($body));
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
17
tests/dynamic_fixtures/middleware/laravel/vuln.php
Normal file
17
tests/dynamic_fixtures/middleware/laravel/vuln.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
// Phase 21 (Track M.3) — Laravel middleware vuln fixture.
|
||||
//
|
||||
// `Audit::handle($request, $next)` splices `$request->body` into a
|
||||
// shell command via `shell_exec` — classic Laravel middleware cmdi.
|
||||
|
||||
// use Illuminate\\Http\\Request;
|
||||
// function handle($request, Closure $next)
|
||||
|
||||
class Audit {
|
||||
public function handle($request, $next) {
|
||||
$body = is_object($request) && isset($request->body) ? (string)$request->body : (string)$request;
|
||||
// SINK: tainted body concatenated into shell command.
|
||||
shell_exec("echo " . $body);
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
14
tests/dynamic_fixtures/middleware/rails/benign.rb
Normal file
14
tests/dynamic_fixtures/middleware/rails/benign.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Phase 21 — Rack middleware benign control.
|
||||
require 'shellwords'
|
||||
|
||||
class AuditMiddleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
payload = (env['nyx.payload'] || env['QUERY_STRING']).to_s
|
||||
system("echo " + Shellwords.escape(payload))
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
17
tests/dynamic_fixtures/middleware/rails/vuln.rb
Normal file
17
tests/dynamic_fixtures/middleware/rails/vuln.rb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Phase 21 (Track M.3) — Rack/Rails middleware vuln fixture.
|
||||
#
|
||||
# `AuditMiddleware#call(env)` splices `env['nyx.payload']` into a shell
|
||||
# command — classic Rack-middleware cmdi shape.
|
||||
|
||||
class AuditMiddleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
payload = env['nyx.payload'] || env['QUERY_STRING'].to_s
|
||||
# SINK: tainted env value concatenated into shell command.
|
||||
system("echo " + payload.to_s)
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
10
tests/dynamic_fixtures/middleware/spring/Benign.java
Normal file
10
tests/dynamic_fixtures/middleware/spring/Benign.java
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Phase 21 — Spring middleware benign control.
|
||||
// implements HandlerInterceptor
|
||||
|
||||
public class Benign {
|
||||
public boolean preHandle(String payload) {
|
||||
String safe = payload.replaceAll("[^A-Za-z0-9 _.-]", "_");
|
||||
System.out.println("intercepted: " + safe);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
16
tests/dynamic_fixtures/middleware/spring/Vuln.java
Normal file
16
tests/dynamic_fixtures/middleware/spring/Vuln.java
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Phase 21 (Track M.3) — Spring HandlerInterceptor middleware vuln
|
||||
// fixture.
|
||||
//
|
||||
// `Vuln#preHandle` splices the request body into a shell command via
|
||||
// Runtime.exec. HandlerInterceptor is referenced as a substring
|
||||
// marker only.
|
||||
//
|
||||
// implements HandlerInterceptor
|
||||
|
||||
public class Vuln {
|
||||
public boolean preHandle(String payload) throws Exception {
|
||||
// SINK: tainted payload concatenated into shell command.
|
||||
Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", "echo " + payload });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
11
tests/dynamic_fixtures/migration/django/benign.py
Normal file
11
tests/dynamic_fixtures/migration/django/benign.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
"""Phase 21 — Django migration benign control."""
|
||||
_NYX_ADAPTER_MARKER = "from django.db import migrations"
|
||||
|
||||
|
||||
def upgrade(table_name="users"):
|
||||
safe = "".join(c for c in str(table_name) if c.isalnum() or c == "_")
|
||||
return "CREATE INDEX idx_" + safe + " ON users(name)"
|
||||
|
||||
|
||||
class Migration:
|
||||
operations = []
|
||||
23
tests/dynamic_fixtures/migration/django/vuln.py
Normal file
23
tests/dynamic_fixtures/migration/django/vuln.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
"""Phase 21 (Track M.3) — Django migration vuln fixture.
|
||||
|
||||
The migration declares `operations = [...]` with a
|
||||
`migrations.RunSQL` op whose statement is built from an external
|
||||
table name via raw string concatenation.
|
||||
"""
|
||||
_NYX_ADAPTER_MARKER = "from django.db import migrations"
|
||||
|
||||
|
||||
class _RunSQL:
|
||||
def __init__(self, sql):
|
||||
self.sql = sql
|
||||
|
||||
|
||||
def upgrade(table_name="users"):
|
||||
# SINK: tainted table name spliced into raw DDL.
|
||||
sql = "CREATE INDEX idx_" + str(table_name) + " ON users(name)"
|
||||
op = _RunSQL(sql)
|
||||
return op
|
||||
|
||||
|
||||
class Migration:
|
||||
operations = []
|
||||
8
tests/dynamic_fixtures/migration/flask/benign.py
Normal file
8
tests/dynamic_fixtures/migration/flask/benign.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
"""Phase 21 — Alembic benign control."""
|
||||
_NYX_ADAPTER_MARKER = "from alembic import op"
|
||||
revision = "deadbeef0001"
|
||||
|
||||
|
||||
def upgrade(column_name="email"):
|
||||
safe = "".join(c for c in str(column_name) if c.isalnum() or c == "_")
|
||||
return "ALTER TABLE users ADD COLUMN " + safe + " TEXT"
|
||||
22
tests/dynamic_fixtures/migration/flask/vuln.py
Normal file
22
tests/dynamic_fixtures/migration/flask/vuln.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"""Phase 21 (Track M.3) — Flask-Migrate / Alembic migration vuln.
|
||||
|
||||
Alembic revisions declare an `upgrade()` function that issues DDL
|
||||
through `op.execute(...)`. The vuln fixture splices a tainted column
|
||||
name into the statement via raw string concat.
|
||||
"""
|
||||
_NYX_ADAPTER_MARKER = "from alembic import op"
|
||||
revision = "abc123def4"
|
||||
down_revision = None
|
||||
|
||||
|
||||
class _Op:
|
||||
def execute(self, sql):
|
||||
print("ALEMBIC_SQL:", sql)
|
||||
|
||||
|
||||
op = _Op()
|
||||
|
||||
|
||||
def upgrade(column_name="email"):
|
||||
# SINK: tainted column name spliced into raw DDL.
|
||||
op.execute("ALTER TABLE users ADD COLUMN " + str(column_name) + " TEXT")
|
||||
13
tests/dynamic_fixtures/migration/laravel/benign.php
Normal file
13
tests/dynamic_fixtures/migration/laravel/benign.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
// Phase 21 — Laravel migration benign control.
|
||||
// use Illuminate\\Database\\Migrations\\Migration;
|
||||
|
||||
class AddUsers {
|
||||
public function up() {
|
||||
$col = getenv('NYX_PAYLOAD') ?: 'email';
|
||||
$safe = preg_replace('/[^A-Za-z0-9_]/', '_', $col);
|
||||
$stmt = "ALTER TABLE users ADD COLUMN " . $safe . " TEXT";
|
||||
echo "LARAVEL_SQL: " . $stmt . "\n";
|
||||
return $stmt;
|
||||
}
|
||||
}
|
||||
25
tests/dynamic_fixtures/migration/laravel/vuln.php
Normal file
25
tests/dynamic_fixtures/migration/laravel/vuln.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
// Phase 21 (Track M.3) — Laravel migration vuln fixture.
|
||||
//
|
||||
// `AddUsers::up()` invokes `Schema::table` via a class-static
|
||||
// fallthrough but splices a tainted column name into a raw
|
||||
// `DB::statement` call.
|
||||
|
||||
// use Illuminate\\Database\\Migrations\\Migration;
|
||||
// use Illuminate\\Database\\Schema;
|
||||
|
||||
class AddUsers {
|
||||
public function up() {
|
||||
$col = getenv('NYX_PAYLOAD') ?: 'email';
|
||||
// SINK: tainted column name concatenated into raw DDL.
|
||||
$stmt = "ALTER TABLE users ADD COLUMN " . $col . " TEXT";
|
||||
DBStatementWrapper::statement($stmt);
|
||||
return $stmt;
|
||||
}
|
||||
}
|
||||
|
||||
class DBStatementWrapper {
|
||||
public static function statement($sql) {
|
||||
echo "LARAVEL_SQL: " . $sql . "\n";
|
||||
}
|
||||
}
|
||||
10
tests/dynamic_fixtures/migration/prisma/benign.js
Normal file
10
tests/dynamic_fixtures/migration/prisma/benign.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Phase 21 — Prisma migration benign control.
|
||||
const _NYX_ADAPTER_MARKER = "require('@prisma/client')";
|
||||
|
||||
async function up(name) {
|
||||
const safe = String(name || process.env.NYX_PAYLOAD || 'users').replace(/[^A-Za-z0-9_]/g, '_');
|
||||
const prisma = global.__nyx_prisma || { $executeRawUnsafe: async (s) => s };
|
||||
return prisma.$executeRawUnsafe('CREATE INDEX idx_' + safe + ' ON users(name)');
|
||||
}
|
||||
|
||||
module.exports = { up };
|
||||
17
tests/dynamic_fixtures/migration/prisma/vuln.js
Normal file
17
tests/dynamic_fixtures/migration/prisma/vuln.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Phase 21 (Track M.3) — Prisma migration vuln fixture.
|
||||
//
|
||||
// `up(name)` runs a raw DDL through `prisma.$executeRawUnsafe` —
|
||||
// classic Prisma migration SQLi shape.
|
||||
const _NYX_ADAPTER_MARKER = "require('@prisma/client')";
|
||||
|
||||
async function up(name) {
|
||||
const target = name || process.env.NYX_PAYLOAD || 'users';
|
||||
// The harness supplies a stubbed `prisma` shim via the synthetic
|
||||
// migration entry path; we route through a module-level stub so the
|
||||
// sink callee is statically present.
|
||||
const prisma = global.__nyx_prisma || { $executeRawUnsafe: async (s) => s };
|
||||
// SINK: tainted table name concatenated into raw DDL.
|
||||
return prisma.$executeRawUnsafe('CREATE INDEX idx_' + target + ' ON users(name)');
|
||||
}
|
||||
|
||||
module.exports = { up };
|
||||
12
tests/dynamic_fixtures/migration/rails/benign.rb
Normal file
12
tests/dynamic_fixtures/migration/rails/benign.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Phase 21 — Rails migration benign control.
|
||||
# class AddIndex < ActiveRecord::Migration[7.0]
|
||||
|
||||
class AddIndex
|
||||
def up
|
||||
add_column :users, :name, :string
|
||||
end
|
||||
|
||||
def add_column(table, name, type)
|
||||
puts "MIGRATION_ADD_COLUMN: #{table}.#{name} :: #{type}"
|
||||
end
|
||||
end
|
||||
23
tests/dynamic_fixtures/migration/rails/vuln.rb
Normal file
23
tests/dynamic_fixtures/migration/rails/vuln.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Phase 21 (Track M.3) — Rails ActiveRecord migration vuln fixture.
|
||||
#
|
||||
# `AddIndex#up` invokes `execute(...)` with a raw, attacker-controlled
|
||||
# table name concatenated into DDL — classic Rails migration SQLi.
|
||||
|
||||
# class AddIndex < ActiveRecord::Migration[7.0]
|
||||
|
||||
class AddIndex
|
||||
attr_accessor :table_name
|
||||
|
||||
def up
|
||||
name = @table_name || ENV['NYX_PAYLOAD'].to_s
|
||||
# SINK: tainted table name spliced into raw DDL.
|
||||
execute("CREATE INDEX idx_#{name} ON users(name)")
|
||||
end
|
||||
|
||||
def execute(sql)
|
||||
# The harness only asserts that execute() is invoked with the
|
||||
# tainted SQL string. A real ActiveRecord::Base.connection would
|
||||
# forward to the DB driver.
|
||||
puts "MIGRATION_SQL: #{sql}"
|
||||
end
|
||||
end
|
||||
12
tests/dynamic_fixtures/migration/sequelize/benign.js
Normal file
12
tests/dynamic_fixtures/migration/sequelize/benign.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Phase 21 — Sequelize benign control.
|
||||
const _NYX_ADAPTER_MARKER = "queryInterface.createTable";
|
||||
|
||||
module.exports.up = async function (queryInterface, Sequelize) {
|
||||
const name = (process.env.NYX_PAYLOAD || 'users').replace(/[^A-Za-z0-9_]/g, '_');
|
||||
if (queryInterface && typeof queryInterface.addColumn === 'function') {
|
||||
await queryInterface.addColumn(name, 'description', { type: 'TEXT' });
|
||||
}
|
||||
return 'addColumn(' + name + ')';
|
||||
};
|
||||
|
||||
module.exports.down = async function () { return 'noop'; };
|
||||
21
tests/dynamic_fixtures/migration/sequelize/vuln.js
Normal file
21
tests/dynamic_fixtures/migration/sequelize/vuln.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Phase 21 (Track M.3) — Sequelize migration vuln fixture.
|
||||
//
|
||||
// `up(queryInterface, Sequelize)` is the canonical migration entry
|
||||
// point. This fixture builds a raw DDL string from an attacker-
|
||||
// controlled table name and routes it through `queryInterface.sequelize.query`.
|
||||
const _NYX_ADAPTER_MARKER = "queryInterface.createTable";
|
||||
|
||||
module.exports.up = async function (queryInterface, Sequelize) {
|
||||
const name = process.env.NYX_PAYLOAD || 'users';
|
||||
// SINK: tainted table name concatenated into raw DDL.
|
||||
const sql = 'CREATE INDEX idx_' + name + ' ON users(name)';
|
||||
if (queryInterface && queryInterface.sequelize && queryInterface.sequelize.query) {
|
||||
await queryInterface.sequelize.query(sql);
|
||||
}
|
||||
return sql;
|
||||
};
|
||||
|
||||
module.exports.down = async function (queryInterface, Sequelize) {
|
||||
// benign in the down direction.
|
||||
return 'DROP INDEX idx_users';
|
||||
};
|
||||
9
tests/dynamic_fixtures/scheduled_job/celery/benign.py
Normal file
9
tests/dynamic_fixtures/scheduled_job/celery/benign.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
"""Phase 21 — Celery scheduled-task benign control."""
|
||||
import os
|
||||
import shlex
|
||||
|
||||
_NYX_ADAPTER_MARKER = "from celery import shared_task"
|
||||
|
||||
|
||||
def tick(payload):
|
||||
os.system("echo " + shlex.quote(str(payload)))
|
||||
15
tests/dynamic_fixtures/scheduled_job/celery/vuln.py
Normal file
15
tests/dynamic_fixtures/scheduled_job/celery/vuln.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
"""Phase 21 (Track M.3) — Celery scheduled-task vuln fixture.
|
||||
|
||||
`tick(payload)` is a Celery task that splices the payload bytes into a
|
||||
shell command via `os.system`. An attacker who can enqueue a task with
|
||||
arbitrary bytes can inject shell metacharacters.
|
||||
"""
|
||||
import os
|
||||
|
||||
_NYX_ADAPTER_MARKER = "from celery import shared_task"
|
||||
_NYX_DECORATOR_MARKER = "@shared_task"
|
||||
|
||||
|
||||
def tick(payload):
|
||||
# SINK: tainted payload concatenated into shell command.
|
||||
os.system("echo " + str(payload))
|
||||
9
tests/dynamic_fixtures/scheduled_job/cron/benign.js
Normal file
9
tests/dynamic_fixtures/scheduled_job/cron/benign.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Phase 21 — node-cron benign control.
|
||||
const _NYX_ADAPTER_MARKER = "require('node-cron')";
|
||||
const _NYX_SCHEDULE_MARKER = "cron.schedule('*/5 * * * *', tick)";
|
||||
|
||||
function tick(payload) {
|
||||
return 'tick: ' + JSON.stringify(payload);
|
||||
}
|
||||
|
||||
module.exports = { tick };
|
||||
17
tests/dynamic_fixtures/scheduled_job/cron/vuln.js
Normal file
17
tests/dynamic_fixtures/scheduled_job/cron/vuln.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Phase 21 (Track M.3) — node-cron scheduled-job vuln fixture.
|
||||
//
|
||||
// `tick(payload)` is a job registered with `cron.schedule(...)` that
|
||||
// splices the payload into a child-process command. An attacker who
|
||||
// can stage payload bytes into the job's input source can inject
|
||||
// shell metacharacters.
|
||||
const _NYX_ADAPTER_MARKER = "require('node-cron')";
|
||||
const _NYX_SCHEDULE_MARKER = "cron.schedule('*/5 * * * *', tick)";
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function tick(payload) {
|
||||
// SINK: tainted payload concatenated into shell command.
|
||||
return execSync('echo ' + String(payload)).toString();
|
||||
}
|
||||
|
||||
module.exports = { tick };
|
||||
8
tests/dynamic_fixtures/scheduled_job/quartz/Benign.java
Normal file
8
tests/dynamic_fixtures/scheduled_job/quartz/Benign.java
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Phase 21 — Quartz benign control.
|
||||
// org.quartz.Job marker (substring scan only).
|
||||
|
||||
public class Benign {
|
||||
public void execute(String payload) {
|
||||
System.out.println("scheduled: " + payload.replaceAll("[^A-Za-z0-9 _.-]", "_"));
|
||||
}
|
||||
}
|
||||
16
tests/dynamic_fixtures/scheduled_job/quartz/Vuln.java
Normal file
16
tests/dynamic_fixtures/scheduled_job/quartz/Vuln.java
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Phase 21 (Track M.3) — Quartz scheduled-job vuln fixture.
|
||||
//
|
||||
// `Vuln` implements the Quartz `Job` interface (substring-marker only
|
||||
// — the real `org.quartz.Job` symbol is not on the JDK classpath).
|
||||
// `execute(JobExecutionContext)` splices the payload into a shell
|
||||
// command via `Runtime.exec`, the classic Quartz job cmdi shape.
|
||||
|
||||
// org.quartz.Job marker (substring scan only — not a real import).
|
||||
// @DisallowConcurrentExecution
|
||||
|
||||
public class Vuln {
|
||||
public void execute(String payload) throws Exception {
|
||||
// SINK: tainted payload concatenated into shell command.
|
||||
Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", "echo " + payload });
|
||||
}
|
||||
}
|
||||
10
tests/dynamic_fixtures/scheduled_job/sidekiq/benign.rb
Normal file
10
tests/dynamic_fixtures/scheduled_job/sidekiq/benign.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Phase 21 — Sidekiq benign control.
|
||||
# include Sidekiq::Worker
|
||||
|
||||
require 'shellwords'
|
||||
|
||||
class TickWorker
|
||||
def perform(payload)
|
||||
system("echo " + Shellwords.escape(payload.to_s))
|
||||
end
|
||||
end
|
||||
20
tests/dynamic_fixtures/scheduled_job/sidekiq/vuln.rb
Normal file
20
tests/dynamic_fixtures/scheduled_job/sidekiq/vuln.rb
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Phase 21 (Track M.3) — Sidekiq scheduled-job vuln fixture.
|
||||
#
|
||||
# `TickWorker` includes the Sidekiq::Worker mixin (substring marker
|
||||
# only — the real Sidekiq gem is not loaded). `perform(payload)`
|
||||
# splices the payload into a shell command via Kernel#system, the
|
||||
# classic worker cmdi shape.
|
||||
|
||||
# include Sidekiq::Worker
|
||||
# sidekiq_options queue: :default
|
||||
|
||||
class TickWorker
|
||||
def self.included_modules
|
||||
[:'Sidekiq::Worker']
|
||||
end
|
||||
|
||||
def perform(payload)
|
||||
# SINK: tainted payload concatenated into shell command.
|
||||
system("echo " + payload.to_s)
|
||||
end
|
||||
end
|
||||
9
tests/dynamic_fixtures/websocket/actioncable/benign.rb
Normal file
9
tests/dynamic_fixtures/websocket/actioncable/benign.rb
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Phase 21 — ActionCable benign control.
|
||||
# class ChatChannel < ApplicationCable::Channel
|
||||
require 'shellwords'
|
||||
|
||||
class ChatChannel
|
||||
def receive(data)
|
||||
system("echo " + Shellwords.escape(data.to_s))
|
||||
end
|
||||
end
|
||||
14
tests/dynamic_fixtures/websocket/actioncable/vuln.rb
Normal file
14
tests/dynamic_fixtures/websocket/actioncable/vuln.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Phase 21 (Track M.3) — Rails ActionCable channel vuln fixture.
|
||||
#
|
||||
# `ChatChannel#receive(data)` splices the inbound WebSocket message
|
||||
# bytes into a shell command via Kernel#system — classic ActionCable
|
||||
# → cmdi shape.
|
||||
|
||||
# class ChatChannel < ApplicationCable::Channel
|
||||
|
||||
class ChatChannel
|
||||
def receive(data)
|
||||
# SINK: tainted data concatenated into shell command.
|
||||
system("echo " + data.to_s)
|
||||
end
|
||||
end
|
||||
15
tests/dynamic_fixtures/websocket/channels/benign.py
Normal file
15
tests/dynamic_fixtures/websocket/channels/benign.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
"""Phase 21 — Django Channels benign control."""
|
||||
import os
|
||||
import shlex
|
||||
|
||||
_NYX_ADAPTER_MARKER = "from channels.generic.websocket import WebsocketConsumer"
|
||||
|
||||
|
||||
class ChatConsumer:
|
||||
def receive(self, text_data=None, bytes_data=None):
|
||||
payload = text_data if text_data is not None else (bytes_data or b"").decode("utf-8", "replace")
|
||||
os.system("echo " + shlex.quote(str(payload)))
|
||||
|
||||
|
||||
def receive(text_data=None, bytes_data=None):
|
||||
return ChatConsumer().receive(text_data, bytes_data)
|
||||
20
tests/dynamic_fixtures/websocket/channels/vuln.py
Normal file
20
tests/dynamic_fixtures/websocket/channels/vuln.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
"""Phase 21 (Track M.3) — Django Channels WebsocketConsumer vuln fixture.
|
||||
|
||||
`ChatConsumer.receive(text_data=None, bytes_data=None)` splices the
|
||||
inbound frame into a shell command via `os.system`.
|
||||
"""
|
||||
import os
|
||||
|
||||
_NYX_ADAPTER_MARKER = "from channels.generic.websocket import WebsocketConsumer"
|
||||
|
||||
|
||||
class ChatConsumer:
|
||||
def receive(self, text_data=None, bytes_data=None):
|
||||
payload = text_data if text_data is not None else (bytes_data or b"").decode("utf-8", "replace")
|
||||
# SINK: tainted frame body concatenated into shell command.
|
||||
os.system("echo " + str(payload))
|
||||
|
||||
|
||||
# Module-level alias for the harness to resolve `receive` directly.
|
||||
def receive(text_data=None, bytes_data=None):
|
||||
return ChatConsumer().receive(text_data, bytes_data)
|
||||
9
tests/dynamic_fixtures/websocket/socketio/benign.py
Normal file
9
tests/dynamic_fixtures/websocket/socketio/benign.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
"""Phase 21 — python-socketio benign control."""
|
||||
import os
|
||||
import shlex
|
||||
|
||||
_NYX_ADAPTER_MARKER = "import socketio"
|
||||
|
||||
|
||||
def message(sid, data):
|
||||
os.system("echo " + shlex.quote(str(data)))
|
||||
14
tests/dynamic_fixtures/websocket/socketio/vuln.py
Normal file
14
tests/dynamic_fixtures/websocket/socketio/vuln.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""Phase 21 (Track M.3) — python-socketio handler vuln fixture.
|
||||
|
||||
`message(sid, data)` is a Socket.IO event handler. It splices the
|
||||
inbound message into a shell command via `os.system`.
|
||||
"""
|
||||
import os
|
||||
|
||||
_NYX_ADAPTER_MARKER = "import socketio"
|
||||
_NYX_EVENT_MARKER = "@sio.on('message')"
|
||||
|
||||
|
||||
def message(sid, data):
|
||||
# SINK: tainted message body concatenated into shell command.
|
||||
os.system("echo " + str(data))
|
||||
8
tests/dynamic_fixtures/websocket/ws/benign.js
Normal file
8
tests/dynamic_fixtures/websocket/ws/benign.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Phase 21 — `ws` WebSocket benign control.
|
||||
const _NYX_ADAPTER_MARKER = "require('ws')";
|
||||
|
||||
function onMessage(data) {
|
||||
return 'echoed: ' + JSON.stringify(String(data));
|
||||
}
|
||||
|
||||
module.exports = { onMessage };
|
||||
15
tests/dynamic_fixtures/websocket/ws/vuln.js
Normal file
15
tests/dynamic_fixtures/websocket/ws/vuln.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Phase 21 (Track M.3) — `ws` WebSocket handler vuln fixture.
|
||||
//
|
||||
// `onMessage(data)` is the `on('message', ...)` listener on a
|
||||
// WebSocketServer instance. It splices the message bytes into a
|
||||
// child-process command — classic WS → cmdi shape.
|
||||
const _NYX_ADAPTER_MARKER = "require('ws')";
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function onMessage(data) {
|
||||
// SINK: tainted message body concatenated into shell command.
|
||||
return execSync('echo ' + String(data)).toString();
|
||||
}
|
||||
|
||||
module.exports = { onMessage };
|
||||
1019
tests/phase21_corpus.rs
Normal file
1019
tests/phase21_corpus.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue