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

Eas 1965 #28

Merged
merged 81 commits into from
Nov 30, 2023
Merged

Eas 1965 #28

merged 81 commits into from
Nov 30, 2023

Conversation

aliner-wang
Copy link
Contributor

Added new pathway for scanning for packages requiring secrets to include * imported packages.

Updated secretdata.yaml with new packages to scan for. Commented out packages use require import method that we don't scan for.

Added package import to DVFA.

Todo: definitely can clean up code from comments and stuff in DVFA and around main. But as_intrinsic in definition.rs is done and cleaned up.

secretdata.yaml Outdated

- package_name: jsonwebtoken
identifier: default
function_name: Some
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't this be sign?

crates/forge_analyzer/src/definitions.rs Show resolved Hide resolved
match *callee {
[PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch),
// [PropPath::Def(def)] => {}]
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't forget to handle the case where it's just a "regular" function call, i.e. with just an identifier. Otherwise, we wouldn't catch this:

import { sign } from 'jsonwebtoken';
sign('foo', 'secret');

Copy link
Contributor Author

@aliner-wang aliner-wang Nov 7, 2023

Choose a reason for hiding this comment

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

How would you handle a case where sign is not from jsonwebtoken? Like in an instance where a function is names sign but not necessarily imported from jsonwebtoken or it could be defined locally? I think the only way to verify for sure is to check for the specific import of the package. Would calling self.res.is_imported_from be enough to verify?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, that would return false if it's not an import or if it's imported from a different project.


// testing all the libraries
// var token = jwt.sign({ foo: 'bar' }, 'peek a boo');
// var hmac = HmacMD5('Secret Message', 'HMAC PASSWORD');
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add test cases for named imports as well? Also for cases where an identifier could shadow an import to see if we don't raise a false positive there.

awang7 added 3 commits November 7, 2023 22:42
…t exports. added new scanning pathway for star imports where prop path is identifier or method with children object imports.
…ed packages. Working on case to catch functions called from packages in nameed imports
false
}
}
_ => false,
Copy link
Contributor

Choose a reason for hiding this comment

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

You shouldn't need this catchall case.

@@ -2981,11 +3503,42 @@ impl Environment {
DefKind::Undefined => DefKind::Undefined,
}
}
pub fn is_import(&self, import_kind: ImportKind, named_import: Option<String>) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably just be a method an ImportKind, since we don't use the resolver here. Also, this could probably just take in a &str and check if the import is the same identifier as what was passed in.

// debug!("{named_import}");
match import_kind {
ImportKind::Star => true,
ImportKind::Default => true,
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be true iff named_import is "default", since this function is for checking if the import matches whatever identifier/string we pass in.

awang7 and others added 6 commits November 20, 2023 17:04
…dded more test cases for named imports. todo: add more edge cases and review is_import
…aml. Todo: resolve edge case where same secret scanner vuln got reported twice - after adding jsonwebtoken funtion verify
Copy link
Contributor

@jwong101 jwong101 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 overall. Mostly stylistic changes asides from moving the match arms.

.gitignore Outdated
@@ -1 +1,2 @@
/target
/test-apps
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you remove this from the .gitignore?

_ => false,
}
}
// pub fn is_named_or(&self, import_kind: Option<String>) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you remove from this if it's unused?

// case where function requires object to be invoked first
// example: import * as cryptoJS from 'crypto-js';
// var aes = cryptoJS.AES.encrypt('Secret message', 'secret password');
if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) {
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
if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) {
let (package_name, import_kind) = self.res.as_foreign_import(def)?;

Copy link
Contributor

Choose a reason for hiding this comment

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

See this for an explanation of how this works.


None
}

[PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you move these two cases above the cases for lowering the secret function intrinsics?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ran into a problem with this one. Moving the last case above all the secret scanner checks led to the secret scanner checks to never get caught.

// Aes.encrypt(blah, blah);
let package_found = self.secret_packages.iter().find(|&package_data| {
if let Some(method) = &package_data.method {
return package_name == package_data.package_name
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
return package_name == package_data.package_name
package_name == package_data.package_name

Copy link
Contributor

Choose a reason for hiding this comment

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

Of course don't forget to remove the semicolon.

@@ -1077,7 +1322,7 @@ impl<'cx> FunctionAnalyzer<'cx> {
match n {
Pat::Ident(BindingIdent { id, .. }) => {
let id = id.to_id();
let def = self.res.get_or_insert_sym(id, self.module);
let def = self.res.get_or_insert_sym(id.clone(), self.module);
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
let def = self.res.get_or_insert_sym(id.clone(), self.module);
let def = self.res.get_or_insert_sym(id, self.module);

_ => {}
}
}
}
}
} else if &ident.sym == "exports" {
} else if ident.sym.to_string() == "exports" {
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
} else if ident.sym.to_string() == "exports" {
} else if ident.sym == "exports" {

// adding the default export, so we can resolve it during the lowering
self.res_table
.exported_names
.insert((ident.sym.clone(), self.curr_mod), self.default.unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you change the ExportCollector::set_default method to return the DefId that was set to self.default at the end of that function?; That way you can do let default_id = self.add_default(DefRes::Undefined, None); and replace self.default.unwrap() with default_id.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no set_default function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tldr on my efforts: For this one, I couldn't find set_default under ExportCollector. Nor did a default_id variable exist.

So I tried to return defid in add_default. But that also messes up some self.add_default(DefRes::Undefined, None); implementations elsewhere because it's expected that nothing gets returned.

I did try creating self.set_default, but it seemed a little repetitive to call it if add_default is already setting self.default. and calling set_default to return a defId add_default is pretty much the same result as the approach described above.

Otherwise, the easiest way to not use .unwrap() might be to use a let statement grab the value in a Some() object instead of returning it.

Do you want me to break a lil and adapt like other places where self.add_default is called?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I meant change add_default to return a DefId. Yeah, that should be fine, since looking at the breakage, it's just match arms that are now returning a DefId instead of (). To fix it, you just need to wrap the arm in braces and add a semicolon.

// adding the default export, so we can resolve it during the lowering
self.res_table.exported_names.insert(
(ident.sym.clone(), self.curr_mod),
self.default.unwrap(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you change the ExportCollector::set_default method to return the DefId that was set to self.default at the end of that function?; That way you can do let default_id = self.add_default(DefRes::Undefined, None); and replace self.default.unwrap() with default_id.

if let Some(mem_expr) = mem_expr_from_assign(n) {
if let MemberProp::Ident(ident_property) = &mem_expr.prop {
if &ident_property.sym == "exports" {
if ident_property.sym.to_string() == "exports" {
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
if ident_property.sym.to_string() == "exports" {
if ident_property.sym == "exports" {

Comment on lines 972 to 977
[PropPath::Def(def), ..] => match self.res.is_imported_from(def, "@forge/api") {
Some(ImportKind::Named(ref name)) if *name == *"authorize" => {
Some(Intrinsic::Authorize(IntrinsicName::Other))
}
_ => None,
},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Specifically moving this case above the secret scanner checks I think catches all statements before secret scanner checks.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, mb I thought that match was a guard statement for some reason. If you change it to a guard statement with if self.res.is_imported_from(def, "@forge/api") == *"authorize" => Some(Intrinsic::Authorize(IntrinsicName::Other)),, then moving it above shouldn't swallow the secret scanner checks.

@jwong101 jwong101 merged commit b2d0f9d into atlassian-labs:main Nov 30, 2023
1 check passed
aliner-wang pushed a commit to aliner-wang/FSRT that referenced this pull request Jan 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants