snackhack2 0.6.5 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/snackhack2/bannergrabber.rb +87 -15
- data/lib/snackhack2/phishing_tlds.rb +228 -138
- data/lib/snackhack2/version.rb +1 -1
- data/lib/snackhack2.rb +18 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b016f87e2b46dc87ffab71256f1b5880f3db8b1bf16ccbc35bbe7aeeb0e7fe26
|
4
|
+
data.tar.gz: 1a9accfc1fe6f09c5d3e5f624eeeb032441f0fac9a25523c0b08f8b34c9963da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce184f5769d385950ebc9a5824f4788398cf301544eacc0dd59a7b2bd64e100f0b4816db9807e307287e8ef89085d18168a575f9b620177dd3bdac53916bcf49
|
7
|
+
data.tar.gz: 82efd660a7495c292a45c527cc9e937e73b143c00fb8e36800be34544c9447f3864f07f1d137d351ab2acd70aa126f8d2ace81080acd79cfcbba61a1b6350a54
|
@@ -8,24 +8,18 @@ module Snackhack2
|
|
8
8
|
def initialize(port: 443, save_file: true)
|
9
9
|
@site = site
|
10
10
|
@port = port
|
11
|
-
@headers = Snackhack2.get(@site).headers
|
12
11
|
@save_file = save_file
|
13
12
|
end
|
14
13
|
|
15
|
-
def site
|
16
|
-
# @site.gsub('https://', '')
|
17
|
-
end
|
18
|
-
|
19
14
|
def run
|
20
15
|
nginx
|
21
16
|
apache2
|
22
17
|
wordpress
|
23
|
-
headers
|
24
18
|
end
|
25
|
-
|
19
|
+
def headers
|
20
|
+
@headers = Snackhack2.get(@site).headers
|
21
|
+
end
|
26
22
|
def nginx
|
27
|
-
return unless @headers['server'].match(/nginx/)
|
28
|
-
|
29
23
|
puts "[+] Server is running NGINX... Now checking if #{File.join(@site, 'nginx_status')} is valid..."
|
30
24
|
nginx = Snackhack2.get(File.join(@site, 'nginx_status'))
|
31
25
|
if nginx.code == 200
|
@@ -37,20 +31,25 @@ module Snackhack2
|
|
37
31
|
|
38
32
|
def curl
|
39
33
|
servers = ''
|
34
|
+
# rus the curl command to get the headers of the given site.
|
40
35
|
cmd = `curl -s -I #{@site.gsub('https://', '')}`
|
36
|
+
# extracts the server header from the curl results
|
41
37
|
version = cmd.split('Server: ')[1].split("\n")[0].strip
|
42
38
|
if @save_file
|
43
39
|
servers += version.to_s
|
44
40
|
else
|
45
41
|
puts "Banner: #{cmd.split('Server: ')[1].split("\n")[0]}"
|
46
42
|
end
|
43
|
+
|
44
|
+
# saves the results if '@save_file' is set to true.
|
47
45
|
Snackhack2.file_save(@site, 'serverversion', servers) if @save_file
|
48
46
|
end
|
49
47
|
|
50
48
|
def apache2
|
51
|
-
if
|
49
|
+
if headers['server'].match(/Apache/)
|
52
50
|
puts "[+] Server is running Apache2... Now checking #{File.join(@site, 'server-status')}..."
|
53
51
|
apache = Snackhack2.get(File.join(@site, 'server-status'))
|
52
|
+
# status code 200 means the request was successful.
|
54
53
|
if apache.code == 200
|
55
54
|
puts "Check #{@site}/server-status"
|
56
55
|
else
|
@@ -67,16 +66,89 @@ module Snackhack2
|
|
67
66
|
|
68
67
|
puts "[+] Wordpress found [+]\n\n\n"
|
69
68
|
end
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
69
|
+
def types
|
70
|
+
{
|
71
|
+
"cloudflare": [ "cf-cache-status", "cf-ray", "cloudflare"],
|
72
|
+
"aws CloudFront": [ "X-Amz-Cf-Pop", "X-Amz-Cf-Id", "CloudFront", "x-amz-cf-pop", "x-amz-cf-id", "cloudfront.net"]
|
73
|
+
}
|
74
74
|
end
|
75
|
+
def find_headers
|
76
|
+
# make a request to the site and grab the headers.
|
77
|
+
Snackhack2.get(@site).headers
|
78
|
+
end
|
79
|
+
def cloudflare(print_status: true)
|
80
|
+
# the purpose of this method is to
|
81
|
+
# check to see if a site has
|
82
|
+
# cloudflare in the headers
|
83
|
+
|
84
|
+
cf_status = false
|
85
|
+
cf_count = 0
|
86
|
+
|
87
|
+
# access the 'types' hash to get the cloudflare strings.
|
88
|
+
cf = types[:"cloudflare"]
|
75
89
|
|
90
|
+
# make a single get request to the site defined at '@site'
|
91
|
+
find_headers.each do |k,v|
|
92
|
+
# if the key is in the array cf
|
93
|
+
if cf.include?(k)
|
94
|
+
cf_status = true
|
95
|
+
cf_count += 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
if print_status
|
99
|
+
# cf_status[0] : the status if cloudflare was found
|
100
|
+
# cf_count[1] : the number of found elements in the 'cloudflare' hash.
|
101
|
+
return [cf_status, cf_count]
|
102
|
+
else
|
103
|
+
if cf_status
|
104
|
+
puts "Cloudflare was found. The count is: #{cf_count}"
|
105
|
+
else
|
106
|
+
puts "Cloudflare was NOT found. The count is #{cf_count}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
def detect_header(return_status: true)
|
111
|
+
# stores the data found in
|
112
|
+
# the headers.
|
113
|
+
data = {}
|
114
|
+
# loops through the hash stored in the 'types' method.
|
115
|
+
# the t_k is the KEY of the hash
|
116
|
+
# the t_v is the VALUE of the hash.
|
117
|
+
types.each do |t_k, t_v|
|
118
|
+
# make a single get request to the site
|
119
|
+
# to get the headers.
|
120
|
+
find_headers.each do |fh_k, fh_v|
|
121
|
+
# Get the keys from the 'types' method
|
122
|
+
# which is basicly a hash
|
123
|
+
type_key = t_k
|
124
|
+
# uses the key of the 'types' hash
|
125
|
+
# to see if includes the string found in 'fh_k'
|
126
|
+
if types[type_key].include?(fh_k)
|
127
|
+
if data.has_key?(type_key)
|
128
|
+
data[type_key] << fh_k
|
129
|
+
else
|
130
|
+
data[type_key] = [fh_k]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
if return_status
|
136
|
+
return data
|
137
|
+
else
|
138
|
+
data.each do |k,v|
|
139
|
+
puts "K:#{k}"
|
140
|
+
puts "V: #{v}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
76
144
|
def server
|
77
145
|
@headers['server']
|
78
146
|
end
|
79
147
|
|
80
148
|
attr_reader :site
|
81
|
-
|
149
|
+
private :find_headers
|
150
|
+
end
|
82
151
|
end
|
152
|
+
# to do: instead of doing mutiple methods for each type of
|
153
|
+
# header make one method that is dynamic...
|
154
|
+
# and can do different ones
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Snackhack2
|
4
|
-
|
4
|
+
class PhishingData
|
5
5
|
def domains
|
6
6
|
[
|
7
7
|
".com",
|
@@ -44,154 +44,244 @@ module Snackhack2
|
|
44
44
|
".work"
|
45
45
|
]
|
46
46
|
end
|
47
|
+
def domain_keywords
|
48
|
+
[
|
49
|
+
"connect",
|
50
|
+
"corp",
|
51
|
+
"duo",
|
52
|
+
"help",
|
53
|
+
"he1p",
|
54
|
+
"helpdesk",
|
55
|
+
"helpnow",
|
56
|
+
"info",
|
57
|
+
"internal",
|
58
|
+
"mfa",
|
59
|
+
"my",
|
60
|
+
"okta",
|
61
|
+
"onelogin",
|
62
|
+
"schedule",
|
63
|
+
"service",
|
64
|
+
"servicedesk",
|
65
|
+
"servicenow",
|
66
|
+
"rci",
|
67
|
+
"rsa",
|
68
|
+
"sso",
|
69
|
+
"ssp",
|
70
|
+
"support",
|
71
|
+
"usa",
|
72
|
+
"vpn",
|
73
|
+
"work",
|
74
|
+
"dev",
|
75
|
+
"workspace",
|
76
|
+
"it",
|
77
|
+
"ops",
|
78
|
+
"hr",
|
79
|
+
"login",
|
80
|
+
"secure"
|
81
|
+
]
|
82
|
+
end
|
47
83
|
private :domains
|
48
84
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
85
|
+
class PhishingTlds < PhishingData
|
86
|
+
attr_reader :site
|
87
|
+
def initialize
|
88
|
+
@site = site
|
89
|
+
end
|
90
|
+
def domain_split
|
91
|
+
# This method splits up the value block_given
|
92
|
+
# given in @site by the period. Which is used
|
93
|
+
# by 'remove_tlds' method to remove the TLDs
|
94
|
+
@site.split(".")
|
95
|
+
end
|
96
|
+
def site=(s)
|
97
|
+
@site = s
|
98
|
+
end
|
99
|
+
def remove_tlds
|
100
|
+
# this method function is to remove
|
101
|
+
# the TLDs from the @site. For Example
|
102
|
+
# it will remove .org, .com
|
103
|
+
|
104
|
+
ds = domain_split
|
105
|
+
|
106
|
+
# remove ".com" (last element in array)
|
107
|
+
ds.pop
|
108
|
+
|
109
|
+
# returns the domain w/o the tlds
|
110
|
+
ds
|
111
|
+
end
|
112
|
+
def check_domains(array: true)
|
113
|
+
# The function of this method is to
|
114
|
+
# check if the given domains are valid or not.
|
115
|
+
# By valid I mean resolvable and active.
|
116
|
+
|
117
|
+
|
118
|
+
# if domains is set to true, this array will hold the domains
|
119
|
+
domains_out = []
|
120
|
+
|
121
|
+
# build the list of domains
|
122
|
+
generated_tlds = change_tld
|
123
|
+
|
124
|
+
valid_domains = []
|
125
|
+
not_valid_domains = []
|
126
|
+
|
127
|
+
generated_tlds.each do |domain|
|
128
|
+
# if array is true; add the domains to array
|
129
|
+
if array
|
130
|
+
domains_out << domain
|
131
|
+
else
|
132
|
+
# if array is false print out the domains
|
133
|
+
puts domain
|
53
134
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
135
|
+
domains_out if array
|
136
|
+
end
|
137
|
+
end
|
138
|
+
def remove_letters(array_out: true)
|
139
|
+
# This method will remove letters that
|
140
|
+
# occur more than once. For example:
|
141
|
+
# google.com would become goggle.com
|
142
|
+
|
143
|
+
# store the letter count in a hash.
|
144
|
+
letter_count = {}
|
145
|
+
|
146
|
+
ds = remove_tlds
|
147
|
+
|
148
|
+
# Creates an array with each character being
|
149
|
+
# stored in a element. It will loop through the array
|
150
|
+
# and figure out the number of occurrences for each character
|
151
|
+
ds.shift.split(//).each do |letter|
|
152
|
+
if letter_count.has_key?(letter)
|
153
|
+
letter_count[letter] += 1
|
154
|
+
else
|
155
|
+
letter_count[letter] = 1
|
59
156
|
end
|
60
|
-
|
61
|
-
|
157
|
+
end
|
158
|
+
|
159
|
+
# After it creates the hash with the character and
|
160
|
+
# the number of time it cocures. This method
|
161
|
+
# will loop through the hash and check to see
|
162
|
+
# if the value is greater than 1. If it is then the key ( the letter)
|
163
|
+
# is added to the array named 'letters_with_more_than_one'
|
164
|
+
letters_with_more_than_one = []
|
165
|
+
letter_count.each do |key, value|
|
166
|
+
if value > 1
|
167
|
+
letters_with_more_than_one << key
|
62
168
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
ds = remove_tlds
|
173
|
+
new_ds = ds.shift
|
174
|
+
|
175
|
+
# the final array with the duplicates letters removed
|
176
|
+
remove_letters_out = []
|
177
|
+
|
178
|
+
# Loops through the 'letters_with_more_than_one'
|
179
|
+
# array and uses 'sub' to remove the occurence
|
180
|
+
# of one of the letters
|
181
|
+
letters_with_more_than_one.each do |l|
|
182
|
+
# removes only first character ( l )
|
183
|
+
remove_letters_out << new_ds.sub(l, "")
|
184
|
+
# removes ALL chracters ( l )
|
185
|
+
remove_letters_out << new_ds.gsub(l, "")
|
186
|
+
end
|
187
|
+
|
188
|
+
domains_with_tlds = []
|
189
|
+
# adding the TLDS to the 'remove_letter_out' array
|
190
|
+
domains.each do |d|
|
191
|
+
remove_letters_out.each do |rl|
|
192
|
+
# adds the words ( rl ) and the TLDS ( d )
|
193
|
+
# to the domains_with_tld array.
|
194
|
+
domains_with_tlds << "#{rl}#{d}"
|
75
195
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
196
|
+
end
|
197
|
+
if array_out
|
198
|
+
domains_with_tlds
|
199
|
+
else
|
200
|
+
# will print the contents of the array
|
201
|
+
# instead of returning the array
|
202
|
+
domains_with_tlds.each { |a| puts a }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
def combosquatting
|
206
|
+
# where the generated domains will be located.
|
207
|
+
results = []
|
208
|
+
|
209
|
+
# get the domain_keywords array from the PhishingData class.
|
210
|
+
keywords = domain_keywords
|
211
|
+
|
212
|
+
prefixes = ["-", ".", "--"]
|
213
|
+
ds = remove_tlds
|
214
|
+
# this will generate the 'new_domain' with the keywords
|
215
|
+
# as a prefix
|
216
|
+
prefixes.each do |pre|
|
217
|
+
ds.each do |domain|
|
218
|
+
keywords.each do |key|
|
219
|
+
new_domain = "#{key}#{pre}#{domain}"
|
220
|
+
results << new_domain
|
100
221
|
end
|
101
222
|
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# Creates an array with each character being
|
113
|
-
# stored in a element. It will loop through the array
|
114
|
-
# and figure out the number of occurrences for each character
|
115
|
-
ds.shift.split(//).each do |letter|
|
116
|
-
if letter_count.has_key?(letter)
|
117
|
-
letter_count[letter] += 1
|
118
|
-
else
|
119
|
-
letter_count[letter] = 1
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
# After it creates the hash with the character and
|
124
|
-
# the number of time it cocures. This method
|
125
|
-
# will loop through the hash and check to see
|
126
|
-
# if the value is greater than 1. If it is then the key ( the letter)
|
127
|
-
# is added to the array named 'letters_with_more_than_one'
|
128
|
-
letters_with_more_than_one = []
|
129
|
-
letter_count.each do |key, value|
|
130
|
-
if value > 1
|
131
|
-
letters_with_more_than_one << key
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
|
136
|
-
ds = remove_tlds
|
137
|
-
new_ds = ds.shift
|
138
|
-
|
139
|
-
# the final array with the duplicates letters removed
|
140
|
-
remove_lettters_out = []
|
141
|
-
|
142
|
-
# Loops through the 'letters_with_more_than_one'
|
143
|
-
# array and uses 'sub' to remove the occurence
|
144
|
-
# of one of the letters
|
145
|
-
letters_with_more_than_one.each do |l|
|
146
|
-
remove_lettters_out << new_ds.sub(l, "")
|
147
|
-
end
|
148
|
-
|
149
|
-
if array_out
|
150
|
-
remove_lettters_out
|
151
|
-
else
|
152
|
-
# will print the contents of the array
|
153
|
-
# instead of returning the array
|
154
|
-
remove_lettters_out.each { |a| puts a }
|
223
|
+
end
|
224
|
+
suffixes = ["-", ".", "--"]
|
225
|
+
# this will generate the 'new_domain' with the keywords
|
226
|
+
# as a suffixes
|
227
|
+
suffixes.each do |suf|
|
228
|
+
ds.each do |domain|
|
229
|
+
keywords.each do |key|
|
230
|
+
new_domain = "#{domain}#{suf}#{key}"
|
231
|
+
results << new_domain
|
155
232
|
end
|
156
233
|
end
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
234
|
+
end
|
235
|
+
final_results = []
|
236
|
+
|
237
|
+
# Loops through the domains array in the PhishingData class
|
238
|
+
domains.each do |tlds|
|
239
|
+
results.each do |r|
|
240
|
+
new_domain = "#{r}#{tlds}"
|
241
|
+
final_results << new_domain
|
242
|
+
end
|
243
|
+
end
|
244
|
+
final_results
|
245
|
+
end
|
246
|
+
def change_tld(no_tld: true)
|
247
|
+
# This method will take the inputted site in @site and
|
248
|
+
# remove the TLDs and add a new TLDs to the domain.
|
249
|
+
# its uses the 'domain' method in the PhishingData class
|
250
|
+
# which has an array of a bunch of different tlds.
|
251
|
+
|
252
|
+
|
253
|
+
# if the @site does not have a tlds
|
254
|
+
if no_tld
|
255
|
+
new_domains = []
|
256
|
+
# loop through the tlds
|
257
|
+
domains.each do |d|
|
258
|
+
# combine the inputed @site
|
259
|
+
# and the tlds
|
260
|
+
new_domains << "#{@site}#{d}"
|
261
|
+
end
|
262
|
+
new_domains
|
263
|
+
else
|
264
|
+
# If the @site does have a TLDs.
|
163
265
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
#
|
178
|
-
|
179
|
-
list_of_domains = []
|
180
|
-
|
181
|
-
# removes .com, .org, etc
|
182
|
-
ds = remove_tlds
|
183
|
-
|
184
|
-
# join the elements together
|
185
|
-
ds = ds.join(".")
|
186
|
-
|
187
|
-
# loops through the tlds
|
188
|
-
domains.each do |tlds|
|
189
|
-
# adds the new domains to the array
|
190
|
-
list_of_domains << ds + tlds
|
191
|
-
end
|
192
|
-
list_of_domains
|
193
|
-
end
|
266
|
+
# this is where the final results
|
267
|
+
# are stored.
|
268
|
+
list_of_domains = []
|
269
|
+
|
270
|
+
# removes .com, .org, etc
|
271
|
+
ds = remove_tlds
|
272
|
+
|
273
|
+
# join the elements together
|
274
|
+
ds = ds.join(".")
|
275
|
+
|
276
|
+
|
277
|
+
# loops through the tlds
|
278
|
+
domains.each do |tlds|
|
279
|
+
# adds the new domains to the array
|
280
|
+
list_of_domains << ds + tlds
|
194
281
|
end
|
195
|
-
|
282
|
+
list_of_domains
|
196
283
|
end
|
284
|
+
end
|
285
|
+
private :remove_tlds, :domain_split
|
286
|
+
end
|
197
287
|
end
|
data/lib/snackhack2/version.rb
CHANGED
data/lib/snackhack2.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'uri'
|
4
4
|
require 'httparty'
|
5
|
-
|
5
|
+
=begin
|
6
6
|
require_relative 'snackhack2/version'
|
7
7
|
require_relative 'snackhack2/bannergrabber'
|
8
8
|
require_relative 'snackhack2/wordpress'
|
@@ -36,6 +36,12 @@ require_relative 'snackhack2/ssrf'
|
|
36
36
|
require_relative 'snackhack2/dns'
|
37
37
|
require_relative 'snackhack2/CVE-2017-9841'
|
38
38
|
require_relative 'snackhack2/phishing_tlds'
|
39
|
+
=end
|
40
|
+
Dir.glob(File.join(File.dirname(__FILE__), "snackhack2", "*")).each do |file|
|
41
|
+
unless File.directory?(file)
|
42
|
+
require_relative file
|
43
|
+
end
|
44
|
+
end
|
39
45
|
module Snackhack2
|
40
46
|
UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
|
41
47
|
def self.read_serverversion
|
@@ -57,10 +63,17 @@ module Snackhack2
|
|
57
63
|
end
|
58
64
|
end
|
59
65
|
|
60
|
-
def self.file_save(site, type, content, ip: false)
|
61
|
-
|
62
|
-
|
63
|
-
|
66
|
+
def self.file_save(site, type, content, ip: false, host: true)
|
67
|
+
if host
|
68
|
+
hostname = URI.parse(site).host
|
69
|
+
File.open("#{hostname}_#{type}.txt", 'w+') { |file| file.write(content) }
|
70
|
+
puts "[+] Saving file to #{hostname}_#{type}.txt..."
|
71
|
+
else
|
72
|
+
File.open("#{site}_#{type}.txt", 'w+') { |file| file.write(content) }
|
73
|
+
puts "[+] Saving file to #{site}_#{type}.txt..."
|
74
|
+
end
|
75
|
+
|
76
|
+
|
64
77
|
end
|
65
78
|
|
66
79
|
def self.get(site)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: snackhack2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mike
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-10-
|
11
|
+
date: 2025-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|