Go Back

0 #include <ngx_config.h>

1 #include <ngx_core.h>

2 #include <ngx_http.h>

3 #include <ngx_crypt.h>

4 #ifdef __linux__

5 #include <sys/random.h>

6 #endif

7 #include "base64.c"

8 #include "sha256.c"

9

10 #define VERIFY_IP /* only allow one ip per challenge */

11 #define WORK 0x00005FFF

12 #define WORK_STR "0x00005FFF"

13

14 #if nginx_version < 1023000

15 #define LEGACY

16 #endif

17

18 #define shield_challenge_str "pow-shield-challenge"

19 ngx_str_t shield_challenge = {

20 sizeof(shield_challenge_str) - 1,

21 (u_char*)shield_challenge_str

22 };

23

24 #define shield_answer_str "pow-shield-answer"

25 ngx_str_t shield_answer = {

26 sizeof(shield_answer_str) - 1,

27 (u_char*)shield_answer_str

28 };

29

30 #define shield_id_str "pow-shield-id"

31 ngx_str_t shield_id = {

32 sizeof(shield_id_str) - 1,

33 (u_char*)shield_id_str

34 };

35

36 const char html_page[] =

37 "<!DOCTYPE html><html><head>"

38 "<meta charset=\"utf-8\"><title>PoW Shield</title>"

39 "</head><body>"

40 "<h1>PoW Shield</h1>"

41 "<noscript><p>Javascript required</p></noscript>"

42 "<p id=\"hash-rate\"></p>"

43 "<script>"

44 "function setCookie(cname, cvalue) {"

45 "document.cookie = cname + \"=\" + cvalue + \";"

46 "path=/;SameSite=Strict\";"

47 "}"

48 "function getCookie(cname) {"

49 "let name = cname + \"=\";"

50 "let ca = document.cookie.split(';');"

51 "for(let i = 0; i < ca.length; i++) {"

52 "let c = ca[i];"

53 "while (c.charAt(0) == ' ') {"

54 "c = c.substring(1);"

55 "}"

56 "if (c.indexOf(name) == 0) {"

57 "return c.substring(name.length, c.length);"

58 "}"

59 "}"

60 "return \"\";"

61 "}"

62 "var challenge = Uint8Array.from(atob(getCookie(\"pow-shield-challenge\")) + "

63 "\"\\0\\0\\0\\0\", c => c.charCodeAt(0));"

64 "var hash = 0;"

65 "var startTime = new Date();"

66 "function updateHashRate() {"

67 "let timeDiff = (new Date() - startTime)/1000;"

68 "document.getElementById(\"hash-rate\").innerHTML =\"Hash-rate : \" +"

69 "String(Math.round(hash/timeDiff)) +\" h/s\";"

70 "}"

71 "async function pow(data, i) {"

72 "let bufView = new Uint32Array(data.buffer);"

73 "bufView[8] = i;"

74 "while (bufView[8] + 1 > -1) {"

75 "let h = new Uint8Array(await "

76 "crypto.subtle.digest(\"SHA-256\", data));"

77 "let view32 = new Uint32Array(h.buffer);"

78 "if (view32[0] <= "WORK_STR") {"

79 "break;"

80 "}"

81 "hash++;"

82 "bufView[8]++;"

83 "}"

84 "setCookie(\"pow-shield-answer\", bufView[8]);"

85 "document.cookie = \"pow-shield-challenge=;"

86 "expires=Thu, 01 Jan 1970 00:00:00 UTC;"

87 "path=/;SameSite=Strict\";"

88 "window.location.reload();"

89 "}"

90 "setInterval(updateHashRate, 1000);"

91 "pow(challenge.slice(0), 0x80000000);"

92 "pow(challenge, 0);"

93 "setTimeout(updateHashRate, 100);"

94 "</script>"

95 "</body></html>"

96 ;

97

98 typedef struct {

99 ngx_http_complex_value_t *realm;

100 } ngx_http_powshield_loc_conf_t;

101

102

103 static ngx_int_t ngx_http_powshield_handler(ngx_http_request_t *r);

104 static void *ngx_http_powshield_create_loc_conf(ngx_conf_t *cf);

105 static char *ngx_http_powshield_merge_loc_conf(ngx_conf_t *cf,

106 void *parent, void *child);

107 static ngx_int_t ngx_http_powshield_init(ngx_conf_t *cf);

108

109 static ngx_command_t ngx_http_powshield_commands[] = {

110

111 { ngx_string("powshield"),

112 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF

113 |NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,

114 ngx_http_set_complex_value_slot,

115 NGX_HTTP_LOC_CONF_OFFSET,

116 offsetof(ngx_http_powshield_loc_conf_t, realm),

117 NULL },

118

119 ngx_null_command

120 };

121

122 static ngx_http_module_t ngx_http_powshield_module_ctx = {

123 NULL, /* preconfiguration */

124 ngx_http_powshield_init, /* postconfiguration */

125 NULL, /* create main configuration */

126 NULL, /* init main configuration */

127 NULL, /* create server configuration */

128 NULL, /* merge server configuration */

129 ngx_http_powshield_create_loc_conf, /* create location configuration */

130 ngx_http_powshield_merge_loc_conf /* merge location configuration */

131 };

132

133

134 ngx_module_t ngx_http_powshield_module = {

135 NGX_MODULE_V1,

136 &ngx_http_powshield_module_ctx, /* module context */

137 ngx_http_powshield_commands, /* module directives */

138 NGX_HTTP_MODULE, /* module type */

139 NULL, /* init master */

140 NULL, /* init module */

141 NULL, /* init process */

142 NULL, /* init thread */

143 NULL, /* exit thread */

144 NULL, /* exit process */

145 NULL, /* exit master */

146 NGX_MODULE_V1_PADDING

147 };

148

149 #define CHALLENGE_DATA_LENGTH 32

150 #define TABLE_SIZE 131072 /* needs to be a power of 2 */

151 #define TABLE_BITS (TABLE_SIZE - 1)

152 #define EXPIRATION 50 /* seconds before expiration */

153 #define EXPIRATION_COMPLETED 600 /* seconds before expiration when completed */

154 #define MAX_USAGE 200 /* usage count before invalidity */

155 struct pow_challenge {

156 ngx_rbtree_node_t rbnode;

157 unsigned char data[CHALLENGE_DATA_LENGTH + sizeof(uint32_t)];

158 uint32_t id;

159 unsigned int used;

160 time_t created;

161 unsigned completed:1;

162 #ifdef VERIFY_IP

163 uint32_t ip;

164 #endif

165 };

166

167 struct pow_tree {

168 ngx_rbtree_t rbtree;

169 ngx_rbtree_node_t sentinel;

170 };

171

172 struct pow_tree* challenges = NULL;

173 ngx_pool_t *cf_pool = NULL;

174

175 uint32_t

176 fnv(void *buf, size_t len)

177 {

178 uint32_t hval = 0;

179 unsigned char *bp = (unsigned char *)buf;

180 unsigned char *be = bp + len;

181 while (bp < be) {

182 hval *= 0x01000193;

183 hval ^= (uint32_t)*bp++;

184 }

185 return hval;

186 }

187 #define FNV(X) fnv(X, sizeof(X))

188

189 uint32_t

190 powshield_get_ip(ngx_http_request_t *r)

191 {

192 struct sockaddr_in *sin;

193 #if (NGX_HAVE_INET6)

194 struct sockaddr_in6 *sin6;

195 #endif

196 switch (r->connection->sockaddr->sa_family) {

197 case AF_INET:

198 sin = (struct sockaddr_in *) r->connection->sockaddr;

199 return sin->sin_addr.s_addr;

200 #if (NGX_HAVE_INET6)

201 case AF_INET6:

202 sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;

203 return FNV(sin6->sin6_addr.s6_addr);

204 #endif

205 }

206 return 0;

207 }

208

209 struct pow_challenge

210 powshield_new_challenge()

211 {

212 struct pow_challenge challenge = {0};

213 challenge.created = time(NULL);

214 #ifdef __linux__

215 if (getrandom(&challenge.id, sizeof(challenge.id), GRND_RANDOM) !=

216 sizeof(challenge.id))

217 challenge.id = rand();

218 if (getrandom(challenge.data, sizeof(challenge.data), GRND_RANDOM) !=

219 sizeof(challenge.data)) {

220 for (size_t i = 0; i < sizeof(challenge.data); i++) {

221 challenge.data[i] = rand();

222 }

223 }

224 #else

225 arc4random_buf(&challenge.id, sizeof(challenge.id));

226 arc4random_buf(challenge.data, sizeof(challenge.data));

227 #endif

228 return challenge;

229 }

230

231 static int

232 powshield_is_expired(struct pow_challenge *challenge, time_t now)

233 {

234 return -(now - challenge->created > (challenge->completed ?

235 EXPIRATION_COMPLETED : EXPIRATION));

236 }

237

238 static int

239 powshield_insert_challenge(struct pow_challenge challenge)

240 {

241 struct pow_challenge *cnode;

242 struct pow_tree *root = &challenges[challenge.id & TABLE_BITS];

243 ngx_rbtree_node_t *node;

244

245 cnode = ngx_palloc(cf_pool, sizeof(challenge));

246 *cnode = challenge;

247

248 node = &cnode->rbnode;

249 node->key = cnode->id;

250

251 ngx_rbtree_insert(&root->rbtree, node);

252 return 0;

253 }

254

255 static struct pow_challenge *

256 powshield_get_challenge(uint32_t id)

257 {

258 struct pow_challenge *n;

259 ngx_rbtree_t *rbtree;

260 ngx_rbtree_node_t *node, *sentinel;

261

262 rbtree = &challenges[id & TABLE_BITS].rbtree;

263 node = rbtree->root;

264 sentinel = rbtree->sentinel;

265

266 while (node != sentinel) {

267

268 n = (struct pow_challenge *) node;

269

270 if (id != (uint32_t)node->key) {

271 node = (id < node->key) ? node->left : node->right;

272 continue;

273 }

274

275 if (powshield_is_expired(n, time(NULL)))

276 return NULL;

277

278 return n;

279 }

280

281 return NULL;

282 }

283

284 static int

285 powshield_use_challenge(struct pow_challenge *challenge)

286 {

287 challenge->used++;

288 if (challenge->used >= MAX_USAGE || time(NULL) - challenge->created >

289 EXPIRATION_COMPLETED) {

290 memset(challenge, 0, sizeof(*challenge));

291 return -1;

292 }

293 return 0;

294 }

295

296 static int

297 powshield_verify_challenge(struct pow_challenge challenge, uint32_t answer)

298 {

299 unsigned char hash[SHA256_BLOCK_SIZE];

300 uint32_t *hash_u32 = (void*)hash;

301 memcpy(&challenge.data[CHALLENGE_DATA_LENGTH],

302 &answer, sizeof(answer));

303 sha256(challenge.data, sizeof(challenge.data), hash);

304 return -(*hash_u32 > WORK);

305 }

306

307 static int

308 powshield_setcookie(ngx_http_request_t *r, const char* name, const char *data,

309 char *buf, size_t buflen)

310 {

311 ngx_table_elt_t *v;

312 const char cookie[] = "%s=%s;path=/;SameSite=Strict";

313 size_t len;

314

315 v = ngx_list_push(&r->headers_out.headers);

316 if (v == NULL) {

317 return -1;

318 }

319 v->hash = rand();

320 v->key.len = sizeof("Set-Cookie") - 1;

321 v->key.data = (u_char *)"Set-Cookie";

322 len = snprintf(buf, buflen, cookie, name, data);

323 v->value.len = len;

324 v->value.data = (u_char *)buf;

325 return 0;

326 }

327

328 static u_char *

329 powshield_getcookie(ngx_http_request_t *r, ngx_str_t *name)

330 {

331 #ifdef LEGACY

332 ngx_str_t value;

333 ngx_int_t n;

334

335 n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, name,

336 &value);

337 if (n != NGX_OK) {

338 return NULL;

339 }

340 return value.data;

341 #else

342 ngx_str_t value;

343 ngx_table_elt_t *elt;

344 elt = ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, name,

345 &value);

346 if (!elt) return NULL;

347 return value.data;

348 #endif

349 }

350

351 static ngx_int_t

352 ngx_http_powshield_handler(ngx_http_request_t *r)

353 {

354 ngx_http_powshield_loc_conf_t *alcf;

355 ngx_str_t realm;

356 ngx_buf_t *b;

357 ngx_chain_t out;

358 char buf[1024], base64[CHALLENGE_DATA_LENGTH * 3 + 64], idbuf[128];

359 const u_char *answer = NULL, *id;

360 uint32_t cid = 0;

361 int len, canswer = 0;

362

363 alcf = ngx_http_get_module_loc_conf(r, ngx_http_powshield_module);

364 if (alcf->realm == NULL) {

365 return NGX_DECLINED;

366 }

367

368 id = powshield_getcookie(r, &shield_id);

369 if (id) {

370 cid = atol((const char*)id);//, NULL, 10);

371 if (!cid) id = NULL;

372 }

373 if (id)

374 answer = powshield_getcookie(r, &shield_answer);

375 if (answer)

376 canswer = atoi((const char *)answer);

377 while (answer) {

378 struct pow_challenge *challenge = powshield_get_challenge(cid);

379 if (!challenge) break;

380 if (challenge->completed) {

381 #ifdef VERIFY_IP

382 if (challenge->ip != powshield_get_ip(r))

383 break;

384 #endif

385 if (powshield_use_challenge(challenge))

386 break;

387 return NGX_DECLINED;

388 }

389 if (powshield_verify_challenge(*challenge, canswer)) break;

390 challenge->completed = 1;

391 return NGX_DECLINED;

392 }

393 if (answer)

394 id = NULL;

395

396 if (ngx_http_complex_value(r, alcf->realm, &realm) != NGX_OK) {

397 return NGX_ERROR;

398 }

399

400 if (realm.len == 3 && ngx_strncmp(realm.data, "off", 3) == 0) {

401 return NGX_DECLINED;

402 }

403

404 if (!id || !powshield_getcookie(r, &shield_challenge)) {

405

406 struct pow_challenge challenge = powshield_new_challenge();

407 char idtmp[32];

408

409 len = base64_encode(challenge.data, CHALLENGE_DATA_LENGTH,

410 (unsigned char*)base64, sizeof(base64));

411 if (len == -1) {

412 return NGX_HTTP_INTERNAL_SERVER_ERROR;

413 }

414 snprintf(idtmp, sizeof(idtmp), "%u", challenge.id);

415 if (powshield_setcookie(r, shield_id_str, idtmp,

416 idbuf, sizeof(idbuf))) {

417 return NGX_HTTP_INTERNAL_SERVER_ERROR;

418 }

419 if (powshield_setcookie(r, shield_challenge_str, base64, buf,

420 sizeof(buf))) {

421 return NGX_HTTP_INTERNAL_SERVER_ERROR;

422 }

423 #ifdef VERIFY_IP

424 challenge.ip = powshield_get_ip(r);

425 #endif

426 powshield_insert_challenge(challenge);

427 }

428

429 r->headers_out.status = NGX_HTTP_OK;

430 r->headers_out.content_length_n = sizeof(html_page) - 1;

431 r->headers_out.content_type.len = sizeof("text/html") - 1;

432 r->headers_out.content_type.data = (u_char *) "text/html";

433

434 ngx_http_send_header(r);

435

436 b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));

437 if (b == NULL) {

438 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,

439 "Failed to allocate response buffer.");

440 return NGX_HTTP_INTERNAL_SERVER_ERROR;

441 }

442

443 b->pos = (u_char*)html_page;

444 b->last = (u_char*)html_page + sizeof(html_page);

445 b->memory = 1; /* content is in read-only memory */

446 b->last_buf = 1; /* there will be no more buffers in the request */

447

448 out.buf = b;

449 out.next = NULL;

450

451 return ngx_http_output_filter(r, &out);

452 }

453

454 static void *

455 ngx_http_powshield_create_loc_conf(ngx_conf_t *cf)

456 {

457 ngx_http_powshield_loc_conf_t *conf;

458

459 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_powshield_loc_conf_t));

460 if (conf == NULL) {

461 return NULL;

462 }

463

464 return conf;

465 }

466

467 static char *

468 ngx_http_powshield_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)

469 {

470 ngx_http_powshield_loc_conf_t *prev = parent;

471 ngx_http_powshield_loc_conf_t *conf = child;

472

473 if (conf->realm == NULL) {

474 conf->realm = prev->realm;

475 }

476

477 return NGX_CONF_OK;

478 }

479

480 static ngx_int_t

481 ngx_http_powshield_init(ngx_conf_t *cf)

482 {

483 ngx_http_handler_pt *h;

484 ngx_http_core_main_conf_t *cmcf;

485

486 cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

487

488 h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);

489 if (h == NULL) {

490 return NGX_ERROR;

491 }

492

493 *h = ngx_http_powshield_handler;

494

495 challenges = ngx_pcalloc(cf->pool, sizeof(*challenges) * TABLE_SIZE);

496 if (!challenges) {

497 return NGX_ERROR;

498 }

499 cf_pool = cf->pool;

500 for (int i = 0; i < TABLE_SIZE; i++) {

501 ngx_rbtree_init(&challenges[i].rbtree, &challenges[i].sentinel,

502 ngx_rbtree_insert_value);

503 }

504

505 return NGX_OK;

506 }

507