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 @@
+
+
+
+ - test1 (1-2) - (fieldName3-1)
+
+ - test2 (2-2) - (fieldName3-2)
+
+ - test3 (3-2) - (fieldName3-3)
+
+ - test4 (4-2) - (fieldName3-4)
+
+ - test5 (5-2) - (fieldName3-5)
+
+
+
\ 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 @@
+
+
+
+ - test1 (1-2) - (fieldName3-1)
+
+ - test2 (2-2) - (fieldName3-2)
+
+ - test3 (3-2) - (fieldName3-3)
+
+ - test4 (4-2) - (fieldName3-4)
+
+ - test5 (5-2) - (fieldName3-5)
+
+
+
\ 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",