Skip to content

Commit 2263b1c

Browse files
authored
Merge pull request juice-shop#1299 from Scar26/metrics-challenge
Add exposed metrics challenge
2 parents 3a61841 + 3a87c66 commit 2263b1c

File tree

8 files changed

+115
-0
lines changed

8 files changed

+115
-0
lines changed

config.schema.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,8 @@ ctf:
598598
type: string
599599
code:
600600
type: string
601+
exposedMetricsChallenge:
602+
name:
603+
type: string
604+
code:
605+
type: string

config/fbctf.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,6 @@ ctf:
283283
svgInjectionChallenge:
284284
name: Tunisia
285285
code: TN
286+
exposedMetricsChallenge:
287+
name: Japan
288+
code: JP

data/static/challenges.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,3 +763,11 @@
763763
hint: 'This challenge would formally have to be in several categories as the developers made multiple gaffes for this to be possible.'
764764
hintUrl: 'https://pwning.owasp-juice.shop/part2/injection.html#stick-cute-cross-domain-kittens-all-over-our-delivery-boxes'
765765
key: svgInjectionChallenge
766+
-
767+
name: 'Exposed Metrics'
768+
category: 'Sensitive Data Exposure'
769+
description: 'Find the endpoint that serves usage data to be scraped by a <a href="https://github.com/prometheus/prometheus">popular monitoring system</a>.'
770+
difficulty: 1
771+
hint: 'Try to guess what URL the endpoint might have.'
772+
hintUrl: ''
773+
key: exposedMetricsChallenge

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"otplib": "^11.0.1",
121121
"pdfkit": "^0.11.0",
122122
"portscanner": "^2.2.0",
123+
"prom-client": "^11.5.3",
123124
"pug": "^2.0.4",
124125
"replace": "^1.1.3",
125126
"request": "^2.88.0",

routes/metrics.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2014-2020 Bjoern Kimminich.
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
const Prometheus = require('prom-client')
7+
const orders = require('../data/mongodb').orders
8+
const challenges = require('../data/datacache').challenges
9+
const users = require('../data/datacache').users
10+
const utils = require('../lib/utils')
11+
12+
exports.serveMetrics = function serveMetrics (reg) {
13+
return (req, res, next) => {
14+
utils.solveIf(challenges.exposedMetricsChallenge, () => (true))
15+
res.set('Content-Type', reg.contentType)
16+
res.end(reg.metrics())
17+
}
18+
}
19+
20+
exports.observeMetrics = function observeMetrics () {
21+
const register = new Prometheus.Registry()
22+
const intervalCollector = Prometheus.collectDefaultMetrics({ timeout: 5000, register })
23+
register.setDefaultLabels({ app: 'juice-shop' })
24+
25+
const orderMetrics = new Prometheus.Gauge({
26+
name: 'juice_shop_orders_placed_total',
27+
help: 'Number of orders placed in juice-shop so far'
28+
})
29+
30+
const challengeMetrics = new Prometheus.Gauge({
31+
name: 'juice_shop_challenges_solved_total',
32+
help: 'Number of challenges that have been solved so far'
33+
})
34+
35+
const userMetrics = new Prometheus.Gauge({
36+
name: 'juice_shop_users_registered_total',
37+
help: 'Number of users registered'
38+
})
39+
40+
register.registerMetric(orderMetrics)
41+
register.registerMetric(challengeMetrics)
42+
register.registerMetric(userMetrics)
43+
44+
const updateLoop = setInterval(() => {
45+
orders.count({}).then(function (orders) {
46+
orderMetrics.set(orders)
47+
})
48+
challengeMetrics.set(Object.keys(challenges).filter((key) => (challenges[key].solved)).length)
49+
userMetrics.set(Object.keys(users).length)
50+
}, 5000)
51+
52+
return {
53+
register: register,
54+
probe: intervalCollector,
55+
updateLoop: updateLoop
56+
}
57+
}

server.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const restoreProgress = require('./routes/restoreProgress')
4343
const fileServer = require('./routes/fileServer')
4444
const keyServer = require('./routes/keyServer')
4545
const logFileServer = require('./routes/logfileServer')
46+
const metrics = require('./routes/metrics')
4647
const authenticatedUsers = require('./routes/authenticatedUsers')
4748
const currentUser = require('./routes/currentUser')
4849
const login = require('./routes/login')
@@ -341,6 +342,11 @@ app.post('/rest/2fa/disable',
341342
insecurity.isAuthorized(),
342343
twoFactorAuth.disable()
343344
)
345+
/* Serve metrics */
346+
const Metrics = metrics.observeMetrics()
347+
const metricsRegister = Metrics.register
348+
const metricsUpdateLoop = Metrics.updateLoop
349+
app.get('/metrics', metrics.serveMetrics(metricsRegister))
344350

345351
/* Verifying DB related challenges can be postponed until the next request for challenges is coming via finale */
346352
app.use(verify.databaseRelatedChallenges())
@@ -532,6 +538,7 @@ exports.start = async function (readyCallback) {
532538

533539
exports.close = function (exitCode) {
534540
if (server) {
541+
clearInterval(metricsUpdateLoop)
535542
server.close()
536543
}
537544
if (exitCode !== undefined) {

test/api/metricsAPISpec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (c) 2014-2020 Bjoern Kimminich.
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
const frisby = require('frisby')
7+
const METRICS_URL = 'http://localhost:3000/metrics/'
8+
9+
describe('Metrics API', () => {
10+
it('check if /metrics endpoint is accessible', () => {
11+
return frisby.get(METRICS_URL)
12+
.expect('status', 200)
13+
.expect('header', 'content-type', /text\/plain/)
14+
.expect('bodyContains', /^juice_shop_users_registered_total{app="juice-shop"} [0-9]*$/gm)
15+
.expect('bodyContains', /^juice_shop_challenges_solved_total{app="juice-shop"} [0-9]*$/gm)
16+
.expect('bodyContains', /^juice_shop_orders_placed_total{app="juice-shop"} [0-9]*$/gm)
17+
})
18+
})

test/e2e/metricsSpec.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2014-2020 Bjoern Kimminich.
3+
* SPDX-License-Identifier: MIT
4+
*/
5+
6+
describe('/metrics/', () => {
7+
describe('challenge "exposedMetrics"', () => {
8+
it('Challenge is solved on accessing the /metrics route', () => {
9+
browser.waitForAngularEnabled(false)
10+
browser.get('/metrics')
11+
browser.waitForAngularEnabled(true)
12+
})
13+
14+
protractor.expect.challengeSolved({ challenge: 'Exposed Metrics' })
15+
})
16+
})

0 commit comments

Comments
 (0)