├── .classpath
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── doubleclick.gif
└── google.gif
├── .gitignore
├── .gitmodules
├── .project
├── .settings
└── org.eclipse.buildship.core.prefs
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── LICENSE
├── README.md
├── build.gradle
├── config.json
├── docs
├── configuration.md
└── technical-details.md
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── release
└── eslinter-all.jar
└── src
├── burp
├── BurpExtender.java
├── CheckPaths.java
├── Config.java
├── Detective.java
└── Extractor.java
├── database
├── Database.java
└── SelectResult.java
├── gui
└── BurpTab.java
├── lint
├── Beautify.java
├── Lint.java
├── LintTask.java
├── LintingThread.java
├── Metadata.java
├── ProcessRequestTask.java
├── ProcessResponseTask.java
└── UpdateTableThread.java
├── linttable
├── LintResult.java
├── LintTable.java
└── LintTableModel.java
├── resources
└── db
│ ├── add_row.sql
│ ├── check_already_processed-trigger.sql
│ ├── create_table.sql
│ ├── get-all_rows.sql
│ ├── get_new_rows.sql
│ ├── update_hash-trigger.sql
│ └── update_row.sql
└── utils
├── BurpLog.java
├── Constants.java
├── CustomException.java
├── Exec.java
├── FileChooser.java
├── Header.java
├── PausableExecutor.java
├── ReqResp.java
├── Resources.java
├── StringUtils.java
└── SystemUtils.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Please fill the bug report information as much as you can
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Error Message**
14 | ```
15 | Paste the error message from Burp here.
16 | ```
17 |
18 | **Platform**
19 | E.g., Windows, Linux, OpenFreeNetBSD.
20 |
21 | **Config file**
22 | Please remove any identifying information from the config file (especially in
23 | the paths).
24 |
25 | ```json
26 | Paste your config file here.
27 | ```
28 |
29 | **Screenshots (optional)**
30 | Remove this section if it's not used. If applicable, add screenshots to help
31 | explain your problem. Not necessary.
32 |
33 | **Additional context (optional)**
34 | Remove this section if it's not used. Add any other context about the problem
35 | here.
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **If the feature is already mentioned in the FAQ. (e.g., "Creating Burp issues from ESLint results."), why is it important?**
11 | Please tell me why you need this feature and why it's important.
12 |
13 | **Is your feature request related to a problem? Please describe.**
14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
15 |
16 | **Describe the solution you'd like**
17 | A clear and concise description of what you want to happen.
18 |
--------------------------------------------------------------------------------
/.github/doubleclick.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/parsiya/eslinter/f2b58eaee43cd724678e04c602f3844a67a69558/.github/doubleclick.gif
--------------------------------------------------------------------------------
/.github/google.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/parsiya/eslinter/f2b58eaee43cd724678e04c602f3844a67a69558/.github/google.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | # *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | # project specific ignores
26 | .gradle
27 | build
28 | bin
29 | release/config.json
30 |
31 | # burp console and error output files
32 | console-output.txt
33 | error-output.txt
34 | *.exe
35 |
36 | # test directories
37 | nem
38 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "eslint-security"]
2 | path = eslint-security
3 | url = https://github.com/parsiya/eslint-security
4 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | eslint-burp
4 | Project eslint-burp created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
25 | 1614986489126
26 |
27 | 30
28 |
29 | org.eclipse.core.resources.regexFilterMatcher
30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | arguments=
2 | auto.sync=false
3 | build.scans.enabled=false
4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
5 | connection.project.dir=
6 | eclipse.preferences.version=1
7 | gradle.user.home=
8 | java.home=C\:/Program Files/AdoptOpenJDK/jdk-11.0.7.10-hotspot
9 | jvm.arguments=
10 | offline.mode=false
11 | override.workspace.settings=true
12 | show.console.view=true
13 | show.executions.view=true
14 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "java",
6 | "name": "BurpExtension",
7 | "request": "attach",
8 | "hostName": "localhost",
9 | "port": 8000 // Change this if you had set a different debug port.
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "java.configuration.updateBuildConfiguration": "automatic",
3 | "files.exclude": {
4 | "**/.classpath": true,
5 | "**/.project": true,
6 | "**/.settings": true,
7 | "**/.factorypath": true
8 | }
9 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "gradle",
8 | "type": "shell",
9 | "command": "gradlew.bat bigjar", // Wrapper on Windows
10 | // "command": "gradlew bigjar", // Wrapper on *nix
11 | "group": {
12 | "kind": "build",
13 | "isDefault": true
14 | }
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Manual JavaScript Linting is a Bug
2 | `ESLinter` is a Burp extension that extracts JavaScript from responses and lints
3 | them with [ESLint][eslint-org] while you do your manual testing.
4 |
5 | [eslint-org]: https://eslint.org/
6 |
7 | ## Features
8 |
9 | 1. Use your own artisanal hand-crafted ESLint rules.
10 | * Extend Burp's JavaScript analysis engine.
11 | 2. Pain-free setup.
12 | * Get up and running with three commands.
13 | 3. Results Are stored in two different places.
14 | * SQLite is forever.
15 | 4. It doesn't interrupt your work flow.
16 | * Let the extension lint while you do your magic.
17 | 5. It's hella configurable.
18 | * Running Burp on a slow machine? Reduce the number of threads.
19 | * Don't want to lint now? Click that shiny `Process` button to pause it.
20 | * Want to close Burp? No problem. Unfinished tasks will be read from the
21 | database and executed when the extension is loaded again.
22 | * Want to only process requests from certain hosts? Add it to the scope and
23 | set the associated key in the config file to `true`.
24 | * Don't like large JavaScript files? Set the max size in the config.
25 | * Want to process requests from another extension? See [Process Requests Made by Other Extensions](#process-requests-made-by-other-extensions).
26 | 6. Filter results by host.
27 | * Start typing in the text field in the extension tab.
28 |
29 | 
30 |
31 | ## Quickstart
32 |
33 | 1. Install `git`, `npm` and `JDK 11`.
34 | 1. [AdoptOpenJDK 11][adoptopenjdk11] is recommended. Make sure `JAVA_HOME`
35 | is set.
36 | 2. Clone the repository.
37 | 3. `gradlew -q clean`. Not needed for a fresh installation.
38 | 4. `gradlew -q install`
39 | 1. Clones the `eslint-security` git submodule.
40 | 2. Runs `npm install` in `eslint-security`.
41 | 5. `gradlew -q config -Ptarget=/relative/or/absolute/path/to/your/desired/project/location`
42 | 1. E.g., `gradlew -q config -Ptarget=testproject` creates a directory named
43 | `testproject` inside the `eslinter` directory.
44 | 2. Creates `config.json` in the `release` directory with a sane configuration.
45 | 6. Add the extension jar at `release/eslint-all.jar` to Burp.
46 | 1. The first time a new config is loaded, you might get an error not being
47 | able to connect to the database, this is OK.
48 | 7. Navigate to the `ESLinter` tab and click on the `Process` button.
49 | 8. Browse the target website normally with Burp as proxy.
50 | 9. Observe the extracted JavaScript being linted.
51 | 10. Look in the project directory to view all extracted and linted files.
52 | 11. Double-click on any result to open a dialog box. Choose a path to save both
53 | the beautified JavaScript and lint results.
54 |
55 | * For build troubleshooting please see [Building the
56 | Extension](#building-the-extension) below.
57 |
58 | **Double click in action**
59 |
60 | 
61 |
62 | [adoptopenjdk11]: https://adoptopenjdk.net/?variant=openjdk11&jvmVariant=hotspot
63 |
64 | ## Table of Content
65 |
66 | - [Features](#features)
67 | - [Quickstart](#quickstart)
68 | - [Extension Configuration](#extension-configuration)
69 | - [Change the ESLint Rules](#change-the-eslint-rules)
70 | - [Change the ESLint Rule File](#change-the-eslint-rule-file)
71 | - [Change the Number of Linting Threads](#change-the-number-of-linting-threads)
72 | - [Process Requests Made by Other Extensions](#process-requests-made-by-other-extensions)
73 | - [Process Requests Made by Other Burp Tools](#process-requests-made-by-other-burp-tools)
74 | - [Customize ESLint Rules](#customize-eslint-rules)
75 | - [Triage The Results](#triage-the-results)
76 | - [Technical Details](#technical-details)
77 | - [Common Bugs](#common-bugs)
78 | - [Supported Platforms](#supported-platforms)
79 | - [The Connection to the Database Is Not Closed](#the-connection-to-the-database-is-not-closed)
80 | - [My Selected Row is Gone](#my-selected-row-is-gone)
81 | - [FAQ](#faq)
82 | - [Why Doesn't the Extension Create Burp Issues?](#why-doesnt-the-extension-create-burp-issues)
83 | - [SHA-1 Is Broken](#sha-1-is-broken)
84 | - [Development](#development)
85 | - [Building the Extension](#building-the-extension)
86 | - [Development](#development-1)
87 | - [Diagnostics](#diagnostics)
88 | - [Debugging](#debugging)
89 | - [Credits](#credits)
90 | - [Lewis Ardern](#lewis-ardern)
91 | - [Jacob Wilkin](#jacob-wilkin)
92 | - [Tom Limoncelli](#tom-limoncelli)
93 | - [Similar Unreleased Extension by David Rook](#similar-unreleased-extension-by-david-rook)
94 | - [Source Code Credit](#source-code-credit)
95 | - [Future Work and Feedback](#future-work-and-feedback)
96 | - [License](#license)
97 |
98 | ## Extension Configuration
99 | It's recommended to use the `config` Gradle task. You can also create your own
100 | extension configs. Open the config file in any text editor and change the
101 | values. For in-depth configuration, please see
102 | [docs/configuration.md](docs/configuration.md).
103 |
104 | ### Change the ESLint Rules
105 |
106 | **Option 1:** If you used the config Gradle task.
107 |
108 | 1. Edit the `eslint-security/eslintrc-parsia.js` file and add/remove rules.
109 | 1. Make a copy first if you want to use it as a guideline.
110 | 2. Reload the extension.
111 |
112 | **Option 2:** If you want to keep your ESLint rules in a different path.
113 |
114 | 1. Create your own rules and store them at any path.
115 | 2. Edit the `release/config.json` file.
116 | 3. Change the `eslint-config-path` to the ESLint rule path from step 1.
117 | 4. Reload the extension.
118 |
119 | ### Change the ESLint Rule File
120 | Edit the `eslint-config-path` key in the `release/config.json` file and point it
121 | to your custom ESLint rule file.
122 |
123 | ### Change the Number of Linting Threads
124 | The number of linting threads can be configured. For slower machines, it might
125 | need to be reduced.
126 |
127 | 1. Edit the extension config file.
128 | 2. Change the value of `number-of-linting-threads`.
129 |
130 | ### Process Requests Made by Other Extensions
131 |
132 | 1. Add `extender` to the `process-tool-list` in the config file.
133 | 2. Move ESLinter to the bottom of your extension list in the Extender tab.
134 | 3. Reload the extension.
135 | 4. ESLinter should be able to see requests created by other extensions.
136 |
137 | ### Process Requests Made by Other Burp Tools
138 |
139 | 1. Add the tool name to the `process-tool-list` in the config file. E.g.,
140 | `Scanner`.
141 | 2. Move ESLinter to the bottom of your extension list in the Extender tab.
142 | 3. Reload the extension.
143 | 4. ESLinter should be able to see requests created by other Burp tools.
144 |
145 | ### Customize ESLint Rules
146 | Start by modifying one of the ESLint rule files in the
147 | [eslint-security][eslint-security] repository.
148 |
149 | To disable a rule either comment it out or change the numeric value of its key
150 | to `0`.
151 |
152 | If you are adding a rule that needs a new plugin you have to add it manually
153 | (usually via npm) to the location of your `eslint` and `js-beautify` commands.
154 |
155 | If you want to contribute your custom ESLint rules please feel free to create
156 | pull requests in [eslint-security][eslint-security].
157 |
158 | [eslint-security]: https://github.com/parsiya/eslint-security
159 |
160 | For more information on configuring ESLint and writing custom rules please see:
161 |
162 | * https://eslint.org/docs/user-guide/configuring
163 | * https://eslint.org/docs/developer-guide/working-with-rules
164 |
165 | ## Triage The Results
166 |
167 | 1. Open the project directory in your editor (set in the config command).
168 | 2. Open any file in the `linted` sub-directory. These files contain the results.
169 | 3. Alternatively, double-click any row in the extension's tab to select a
170 | directory to save both the original JavaScript and lint results for an
171 | individual request.
172 | 4. The extension uses the ESLint [codeframe][eslint-codeframe] output format.
173 | This format includes a few lines of code before and after what was flagged by
174 | ESLint. You can use these results to understand the context. This is usually
175 | not enough.
176 | 5. To view the corresponding JavaScript file, open the file with the same name
177 | (minus `-linted`) in the `beautified` sub-directory.
178 | 6. The json object at the top of every file contains the URL and the referer of
179 | the request that contained the JavaScript. Use this information to figure out
180 | where this JavaScript was located.
181 |
182 | [eslint-codeframe]: https://eslint.org/docs/user-guide/formatters/#codeframe
183 |
184 | ## Technical Details
185 | The innerworkings of the extension are discussed in
186 | [docs/technical-details.md](docs/technical-details.md).
187 |
188 | ## Common Bugs
189 | Make a Github issue if you encounter a bug. Please use the Bug issue template
190 | and fill it as much as you can. Be sure to remove any identifying information
191 | from the config file.
192 |
193 | ### Supported Platforms
194 | ESLinter was developed and tested on Windows and Burp 2.1. It should work on
195 | most platforms. If it does not please make a Github issue.
196 |
197 | ### The Connection to the Database Is Not Closed
198 | You cannot delete the database if you unload the extension.
199 |
200 | Workaround:
201 |
202 | * Close Burp and delete the file.
203 |
204 | ### My Selected Row is Gone
205 | The table in the extension tab is updated every few seconds (controlled via the
206 | `update-table-delay` key in the config file). This means your selected row will
207 | be unselected when the table updates. This is not an issue.
208 |
209 | This might look odd when double-clicking a row. The FileChooser dialog pops up
210 | to select a path. When the table is updated, the selection is visually gone.
211 | This is not an issue. The data in the row is retrieved when you double-click
212 | and is not interrupted when the row is deselected after the table update.
213 |
214 | ## FAQ
215 |
216 | ### Why Doesn't the Extension Create Burp Issues?
217 |
218 | 1. This is not a Burp pro extension. Burp Issues are supported in the pro
219 | version.
220 | 2. Depending on the ESLint rules, this will create a lot of noise.
221 |
222 | ### SHA-1 Is Broken
223 | Yes, but the extension uses SHA-1 to create a hash of JavaScript text. This hash
224 | is an identifier to detect duplicates. Adversarial collisions are not important
225 | here.
226 |
227 | ## Development
228 |
229 | ### Building the Extension
230 |
231 | 1. Install [AdoptOpenJDK 11][adoptopenjdk11]
232 | 1. Run `gradlew bigjar`.
233 | 2. The jar file will be stored inside the `release` directory.
234 |
235 | ### Development
236 |
237 | 1. Fork the repository.
238 | 2. Create a new branch.
239 | 3. Modify the extension.
240 | 4. Run `gradlew bigjar` to build it. Then test it in Burp.
241 | 5. Create a pull request. Please mention what has been modified.
242 |
243 | ### Diagnostics
244 | Set `"diagnostics": true` in the config file to see debug messages. These
245 | messages are useful when you are testing a single file in Burp Repeater. For
246 | more information, please see the `The Diagnostics Flag` section in
247 | [docs/configuration.md](docs/configuration.md).
248 |
249 | ### Debugging
250 | See the following blog post to see how you can debug Java Burp extensions in
251 | [Visual Studio Code][vscode-website]. The instructions can be adapted to use in
252 | other IDEs/editors.
253 |
254 | * https://parsiya.net/blog/2019-12-02-developing-and-debugging-java-burp-extensions-with-visual-studio-code/
255 |
256 | [vscode-website]: https://code.visualstudio.com/
257 |
258 | ## Credits
259 |
260 | ### Lewis Ardern
261 | For being a [Solid 5/7 JavaScript guy][lewis-twitter].
262 |
263 | See his presentation [Manual JavaScript Analysis is a Bug][lewis-slides].
264 |
265 | [lewis-twitter]: https://twitter.com/lewisardern
266 | [lewis-slides]: https://www.slideshare.net/LewisArdern/manual-javascript-anaylsis-is-a-bug-176308491
267 |
268 | ### Jacob Wilkin
269 | The original idea for the ESLinting JavaScript received in Burp was from the
270 | following blog post by [Jacob Wilkin][jacob-wilkin-twitter]:
271 |
272 | * https://medium.com/greenwolf-security/linting-for-bugs-vulnerabilities-49bc75a61c6
273 |
274 | Summary:
275 |
276 | 1. Browse the target and perform manual testing as usual.
277 | 2. Extract JavaScript from Burp.
278 | 3. Clean them up a bit and remove minified standard libraries.
279 | 4. Run ESLint with some security rules on the remaining JavaScript.
280 | 5. Triage the results.
281 | 6. ???
282 | 7. Profit.
283 |
284 | [jacob-wilkin-twitter]: https://twitter.com/jacob_wilkin
285 |
286 | ### Tom Limoncelli
287 | My main drive for automation comes from reading the amazing article named
288 | [Manual Work is a Bug][manual-work] by [Thomas Limoncelli][tom-twitter].
289 | **READ IT**.
290 |
291 | The article defines four levels of automation:
292 |
293 | 1. Document the steps.
294 | * Jacob's post above.
295 | 2. Create automation equivalents.
296 | * I created a prototype that linted JavaScript files after I extracted them
297 | from Burp manually.
298 | 3. Create automation.
299 | * This extension.
300 | 4. Self-service and autonomous systems.
301 | * Almost there in future work.
302 |
303 | [manual-work]: https://queue.acm.org/detail.cfm?id=3197520
304 | [tom-twitter]: https://twitter.com/yesthattom
305 |
306 | ### Similar Unreleased Extension by David Rook
307 | Searching for ["eslint burp" on Twitter][eslint-burp-twitter] returns a series
308 | of tweets from 2015 by [David Rook][david-rook-twitter]. It appears that he was
309 | working on a Burp extension that used ESLint to create issues. The extension was
310 | never released.
311 |
312 | [eslint-burp-twitter]: https://twitter.com/search?q=eslint%20burp&src=typed_query
313 | [david-rook-twitter]: https://twitter.com/davidrook
314 |
315 | ### Source Code Credit
316 | This extension uses a few open source libraries. You can see them in the
317 | `dependencies` section of the [build.gradle](build.gradle) file.
318 |
319 | In addition, it uses code copied from Apache Commons libraries. I copied
320 | individual files instead of the complete Apache Commons-Lang library.
321 |
322 | * [src/utils/StringUtils.java](src/utils/StringUtils.java) uses code from the
323 | Apache commons-lang.StringUtils.
324 | * [src/utils/SystemUtils](src/utils/SystemUtils.java) is an almost exact copy of
325 | Apache commons-lang.SystemUtils.
326 |
327 | ## Future Work and Feedback
328 | Please see the Github issues. If you have an idea, please make a Github issue
329 | and use the `Feature request` template.
330 |
331 | ## License
332 | Opensourced under the "GNU General Public License v3.0" and later. Please see
333 | [LICENSE](LICENSE) for details.
334 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Apply the Java plugin
2 | apply plugin: 'java'
3 |
4 | // Use Maven (because Burp Extender is on Maven)
5 | repositories {
6 | mavenCentral()
7 | }
8 |
9 | dependencies {
10 | // Add the Burp Extender interface
11 | // https://mvnrepository.com/artifact/net.portswigger.burp.extender/burp-extender-api
12 | compile group: 'net.portswigger.burp.extender', name: 'burp-extender-api', version: '2.1'
13 |
14 | // https://mvnrepository.com/artifact/com.google.code.gson/gson
15 | compile group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
16 |
17 | // https://mvnrepository.com/artifact/commons-io/commons-io
18 | compile group: 'commons-io', name: 'commons-io', version: '2.6'
19 |
20 | // https://mvnrepository.com/artifact/org.apache.commons/commons-exec
21 | compile group: 'org.apache.commons', name: 'commons-exec', version: '1.3'
22 |
23 | // https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc
24 | // Adds almost 6 MBs to the final jar file :(
25 | compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.30.1'
26 | }
27 |
28 | sourceSets {
29 | main {
30 | java {
31 | // Set the source directory to "src"
32 | srcDir 'src'
33 | // Exclude 'resources'
34 | exclude 'resources/'
35 | }
36 | }
37 | main {
38 | resources {
39 | // Set the resource directory to "src/resources"
40 | srcDir 'src/resources'
41 | }
42 | }
43 | }
44 |
45 | // Put the final jar file in a different location
46 | libsDirName = '../release'
47 |
48 | // Create a task for bundling all dependencies into a jar file.
49 | task bigJar(type: Jar) {
50 | baseName = project.name + '-all'
51 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
52 | with jar
53 | }
54 |
55 | // Extend clean to delete eslint-security
56 | clean.doFirst {
57 | println 'Deleting eslint-security'
58 | delete 'eslint-security'
59 | println 'Deleting release'
60 | delete 'release'
61 | }
62 |
63 | // Create the install task here.
64 |
65 | // OS Detection. Source: https://stackoverflow.com/a/54315477
66 | import org.apache.tools.ant.taskdefs.condition.Os
67 |
68 | // How to use example.
69 | // task executeCommand(type: Exec) {
70 | // commandLine osAdaptiveCommand('aws', 'ecr', 'get-login', '--no-include-email')
71 | // }
72 |
73 | private static Iterable osAdaptiveCommand(String... commands) {
74 | def newCommands = []
75 | if (Os.isFamily(Os.FAMILY_WINDOWS)) {
76 | newCommands = ['cmd', '/c']
77 | }
78 |
79 | newCommands.addAll(commands)
80 | return newCommands
81 | }
82 | // End of OS detection.
83 |
84 |
85 |
86 | // The install task will:
87 | // 1. Build the extension jar.
88 | // 2. Get the git submodule in `eslint-security`.
89 | // 3. Runs `npm-install` in `eslint-security`.
90 | task install() {
91 |
92 | // 2. Build the project via gradle.
93 | dependsOn 'bigJar'
94 |
95 | // If these are not in doLast, they will be executed in the configuration
96 | // phase. AKA every time any task is run.
97 | doLast {
98 | println '\ngit submodule update --init --recursive --remote'
99 | exec {
100 | // 2. Get the eslint-security git submodule.
101 | commandLine osAdaptiveCommand('git', 'submodule', 'update', '--init', '--recursive', '--remote')
102 | }
103 |
104 | println '\ncd eslint-security & npm install'
105 | exec {
106 | // 3. Navigate to eslint-security and run npm install.
107 | commandLine osAdaptiveCommand('cd', 'eslint-security', '&&', 'npm', 'install')
108 | }
109 | }
110 | }
111 |
112 | import java.nio.file.Files
113 | import java.nio.file.Paths
114 | import java.io.File
115 |
116 | task config() {
117 | doLast {
118 | // Check if target was provided.
119 | // 1. Read a command-line parameter. This will be the project path.
120 | if (!project.hasProperty('target')) {
121 | // Return an error if target is not provided.
122 | println 'Please provide the target path in this format'
123 | println 'Relative path: `gradlew config -Ptarget=/relative/path/to/target'
124 | println 'Absolute path: `gradlew config -Ptarget=c:/absolute/path/to/target'
125 | throw new GradleException('target parameter not provided')
126 | }
127 |
128 |
129 | String currentDir = System.properties['user.dir']
130 | // 2. Check if install has been called by checking for the existence of
131 | // currentDir/eslint-security/node_modules. If it's not there, return
132 | // an error.
133 | if (Files.isDirectory(Paths.get(currentDir, 'eslint-security/node_modules'))) {
134 | println 'eslint-security/node_modules exists'
135 | } else {
136 | throw new GradleException('`eslint-security/node_modules` does not exist, have you run `gradlew install')
137 | }
138 |
139 | // 3. Check if target is an absolute path.
140 | String targetDir = project.target
141 | File tar = new File(targetDir)
142 | if (!tar.isAbsolute()) {
143 | // If target is not absolute, concat target with the current working
144 | // directory and normalize it.
145 | targetDir = Paths.get(currentDir, targetDir).normalize()
146 | }
147 | // 4.1 Path to store extracted JavaScript files: `target/beautified`
148 | String beautified = Paths.get(targetDir, "beautified").toString().replace("\\", "/")
149 |
150 | // 4.2 Path to store ESLint results: `target/linted`
151 | String linted = Paths.get(targetDir, "linted").toString().replace("\\", "/")
152 |
153 | // 4.3 Location of the target database: `target/eslinter.sqlite`
154 | String db = Paths.get(targetDir, "eslinter.sqlite").toString().replace("\\", "/")
155 |
156 | // 4.4 Path to the eslint command: `currentDir/eslint-security/node_modules/.bin/eslint`
157 | String eslint = Paths.get(currentDir, 'eslint-security/node_modules/.bin/eslint').toString().replace("\\", "/")
158 |
159 | // 4.5 Path to the js-beautify command: `currentDir/eslint-security/node_modules/.bin/js-beautify`
160 | String jsbeautify = Paths.get(currentDir, 'eslint-security/node_modules/.bin/js-beautify').toString().replace("\\", "/")
161 |
162 | // 4.6 Detect OS. If Windows, add ".cmd" to the end of commands.
163 | if (System.properties['os.name'].toLowerCase().contains('windows')) {
164 | eslint += ".cmd"
165 | jsbeautify += ".cmd"
166 | }
167 |
168 | // 4.7 Path to the ESLint configuration file: `currentDir/eslint-security/configs/eslintrc-light.js`
169 | String cfgFile = Paths.get(currentDir, 'eslint-security/configs/eslintrc-parsia.js').toString().replace("\\", "/")
170 |
171 | // 5.0 Create the json file.
172 | String cfgStr =
173 | """
174 | {
175 | "beautified-javascript-path": "${beautified}",
176 | "lint-result-path": "${linted}",
177 | "database-path": "${db}",
178 | "eslint-config-path": "${cfgFile}",
179 | "eslint-command-path": "${eslint}",
180 | "jsbeautify-command-path": "${jsbeautify}",
181 | "only-process-in-scope": false,
182 | "highlight": true,
183 | "diagnostics": false,
184 | "process-tool-list": [
185 | "Proxy",
186 | "Scanner",
187 | "Repeater"
188 | ],
189 | "number-of-linting-threads": 3,
190 | "lint-timeout": 60,
191 | "number-of-request-threads": 10,
192 | "threadpool-timeout": 10,
193 | "lint-task-delay": 10,
194 | "update-table-delay": 5,
195 | "maximum-js-size": 0,
196 | "js-mime-types": [
197 | "application/javascript",
198 | "application/ecmascript",
199 | "application/x-ecmascript",
200 | "application/x-javascript",
201 | "text/javascript",
202 | "text/ecmascript",
203 | "text/javascript1.0",
204 | "text/javascript1.1",
205 | "text/javascript1.2",
206 | "text/javascript1.3",
207 | "text/javascript1.4",
208 | "text/javascript1.5",
209 | "text/jscript",
210 | "text/livescript",
211 | "text/x-ecmascript",
212 | "text/x-javascript",
213 | "script"
214 | ],
215 | "javascript-file-extensions": [
216 | "js",
217 | "javascript"
218 | ],
219 | "contains-javascript": [
220 | "text/html",
221 | "application/xhtml+xml"
222 | ],
223 | "removable-headers": [
224 | "If-Modified-Since",
225 | "If-None-Match"
226 | ]
227 | }
228 | """
229 |
230 | File configFile = new File(Paths.get(currentDir, 'release/config.json').toString())
231 | configFile.write(cfgStr)
232 |
233 | println "Configuration finished."
234 | println "Results will be stored in ${targetDir.toString().toString().replace("\\", "/")}."
235 | println "Config file is stored at ${Paths.get(currentDir,'release/config.json').toString().replace("\\", "/")}."
236 | println "Add the extension jar file to Burp and start linting."
237 |
238 | }
239 | }
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "beautified-javascript-path": "CHANGEME",
3 | "lint-result-path": "CHANGEME",
4 | "eslint-command-path": "CHANGEME",
5 | "eslint-config-path": "CHANGEME",
6 | "jsbeautify-command-path": "CHANGEME",
7 | "database-path": "CHANGEME",
8 | "only-process-in-scope": false,
9 | "highlight": true,
10 | "diagnostics": false,
11 | "process-tool-list": [
12 | "Proxy",
13 | "Scanner",
14 | "Repeater"
15 | ],
16 | "number-of-linting-threads": 3,
17 | "lint-timeout": 60,
18 | "number-of-request-threads": 10,
19 | "threadpool-timeout": 10,
20 | "lint-task-delay": 10,
21 | "update-table-delay": 5,
22 | "maximum-js-size": 0,
23 | "js-mime-types": [
24 | "application/javascript",
25 | "application/ecmascript",
26 | "application/x-ecmascript",
27 | "application/x-javascript",
28 | "text/javascript",
29 | "text/ecmascript",
30 | "text/javascript1.0",
31 | "text/javascript1.1",
32 | "text/javascript1.2",
33 | "text/javascript1.3",
34 | "text/javascript1.4",
35 | "text/javascript1.5",
36 | "text/jscript",
37 | "text/livescript",
38 | "text/x-ecmascript",
39 | "text/x-javascript",
40 | "script"
41 | ],
42 | "javascript-file-extensions": [
43 | "js",
44 | "javascript"
45 | ],
46 | "contains-javascript": [
47 | "text/html",
48 | "application/xhtml+xml"
49 | ],
50 | "removable-headers": [
51 | "If-Modified-Since",
52 | "If-None-Match"
53 | ]
54 | }
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 | `ESLinter` uses a config file in json format.
3 |
4 | - [Loading and Storing Configurations](#loading-and-storing-configurations)
5 | - [The Default Configuration File](#the-default-configuration-file)
6 | - [Saving and Loading Configuration Files](#saving-and-loading-configuration-files)
7 | - [Manual Configuration Steps](#manual-configuration-steps)
8 | - [Configuration File Elements](#configuration-file-elements)
9 | - [Storage Paths](#storage-paths)
10 | - [Command Paths](#command-paths)
11 | - [Highlight Requests](#highlight-requests)
12 | - [Process Requests Created by Specific Burp Tools](#process-requests-created-by-specific-burp-tools)
13 | - [Only Process Requests in Scope](#only-process-requests-in-scope)
14 | - [Performance](#performance)
15 | - [Configuring JavaScript Detection](#configuring-javascript-detection)
16 | - [Pure JavaScript](#pure-javascript)
17 | - [Embedded JavaScript](#embedded-javascript)
18 | - [Removing Request Headers](#removing-request-headers)
19 | - [The Diagnostics Flag](#the-diagnostics-flag)
20 |
21 | ## Loading and Storing Configurations
22 |
23 | ### The Default Configuration File
24 | At startup, the extension looks for a file named `config.json` in the same
25 | path as the jar file. That file will override the current configuration. If
26 | that file is modified, you need to reload the extension for the changes to take
27 | effect.
28 |
29 | For testing different configurations, create such a file and store it beside
30 | the jar file. This will ensure that you are always using a configuration that
31 | you have set.
32 |
33 | ### Saving and Loading Configuration Files
34 | To create a prepopulated config file, use the `gradlew config` task.
35 |
36 | To load a config file, use the `Load Config` button. `Save Config` saves the
37 | current configuration to a file.
38 |
39 | The extension saves the configuration to Burp's extension settings. There is no
40 | need to load the configuration file every time the extension starts. After a
41 | config is loaded, it will be reused (absent the existence of `config.json`
42 | explained above).
43 |
44 | ## Manual Configuration Steps
45 | It's recommended to use the `config` Gradle task. But you can also create your
46 | own config files.
47 |
48 | 1. Create a sample config file. This could be an existing one or a new one
49 | created by the config.
50 | 2. Edit the config file in your favorite editor.
51 | 3. At a minimum, you need to provide paths to (see
52 | [docs/configuration.md](docs/configuration.md) for more information):
53 | * `beautified-javascript-path`: Path to store extracted JavaScript files.
54 | * `lint-result-path`: Path to store ESLint results.
55 | * `database-path`: Location of the target database (it will be created if it
56 | does not exist).
57 | * `eslint-config-path`: Path to the ESLint configuration file.
58 | * `eslint-command-path`: Path to the `eslint` command.
59 | * `jsbeautify-command-path`: Path to the `js-beautify` command.
60 | 4. Modify any other settings. See the
61 | [Configuration File Elements](#configuration-file-elements) section.
62 | 5. Put the config file in the `release` directory or where the jar
63 | file is located.
64 |
65 | Note that Windows accepts paths with forward slashes. So
66 | `c:/eslint-security/node_modules/.bin/eslint.cmd` is a valid path. If you are
67 | providing paths with backslashes be sure to escape them. E.g.,
68 | `c:\\eslint-security\\nod_modules\\.bin\\eslint.cmd`.
69 |
70 | ## Configuration File Elements
71 | The configuration file provides several options to control the behavior of the
72 | extension.
73 |
74 | ### Storage Paths
75 | The extension stores every extracted JavaScript and every ESLint result on
76 | the file system, too. This can be used to quickly see every result without
77 | having to export it from the database.
78 |
79 | * `beautified-javascript-path`: Where all beautified JavaScripts are stored.
80 | Each file contains the extracted JavaScript for one request. It will be
81 | created (including any parent directories) if it does not exist.
82 | * `lint-result-path`: Where all ESLint results are stored. Each file contains
83 | the results for one file from above. These files have the same name as their
84 | JavaScript counterparts with `-linted` appended. For example, the results for
85 | `google.com-whatever.js` will be in `google.com-whatever-linted.js`. It will
86 | be created (including any parent directories) if it does not exist.
87 | * `database-path`: Path to the SQLite database file. If the file does not exist,
88 | it will be created.
89 |
90 | Inside each JavaScript file (and ESLint result file), there is a comment that
91 | identifies the URL and the referer. Using this information you can figure
92 | out where this JavaScript came from and how to apply the results.
93 |
94 | ### Command Paths
95 | The extension needs to know where it can run `eslint` and `js-beautify`
96 | commands. This information is in the following keys:
97 |
98 | * `eslint-command-path`
99 | * `jsbeautify-command-path`
100 |
101 | The git submodule [eslint-security][eslint-security] takes care of
102 | installing these commands and the ESLint plugins. The commands will be located
103 | in `eslint-security/node_modules/.bin/`.
104 |
105 | On Windows be sure to point these to `eslint.cmd` and `js-beautify.cmd` and not
106 | just `eslint` and `js-beautify`.
107 |
108 | [eslint-security]: https://github.com/parsiya/eslint-security
109 |
110 | ### Highlight Requests
111 | The extension can highlight requests in Burp's HTTP History. `"highlight" :true`
112 | enables this behavior. This can help you quickly figure out which requests have
113 | JavaScript.
114 |
115 | * `cyan`: Requests that point to a JavaScript resource. E.g.,
116 | `https://example.net/whatever.js`.
117 | * `yellow`: Requests that contain JavaScript but are not JavaScript files. These
118 | are mostly `text/html` files.
119 |
120 | The default value is `false`.
121 |
122 | ### Process Requests Created by Specific Burp Tools
123 | It's possible to tell the extension to only process requests/responses sent from
124 | certain Burp tools. For example, if you do not want to process anything coming
125 | from Proxy and are only interested in output from your own extension (or another
126 | Burp tool like Repeater), you can set it.
127 |
128 | This is controlled by the `process-tool-list` key in the config file. It
129 | contains an array where each element is the **name of the tool**. This is the
130 | result from the [IBurpExtenderCallbacks.getToolName][getToolName-doc] function.
131 | The following table shows all available options.
132 |
133 | | ToolFlag | getToolName |
134 | |----------------|-------------|
135 | | TOOL_SUITE | Suite |
136 | | TOOL_TARGET | Target |
137 | | TOOL_PROXY | Proxy |
138 | | TOOL_SPIDER | Scanner |
139 | | TOOL_SCANNER | Scanner |
140 | | TOOL_INTRUDER | Intruder |
141 | | TOOL_REPEATER | Repeater |
142 | | TOOL_SEQUENCER | Sequencer |
143 | | TOOL_DECODER | null |
144 | | TOOL_COMPARER | null |
145 | | TOOL_EXTENDER | Extender |
146 |
147 | Default values are:
148 |
149 | ```json
150 | "Proxy",
151 | "Scanner",
152 | "Repeater"
153 | ```
154 |
155 | [getToolName-doc]: https://portswigger.net/burp/extender/api/burp/IBurpExtenderCallbacks.html#getToolName(int)
156 |
157 | ### Only Process Requests in Scope
158 | By setting the `only-process-in-scope` key to `true`. The extension only
159 | processes requests set in the scope tab. This useful when you are only
160 | interested in JavaScript files in a specific scope.
161 |
162 | The extension uses the [IBurpExtenderCallbacks.isInScope][isinscope-doc]
163 | function to decide if a request is in scope.
164 |
165 | Note: This setting is not retroactive. Setting this to `false` or changing the
166 | scope does not go back and process all previous files that were received
167 | earlier. The extension only process requests when they are received and does
168 | look back in history.
169 |
170 | [isinscope-doc]: https://portswigger.net/burp/extender/api/burp/IBurpExtenderCallbacks.html#isInScope(java.net.URL)
171 |
172 | ### Performance
173 | linting and beautifying commands are computationally expensive. A single web
174 | page could load a few dozen JavaScript files or a large vendored file. You can
175 | configure the number of threads used by the extension to configure the load for
176 | your machine.
177 |
178 | `number-of-linting-threads` is the most important item in this section. You can
179 | most likely keep the default values. If you are running Burp on a slow machine
180 | or one without a lot of RAM, reduce this number.
181 |
182 | Note that most concurrent operations use threadpools. Meaning if you set a low
183 | number, nothing is lost and the work is queued. The results are also
184 | stored in the database, so if you unload the extension (or close Burp) in the
185 | middle of processing nothing is lost. Items can be processed when the
186 | extension is loaded again.
187 |
188 | * `number-of-linting-threads`: Number of concurrent thread beautifying and
189 | linting JavaScript. This is the most expensive operation. By default, the
190 | value of this key is `3`. Note that you can also stop processing using the
191 | `Process` toggle button in the extension interface.
192 | * `number-of-request-threads`: Every request and response is processed in a
193 | separate thread. This element controls the number of concurrent request and
194 | response processing threads.
195 | * `lint-timeout`: The maximum number of seconds for each beautifying and
196 | linting task. Increase this number if you are processing large JavaScript
197 | files.
198 | * `maximum-js-size`: Files over this number (in KiloBytes) are not processed.
199 | `0` disables this setting. This is useful if you are dealing a lot of 3rd
200 | party libraries or vendor files on a slower machine or you are not interested
201 | in large files.
202 | * `lint-task-delay`: Number of seconds to wait before reading new rows from the
203 | database and adding them to the linting threadpool. Increase this number if
204 | you are not dealing with a lot of JavaScript. An extension thread constantly
205 | reads from the database and adds the rows that are not processed to the
206 | linting threadpool. This is the number of the delay in seconds between reads.
207 | * `update-table-delay`: Number of seconds to wait before updating the table in
208 | the extension tab. Increase this number if you are not processing a lot of
209 | JavaScript files.
210 | * `threadpool-timeout`: Number of seconds to wait for the threadpool tasks to
211 | finish before shutdown. The threadpools are shutdown when a new config is
212 | loaded and when the extension is unloaded. Decrease this number if you are
213 | experimenting with new configurations or are testing the extension.
214 |
215 | ### Configuring JavaScript Detection
216 | You can configure what responses are looked at. The default values do a great
217 | job for most web applications. But if you have JavaScript in non-traditional
218 | files/extensions/content-types/MIME types you can add them here.
219 |
220 | From the extension's perspective, there are three kinds of request/response
221 | pairs:
222 |
223 | * "Pure" JavaScript: All of the content in the body of the response is
224 | JavaScript. E.g., js files.
225 | * Embedded JavaScript: The response contains some JavaScript. E.g., HTML files.
226 | * No JavaScript: The response does not have any JavaScript.
227 |
228 | #### Pure JavaScript
229 | All files with MIME types included in `js-mime-types` and URLs ending in
230 | extensions in `javascript-file-extensions` are considered pure JavaScript. The
231 | complete body of these responses will be stored in a file and linted.
232 |
233 | Note: This is different from files that have embedded JavaScript like HTML
234 | files. If your response body is not pure JavaScript, do not include them here.
235 | Including these files in these settings will only result in parsing errors. For
236 | these files see the [Embedded JavaScript](#embedded-javascript) section below.
237 |
238 | Burp has two MIME type detection methods for responses:
239 |
240 | * `getInferredMimeType()`
241 | * `getStatedMimeType()`
242 |
243 | These methods return `script` if Burp thinks the response is a JavaScript file.
244 | It's not always accurate but it's usually correct. If you are looking to process
245 | MIME types (returned by Burp) that are not in the default list (see the sample
246 | config file). Add them to the end of the list in your extension config file.
247 |
248 | The following URL lists all JavaScript MIME types (search for `text/javascript`
249 | in the page). It appears that `text/javascript` is the most common.
250 |
251 | * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
252 |
253 | ```json
254 | "js-mime-types": [
255 | "application/javascript",
256 | "application/ecmascript",
257 | "application/x-ecmascript",
258 | "application/x-javascript",
259 | "text/javascript",
260 | "text/ecmascript",
261 | "text/javascript1.0",
262 | "text/javascript1.1",
263 | "text/javascript1.2",
264 | "text/javascript1.3",
265 | "text/javascript1.4",
266 | "text/javascript1.5",
267 | "text/jscript",
268 | "text/livescript",
269 | "text/x-ecmascript",
270 | "text/x-javascript",
271 | "script"
272 | ]
273 | ```
274 |
275 | Any URL with extensions in `javascript-file-extensions` is considered pure
276 | JavaScript. If you have extensions that have JavaScript, add them here.
277 |
278 | ```json
279 | "javascript-file-extensions": [
280 | "js",
281 | "javascript"
282 | ],
283 | ```
284 |
285 | #### Embedded JavaScript
286 | The extension extracts the JavaScript in these responses. All text
287 | between `script` HTML tags is grabbed, beautified and ESLinted.
288 |
289 | The extension detects these responses through their `Content-Type` headers. Any
290 | content-type included in the `contains-javascript` item will be considered to
291 | have embedded JavaScript.
292 |
293 | ```json
294 | "contains-javascript": [
295 | "text/html",
296 | "application/xhtml+xml"
297 | ]
298 | ```
299 |
300 | Note: Adding pure JavaScript responses here will result in their JavaScript not
301 | detected. Because pure JavaScript files do not wrap their content in `script`
302 | HTML tags.
303 |
304 | ### Removing Request Headers
305 | The extension supports removing headers from requests. Any header included in
306 | `removable-headers` will be removed from requests processed by the
307 | extension.
308 |
309 | This is useful for removing cache-control headers. Applications and browsers
310 | usually try to re-use cached assets. If a cached asset is requested and these
311 | headers are not removed, the response will be a [304 Not Modified][304-docs]
312 | with no content. Such a response is useless to the extension.
313 |
314 | Note: The extension does a good job of detecting duplicate resources and reusing
315 | lint results. See the [technical-details.md](technical-details.md) file for
316 | details.
317 |
318 | [304-docs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304
319 |
320 | ### The Diagnostics Flag
321 | Setting `diagnostics` to `true` will print diagnostics messages to the
322 | extension's console and add headers to the responses.
323 |
324 | These headers are `Is-Script`, `Contains-Script` and `MIMETYPEs` and can be used
325 | to see how the JavaScript detection works on responses.
326 |
327 | It is intended for troubleshooting and testing. The best way to use it is to
328 | isolate a single request/response that is causing the error. Enable diagnostics
329 | and send the request in Repeater to see how it is processed.
330 |
--------------------------------------------------------------------------------
/docs/technical-details.md:
--------------------------------------------------------------------------------
1 | # Technical Details
2 | This section talks about the technical details of the extension.
3 |
4 | - [Optimizations](#optimizations)
5 | - [JavaScript Detection](#javascript-detection)
6 | - [Duplicate Asset Detection](#duplicate-asset-detection)
7 | - [Using Threadpools](#using-threadpools)
8 | - [SQLite Database to Persists Data](#sqlite-database-to-persists-data)
9 | - [Request/Response Processing Logic](#requestresponse-processing-logic)
10 |
11 | ## Optimizations
12 |
13 | ### JavaScript Detection
14 | The extension uses some simple ways to detect JavaScript in responses. Most of
15 | it is guided by the items in the config file (see "Configuring JavaScript
16 | Detection" in [configuration.md](configuration.md)).
17 |
18 | * MIME type returned by Burp.
19 | * `Content-Type` response header.
20 | * URL extension. E.g., everything that ends in `.js`.
21 |
22 | ### Duplicate Asset Detection
23 | The extension generates the hash of all JavaScript in a response and uses it to
24 | detect duplicates. If a certain JavaScript file (or content) is processed before
25 | and exists in the database, it's not processed again its record is updated when
26 | it's entered into the database. This is done with the trigger
27 | `resources/db/update_hash-trigger.sql`.
28 |
29 | ### Using Threadpools
30 | Each request, response and compute task is added to a threadpool. But
31 | configuring the number of threads, we can optimize the extension for the machine
32 | and load. Data are queued and submitted to the threadpool and are not lost.
33 |
34 | ### SQLite Database to Persists Data
35 | Each request and response is stored in a SQLite database. Closing the extension
36 | before some are processed does not lose the data. When the extension is loaded
37 | again and the `Process` button is toggled, all rows will be read from the
38 | database and processed again.
39 |
40 | Every beautified JavaScript and its ESLint results are also stored on the file
41 | system.
42 |
43 | ## Request/Response Processing Logic
44 |
45 | 1. Check if we got a request.
46 | 2. If it's a request, remove the headers and return.
47 | 3. If it's a response, check for JavaScript.
48 | 4. Extract the JavaScript.
49 | 5. Check the database to see if the hash of the body is already in the table.
50 | 6. If the hash exists.
51 | 1. Copy `beautified_javascript`, `status`, `results`, `is_processed` and
52 | `number_of_results`.
53 | 2. If `is_processed == 0`, then the rest of the columns do not have valid
54 | data and will be populated when this hash is processed.
55 | 3. Store the beautified JS file and results in their correct places.
56 | 4. Go to 8.
57 | 7. If the hash does not exist.
58 | 1. Beautify the extracted JS.
59 | 2. Populate the rest of the columns.
60 | 1. `beautified_javascript`: Beautified extracted JS.
61 | 2. `status` = pending.
62 | 3. `results` = empty. Don't care.
63 | 4. `is_processed` = 0.
64 | 5. `number_of_results` = 0. Don't care.
65 | 3. Store the beautified JS file and results in their correct places.
66 | 4. Go to 8.
67 | 8. Add the request to the table.
68 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/parsiya/eslinter/f2b58eaee43cd724678e04c602f3844a67a69558/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=`expr $i + 1`
158 | done
159 | case $i in
160 | 0) set -- ;;
161 | 1) set -- "$args0" ;;
162 | 2) set -- "$args0" "$args1" ;;
163 | 3) set -- "$args0" "$args1" "$args2" ;;
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=`save "$@"`
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | exec "$JAVACMD" "$@"
184 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/release/eslinter-all.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/parsiya/eslinter/f2b58eaee43cd724678e04c602f3844a67a69558/release/eslinter-all.jar
--------------------------------------------------------------------------------
/src/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.awt.Component;
4 | import java.io.File;
5 | import java.io.FileNotFoundException;
6 | import java.io.IOException;
7 | import java.security.NoSuchAlgorithmException;
8 | import java.sql.SQLException;
9 | import java.util.concurrent.ExecutorService;
10 | import java.util.concurrent.Executors;
11 | import java.util.concurrent.TimeUnit;
12 | import org.apache.commons.io.FileUtils;
13 | import org.apache.commons.io.FilenameUtils;
14 | import database.Database;
15 | import gui.BurpTab;
16 | import lint.Metadata;
17 | import lint.LintingThread;
18 | import lint.ProcessRequestTask;
19 | import lint.ProcessResponseTask;
20 | import lint.UpdateTableThread;
21 | import utils.BurpLog;
22 | import utils.CustomException;
23 | import utils.PausableExecutor;
24 | import utils.ReqResp;
25 | import utils.StringUtils;
26 |
27 | public class BurpExtender implements IBurpExtender, ITab, IHttpListener, IExtensionStateListener {
28 |
29 | public static IBurpExtenderCallbacks callbacks;
30 | public static IExtensionHelpers helpers;
31 | public static Config extensionConfig;
32 | public static BurpLog log;
33 | public static BurpTab mainTab;
34 | public static Database db;
35 | public static volatile boolean keepThread;
36 | public static PausableExecutor lintPool;
37 |
38 | private static ExecutorService requestPool;
39 | private static ExecutorService responsePool;
40 | private static UpdateTableThread updateThread;
41 | private static LintingThread lintThread;
42 |
43 |
44 | /**
45 | * Implement IBurpExtender.
46 | */
47 | @Override
48 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks burpCallbacks) {
49 |
50 | callbacks = burpCallbacks;
51 | helpers = callbacks.getHelpers();
52 | // Set the extension name.
53 | callbacks.setExtensionName(Config.extensionName);
54 |
55 | // Create the logger.
56 | log = new BurpLog(true);
57 |
58 | // Use the default config. This is needed in case there is no config
59 | // saved or there is no default config file.
60 | extensionConfig = getDefaultConfig();
61 |
62 | // Check if the default config file exists.
63 | String defaultConfigFile = Config.getDefaultConfigFullPath();
64 |
65 | // This means that if the default config file contains a bad config, we
66 | // will not silenty fallback to the saved extension settings.
67 | if (CheckPaths.fileExists(defaultConfigFile)) {
68 | // Search for the default config file and load it if it exists.
69 | loadDefaultConfigFile(Config.defaultConfigName);
70 | } else {
71 | // Load the saved config file from the extension settings (if any).
72 | loadSavedConfig();
73 | }
74 |
75 | // Check and create paths. We might get errors if we are loading a new
76 | // config but users should be able diagnose that.
77 | try {
78 | CheckPaths.checkAndCreatePaths(extensionConfig);
79 | } catch (Exception e) {
80 | // Issue41 - catch everything here.
81 | log.error("%s", StringUtils.getStackTrace(e));
82 | log.alert("Bad config file, check the extension's error tab");
83 | }
84 | log.debug("Finished path check.");
85 |
86 | // Set the debug flag from the loaded config.
87 | log.setDebugMode(extensionConfig.diagnostics);
88 |
89 | // Configure the process request and response threadpools.
90 | requestPool = Executors.newFixedThreadPool(extensionConfig.numRequestThreads);
91 | responsePool = Executors.newFixedThreadPool(extensionConfig.numRequestThreads);
92 |
93 | // Configure the beautify executor service.
94 | // pool = Executors.newFixedThreadPool(extensionConfig.numberOfThreads);
95 | lintPool = new PausableExecutor(extensionConfig);
96 | log.debug("Using %d threads.", extensionConfig.numLintThreads);
97 | // Default is paused.
98 | lintPool.pause();
99 | // Create the ProcessLintQueue object and assigned the threadpool.
100 | lintThread = new LintingThread(extensionConfig, lintPool);
101 | lintThread.start();
102 |
103 | // Connect to the database (or create it if it doesn't exist). If the
104 | // database connection is not established before the threads start, we
105 | // might have issues.
106 | try {
107 | databaseConnect(extensionConfig.dbPath);
108 | } catch (SQLException | IOException e) {
109 | log.error("%s", StringUtils.getStackTrace(e));
110 | log.alert(
111 | "Could not connect to the database: %s",
112 | e.getMessage()
113 | );
114 | }
115 |
116 | // Create the table update thread.
117 | updateThread = new UpdateTableThread(extensionConfig.updateTableDelay);
118 | updateThread.start();
119 |
120 | log.debug("Started both threads.");
121 |
122 | // Create the main tab.
123 | mainTab = new BurpTab();
124 | log.debug("Created the main tab.");
125 | callbacks.customizeUiComponent(mainTab.panel);
126 |
127 | // Add the tab to Burp.
128 | callbacks.addSuiteTab(BurpExtender.this);
129 | // Register the listener.
130 | callbacks.registerHttpListener(BurpExtender.this);
131 | // Register the extension state listener to handle extension unload.
132 | callbacks.registerExtensionStateListener(BurpExtender.this);
133 |
134 | log.debug("Loaded the extension. End of registerExtenderCallbacks.");
135 | }
136 |
137 | @Override
138 | public String getTabCaption() {
139 | return Config.tabName;
140 | }
141 |
142 | @Override
143 | public Component getUiComponent() {
144 | // Return the tab here.
145 | return mainTab.panel;
146 | }
147 |
148 | @Override
149 | public void processHttpMessage(final int toolFlag, final boolean isRequest, IHttpRequestResponse requestResponse) {
150 |
151 | if (requestResponse == null) return;
152 |
153 | // If it's a request, spawn a new thread and process it. We do not need
154 | // to worry about responses being processed before their requests
155 | // because the response will only arrive after the request is processed
156 | // and sent out. D'oh.
157 | if (isRequest) {
158 | // Use process requests so we can shut it down when unload the
159 | // extension.
160 | ProcessRequestTask processRequest =
161 | new ProcessRequestTask(
162 | toolFlag, requestResponse, extensionConfig
163 | );
164 |
165 | requestPool.execute(processRequest);
166 | return;
167 | }
168 |
169 | // Here we have responses.
170 | log.debug("----------");
171 | log.debug("Got a response.");
172 | // Create the metadata, it might not be needed if there's nothing in the
173 | // response but this is a small overhead for more readable code.
174 | Metadata metadata = new Metadata();
175 | try {
176 | metadata = ReqResp.getMetadata(requestResponse);
177 | } catch (final NoSuchAlgorithmException e) {
178 | // This should not happen because we are passing "SHA-1" to the
179 | // digest manually. If we do not have the algorithm in Burp then
180 | // we have bigger problems.
181 | final String errMsg = StringUtils.getStackTrace(e);
182 | log.alert(errMsg);
183 | log.error(
184 | "Error creating metadata, algo name is probably wrong: %s.",
185 | errMsg
186 | );
187 | log.debug("Returning from processHttpMessage because of %s", errMsg);
188 | return;
189 | }
190 | log.debug("Response metadata:\n%s", metadata.toString());
191 |
192 | // Here we have responses.
193 | final IResponseInfo respInfo = helpers.analyzeResponse(requestResponse.getResponse());
194 |
195 | String scriptHeader = "false";
196 | String containsScriptHeader = "false";
197 | String javascript = "";
198 |
199 | if (Detective.isScript(requestResponse)) {
200 | log.debug("Detected a script response.");
201 |
202 | if (extensionConfig.highlight) {
203 | scriptHeader = "true";
204 | requestResponse.setHighlight("cyan");
205 | }
206 |
207 | // Get the response body.
208 | final byte[] bodyBytes = ReqResp.getResponseBody(requestResponse);
209 | if (bodyBytes.length == 0) {
210 | log.debug("Empty response, returning from processHttpMessage.");
211 | return;
212 | }
213 | javascript = StringUtils.bytesToString(bodyBytes);
214 | } else if (Detective.containsScript(requestResponse)) {
215 | // Not a JavaScript file, but it might contain JavaScript.
216 | log.debug("Detected a contains-script response.");
217 |
218 | if (extensionConfig.highlight) {
219 | containsScriptHeader = "true";
220 | requestResponse.setHighlight("yellow");
221 | }
222 |
223 | // Extract any JavaScript from the response.
224 | javascript = Extractor.getJS(requestResponse.getResponse());
225 | }
226 |
227 | if (StringUtils.isEmpty(javascript)) {
228 | log.debug("Cound not find any in-line JavaScript, returning.");
229 | return;
230 | }
231 |
232 | // Don't uncomment this unless you are debugging in Repeater. It will
233 | // fill the debug log with noise.
234 | // log.debug("Extracted JavaScript:\n%s", javascript);
235 | // log.debug("End of extracted JavaScript ----------");
236 |
237 | // Set the debug headers. Having this after checking if extracted
238 | // JavaScript is empty prevents highlighting requests that do not have
239 | // any extracted JavaScript.
240 | if (extensionConfig.diagnostics) {
241 | requestResponse = ReqResp.addHeader(isRequest, requestResponse, "Is-Script", scriptHeader);
242 | requestResponse = ReqResp.addHeader(isRequest, requestResponse, "Contains-Script", containsScriptHeader);
243 | requestResponse = ReqResp.addHeader(isRequest, requestResponse, "MIMETYPEs",
244 | String.format("%s -- %s", respInfo.getInferredMimeType(), respInfo.getStatedMimeType()));
245 | }
246 |
247 | // Check for jsMaxSize.
248 | if (javascript.length() >= (extensionConfig.jsMaxSize * 1024) && extensionConfig.jsMaxSize != 0) {
249 | log.debug("Length of JavaScript: %d > %d threshold, returning.",
250 | javascript.length(), extensionConfig.jsMaxSize * 1024);
251 | return;
252 | }
253 |
254 | try {
255 | // Spawn a new processResponse task that adds the captured
256 | // JavaScript to the db.
257 | final Runnable processResponse = new ProcessResponseTask(
258 | javascript, metadata
259 | );
260 |
261 | responsePool.execute(processResponse);
262 |
263 | } catch (final Exception e) {
264 | log.debug("%s", StringUtils.getStackTrace(e));
265 | }
266 | }
267 |
268 | // Returns the default config. Default config is the default values for the
269 | // Config object as set in Config.java.
270 | private static Config getDefaultConfig() {
271 | return new Config();
272 | }
273 |
274 | // Get saved config.
275 | private static void loadSavedConfig() {
276 | // See if the extension config was saved in extension settings. If
277 | // default config was loaded from the file above, it will be saved.
278 | final String savedConfig = callbacks.loadExtensionSetting("config");
279 | String decodedConfig = "";
280 |
281 | if (StringUtils.isEmpty(savedConfig)) {
282 | // No saved config. Use the default version and prompt the user.
283 | log.alert("No saved config found, please choose one after the extension has loaded.");
284 | } else {
285 | // Base64 decode the config string.
286 | decodedConfig = StringUtils.base64Decode(savedConfig);
287 | extensionConfig = Config.configBuilder(decodedConfig);
288 | StringUtils.print("Config loaded from extension settings.");
289 | log.debug("Decoded config (if any):\n%s", decodedConfig);
290 | // log.debug("savedConfig: %s", savedConfig);
291 | }
292 | }
293 |
294 | private static void loadDefaultConfigFile(String cfgFileName) {
295 | // Check if there is a file named extensionConfig.defaultConfigName in
296 | // the current directory, if so, load it and overwrite the extension.
297 | try {
298 | String defaultConfigFullPath = Config.getDefaultConfigFullPath();
299 | File f = new File(defaultConfigFullPath);
300 |
301 | String cfgFile = FileUtils.readFileToString(f, StringUtils.UTF8);
302 | extensionConfig = Config.loadConfig(cfgFile);
303 | log.debug("Config loaded from default config file %s", defaultConfigFullPath);
304 | } catch (FileNotFoundException e) {
305 | log.debug(
306 | "Default config file '%s' was not found.",
307 | Config.defaultConfigName
308 | );
309 | } catch (Exception e) {
310 | // If anything goes wrong here, then something else was wrong other
311 | // than the file not having the correct content.
312 | log.debug(
313 | "Error loading default config file %s: %s",
314 | Config.defaultConfigName,
315 | StringUtils.getStackTrace(e)
316 | );
317 | log.debug("This is not a show stopper, the extension is will continue loading");
318 | }
319 | }
320 |
321 | // Connects to the database (or creates it if it does not exist).
322 | public static void databaseConnect(String dbPath) throws SQLException, IOException {
323 | // Create the database.
324 | db = new Database(dbPath);
325 | log.debug("Created a connection to the database: %s", dbPath);
326 | }
327 |
328 | // Invoked when the extension is unloaded.
329 | @Override
330 | public void extensionUnloaded() {
331 | log.debug("Starting to unload the extension");
332 | unloadExtension();
333 | // Stop the threads.
334 | updateThread.stop();
335 | lintThread.stop();
336 |
337 | log.debug("Unloaded the extension.");
338 | }
339 |
340 | // Shutdowns the threadpool and waits for the active threads to finish.
341 | // Closes the DB connection.
342 | public static void unloadExtension() {
343 | // Shutdown the threadpool and wait for termination.
344 | // https://stackoverflow.com/a/1250655
345 |
346 | if (requestPool != null) {
347 | // Shutdown requestPool. This should be quick.
348 | requestPool.shutdown();
349 | try {
350 | requestPool.awaitTermination(extensionConfig.threadpoolTimeout, TimeUnit.SECONDS);
351 | log.debug("requestPool terminated.");
352 | } catch (Exception e) {
353 | log.error("Could not terminate requestPool: %s", StringUtils.getStackTrace(e));
354 | }
355 | }
356 |
357 | if (responsePool != null) {
358 | // Shutdown responsePool. This should be quick.
359 | responsePool.shutdown();
360 | try {
361 | responsePool.awaitTermination(extensionConfig.threadpoolTimeout, TimeUnit.SECONDS);
362 | log.debug("responsePool terminated.");
363 | } catch (Exception e) {
364 | log.error("Could not terminate responsePool: %s", StringUtils.getStackTrace(e));
365 | }
366 | }
367 |
368 | if (lintPool != null) {
369 | // Losing the tasks in the pool but everything is stored in the db.
370 | lintPool.shutdownNow();
371 | try {
372 | lintPool.awaitTermination(extensionConfig.threadpoolTimeout, TimeUnit.SECONDS);
373 | log.debug("All threads are terminated.");
374 | } catch (InterruptedException e) {
375 | log.error("Could not terminate all threads: %s", StringUtils.getStackTrace(e));
376 | }
377 | }
378 |
379 | try {
380 | if (db != null) {
381 | db.close();
382 | log.debug("Closed the database connection");
383 | }
384 | } catch (SQLException e) {
385 | log.error("Error closing the database connection: %s", StringUtils.getStackTrace(e));
386 | }
387 | }
388 | }
--------------------------------------------------------------------------------
/src/burp/CheckPaths.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.io.File;
4 | import java.nio.file.Path;
5 | import java.nio.file.Paths;
6 | import utils.CustomException;
7 | import utils.Exec;
8 | import utils.StringUtils;
9 | import java.nio.file.Files;
10 |
11 | /**
12 | * CheckPaths has static methods to check paths in the config file.
13 | */
14 | public class CheckPaths {
15 |
16 | // Returns true if all paths checkout, otherwise throws an exception with all
17 | // the errors.
18 | public static boolean checkAndCreatePaths(Config extensionConfig) throws CustomException {
19 |
20 | String err = "";
21 |
22 | // Create storagePath and check access.
23 | if (createDirectory(extensionConfig.storagePath)) {
24 | // storagePath is created or exists.
25 | } else {
26 | // storagePath was not created.
27 | // Check if we can write to it.
28 | if (!canWrite(extensionConfig.storagePath)) {
29 | err += String.format(
30 | "Could not create or write to storagePath at %s.\n",
31 | extensionConfig.storagePath
32 | );
33 | }
34 | }
35 |
36 | // Create eslintOutputPath and check access.
37 | if (createDirectory(extensionConfig.lintOutputPath)) {
38 | // eslintOutputPath is created or exists.
39 | } else {
40 | // eslintOutputPath was not created.
41 | // Check if we can write to it.
42 | if (!canWrite(extensionConfig.lintOutputPath)) {
43 | err += String.format(
44 | "Could not create or write to eslintOutputPath at %s.\n",
45 | extensionConfig.lintOutputPath
46 | );
47 | }
48 | }
49 |
50 | // Run eslintCommandPath to check if eslint exists.
51 | String runESLint = commandExists(extensionConfig.eslintCommandPath);
52 | if (StringUtils.isNotEmpty(runESLint)) {
53 | err += String.format(
54 | "Could not run ESLint at %s: %s.\n",
55 | extensionConfig.eslintCommandPath,
56 | runESLint
57 | );
58 | }
59 |
60 | // Run jsBeautifyCommandPath to check if eslint exists.
61 | String runJSBeautify = commandExists(
62 | extensionConfig.jsBeautifyCommandPath,
63 | 1 // js-beautify returns 1 if run without any input or parameters.
64 | );
65 |
66 | if (StringUtils.isNotEmpty(runJSBeautify)) {
67 | err += String.format(
68 | "Could not run js-beautify at %s: %s.\n",
69 | extensionConfig.jsBeautifyCommandPath,
70 | runJSBeautify
71 | );
72 | }
73 |
74 | // Check if eslintConfigPath exists.
75 | if (!fileExists(extensionConfig.eslintConfigPath)) {
76 | err += String.format(
77 | "Could not find the ESLint rule file at %s.\n",
78 | extensionConfig.eslintConfigPath
79 | );
80 | }
81 |
82 | // Check if the directory with the database is writable. Connect will
83 | // take care of creating the file.
84 | String dbDirectory = StringUtils.getParentDirectory(extensionConfig.dbPath);
85 | if (createDirectory(dbDirectory)) {
86 | // dbDirectory is created or exists.
87 | } else {
88 | // dbDirectory was not created, check if we can write to it.
89 | if (!canWrite(dbDirectory)) {
90 | err += String.format(
91 | "Could not write to the database directory at %s.\n",
92 | dbDirectory
93 | );
94 | }
95 | }
96 |
97 | if (StringUtils.isNotEmpty(err)) {
98 | throw new CustomException(err);
99 | } else {
100 | return true;
101 | }
102 | }
103 |
104 | // Return an empty string if command exists and executes successfully.
105 | // Otherwise, it returns the exception.
106 | private static String commandExists(String path, int ...exitValues) {
107 | Exec cmd = new Exec(
108 | path,
109 | new String[] {""},
110 | StringUtils.getParentDirectory(path),
111 | exitValues
112 | );
113 |
114 | try {
115 | cmd.exec();
116 | } catch (Exception e) {
117 | return StringUtils.getStackTrace(e);
118 | }
119 | return "";
120 | }
121 |
122 |
123 | // Returns true if the application can write to the directory or file.
124 | private static boolean canWrite(String dir) {
125 | Path p = Paths.get(dir);
126 | return Files.isWritable(p);
127 | }
128 |
129 | // Creates the directory in dir. Returns true if directory was created. What
130 | // does it return if it already existed?
131 | private static boolean createDirectory(String dir) {
132 | File f = new File(dir);
133 | return f.mkdirs();
134 | }
135 |
136 | // Returns true if a file exists.
137 | public static boolean fileExists(String file) {
138 | return new File(file).exists();
139 | }
140 | }
--------------------------------------------------------------------------------
/src/burp/Config.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import static burp.BurpExtender.callbacks;
4 | import static burp.BurpExtender.log;
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.sql.SQLException;
8 | import com.google.gson.Gson;
9 | import com.google.gson.GsonBuilder;
10 | import com.google.gson.JsonSyntaxException;
11 | import com.google.gson.annotations.SerializedName;
12 | import org.apache.commons.io.FileUtils;
13 | import org.apache.commons.io.FilenameUtils;
14 | import utils.StringUtils;
15 |
16 |
17 | /**
18 | * Config
19 | */
20 | public class Config {
21 |
22 | // Transient fields are not serialized or deserialized.
23 | // This appears in Extender.
24 | final public static transient String extensionName = "ESLinter";
25 | // This is the extension's tab name.
26 | final public static transient String tabName = "ESLinter";
27 | // Table's column names.
28 | final public static transient String[] lintTableColumnNames =
29 | new String[] {"Host", "URL", "Status", "Number of Findings"};
30 | // Table's column classes.
31 | final public static transient Class[] lintTableColumnClasses =
32 | new Class[] {java.lang.String.class, java.lang.String.class, java.lang.String.class,
33 | java.lang.String.class // Although last column is int, we want it to be left-aligned.
34 | };
35 | // Maximum number of characters from the URL.
36 | final public static transient int urlFileNameLimit = 50;
37 | // Default config file name.
38 | final public static transient String defaultConfigName = "config.json";
39 |
40 | // End transient fields.
41 |
42 | // Storage path for extracted beautified JavaScript files.
43 | @SerializedName("beautified-javascript-path")
44 | public String storagePath = "";
45 |
46 | // Where lint results are stored.
47 | @SerializedName("lint-result-path")
48 | public String lintOutputPath = "";
49 |
50 | // Full path to the sqlite database file. It will be created if it does not
51 | // exist.
52 | @SerializedName("database-path")
53 | public String dbPath = "";
54 |
55 | // Path to the ESLint configuration file.
56 | @SerializedName("eslint-config-path")
57 | public String eslintConfigPath = "";
58 |
59 | // ESLint binary full path. [path]/node_modules/.bin/eslint
60 | @SerializedName("eslint-command-path")
61 | public String eslintCommandPath = "";
62 |
63 | // Full path to the js-beautify binary/command. [path]/node_modules/.bin/eslint
64 | @SerializedName("jsbeautify-command-path")
65 | public String jsBeautifyCommandPath = "";
66 |
67 | // If true, only in-scope requests will be processed.
68 | @SerializedName("only-process-in-scope")
69 | public boolean processInScope = false;
70 |
71 | // If true, requests containing JavaScript will be highlighted in history.
72 | @SerializedName("highlight")
73 | public boolean highlight = false;
74 |
75 | // If set to true, the extension will print extra information. This can be
76 | // used for troubleshooting.
77 | @SerializedName("diagnostics")
78 | public boolean diagnostics = true;
79 |
80 | // Only lint requests made by these tools. The names here must be the same
81 | // as the getToolName column (case-insensitive):
82 | // | ToolFlag | getToolName |
83 | // |----------------|-------------|
84 | // | TOOL_SUITE | Suite |
85 | // | TOOL_TARGET | Target |
86 | // | TOOL_PROXY | Proxy |
87 | // | TOOL_SPIDER | Scanner |
88 | // | TOOL_SCANNER | Scanner |
89 | // | TOOL_INTRUDER | Intruder |
90 | // | TOOL_REPEATER | Repeater |
91 | // | TOOL_SEQUENCER | Sequencer |
92 | // | TOOL_DECODER | null |
93 | // | TOOL_COMPARER | null |
94 | // | TOOL_EXTENDER | Extender |
95 | @SerializedName("process-tool-list")
96 | public String[] processToolList = new String[] {
97 | "Proxy",
98 | "Scanner",
99 | "Repeater"
100 | };
101 |
102 | // Maximum number of linting threads.
103 | @SerializedName("number-of-linting-threads")
104 | public int numLintThreads = 3;
105 |
106 | // How many seconds to wait for a linting task to complete. Increase this if
107 | // you are beautifying and linting huge files.
108 | @SerializedName("lint-timeout")
109 | public int lintTimeout = 60;
110 |
111 | // Maximum number of request/response processing threads.
112 | // These tasks are light-weight.
113 | @SerializedName("number-of-request-threads")
114 | public int numRequestThreads = 10;
115 |
116 | // Threadpool shutdown timeout in seconds. How many seconds to wait before
117 | // shutting down threadpools when unloading the extension.
118 | @SerializedName("threadpool-timeout")
119 | public int threadpoolTimeout = 10;
120 |
121 | // The number of seconds the lint task sleeps between reading new lint tasks
122 | // from the database.
123 | @SerializedName("lint-task-delay")
124 | public int lintTaskDelay = 10;
125 |
126 | // Update table frequency in seconds. The number of seconds the updat table
127 | // task sleeps between updates.
128 | @SerializedName("update-table-delay")
129 | public int updateTableDelay = 5;
130 |
131 | // Maximum size of JavaScript to process in KBs. 0 == unlimited.
132 | @SerializedName("maximum-js-size")
133 | public int jsMaxSize = 0;
134 |
135 | /**
136 | * JavaScript MIME types. Search for "text/javascript" here
137 | * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types Only
138 | * "text/javascript" is supposedly supported but who knows. Should be entered as lowercase here.
139 | * Burp returns "script" for JavaScript.
140 | */
141 | @SerializedName("js-mime-types")
142 | public String[] jsTypes = new String[] {
143 | "application/javascript",
144 | "application/ecmascript",
145 | "application/x-ecmascript",
146 | "application/x-javascript",
147 | "text/javascript",
148 | "text/ecmascript",
149 | "text/javascript1.0",
150 | "text/javascript1.1",
151 | "text/javascript1.2",
152 | "text/javascript1.3",
153 | "text/javascript1.4",
154 | "text/javascript1.5",
155 | "text/jscript",
156 | "text/livescript",
157 | "text/x-ecmascript",
158 | "text/x-javascript",
159 | "script" // This is what Burp returns as the MIMEType if it detects js.
160 | };
161 |
162 | // File extensions that might contain JavaScript.
163 | @SerializedName("javascript-file-extensions")
164 | public String[] fileExtensions = new String[] {
165 | "js",
166 | "javascript"
167 | };
168 |
169 | // Content-Types that might contain scripts, the JavaScript inside these
170 | // will be extracted and used.
171 | // Should be entered as lowercase here.
172 | @SerializedName("contains-javascript")
173 | public String[] containsScriptTypes = new String[] {
174 | "text/html",
175 | "application/xhtml+xml" // XHTML, be sure to remove the CDATA tags.
176 | };
177 |
178 | /**
179 | * Removable headers. These headers will be removed from the requests. The
180 | * change will not appear in Burp history but the outgoing request will not
181 | * have these headers.
182 | */
183 | @SerializedName("removable-headers")
184 | public String[] headersToRemove = new String[] {
185 | "If-Modified-Since",
186 | "If-None-Match"
187 | };
188 |
189 | // Convert the config to JSON.
190 | public String toString() {
191 | return new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(this);
192 | }
193 |
194 | // No-args constructor for Gson.
195 | public Config() {
196 | }
197 |
198 | // Creates a config object from the json string.
199 | public static Config configBuilder(String json) throws JsonSyntaxException {
200 | return new Gson().fromJson(json, Config.class);
201 | }
202 |
203 | // Writes the config files to file.
204 | public void writeToFile(File path) throws IOException {
205 | FileUtils.writeStringToFile(path, toString(), StringUtils.UTF8);
206 | }
207 |
208 | // 1. Converts the Config object to a json string.
209 | // 2. Encodes it in base64.
210 | // 3. Saves the config to extension settings.
211 | public void saveConfigToExtensionSettings() {
212 | // 1. Convert to string.
213 | String cfgStr = toString();
214 | // 2. Base64 encode.
215 | String cfgBase64 = StringUtils.base64Encode(cfgStr);
216 | // 3. Save it to extension settings.
217 | callbacks.saveExtensionSetting("config", cfgBase64);
218 | }
219 |
220 | // Creates a new config from the json string and returns it. Also waits for
221 | // the threadpool to shutdown, closes the DB connection and establishes a
222 | // connection to the new DB set in the new config file.
223 | public static Config loadConfig(String json) throws IOException {
224 | // Unload the extension, because we are loading a new config.
225 | BurpExtender.unloadExtension();
226 | // Read the json string and create a new config.
227 | Config cfg = configBuilder(json);
228 | try {
229 | // Connect to the new database file.
230 | BurpExtender.databaseConnect(cfg.dbPath);
231 | } catch (SQLException e) {
232 | log.debug(
233 | "Could not create the database file: %s",
234 | e.getMessage()
235 | );
236 | }
237 | // Save the config file in extension settings.
238 | cfg.saveConfigToExtensionSettings();
239 | return cfg;
240 | }
241 |
242 | // Returns the full path to the default config file.
243 | public static String getDefaultConfigFullPath(){
244 | // Get the extension jar path.
245 | String jarPath = callbacks.getExtensionFilename();
246 | // Get the parent directory of the jar path.
247 | String jarDirectory = StringUtils.getParentDirectory(jarPath);
248 | // Create the full path for the default config file.
249 | // jarDirectory/Config.defaultConfigName.
250 | return FilenameUtils.concat(jarDirectory, defaultConfigName);
251 | }
252 | }
--------------------------------------------------------------------------------
/src/burp/Detective.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import static burp.BurpExtender.extensionConfig;
4 | import static utils.Constants.EMPTY_STRING;
5 | import java.net.URL;
6 | import java.util.ArrayList;
7 | import utils.ReqResp;
8 |
9 |
10 | /**
11 | * Detective contains the JavaScript detection functions.
12 | */
13 | public class Detective {
14 |
15 | private static final String JAVASCRIPT_MIMETYPE = "text/javascript";
16 |
17 | public static boolean isScript(IHttpRequestResponse requestResponse) {
18 |
19 | // 1. Check the requests' extension.
20 | if (isJSURL(requestResponse)) {
21 | return true;
22 | }
23 |
24 | // 2. Check the MIMEType
25 | String mType = getMIMEType(requestResponse);
26 | if ((isJSMimeType(mType)) && (mType != EMPTY_STRING)){
27 | return true;
28 | }
29 | return false;
30 | }
31 |
32 | public static String getMIMEType(IHttpRequestResponse requestResponse) {
33 | // 0. Process the response.
34 | IResponseInfo respInfo = BurpExtender.helpers.analyzeResponse(requestResponse.getResponse());
35 |
36 | // 1. Try to get the MIME type from the response using Burp.
37 | String mimeType = respInfo.getStatedMimeType();
38 | if (mimeType != EMPTY_STRING) return mimeType;
39 | mimeType = respInfo.getInferredMimeType();
40 | if (mimeType != EMPTY_STRING) return mimeType;
41 |
42 | // I do not think we can do better at Burp but that is not tested yet.
43 | // 2. Get the "Content-Type" header of the response.
44 | ArrayList contentTypes = ReqResp.getHeader("Content-Type", false, requestResponse);
45 | if (contentTypes == null) return EMPTY_STRING;
46 | for (String cType : contentTypes) {
47 | // if (cType == null) continue;
48 | // Check if cType is in Config.JSMIMETypes.
49 | if (isJSMimeType(cType)) {
50 | return JAVASCRIPT_MIMETYPE;
51 | }
52 | }
53 |
54 | // 3. guessContentTypeFromName does not detect *.js files.
55 |
56 | // TODO Anything else?
57 | return EMPTY_STRING;
58 | }
59 |
60 | public static boolean containsScript(IHttpRequestResponse requestResponse) {
61 | // Get the Content-Type and check it against ContainsScriptTypes.
62 | // If so, get everything between "(.*)".
63 | ArrayList responseContentType =
64 | ReqResp.getHeader("Content-Type", false, requestResponse);
65 |
66 | if (responseContentType == null) return false;
67 | for (String cType : responseContentType) {
68 | if (cType == null) continue;
69 | if(isContainsScriptType(cType)) {
70 | return true;
71 | }
72 | }
73 | return false;
74 | }
75 |
76 | private static boolean isContainsScriptType(String cType) {
77 | if (cType == null) {
78 | return false;
79 | }
80 | for (String ct : extensionConfig.containsScriptTypes) {
81 | if (cType.contains(ct)) return true;
82 | }
83 | return false;
84 | }
85 |
86 | private static boolean isJSMimeType(String mType) {
87 | // return Arrays.asList(Config.JSTypes).contains(mType.toLowerCase());
88 | if (mType == null) {
89 | return false;
90 | }
91 | // Loop through all JSTypes and see if they occur in any of the headers.
92 | // This is better because the header usually contains the content-type
93 | // and stuff like charset.
94 | for (String jt : extensionConfig.jsTypes) {
95 | if (mType.contains(jt)) return true;
96 | }
97 | return false;
98 | }
99 |
100 | private static boolean isJSURL(IHttpRequestResponse requestResponse) {
101 | // Get the extension URL.
102 | String ext = ReqResp.getRequestExtension(requestResponse);
103 | // Return true if it's one of the extensions we are looking for.
104 | if (ext == null) return false;
105 | for (String extension : extensionConfig.fileExtensions) {
106 | if (ext.equalsIgnoreCase(extension)) {
107 | return true;
108 | }
109 | }
110 | return false;
111 | }
112 |
113 | public static URL getRequestURL(IHttpRequestResponse requestResponse) {
114 | IRequestInfo reqInfo = BurpExtender.helpers.analyzeRequest(requestResponse);
115 | return reqInfo.getUrl();
116 | }
117 |
118 | }
--------------------------------------------------------------------------------
/src/burp/Extractor.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.util.regex.Pattern;
4 | import java.util.regex.Matcher;
5 |
6 | import utils.StringUtils;
7 |
8 |
9 | /**
10 | * Extractor
11 | */
12 | public class Extractor {
13 |
14 | private static String pattern = "