Skip to content

Exclude injection alerts where the input data type is not String #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/javascript.sarif.expected

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ConstantOnlyTemplateLiteral extends TemplateLiteral {
}

/**
* Arguments of calls to `cds.log.{trace, debug, info, log, warn, error}`
* Arguments of calls to `cds.log.{trace, debug, info, log, warn, error}`.
*/
class CdsLogSink extends DataFlow::Node {
CdsLogSink() {
Expand All @@ -49,5 +49,12 @@ class CAPLogInjectionConfiguration extends LogInjectionConfiguration {
start instanceof RemoteFlowSource
}

override predicate isBarrier(DataFlow::Node node) {
exists(HandlerParameterData handlerParameterData |
node = handlerParameterData and
not handlerParameterData.getType() = ["cds.String", "cds.LargeString"]
)
}

override predicate isSink(DataFlow::Node end) { end instanceof CdsLogSink }
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ class CdlService extends CdlElement {

CdlEntity getAnEntity() { result = this.getEntity(_) }

CdlActionOrFunction getAnActionOrFunction() {
result = this.getAnEvent() or result = this.getAnAction()
}

CdlEvent getEvent(string eventName) {
result.getName() = eventName and this.getName() = result.getName().splitAt(".", 0)
}
Expand Down Expand Up @@ -232,13 +236,27 @@ class CdlEntity extends CdlElement {
}
}

class CdlEvent extends CdlElement {
abstract class CdlActionOrFunction extends CdlElement {
/**
* Gets a parameter definition of this action with a given name.
*/
CdlAttribute getParameter(string paramName) {
result = this.getPropValue("params").getPropValue(paramName)
}

/**
* Gets a parameter definition of this action.
*/
CdlAttribute getAParameter() { result = this.getParameter(_) }
}

class CdlEvent extends CdlActionOrFunction {
CdlEvent() { kind = CdlEventKind(this.getPropStringValue("kind")) }

string getBasename() { result = name.splitAt(".", count(name.indexOf("."))) }
}

class CdlAction extends CdlElement {
class CdlAction extends CdlActionOrFunction {
CdlAction() { kind = CdlActionKind(this.getPropStringValue("kind")) }

predicate belongsToServiceWithNoAuthn() {
Expand All @@ -260,7 +278,10 @@ class CdlAttribute extends CdlObject {
string name;

CdlAttribute() {
exists(CdlElement entity | this = entity.getPropValue("elements").getPropValue(name))
exists(CdlElement entity | this = entity.getPropValue("elements").getPropValue(name)) or
exists(CdlActionOrFunction actionOrFunction |
this = actionOrFunction.getPropValue("params").getPropValue(name)
)
}

override string getObjectLocationName() { result = getName() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import advanced_security.javascript.frameworks.cap.TypeTrackers
import advanced_security.javascript.frameworks.cap.PackageJson
import advanced_security.javascript.frameworks.cap.CDL
import advanced_security.javascript.frameworks.cap.CQL
import advanced_security.javascript.frameworks.cap.RemoteFlowSources

/**
* ```js
Expand Down Expand Up @@ -701,3 +702,48 @@ class EntityReferenceFromCqlClause extends EntityReference, ExprNode {

override CdlEntity getCqlDefinition() { result = cql.getAccessingEntityDefinition() }
}

/**
* The `"data"` property of the handler's parameter that represents the request or message passed to this handler.
* This property carries the user-provided payload provided to the CAP application. e.g.
* ``` javascript
* srv.on("send", async (msg) => {
* const { payload } = msg.data;
* })
* ```
* The `payload` carries the data that is sent to this application on the action or event named `send`.
*/
class HandlerParameterData instanceof PropRead {
HandlerParameter handlerParameter;
string dataName;

HandlerParameterData() {
this = handlerParameter.getAPropertyRead("data").getAPropertyRead(dataName)
}

/**
* Gets the type of this handler parameter data.
*/
string getType() {
/*
* The result string is the type of this parameter if:
* - There is an actionName which is this HandlerRegistration's action/function name, and
* - The actionName in the CDS declaration has params with the same name of this parameter, and
* - The result is the name of the type of the parameter.
*/

exists(
UserDefinedApplicationService userDefinedApplicationService,
CdlActionOrFunction cdlActionOrFunction, HandlerRegistration handlerRegistration
|
handlerRegistration = userDefinedApplicationService.getAHandlerRegistration() and
handlerRegistration = handlerParameter.getHandlerRegistration() and
handlerRegistration.getAnEventName() = cdlActionOrFunction.getUnqualifiedName() and
cdlActionOrFunction =
userDefinedApplicationService.getCdsDeclaration().getAnActionOrFunction() and
result = cdlActionOrFunction.getParameter(dataName).getType()
)
}

string toString() { result = this.(PropRead).toString() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import advanced_security.javascript.frameworks.cap.CDS
* All the parameters named `req` and `msg` are captured in the above example.
*/
class HandlerParameter extends ParameterNode, RemoteFlowSource {
Handler handler;
HandlerRegistration handlerRegistration;

HandlerParameter() {
exists(
Handler handler, HandlerRegistration handlerRegistration,
UserDefinedApplicationService service
|
exists(UserDefinedApplicationService service |
handler = handlerRegistration.getHandler() and
this = handler.getParameter(0) and
service.getAHandlerRegistration() = handlerRegistration and
Expand All @@ -27,13 +27,26 @@ class HandlerParameter extends ParameterNode, RemoteFlowSource {
override string getSourceType() {
result = "Parameter of an event handler belonging to an exposed service"
}

/**
* Gets the handler this is a parameter of.
*/
Handler getHandler() { result = handler }

/**
* Gets the handler registration registering the handler it is a parameter of.
*/
HandlerRegistration getHandlerRegistration() { result = handlerRegistration }
}

/**
* A service may be described only in a CDS file, but event handlers may still be registered in a format such as:
* ```javascript
* module.exports = srv => {
* srv.before('CREATE', 'Media', req => { //an entity name is used to describe which to register this handler to
* srv.before('CREATE', 'Media', req => { // an entity name is used to describe which to register this handler to.
* ...
* });
* }
* ```
* parameters named `req` are captured in the above example.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Sanitized Log Injection

This application demonstrates how a potential injection vulnerability is not reported if the data type definied in the service description is not strings.

## It _is_ a false positive case

Service responds to a Received event and logs the data. However, the type of the message (Integer) does not allow for the injection to succeed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace advanced_security.log_injection.sample_entities;

entity Entity1 {
Attribute1 : String(100);
Attribute2 : String(100)
}

entity Entity2 {
Attribute3 : String(100);
Attribute4 : String(100)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
nodes
edges
#select
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
loginjection/LogInjection.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@advanced-security/log-injection",
"version": "1.0.0",
"dependencies": {
"@cap-js/sqlite": "*",
"@sap/cds": "^7.9.5",
"@sap/cds-dk": "^8.6.1",
"express": "^4.17.1"
},
"scripts": {
"start": "cds-serve",
"watch": "cds watch"
},
"cds": {
"requires": {
"service": {
"impl": "srv/service.js"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const cds = require('@sap/cds');
const app = require('express')();

cds.serve('all').in(app);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using { advanced_security.log_injection.sample_entities as db_schema } from '../db/schema';

service Service @(path: '/service') {

Check warning

Code scanning / CodeQL

Entity exposed without authentication Medium test

The CDS service Service is exposed without any authentication.
/* Entity to send READ/GET about. */
entity ServiceEntity as projection on db_schema.Entity2 excluding { Attribute4 }

Check warning

Code scanning / CodeQL

Entity exposed without authentication Medium test

The CDS entity Service.ServiceEntity is exposed without any authentication.

/* API to talk to Service. */
action send (

Check warning

Code scanning / CodeQL

Entity exposed without authentication Medium test

The CDS action Service.send is exposed without any authentication.
messageToPass: Integer
) returns String;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const cds = require("@sap/cds");
const LOG = cds.log("logger");

module.exports = cds.service.impl(function() {
/* Log upon receiving an "send" event. */
this.on("send", async (msg) => {
const { messageToPass } = msg.data;
/* A log injection sink. */
LOG.info("Received: ", messageToPass); // messageToPass is Integer, not a log injection!
});
})
Loading