diff --git a/cachefiles/4455/1111/19c2433941401f3fcaaf8c5384acfef0ac7fa2fa71482025e7afc68eb261fb45.bin b/cachefiles/4455/1111/19c2433941401f3fcaaf8c5384acfef0ac7fa2fa71482025e7afc68eb261fb45.bin new file mode 100644 index 0000000..c2d7996 --- /dev/null +++ b/cachefiles/4455/1111/19c2433941401f3fcaaf8c5384acfef0ac7fa2fa71482025e7afc68eb261fb45.bin @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/cachefiles/4455/1111/b7b0d7924fb687b0f22da3473d5bdae60af3d44870263da137aefad91c96e0a8.bin b/cachefiles/4455/1111/b7b0d7924fb687b0f22da3473d5bdae60af3d44870263da137aefad91c96e0a8.bin new file mode 100644 index 0000000..c2d7996 --- /dev/null +++ b/cachefiles/4455/1111/b7b0d7924fb687b0f22da3473d5bdae60af3d44870263da137aefad91c96e0a8.bin @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/endPoint/h2HttpHostEndPoint.js b/endPoint/h2HttpHostEndPoint.js index f58e961..61af0ba 100644 --- a/endPoint/h2HttpHostEndPoint.js +++ b/endPoint/h2HttpHostEndPoint.js @@ -36,7 +36,14 @@ export default class H2HttpHostEndPoint extends SecureHttpHostEndPoint { this.#options.minVersion = "TLSv1.2"; this.#options.allowHTTP1 = true; } - + _sqlInjectionMiddleware = (stream, headers) => { + const reqUrl = headers[http2.constants.HTTP2_HEADER_PATH] || ''; + const host = headers[http2.constants.HTTP2_HEADER_AUTHORITY] || 'localhost'; + const url = new URL(reqUrl, `https://${host}`); + const queryString = this._decodeURIComponentSafe(url.search); + const sqlInjectionPattern = /(?:\b(SELECT|INSERT|DELETE|UPDATE|WHERE|DROP|EXEC|UNION|--|\*|#)\b.*?[=;]|\b(OR|AND)\b\s*?['"\d])/i; + return sqlInjectionPattern.test(queryString); +}; /** * @param {string} urlStr * @param {string} method @@ -79,6 +86,20 @@ export default class H2HttpHostEndPoint extends SecureHttpHostEndPoint { .createSecureServer(this.#options) .on("stream", async (stream, headers) => { this._checkCacheAsync(stream, headers, async () => { + if (this._sqlInjectionMiddleware(stream, headers)) { + stream.respond({ ':status': 400, 'content-type': 'text/plain' }); + return stream.end('Bad Request: Potential SQL injection detected.'); + } + const ip = stream.session.socket.remoteAddress; + try{ + await this.rateLimiter.consume(ip) + }catch(err){ + stream.respond({ + ':status': 429, + 'retry-after': 60, + }); + stream.end("Too many requests - try again later"); + } /** @type {Request} */ let cms = null; /** @type {BinaryContent[]} */ diff --git a/endPoint/httpHostEndPoint.js b/endPoint/httpHostEndPoint.js index b8dd6a1..0967a1e 100644 --- a/endPoint/httpHostEndPoint.js +++ b/endPoint/httpHostEndPoint.js @@ -16,6 +16,7 @@ import CacheConnectionBase from "../models/CacheCommands/CacheConnection/CacheCo import { HostEndPointOptions } from "../models/model.js"; import SqliteCacheConnection from "../models/CacheCommands/CacheConnection/SqliteCacheConnection.js"; import CacheSettings from "../models/options/CacheSettings.js"; +import {RateLimiterMemory} from "rate-limiter-flexible" let requestId = 0; class HttpHostEndPoint extends HostEndPoint { @@ -27,6 +28,8 @@ class HttpHostEndPoint extends HostEndPoint { _cacheConnection; /** @type {http.Server} */ _server; + /** @type {RateLimiterMemory} */ + rateLimiter; /** * * @param {string} ip @@ -38,6 +41,10 @@ class HttpHostEndPoint extends HostEndPoint { super(ip, port); this._service = service; this._cacheOptions = cacheOptions; + this.rateLimiter =new RateLimiterMemory({ + points: 10, + duration: 60, + }); if (cacheOptions && cacheOptions.connectionType) { switch (cacheOptions.connectionType) { case "sqlite": { @@ -353,5 +360,23 @@ class HttpHostEndPoint extends HostEndPoint { this._server.close(); console.log(`server ip ${this._ip} and port ${this._port} killed`); } + _decodeURIComponentSafe = (s) => { + try { + return decodeURIComponent(s); + } catch { + return s; // Return as-is if decoding fails + } +}; + _sqlInjectionMiddleware = (req, res, next) => { + const url = new URL(req.url, `https://${req.headers.host}`); + const queryString = url.search; + const sqlInjectionPattern = /[\s'";()&|]|(SELECT|select|INSERT|insert|DELETE|delete|UPDATE|update|WHERE|where|DROP|drop|EXEC|exec|UNION|union|--|\*|#|\/\*|\/\*.*?\*\/)/; + if (sqlInjectionPattern.test(queryString)) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Bad Request: Potential SQL injection detected.'); + } else { + next(); + } +}; } export default HttpHostEndPoint; diff --git a/endPoint/nonSecureHttpHostEndPoint.js b/endPoint/nonSecureHttpHostEndPoint.js index a4143c3..aa338e0 100644 --- a/endPoint/nonSecureHttpHostEndPoint.js +++ b/endPoint/nonSecureHttpHostEndPoint.js @@ -4,8 +4,6 @@ import { StatusCodes } from "http-status-codes"; import HttpHostEndPoint from "./HttpHostEndPoint.js"; import { HostService } from "../services/hostServices.js"; import LightgDebugStep from "../renderEngine/Models/LightgDebugStep.js"; -import StringResult from "../renderEngine/Models/StringResult.js"; -import fs from "fs"; export default class NonSecureHttpHostEndPoint extends HttpHostEndPoint { /** @@ -17,71 +15,82 @@ export default class NonSecureHttpHostEndPoint extends HttpHostEndPoint { constructor(ip, port, service, cacheOptions) { super(ip, port, service, cacheOptions); } - _createServer() { this._server = http.createServer(async (req, res) => { try { - this._securityHeadersMiddleware(req, res, async () => { - this._handleContentTypes(req, res, async () => { - this._checkCacheAsync(req, res, false, async () => { - const createCmsAndCreateResponseAsync = async () => { - const queryObj = url.parse(req.url, true).query; - let debugCondition = - queryObj.debug == "true" || - queryObj.debug == "1" || - queryObj.debug == "2"; - let routingDataStep = debugCondition - ? new LightgDebugStep(null, "Get Routing Data") - : null; - let rawRequest = debugCondition - ? this.joinHeaders(req.rawHeaders) - : null; - /** @type {Request} */ - let cms = await this._createCmsObjectAsync( - req.url, - req.method, - req.headers, - req.formFields, - req.jsonHeaders ? req.jsonHeaders : {}, - req.socket, - req.bodyStr, - false - ); - let result = await this._service.processAsync( - cms, - req.fileContents - ); - let cachecms; - if (result.result) { - - cachecms = result.responseCms; - result = result.result; - } - routingDataStep?.complete(); - const [code, headers, body] = await result.getResultAsync( - routingDataStep, - rawRequest, - debugCondition ? cms.dict : undefined - ); - const statuscode = Number( - result._request.webserver.headercode.split(" ")[0] - ); - if (statuscode != 301 && statuscode != 302) { - this.addCacheContentAsync( - `http://${req.headers.host}${req.url}`, - body, - headers, + const ip = req.socket.remoteAddress; + try{ + await this.rateLimiter.consume(ip) + }catch(err){ + res.writeHead(429, { + 'Content-Type': 'text/plain', + 'Retry-After': 60, + }); + return res.end("Too many requests - try again later"); + } + this._sqlInjectionMiddleware(req, res, async () => { + this._securityHeadersMiddleware(req, res, async () => { + this._handleContentTypes(req, res, async () => { + this._checkCacheAsync(req, res, false, async () => { + const createCmsAndCreateResponseAsync = async () => { + const queryObj = url.parse(req.url, true).query; + let debugCondition = + queryObj.debug == "true" || + queryObj.debug == "1" || + queryObj.debug == "2"; + let routingDataStep = debugCondition + ? new LightgDebugStep(null, "Get Routing Data") + : null; + let rawRequest = debugCondition + ? this.joinHeaders(req.rawHeaders) + : null; + /** @type {Request} */ + let cms = await this._createCmsObjectAsync( + req.url, req.method, - cachecms + req.headers, + req.formFields, + req.jsonHeaders ? req.jsonHeaders : {}, + req.socket, + req.bodyStr, + false + ); + let result = await this._service.processAsync( + cms, + req.fileContents + ); + let cachecms; + if (result.result) { + + cachecms = result.responseCms; + result = result.result; + } + routingDataStep?.complete(); + const [code, headers, body] = await result.getResultAsync( + routingDataStep, + rawRequest, + debugCondition ? cms.dict : undefined + ); + const statuscode = Number( + result._request.webserver.headercode.split(" ")[0] ); - } - res.writeHead(code, headers); - res.end(body); - }; - await createCmsAndCreateResponseAsync(); + if (statuscode != 301 && statuscode != 302) { + this.addCacheContentAsync( + `http://${req.headers.host}${req.url}`, + body, + headers, + req.method, + cachecms + ); + } + res.writeHead(code, headers); + res.end(body); + }; + await createCmsAndCreateResponseAsync(); + }); }); }); - }); + }) } catch (ex) { console.error(ex); res.writeHead(StatusCodes.INTERNAL_SERVER_ERROR); diff --git a/endPoint/secureHttpHostEndPoint.js b/endPoint/secureHttpHostEndPoint.js index ee3b342..748bee1 100644 --- a/endPoint/secureHttpHostEndPoint.js +++ b/endPoint/secureHttpHostEndPoint.js @@ -37,67 +37,79 @@ export default class SecureHttpHostEndPoint extends HttpHostEndPoint { .createServer(this.#options, async (req, res) => { /** @type {Request} */ let cms = null; - this._securityHeadersMiddleware(req, res, async () => { - this._handleContentTypes(req, res, async () => { - this._checkCacheAsync(req, res, async () => { - const createCmsAndCreateResponseAsync = async () => { - const queryObj = url.parse(req.url, true).query; - let debugCondition = - queryObj.debug == "true" || - queryObj.debug == "1" || - queryObj.debug == "2"; - let routingDataStep = debugCondition - ? new LightgDebugStep(null, "Get Routing Data") - : null; - let rawRequest = debugCondition - ? this.joinHeaders(req.rawHeaders) - : null; - cms = await this._createCmsObjectAsync( - req.url, - req.method, - req.headers, - req.formFields, - req.jsonHeaders ? req.jsonHeaders : {}, - req.socket, - req.bodyStr, - true - ); - const result = await this._service.processAsync( - cms, - req.fileContents - ); - routingDataStep?.complete(); - const [code, headers, body] = await result.getResultAsync( - routingDataStep, - rawRequest, - debugCondition ? cms.dict : undefined - ); - const statuscode = Number( - result._request.webserver.headercode.split(" ")[0] - ); - if (statuscode != 301 && statuscode != 302) { - this.addCacheContentAsync( - `https://${req.headers.host}${req.url}`, - body, - headers, + const ip = req.socket.remoteAddress; + try{ + await this.rateLimiter.consume(ip) + }catch(err){ + res.writeHead(429, { + 'Content-Type': 'text/plain', + 'Retry-After': 60, + }); + return res.end("Too many requests - try again later"); + } + this._sqlInjectionMiddleware(req, res, async () => { + this._securityHeadersMiddleware(req, res, async () => { + this._handleContentTypes(req, res, async () => { + this._checkCacheAsync(req, res, async () => { + const createCmsAndCreateResponseAsync = async () => { + const queryObj = url.parse(req.url, true).query; + let debugCondition = + queryObj.debug == "true" || + queryObj.debug == "1" || + queryObj.debug == "2"; + let routingDataStep = debugCondition + ? new LightgDebugStep(null, "Get Routing Data") + : null; + let rawRequest = debugCondition + ? this.joinHeaders(req.rawHeaders) + : null; + cms = await this._createCmsObjectAsync( + req.url, req.method, - cms + req.headers, + req.formFields, + req.jsonHeaders ? req.jsonHeaders : {}, + req.socket, + req.bodyStr, + true ); - } + const result = await this._service.processAsync( + cms, + req.fileContents + ); + routingDataStep?.complete(); + const [code, headers, body] = await result.getResultAsync( + routingDataStep, + rawRequest, + debugCondition ? cms.dict : undefined + ); + const statuscode = Number( + result._request.webserver.headercode.split(" ")[0] + ); + if (statuscode != 301 && statuscode != 302) { + this.addCacheContentAsync( + `https://${req.headers.host}${req.url}`, + body, + headers, + req.method, + cms + ); + } - res.writeHead(code, headers); - res.end(body); - }; - try { - createCmsAndCreateResponseAsync(); - } catch (ex) { - console.error(ex); - res.writeHead(StatusCodes.INTERNAL_SERVER_ERROR); - res.end(ex.toString()); - } + res.writeHead(code, headers); + res.end(body); + }; + try { + createCmsAndCreateResponseAsync(); + } catch (ex) { + console.error(ex); + res.writeHead(StatusCodes.INTERNAL_SERVER_ERROR); + res.end(ex.toString()); + } + }); }); }); - }); + }) }) .on("error", (er) => console.error(er)) .on("clientError", (er) => console.error(er)) diff --git a/package-lock.json b/package-lock.json index 6215c26..f4a83b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,11 +27,13 @@ "node-html-encoder": "^0.0.2", "pako": "^2.1.0", "promised-sqlite3": "^2.1.0", + "rate-limiter-flexible": "^5.0.4", "sharp": "^0.33.5", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", "sqlite": "^5.1.1", "sqlite3": "^5.1.6", + "validator": "^13.12.0", "winston": "^3.13.0", "winston-transport": "^4.7.0", "ws": "^8.18.0" @@ -8937,6 +8939,12 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/rate-limiter-flexible": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-5.0.4.tgz", + "integrity": "sha512-ftYHrIfSqWYDIJZ4yPTrgOduByAp+86gUS9iklv0JoXVM8eQCAjTnydCj1hAT4MmhmkSw86NaFEJ28m/LC1pKA==", + "license": "ISC" + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -10790,6 +10798,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index f7ce141..22e40c3 100644 --- a/package.json +++ b/package.json @@ -91,11 +91,13 @@ "node-html-encoder": "^0.0.2", "pako": "^2.1.0", "promised-sqlite3": "^2.1.0", + "rate-limiter-flexible": "^5.0.4", "sharp": "^0.33.5", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", "sqlite": "^5.1.1", "sqlite3": "^5.1.6", + "validator": "^13.12.0", "winston": "^3.13.0", "winston-transport": "^4.7.0", "ws": "^8.18.0" diff --git a/test/command/Group/util.js b/test/command/Group/util.js index 9c4d219..e2a05e0 100644 --- a/test/command/Group/util.js +++ b/test/command/Group/util.js @@ -12,7 +12,7 @@ const context = new TestContext( CommandUtil.addDefaultCommands() ); context.cancellation = new CancellationToken(); -context.addSource(new JsonSource([{ dmntoken: "ttttdggdgg" }], "db.dmntoken")); +context.addSource(new JsonSource([{ dmntokrn: "ttttdggdgg" }], "db.dmntoken")); const il = { $type: "group",