Skip to content

Commit f252980

Browse files
authored
Merge pull request #12 from devaa-security/s5dev/ast-java-parsing
Add Java Parser for Parsing Java Source Code (AST)
2 parents 833e45a + a3297f8 commit f252980

7 files changed

Lines changed: 120 additions & 69 deletions

File tree

manifest-scanner/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@types/node": "^16.18.23",
3131
"@types/xml2js": "^0.4.11",
3232
"chai": "^4",
33+
"copyfiles": "^2.4.1",
3334
"eslint": "^7.32.0",
3435
"eslint-config-oclif": "^4",
3536
"eslint-config-oclif-typescript": "^1.0.3",
@@ -62,7 +63,8 @@
6263
"posttest": "npm run lint",
6364
"prepack": "npm run build && oclif manifest && oclif readme",
6465
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
65-
"version": "oclif readme && git add README.md"
66+
"version": "oclif readme && git add README.md",
67+
"copy-jar": "copyfiles -u 1 src/**/*.jar dist/"
6668
},
6769
"engines": {
6870
"node": ">=12.0.0"

manifest-scanner/src/commands/scan.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const fs = require('fs/promises')
66

77
export default class Scan extends Command {
88
static description =
9-
'DEVAA Manifest Scanner helps to scan for vulnerable configurations in Android Manifest file';
9+
'DEVAA Manifest Scanner helps to scan for vulnerable configurations in Android Studio Project';
1010

1111
issues = [];
1212

@@ -25,6 +25,10 @@ export default class Scan extends Command {
2525
char: 'o',
2626
description: 'Output File Path',
2727
}),
28+
enableAST: Flags.boolean({
29+
char: 'a',
30+
description: 'Enable AST to parse Android Studio Project Java & Kotlin source code',
31+
}),
2832
};
2933

3034
// static args = {
@@ -61,6 +65,7 @@ export default class Scan extends Command {
6165
AndroidManifestXML,
6266
filePath,
6367
flags.file,
68+
flags.enableAST
6469
)
6570

6671
const folders = [path.join(__dirname, '..', 'plugins', 'manifest')];

manifest-scanner/src/plugins/ManifestPlugin.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export default abstract class ManifestPlugin extends BasePlugin {
88
static targetSdk = -1;
99
static packageName = 'PACKAGE_NOT_FOUND';
1010
static androidProjectDirectory = '';
11+
static isASTEnabled = false
1112

1213
// add constructor accepting category, severity and description
1314
constructor(category: string, severity: Severity, description: string) {
@@ -19,11 +20,13 @@ export default abstract class ManifestPlugin extends BasePlugin {
1920
manifestXMLObject: any,
2021
ManifestPath: string,
2122
projectDirectory: any,
23+
isASTEnabled: boolean,
2224
) {
2325
// Users of this class should call this method instead of changing class attributes directly
2426
this.manifestXMLObject = manifestXMLObject
2527
this.manifestPath = ManifestPath
2628
this.androidProjectDirectory = projectDirectory
29+
this.isASTEnabled = isASTEnabled
2730

2831
try {
2932
this.minSdk =

manifest-scanner/src/plugins/manifest/ExportedComponentRule.ts

Lines changed: 105 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import {BaseJavaCstVisitorWithDefaults} from 'java-parser'
2-
import {ManifestPlugin} from '../ManifestPlugin'
3-
import {Severity, getRelativePath, searchKeywordInFile} from '../util'
4-
import {getJavaFiles} from '../../utils/fileutils'
5-
const {parse} = require('java-parser')
6-
import * as fs from 'node:fs'
1+
import { BaseJavaCstVisitorWithDefaults } from 'java-parser'
2+
import { ManifestPlugin } from '../ManifestPlugin'
3+
import { Severity, getRelativePath, searchKeywordInFile } from '../util'
4+
import path from 'node:path';
5+
import { execFileSync } from 'node:child_process';
6+
const { execFile } = require('child_process');
7+
78

89
export default class ExportedComponentRule extends ManifestPlugin {
910
BAD_EXPORTED_TAGS = [
@@ -52,8 +53,6 @@ export default class ExportedComponentRule extends ManifestPlugin {
5253
'getShortExtra',
5354
'getStringArrayExtra',
5455
'getStringArrayListExtra',
55-
'getString',
56-
'getInt',
5756
];
5857

5958
PROTECTED_BROADCASTS = [
@@ -250,26 +249,33 @@ if the Intent carries data that is tainted (2nd order injection)`;
250249
}
251250
}
252251

253-
// // write a code to traverse directory recursively and get all java files
254-
// const directoryPath =
255-
// 'C:\\Users\\Shiva\\AndroidStudioProjects\\DEVAAVulnerableApp'
256-
// let javaFiles = []
257-
// javaFiles = getJavaFiles(directoryPath)
258-
// // console.log(javaFiles)
259-
260-
// for (const javaFile of javaFiles) {
261-
// // read file using fs
262-
// // const file = fs.readFileSync(javaFile, "utf8");
263-
// // // console.log(file);
264-
// // const cst = parse(file);
265-
// // // console.log(cst);
266-
// // const methodcollector = new MethodCollector();
267-
// // // The CST result from the previous code snippet
268-
// // methodcollector.visit(cst);
269-
// // methodcollector.customResult.forEach((arrowOffset) => {
270-
// // console.log(arrowOffset);
271-
// // });
252+
// // check if enableAST flag is set
253+
// if (ManifestPlugin.isASTEnabled) {
254+
// const resourceDir = path.resolve(path.join(__dirname, "..", "..", "resource"))
255+
256+
// const javaPath = 'java';
257+
// const jarPath = path.join(resourceDir, 'android-project-parser-1.0-SNAPSHOT-shaded.jar');
258+
// const className = 'MainActivity';
259+
260+
// const args = [
261+
// '-jar',
262+
// jarPath,
263+
// 'find-methods-declaration-invocations-arguments',
264+
// ManifestPlugin.androidProjectDirectory,
265+
// className
266+
// ];
267+
268+
// execFile(javaPath, args, (error: Error | null, stdout: string, stderr: string) => {
269+
// if (error) {
270+
// console.error(`exec error: ${error}`);
271+
// } else {
272+
// const methodResults = JSON.parse(stdout)
273+
// console.log(methodResults[0].methodInvocations[6].methodName)
274+
// }
275+
// });
276+
272277
// }
278+
273279
}
274280

275281
checkManifestIssue(exported_tag: string, tag: any): void {
@@ -279,11 +285,48 @@ if the Intent carries data that is tainted (2nd order injection)`;
279285
const name = tag.$['android:name']
280286
const tag_info = TAG_INFO[exported_tag]
281287
const isProvider = exported_tag === 'provider'
288+
let methodResults = []
289+
let argumentVal: string[] = []
282290

283291
if (isExported === 'false') {
284292
return
285293
}
286294

295+
if (ManifestPlugin.isASTEnabled) {
296+
const resourceDir = path.resolve(path.join(__dirname, "..", "..", "resource"))
297+
298+
const javaPath = 'java';
299+
const jarPath = path.join(resourceDir, 'android-project-parser-1.0-SNAPSHOT-shaded.jar');
300+
let lastDotIndex = name.lastIndexOf('.');
301+
const className = name.substring(lastDotIndex + 1);
302+
303+
const args = [
304+
'-jar',
305+
jarPath,
306+
'find-methods-declaration-invocations-arguments',
307+
ManifestPlugin.androidProjectDirectory,
308+
className
309+
];
310+
311+
const result = execFileSync(javaPath, args);
312+
313+
if (result) {
314+
methodResults = JSON.parse(result.toString());
315+
if (!methodResults.errorMessage && methodResults.length > 0) {
316+
let declaredMethods = methodResults;
317+
declaredMethods.forEach((declaredMethod: { methodInvocations: any[]; }) => {
318+
if (declaredMethod.methodInvocations.length > 0) {
319+
declaredMethod.methodInvocations.forEach((methodInvocation: { methodName: string, arguments: [] }) => {
320+
if (this.EXTRAS_METHOD_NAMES.includes(methodInvocation.methodName)) {
321+
argumentVal = argumentVal.concat(methodInvocation.arguments)
322+
}
323+
});
324+
}
325+
});
326+
}
327+
}
328+
}
329+
287330
if ((isExported && isExported !== 'false') || isProvider) {
288331
if (
289332
(isProvider && ManifestPlugin.minSdk > 16) ||
@@ -299,7 +342,7 @@ if the Intent carries data that is tainted (2nd order injection)`;
299342
)
300343

301344
let description = this.EXPORTED_AND_PERMISSION_TAG
302-
description = description.replace('{tag}', exported_tag)
345+
description = description.replaceAll('{tag}', exported_tag)
303346
description = description.replace('{tag_name}', name)
304347

305348
this.issues.push({
@@ -311,6 +354,11 @@ if the Intent carries data that is tainted (2nd order injection)`;
311354
ManifestPlugin.androidProjectDirectory,
312355
ManifestPlugin.manifestPath,
313356
),
357+
exploit: {
358+
"exported_enum": name,
359+
"tag_name": exported_tag,
360+
"arguments": argumentVal,
361+
},
314362
line: result?.line,
315363
start_column: result?.start_column,
316364
end_column: result?.end_column,
@@ -322,7 +370,7 @@ if the Intent carries data that is tainted (2nd order injection)`;
322370
)
323371

324372
let description = this.EXPORTED
325-
description = description.replace('{tag}', exported_tag)
373+
description = description.replaceAll('{tag}', exported_tag)
326374
description = description.replace('{tag_name}', name)
327375

328376
this.issues.push({
@@ -334,6 +382,12 @@ if the Intent carries data that is tainted (2nd order injection)`;
334382
ManifestPlugin.androidProjectDirectory,
335383
ManifestPlugin.manifestPath,
336384
),
385+
exploit: {
386+
"exported_enum": name,
387+
"tag_name": exported_tag,
388+
"package_name": ManifestPlugin.packageName,
389+
"arguments": argumentVal
390+
},
337391
line: result?.line,
338392
start_column: result?.start_column,
339393
end_column: result?.end_column,
@@ -355,7 +409,7 @@ if the Intent carries data that is tainted (2nd order injection)`;
355409
if (this.PROTECTED_BROADCASTS.includes(actionName)) {
356410

357411
let description = this.EXPORTED_IN_PROTECTED
358-
description = description.replace('{tag}', exported_tag)
412+
description = description.replaceAll('{tag}', exported_tag)
359413
description = description.replace('{tag_name}', name)
360414

361415
this.issues.push({
@@ -367,14 +421,20 @@ if the Intent carries data that is tainted (2nd order injection)`;
367421
ManifestPlugin.androidProjectDirectory,
368422
ManifestPlugin.manifestPath,
369423
),
424+
exploit: {
425+
"exported_enum": name,
426+
"tag_name": exported_tag,
427+
"package_name": ManifestPlugin.packageName,
428+
"arguments": argumentVal
429+
},
370430
line: result?.line,
371431
start_column: result?.start_column,
372432
end_column: result?.end_column,
373433
})
374434
} else if (permission && ManifestPlugin.minSdk < 20) {
375435

376436
let description = this.EXPORTED_AND_PERMISSION_TAG
377-
description = description.replace('{tag}', exported_tag)
437+
description = description.replaceAll('{tag}', exported_tag)
378438
description = description.replace('{tag_name}', name)
379439

380440
this.issues.push({
@@ -386,14 +446,20 @@ if the Intent carries data that is tainted (2nd order injection)`;
386446
ManifestPlugin.androidProjectDirectory,
387447
ManifestPlugin.manifestPath,
388448
),
449+
exploit: {
450+
"exported_enum": name,
451+
"tag_name": exported_tag,
452+
"package_name": ManifestPlugin.packageName,
453+
"arguments": argumentVal
454+
},
389455
line: result?.line,
390456
start_column: result?.start_column,
391457
end_column: result?.end_column,
392458
})
393459
} else {
394460

395461
let description = this.EXPORTED
396-
description = description.replace('{tag}', exported_tag)
462+
description = description.replaceAll('{tag}', exported_tag)
397463
description = description.replace('{tag_name}', name)
398464

399465
this.issues.push({
@@ -404,6 +470,12 @@ if the Intent carries data that is tainted (2nd order injection)`;
404470
ManifestPlugin.androidProjectDirectory,
405471
ManifestPlugin.manifestPath,
406472
),
473+
exploit: {
474+
"exported_enum": name,
475+
"tag_name": exported_tag,
476+
"package_name": ManifestPlugin.packageName,
477+
"arguments": argumentVal
478+
},
407479
name: 'Exported Components Check',
408480
line: result?.line,
409481
start_column: result?.start_column,
@@ -454,36 +526,4 @@ const TAG_INFO: any = {
454526
activity: Activity,
455527
'activity-alias': Activity,
456528
service: Service,
457-
}
458-
459-
class MethodCollector extends BaseJavaCstVisitorWithDefaults {
460-
customResult: any[];
461-
constructor() {
462-
super()
463-
this.customResult = []
464-
this.validateVisitor()
465-
}
466-
467-
// methodDeclarator - method declaration
468-
// method
469-
470-
// This method override gives you actual method declaration like onCreate, onClick, onCreateOptionsMenu
471-
methodDeclarator(ctx: any) {
472-
// console.log(ctx.Identifier[0].image);
473-
this.customResult.push(ctx.Identifier[0].image)
474-
}
475-
476-
// this method resembles method invocation call
477-
// methodInvocationSuffix(ctx: any) {
478-
// console.log(ctx)
479-
// }
480-
481-
// this method gets you the method invoker argument list
482-
// argumentList(ctx: any) {
483-
// console.log(ctx.expression);
484-
// }
485-
486-
// importDeclaration(ctx: any) {
487-
// console.log(ctx.packageOrTypeName[0].children.Identifier);
488-
// }
489-
}
529+
}
Binary file not shown.
Binary file not shown.

manifest-scanner/tsconfig.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
"outDir": "dist",
77
"rootDir": "src",
88
"strict": true,
9-
"target": "es2019",
9+
"target": "es2021",
1010
"esModuleInterop": true
1111
},
12-
"include": ["src/**/*"]
12+
"include": ["src/**/*"],
13+
"lib": ["es2021"]
1314
}

0 commit comments

Comments
 (0)