Skip to content
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

JS: Add Permissive CORS query (CWE-942) #14342

Open
wants to merge 30 commits into
base: main
Choose a base branch
from

Conversation

maikypedia
Copy link
Contributor

@maikypedia maikypedia commented Sep 29, 2023

This pull request adds a query for Permissive CORS to prevent CSRF attacks for Apollo Server. I plan to add a couple more libraries, so I'll leave it in draft for now. 😁

Looking forward to your suggestions.

@maikypedia maikypedia requested a review from a team as a code owner September 29, 2023 16:40
@github-actions github-actions bot added the JS label Sep 29, 2023
@maikypedia maikypedia marked this pull request as draft September 29, 2023 17:00
Copy link
Contributor

@erik-krogh erik-krogh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a first review. I've only really looked at the implementation, and I haven't taken a good look at the actual vulnerability yet.

}

/** true and null are considered bad values */
class BadValues extends Source instanceof DataFlow::Node {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class BadValues extends Source instanceof DataFlow::Node {
class BadValues extends Source {

Adding DataFlow::Node should make no difference here.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 2, 2023

QHelp previews:

javascript/ql/src/experimental/Security/CWE-942/CorsPermissiveConfiguration.qhelp

overly CORS configuration

A server can use CORS (Cross-Origin Resource Sharing) to relax the restrictions imposed by the SOP (Same-Origin Policy), allowing controlled, secure cross-origin requests when necessary. A server with an overly permissive CORS configuration may inadvertently expose sensitive data or lead to CSRF which is an attack that allows attackers to trick users into performing unwanted operations in websites they're authenticated to.

Recommendation

When the origin is set to true, it signifies that the server is accepting requests from any origin, potentially exposing the system to CSRF attacks. This can be fixed using false as origin value or using a whitelist.

On the other hand, if the origin is set to null, it can be exploited by an attacker to deceive a user into making requests from a null origin form, often hosted within a sandboxed iframe.

If the origin value is user controlled, make sure that the data is properly sanitized.

Example

In the example below, the server_1 accepts requests from any origin since the value of origin is set to true. And server_2's origin is user-controlled.

import { ApolloServer } from 'apollo-server';
var https = require('https'),
    url = require('url');

var server = https.createServer(function () { });

server.on('request', function (req, res) {
    // BAD: origin is too permissive
    const server_1 = new ApolloServer({
        cors: { origin: true }
    });

    let user_origin = url.parse(req.url, true).query.origin;
    // BAD: CORS is controlled by user
    const server_2 = new ApolloServer({
        cors: { origin: user_origin }
    });
});

In the example below, the server_1 CORS is restrictive so it's not vulnerable to CSRF attacks. And server_2's is using properly sanitized user-controlled data.

import { ApolloServer } from 'apollo-server';
var https = require('https'),
    url = require('url');

var server = https.createServer(function () { });

server.on('request', function (req, res) {
    // GOOD: origin is restrictive
    const server_1 = new ApolloServer({
        cors: { origin: false }
    });

    let user_origin = url.parse(req.url, true).query.origin;
    // GOOD: user data is properly sanitized
    const server_2 = new ApolloServer({
        cors: { origin: (user_origin === "https://allowed1.com" || user_origin === "https://allowed2.com") ? user_origin : false }
    });
});

References

@erik-krogh
Copy link
Contributor

The QLDoc check is still complaining about missing documentation string on CorsPermissiveConfigurationCustomizations::CorsPermissiveConfiguration. It's one of the review comments I gave above.
Can you fix that? It feels better reviewing something when the CI check is green.

@maikypedia maikypedia marked this pull request as ready for review October 16, 2023 14:48
@maikypedia
Copy link
Contributor Author

maikypedia commented Oct 16, 2023

Since I used null and true as sources for taint track (apollo), is there any "not-ugly" way to include * as source for express?

@maikypedia
Copy link
Contributor Author

^ express cors library covered

@erik-krogh
Copy link
Contributor

Since I used null and true as sources for taint track (apollo), is there any "not-ugly" way to include * as source for express?

What do you mean by *?

@maikypedia
Copy link
Contributor Author

Since I used null and true as sources for taint track (apollo), is there any "not-ugly" way to include * as source for express?

What do you mean by *?

// BAD: CORS too permissive 
var app3 = express();
var corsOption3 = {
    origin: '*'
};
app3.use(cors(corsOption3));

The value of origin, wildcard includes any origin

@erik-krogh
Copy link
Contributor

Since I used null and true as sources for taint track (apollo), is there any "not-ugly" way to include * as source for express?

What do you mean by *?

// BAD: CORS too permissive 
var app3 = express();
var corsOption3 = {
    origin: '*'
};
app3.use(cors(corsOption3));

The value of origin, wildcard includes any origin

Hmm. Yes you can, but it's not simple, so maybe you should just skip it.
You can use flow-labels to track multiple kinds of values that are relevant for different kinds of sinks.
There is some documentation here: https://codeql.github.com/docs/codeql-language-guides/using-flow-labels-for-precise-data-flow-analysis/


Also, you should move the rest of your implementation into the experimental/ folder.
Your query is extremely close to our existing js/cors-misconfiguration-for-credentials query, but with some new library models (which is your "real" contribution).
So I think we want to do something else before we move it out of experimental.

@sidshank sidshank added the awaiting-response The CodeQL team is awaiting further input or clarification from the original reporter of this issue. label Nov 6, 2023
@maikypedia
Copy link
Contributor Author

Sorry for the delay. Should I move CorsPermissiveConfigurationCustomizations.qll and CorsPermissiveConfigurationQuery.qll to /codeql/javascript/ql/experimental

@erik-krogh
Copy link
Contributor

Sorry for the delay. Should I move CorsPermissiveConfigurationCustomizations.qll and CorsPermissiveConfigurationQuery.qll to /codeql/javascript/ql/experimental

Yes please.

@maikypedia
Copy link
Contributor Author

Moved 👍

javascript/ql/lib/semmle/javascript/frameworks/Apollo.qll Outdated Show resolved Hide resolved
javascript/ql/lib/semmle/javascript/frameworks/Cors.qll Outdated Show resolved Hide resolved
javascript/ql/lib/semmle/javascript/frameworks/Cors.qll Outdated Show resolved Hide resolved
* An express route setup configured with the `cors` package.
*/
class CorsConfiguration extends DataFlow::MethodCallNode {
CorsConfiguration() { exists(Express::RouteSetup setup | this = setup | setup.isUseCall()) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This characteristic predicate holds for any .use() call, you haven't restricted it to only be the ´.use() ´ calls that are configured with the Cors package.

I think you should add Cors::Cors as a field on this class. That should make it easier to implement the restriction.

Additionally (as I have mentioned before).
The cors package can also be used like: app.get('/products/:id', cors(corsOptions) ....
With RouteSetup, I think it should be easy to model that.
So if setup.isUseCall(), then the first argument should be the call to Cors.
And if setup.isUseCall() doesn't hold, then the later arguments can be the call to Cors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be simplified to just setup.getAnArgument() instanceof Cors::Cors?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite.
Your Cors::Cors getArgument() predicate on the CorsConfiguration class doesn't work, because that predicate just assumes that the first argument is the cors configuration.

I've added some suggestions to use a field such that the result of getArgument depends on the routesetup in the characteristic predicate.

maikypedia and others added 2 commits December 5, 2023 09:05
Co-authored-by: Erik Krogh Kristensen <erik-krogh@github.com>
javascript/ql/lib/semmle/javascript/frameworks/Express.qll Outdated Show resolved Hide resolved
* An express route setup configured with the `cors` package.
*/
class CorsConfiguration extends DataFlow::MethodCallNode {
CorsConfiguration() { exists(Express::RouteSetup setup | this = setup | setup.isUseCall()) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite.
Your Cors::Cors getArgument() predicate on the CorsConfiguration class doesn't work, because that predicate just assumes that the first argument is the cors configuration.

I've added some suggestions to use a field such that the result of getArgument depends on the routesetup in the characteristic predicate.

maikypedia and others added 2 commits December 18, 2023 12:37
Co-authored-by: Erik Krogh Kristensen <erik-krogh@github.com>
Co-authored-by: Erik Krogh Kristensen <erik-krogh@github.com>
@erik-krogh
Copy link
Contributor

The javascript/ql/lib/semmle/javascript/frameworks/Apollo.qll file is failing the autoformat check.

You can use codeql query format -i <path-to-file> to run the autoformatter.

@erik-krogh
Copy link
Contributor

One last thing, and then we can merge.

The Cors.qll file and the CorsConfiguration class in Express.qll are only used in your new experimental query.
Could you move those to experimental/?
The CorsConfiguration class fits in CorsPermissiveConfigurationCustomizations.qll, and you can move the Cors.qll file to the javascript/ql/src/experimental/Security/CWE-942/ folder.

@erik-krogh
Copy link
Contributor

The CorsPermissiveConfiguration.ql query failed to compile (that's the CI failure).
I'm not sure why, but I suspect you need to add/update an import after your recent change.

@maikypedia
Copy link
Contributor Author

It should compile, I don't know why it didn't, let's try again.

@erik-krogh
Copy link
Contributor

The error was due to a warning which was caused by Cors being ambiguous.
I fixed the issue and pushed to this PR.

Lets see if the CI goes green.

@erik-krogh erik-krogh removed the awaiting-response The CodeQL team is awaiting further input or clarification from the original reporter of this issue. label Mar 13, 2024
@maikypedia
Copy link
Contributor Author

Now it's working! Thanks 😄

@maikypedia
Copy link
Contributor Author

ping @erik-krogh :)

Copy link
Contributor

@erik-krogh erik-krogh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ping @erik-krogh :)

Sorry, I've had a lot to look at.
One more file move, and then I think we'll be there.

Comment on lines 1 to 5
/**
* Provides classes for working with Apollo GraphQL connectors.
*/

import javascript
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is only used within your experimental query, so it should probably be moved to the same folder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done 👍

Comment on lines 1 to 3
---
category: minorAnalysis
---
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, this change-note should probably be in the javascript/ql/src/change-notes folder instead.

Copy link
Contributor

@erik-krogh erik-krogh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but now there is a nit about imports.
You are importing from a file named Apollo, which imports a module also named Apollo, which means that there could be confusion about which Apollo is referenced at the import, and that is what the compiler-warning is about.

I added suggestions to fix it, by not directly importing anything named Apollo.

maikypedia and others added 2 commits June 12, 2024 19:38
…ConfigurationCustomizations.qll

Co-authored-by: Erik Krogh Kristensen <erik-krogh@github.com>
…ConfigurationCustomizations.qll

Co-authored-by: Erik Krogh Kristensen <erik-krogh@github.com>
@erik-krogh
Copy link
Contributor

A CI job keeps failing. I think that is from your branch being quite out-of-date, can you do a merge with main?
(I tried myself, but I can't push to your branch).

Sorry it's taking so long.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants