passivetotal 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +10 -0
- data/bin/passivetotal +3 -0
- data/lib/passivetotal.rb +6 -0
- data/lib/passivetotal/api.rb +309 -0
- data/lib/passivetotal/cli.rb +158 -0
- data/lib/passivetotal/version.rb +3 -0
- data/passivetotal.gemspec +26 -0
- data/utils/console +14 -0
- data/utils/setup +7 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 466cf1dbb64b1dd6a08c99f7fe7b704abd5174f7
|
4
|
+
data.tar.gz: 7d83f321c9d76c14fa90ab614a44a60dbb2aa1bf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 30b2003a7f8a0336eb11110e3fbce59a9a0ddeb62493c0ad9c1244d1f44226d16a8f8fafeda08296e5e34b756baf0cfea7cabde3792d9ea311f8fdf6b88cc63f
|
7
|
+
data.tar.gz: 3b0127725ccba2de6fe62a8baf99aefaa18f4dc032fa5b32a47a9160398e37f1e905c280f7b4e24971589ef26ca3fbcd7d24f9212a3b058fe8959e9a6d49f760
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 chrislee35
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# PassiveTotal
|
2
|
+
|
3
|
+
The PassiveTotal gem is (currently) a thin wrapper around PassiveTotal.org's Web-based API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'passivetotal'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install passivetotal
|
20
|
+
|
21
|
+
## Command Line Tool
|
22
|
+
|
23
|
+
Included in the gem is a command-line tool, passivetotal, with the following usage:
|
24
|
+
|
25
|
+
Usage: passivetotal [-h] [-v] [-k <apikey>] [[-m|-p|-c|-t|-e|-w] <ip or dom>] [[-s|-d] <dom>] [-x <ip>] [-l <ip or hash>] [-i <value>]
|
26
|
+
-h Help
|
27
|
+
-v Verbose output
|
28
|
+
-k <apikey> Sets the APIKEY, defaults to the environment variable PASSIVETOTAL_APIKEY
|
29
|
+
ACTIONS (You have to select one, last one wins) -m <ip or dom> Queries metadata for given IP or domain
|
30
|
+
-p <ip or dom> Queries passive DNS data for given IP or domain
|
31
|
+
-c <ip or dom> Queries (or sets) the classification for a given IP or domain
|
32
|
+
-t <ip or dom> Queries (adds or removes) the tags associated with a given IP or domain
|
33
|
+
* To remove a tag, prepend a dash, '-' to the tag name when using the -i option
|
34
|
+
-e <ip or dom> Queries (or sets) the ever compromised flag on a given IP or domain
|
35
|
+
-w <ip or dom> Queries (or sets) the watched flag on a given IP or domain
|
36
|
+
-s <dom> Queries the subdomains for a given domain
|
37
|
+
-d <dom> Queries (or sets) if a domain is a dynamic DNS domain
|
38
|
+
-x <ip> Queries (or sets) if a given IP is a sinkhole
|
39
|
+
-l <ip or hash> Queries for SSL Certificates/IP addresses associated with a given IP or SHA-1 hash
|
40
|
+
SETTING VALUES -i <value> Sets the value, used in conjuntion with -c, -t, -e, -w, -d, or -x
|
41
|
+
Valid values for -i depend on what it's used with:
|
42
|
+
-c : targeted, crime, multiple, benign
|
43
|
+
-t : <a tag name consisting of characters: [a-zA-Z_]>
|
44
|
+
-e, -w, -d, -x: true, false
|
45
|
+
|
46
|
+
## Usage
|
47
|
+
|
48
|
+
# Initialize the API wrapper with an apikey (using the default endpoint URL of https://www.passivetotal.org/api/v1/)
|
49
|
+
pt = PassiveTotal::API.new(apikey)
|
50
|
+
# Create an array to shove results into
|
51
|
+
res = []
|
52
|
+
# query metadata for the domain, www.passivetotal.org
|
53
|
+
res << @pt.metadata('www.passivetotal.org')
|
54
|
+
# query metadata for the ipv4 address, 107.170.89.121
|
55
|
+
res << @pt.metadata('107.170.89.121')
|
56
|
+
# query passive DNS results for the domain, www.passivetotal.org
|
57
|
+
res << @pt.passive('www.passivetotal.org')
|
58
|
+
# query passive DNS results for the ipv4 address, 107.170.89.121
|
59
|
+
res << @pt.passive('107.170.89.121')
|
60
|
+
# query for subdomains of passivetotal.org
|
61
|
+
res << @pt.subdomains('passivetotal.org')
|
62
|
+
# query for unique IPv4 resolutions of passivetotal.org
|
63
|
+
res << @pt.unique('passivetotal.org')
|
64
|
+
# query for the classification of www.passivetotal.org
|
65
|
+
res << @pt.classification('www.passivetotal.org')
|
66
|
+
# set the classification of www.passivetotal.org as benign
|
67
|
+
res << @pt.classification('www.passivetotal.org', 'benign')
|
68
|
+
# query for the tags associated with www.chrisleephd.us
|
69
|
+
res << @pt.tags('www.chrisleephd.us')
|
70
|
+
# add the "cool" tag to www.chrisleephd.us
|
71
|
+
res << @pt.add_tag('www.chrisleephd.us', 'cool')
|
72
|
+
# remove the "cool" tag from www.chrisleephd.us (aww, I was cool for a few milliseconds :( )
|
73
|
+
res << @pt.remove_tag('www.chrisleephd.us', 'cool')
|
74
|
+
# query if 107.170.89.121 is a sinkhole
|
75
|
+
res << @pt.sinkhole('107.170.89.121')
|
76
|
+
# set 107.170.89.121 as not a sinkhole
|
77
|
+
res << @pt.sinkhole('107.170.89.121', false)
|
78
|
+
# query if www.passivetotal.org has ever been listed as compromised
|
79
|
+
res << @pt.ever_compromised('www.passivetotal.org')
|
80
|
+
# set the ever_compromised flag for www.passivetotal.org to false to indicate that it was never compromised or that it is in sole control of a malicious actor.
|
81
|
+
res << @pt.ever_compromised('www.passivetotal.org', false)
|
82
|
+
# check if www.passivetotal.org is a dynamic dns domain/host
|
83
|
+
res << @pt.dynamic('www.passivetotal.org')
|
84
|
+
# flag www.passivetotal.org as not a dynamic dns domain/host
|
85
|
+
res << @pt.dynamic('www.passivetotal.org', false)
|
86
|
+
# check if www.passivetotal.org is being watched
|
87
|
+
res << @pt.watching('www.passivetotal.org')
|
88
|
+
# unwatch www.passivetotal.org
|
89
|
+
res << @pt.watching('www.passivetotal.org', false)
|
90
|
+
# list SSL certificates associated with IPV4 address 104.131.121.205
|
91
|
+
res << @pt.ssl_certificate('104.131.121.205')
|
92
|
+
# list sites associated with SSL certificates with SHA-1 hash of e9a6647d6aba52dc47b3838c920c9ee59bad7034
|
93
|
+
res << @pt.ssl_certificate('e9a6647d6aba52dc47b3838c920c9ee59bad7034')
|
94
|
+
# dump all this glorious information to feast your eyes upon
|
95
|
+
pp res
|
96
|
+
|
97
|
+
## Development
|
98
|
+
|
99
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
100
|
+
|
101
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
102
|
+
|
103
|
+
## Contributing
|
104
|
+
|
105
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/chrislee35/passivetotal.
|
106
|
+
|
107
|
+
|
108
|
+
## License
|
109
|
+
|
110
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
111
|
+
|
data/Rakefile
ADDED
data/bin/passivetotal
ADDED
data/lib/passivetotal.rb
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
require 'passivetotal/version'
|
6
|
+
|
7
|
+
# DESCRIPTION: rubygem for querying PassiveTotal.org's web API
|
8
|
+
|
9
|
+
module PassiveTotal # :nodoc:
|
10
|
+
|
11
|
+
class InvalidAPIKeyError < ArgumentError; end
|
12
|
+
|
13
|
+
# The API class wraps the PassiveTotal.org web API for all the verbs that it supports
|
14
|
+
# See https://www.passivetotal.org/api/docs for the API documentation.
|
15
|
+
class API
|
16
|
+
# The TLDS array helps the interface detect valid domains.
|
17
|
+
# This list was generated by parsing the NS records from a zone transfer of the root
|
18
|
+
# The same list could have been downloaded from http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
19
|
+
TLDS = "abb,abbott,abogado,ac,academy,accenture,accountant,accountants,active,actor,ad,ads,adult,ae,aeg,aero,af,afl,ag,agency,ai,aig,airforce,al,allfinanz,alsace,am,amsterdam,an,android,ao,apartments,aq,aquarelle,ar,archi,army,arpa,as,asia,associates,at,attorney,au,auction,audio,auto,autos,aw,ax,axa,az,azure,ba,band,bank,bar,barclaycard,barclays,bargains,bauhaus,bayern,bb,bbc,bbva,bd,be,beer,berlin,best,bf,bg,bh,bharti,bi,bible,bid,bike,bing,bingo,bio,biz,bj,black,blackfriday,bloomberg,blue,bm,bmw,bn,bnl,bnpparibas,bo,boats,bond,boo,boutique,br,bradesco,bridgestone,broker,brother,brussels,bs,bt,budapest,build,builders,business,buzz,bv,bw,by,bz,bzh,ca,cab,cafe,cal,camera,camp,cancerresearch,canon,capetown,capital,caravan,cards,care,career,careers,cars,cartier,casa,cash,casino,cat,catering,cba,cbn,cc,cd,center,ceo,cern,cf,cfa,cfd,cg,ch,channel,chat,cheap,chloe,christmas,chrome,church,ci,cisco,citic,city,ck,cl,claims,cleaning,click,clinic,clothing,cloud,club,cm,cn,co,coach,codes,coffee,college,cologne,com,commbank,community,company,computer,condos,construction,consulting,contractors,cooking,cool,coop,corsica,country,coupons,courses,cr,credit,creditcard,cricket,crown,crs,cruises,cu,cuisinella,cv,cw,cx,cy,cymru,cyou,cz,dabur,dad,dance,date,dating,datsun,day,dclk,de,deals,degree,delivery,democrat,dental,dentist,desi,design,dev,diamonds,diet,digital,direct,directory,discount,dj,dk,dm,dnp,do,docs,dog,doha,domains,doosan,download,drive,durban,dvag,dz,earth,eat,ec,edu,education,ee,eg,email,emerck,energy,engineer,engineering,enterprises,epson,equipment,er,erni,es,esq,estate,et,eu,eurovision,eus,events,everbank,exchange,expert,exposed,express,fail,faith,fan,fans,farm,fashion,feedback,fi,film,finance,financial,firmdale,fish,fishing,fit,fitness,fj,fk,flights,florist,flowers,flsmidth,fly,fm,fo,foo,football,forex,forsale,foundation,fr,frl,frogans,fund,furniture,futbol,fyi,ga,gal,gallery,garden,gb,gbiz,gd,gdn,ge,gent,genting,gf,gg,ggee,gh,gi,gift,gifts,gives,gl,glass,gle,global,globo,gm,gmail,gmo,gmx,gn,gold,goldpoint,golf,goo,goog,google,gop,gov,gp,gq,gr,graphics,gratis,green,gripe,gs,gt,gu,guge,guide,guitars,guru,gw,gy,hamburg,hangout,haus,healthcare,help,here,hermes,hiphop,hitachi,hiv,hk,hm,hn,hockey,holdings,holiday,homedepot,homes,honda,horse,host,hosting,hoteles,hotmail,house,how,hr,ht,hu,ibm,icbc,icu,id,ie,ifm,il,im,immo,immobilien,in,industries,infiniti,info,ing,ink,institute,insure,int,international,investments,io,iq,ir,irish,is,it,iwc,java,jcb,je,jetzt,jewelry,jlc,jll,jm,jo,jobs,joburg,jp,juegos,kaufen,kddi,ke,kg,kh,ki,kim,kitchen,kiwi,km,kn,koeln,komatsu,kp,kr,krd,kred,kw,ky,kyoto,kz,la,lacaixa,land,lasalle,lat,latrobe,law,lawyer,lb,lc,lds,lease,leclerc,legal,lgbt,li,liaison,lidl,life,lighting,limited,limo,link,lk,loan,loans,lol,london,lotte,lotto,love,lr,ls,lt,ltda,lu,lupin,luxe,luxury,lv,ly,ma,madrid,maif,maison,management,mango,market,marketing,markets,marriott,mba,mc,md,me,media,meet,melbourne,meme,memorial,men,menu,mg,mh,miami,microsoft,mil,mini,mk,ml,mm,mma,mn,mo,mobi,moda,moe,monash,money,montblanc,mormon,mortgage,moscow,motorcycles,mov,movie,movistar,mp,mq,mr,ms,mt,mtn,mtpc,mu,museum,mv,mw,mx,my,mz,na,nadex,nagoya,name,navy,nc,ne,nec,net,netbank,network,neustar,new,news,nexus,nf,ng,ngo,nhk,ni,nico,ninja,nissan,nl,no,np,nr,nra,nrw,ntt,nu,nyc,nz,office,okinawa,om,omega,one,ong,onl,online,ooo,oracle,org,organic,osaka,otsuka,ovh,pa,page,panerai,paris,partners,parts,party,pe,pf,pg,ph,pharmacy,philips,photo,photography,photos,physio,piaget,pics,pictet,pictures,pink,pizza,pk,pl,place,play,plumbing,plus,pm,pn,pohl,poker,porn,post,pr,praxi,press,pro,prod,productions,prof,properties,property,ps,pt,pub,pw,py,qa,qpon,quebec,racing,re,realtor,recipes,red,redstone,rehab,reise,reisen,reit,ren,rent,rentals,repair,report,republican,rest,restaurant,review,reviews,rich,ricoh,rio,rip,ro,rocks,rodeo,rs,rsvp,ru,ruhr,run,rw,ryukyu,sa,saarland,sale,samsung,sandvik,sandvikcoromant,sap,sarl,saxo,sb,sc,sca,scb,schmidt,scholarships,school,schule,schwarz,science,scor,scot,sd,se,seat,sener,services,sew,sex,sexy,sg,sh,shiksha,shoes,show,shriram,si,singles,site,sj,sk,ski,sky,skype,sl,sm,sn,sncf,so,soccer,social,software,sohu,solar,solutions,sony,soy,space,spiegel,spreadbetting,sr,st,starhub,statoil,study,style,su,sucks,supplies,supply,support,surf,surgery,suzuki,sv,swatch,swiss,sx,sy,sydney,systems,sz,taipei,tatar,tattoo,tax,taxi,tc,td,team,tech,technology,tel,telefonica,temasek,tennis,tf,tg,th,thd,theater,tickets,tienda,tips,tires,tirol,tj,tk,tl,tm,tn,to,today,tokyo,tools,top,toray,toshiba,tours,town,toys,tr,trade,trading,training,travel,trust,tt,tui,tv,tw,tz,ua,ug,uk,university,uno,uol,us,uy,uz,va,vacations,vc,ve,vegas,ventures,versicherung,vet,vg,vi,viajes,video,villas,vision,vista,vistaprint,vlaanderen,vn,vodka,vote,voting,voto,voyage,vu,wales,walter,wang,watch,webcam,website,wed,wedding,weir,wf,whoswho,wien,wiki,williamhill,win,windows,wme,work,works,world,ws,wtc,wtf,xbox,xerox,xin,xn--1qqw23a,xn--30rr7y,xn--3bst00m,xn--3ds443g,xn--3e0b707e,xn--45brj9c,xn--45q11c,xn--4gbrim,xn--55qw42g,xn--55qx5d,xn--6frz82g,xn--6qq986b3xl,xn--80adxhks,xn--80ao21a,xn--80asehdb,xn--80aswg,xn--90a3ac,xn--90ais,xn--9et52u,xn--b4w605ferd,xn--c1avg,xn--cg4bki,xn--clchc0ea0b2g2a9gcd,xn--czr694b,xn--czrs0t,xn--czru2d,xn--d1acj3b,xn--d1alf,xn--estv75g,xn--fiq228c5hs,xn--fiq64b,xn--fiqs8s,xn--fiqz9s,xn--fjq720a,xn--flw351e,xn--fpcrj9c3d,xn--fzc2c9e2c,xn--gecrj9c,xn--h2brj9c,xn--hxt814e,xn--i1b6b1a6a2e,xn--imr513n,xn--io0a7i,xn--j1amh,xn--j6w193g,xn--kcrx77d1x4a,xn--kprw13d,xn--kpry57d,xn--kput3i,xn--l1acc,xn--lgbbat1ad8j,xn--mgb9awbf,xn--mgba3a4f16a,xn--mgbaam7a8h,xn--mgbab2bd,xn--mgbayh7gpa,xn--mgbbh1a71e,xn--mgbc0a9azcg,xn--mgberp4a5d4ar,xn--mgbpl2fh,xn--mgbx4cd0ab,xn--mxtq1m,xn--ngbc5azd,xn--node,xn--nqv7f,xn--nqv7fs00ema,xn--nyqy26a,xn--o3cw4h,xn--ogbpf8fl,xn--p1acf,xn--p1ai,xn--pgbs0dh,xn--q9jyb4c,xn--qcka1pmc,xn--rhqv96g,xn--s9brj9c,xn--ses554g,xn--unup4y,xn--vermgensberater-ctb,xn--vermgensberatung-pwb,xn--vhquv,xn--vuq861b,xn--wgbh1c,xn--wgbl6a,xn--xhq521b,xn--xkc2al3hye2a,xn--xkc2dl3a5ee0h,xn--y9a3aq,xn--yfro4i67o,xn--ygbi2ammx,xn--zfr164b,xxx,xyz,yachts,yandex,ye,yodobashi,yoga,yokohama,youtube,yt,za,zip,zm,zone,zuerich,zw".split(/,/)
|
20
|
+
|
21
|
+
# initialize a new PassiveTotal::API object
|
22
|
+
# apikey: is 64-hexcharacter string
|
23
|
+
# endpoint: base URL for the web service, defaults to https://www.passivetotal.org/api/v1/
|
24
|
+
def initialize(apikey, endpoint = 'https://www.passivetotal.org/api/v1/')
|
25
|
+
unless apikey =~ /^[a-fA-F0-9]{64}$/
|
26
|
+
raise ArgumentError.new("apikey must be a 64 character hex string")
|
27
|
+
end
|
28
|
+
@apikey = apikey
|
29
|
+
@endpoint = endpoint
|
30
|
+
end
|
31
|
+
|
32
|
+
# Metadata describes the item being queried and includes many of the options available inside of the action API calls.
|
33
|
+
# query: A domain or IP address to query
|
34
|
+
def metadata(query)
|
35
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
36
|
+
if domain?(query)
|
37
|
+
query = normalize_domain(query)
|
38
|
+
end
|
39
|
+
get(__method__, query)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Passive provides a complete passive DNS picture for a domain or IP address including first/last seen values, deconflicted values, sources used, unique counts and enrichment for all values.
|
43
|
+
# query: A domain or IP address to query
|
44
|
+
def passive(query)
|
45
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
46
|
+
if domain?(query)
|
47
|
+
query = normalize_domain(query)
|
48
|
+
end
|
49
|
+
get(__method__, query)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Subdomains provides a comprehensive view of all known subdomains for a registered domain with associated passive DNS information. This call is best used to understand the activity of a particular domain over a period of time. Passive DNS information is only deconflicted at the subdomain level, not across the entire domain.
|
53
|
+
# query: A domain to query
|
54
|
+
def subdomains(query)
|
55
|
+
is_valid_with_error(__method__, [:domain], query)
|
56
|
+
query = normalize_domain(query)
|
57
|
+
get(__method__, query)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Each domain or IP address with results has a unique set of resolving items. This call provides those unique items and a frequency count of how often they show up in sorted order.
|
61
|
+
# query: A domain or IP address to query
|
62
|
+
def unique(query)
|
63
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
64
|
+
if domain?(query)
|
65
|
+
query = normalize_domain(query)
|
66
|
+
end
|
67
|
+
get(__method__, query)
|
68
|
+
end
|
69
|
+
|
70
|
+
# PassiveTotal uses the notion of classifications to highlight table rows a certain color based on how they have been rated.
|
71
|
+
# PassiveTotal::API#classification() queries if only one argument is given, and sets if both are given
|
72
|
+
# query: A domain or IP address to query
|
73
|
+
# set: classification label, one of [targeted, crime, multiple, benign]
|
74
|
+
def classification(query, set=nil)
|
75
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
76
|
+
if domain?(query)
|
77
|
+
query = normalize_domain(query)
|
78
|
+
end
|
79
|
+
if set.nil?
|
80
|
+
get(__method__, query)
|
81
|
+
else
|
82
|
+
is_valid_with_error(__method__, [:classification], set)
|
83
|
+
post(__method__, query, set)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# PassiveTotal allows users to notate if an IP address is a known sinkhole. These values are shared globally with everyone in the platform.
|
88
|
+
# PassiveTotal::API#sinkhole() queries if only one argument is given, and sets if both are given
|
89
|
+
# query: An IP address to set as a sinkhole or not
|
90
|
+
# set: String-boolean of "true" or "false"
|
91
|
+
def sinkhole(query, set=nil)
|
92
|
+
is_valid_with_error(__method__, [:ipv4], query)
|
93
|
+
if set.nil?
|
94
|
+
get(__method__, query)
|
95
|
+
else
|
96
|
+
is_valid_with_error(__method__, [:bool], set)
|
97
|
+
post(__method__, query, set)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# PassiveTotal allows users to notate if a domain or IP address have ever been compromised. These values aid in letting users know that a site may be benign, but it was used in an attack at some point in time.
|
102
|
+
# PassiveTotal::API#ever_compromised() queries if only one argument is given, and sets if both are given
|
103
|
+
# query: A domain or IP address to query
|
104
|
+
# set: String-boolean of "true" or "false"
|
105
|
+
def ever_compromised(query, set=nil)
|
106
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
107
|
+
if domain?(query)
|
108
|
+
query = normalize_domain(query)
|
109
|
+
end
|
110
|
+
if set.nil?
|
111
|
+
get(__method__, query)
|
112
|
+
else
|
113
|
+
is_valid_with_error(__method__, [:bool], set)
|
114
|
+
post(__method__, query, set)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# PassiveTotal allows users to notate if a domain is associated with a dynamic DNS provider.
|
119
|
+
# PassiveTotal::API#dynamic() queries if only one argument is given, and sets if both are given
|
120
|
+
# query: A domain to query
|
121
|
+
# set: String-boolean of "true" or "false"
|
122
|
+
def dynamic(query, set=nil)
|
123
|
+
is_valid_with_error(__method__, [:domain], query)
|
124
|
+
query = normalize_domain(query)
|
125
|
+
if set.nil?
|
126
|
+
get(__method__, query)
|
127
|
+
else
|
128
|
+
is_valid_with_error(__method__, [:bool], set)
|
129
|
+
post(__method__, query, set)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# PassiveTotal allows users to "watch" domains or IP addresses in order to get notified of any changes.
|
134
|
+
# PassiveTotal::API#watching() queries if only one argument is given, and sets if both are given
|
135
|
+
# query: A domain or IP address to query
|
136
|
+
# set: String-boolean of "true" or "false"
|
137
|
+
def watching(query, set=nil)
|
138
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
139
|
+
if domain?(query)
|
140
|
+
query = normalize_domain(query)
|
141
|
+
end
|
142
|
+
if set.nil?
|
143
|
+
get(__method__, query)
|
144
|
+
else
|
145
|
+
is_valid_with_error(__method__, [:bool], set)
|
146
|
+
post(__method__, query, set)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# PassiveTotal uses three types of tags (user, global, and temporal) in order to provide context back to the user.
|
151
|
+
# query: A domain or IP address to query
|
152
|
+
def tags(query)
|
153
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
154
|
+
get("user/tags", query)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Add a user-tag to an IP or domain
|
158
|
+
# query: A domain or IP address to tag
|
159
|
+
# tag: Value used to tag query value. Should only consist of alphanumeric, underscores and hyphen values
|
160
|
+
def add_tag(query, tag)
|
161
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
162
|
+
is_valid_with_error(__method__, [:tag], tag)
|
163
|
+
post_tag("user/tag/add", query, tag)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Remove a user-tag to an IP or domain
|
167
|
+
# query: A domain or IP address to remove a tag from
|
168
|
+
# tag: Value used to tag query value. Should only consist of alphanumeric, underscores and hyphen values
|
169
|
+
def remove_tag(query, tag)
|
170
|
+
is_valid_with_error(__method__, [:ipv4, :domain], query)
|
171
|
+
is_valid_with_error(__method__, [:tag], tag)
|
172
|
+
post_tag("user/tag/remove", query, tag)
|
173
|
+
end
|
174
|
+
|
175
|
+
# PassiveTotal collects and provides SSL certificates as an enrichment point when possible. Beyond the certificate data itself, PassiveTotal keeps a record of the IP address of where the certificate was found and the time in which it was collected.
|
176
|
+
# query: An IP address or SHA-1 hash to query
|
177
|
+
def ssl_certificate(query)
|
178
|
+
is_valid_with_error(__method__, [:ipv4, :hash], query)
|
179
|
+
if ipv4?(query)
|
180
|
+
get("ssl_certificate/ip_address", query)
|
181
|
+
elsif hash?(query)
|
182
|
+
get("ssl_certificate/hash", query)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
# returns true if the given string is a dotted quad IPv4 address
|
189
|
+
def ipv4?(ip)
|
190
|
+
if ip =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/
|
191
|
+
return true
|
192
|
+
end
|
193
|
+
false
|
194
|
+
end
|
195
|
+
|
196
|
+
# returns true if the given string looks like a domain and ends with a known top-level domain (TLD)
|
197
|
+
def domain?(domain)
|
198
|
+
return false if domain.nil?
|
199
|
+
domain = normalize_domain(domain)
|
200
|
+
domain =~ /^[a-zA-Z0-9\-\.]{3,255}$/ and TLDS.index(domain.split(/\./).last)
|
201
|
+
end
|
202
|
+
|
203
|
+
# returns true if the given string looks like a SHA-1 hash, i.e., 40 character hex string
|
204
|
+
def hash?(hash)
|
205
|
+
return false if hash.nil?
|
206
|
+
if hash =~ /^[a-fA-F0-9]{40}$/
|
207
|
+
return true
|
208
|
+
end
|
209
|
+
false
|
210
|
+
end
|
211
|
+
|
212
|
+
# returns true if the given string matches a valid classification
|
213
|
+
def classification?(c)
|
214
|
+
not ['targeted', 'crime', 'multiple', 'benign'].index(c).nil?
|
215
|
+
end
|
216
|
+
|
217
|
+
# returns true is the given object matches true or false
|
218
|
+
def bool?(b)
|
219
|
+
not ['true', 'false'].index(b.to_s).nil?
|
220
|
+
end
|
221
|
+
|
222
|
+
# returns true if the given string looks like a valid tag
|
223
|
+
def tag?(t)
|
224
|
+
return false if t.nil?
|
225
|
+
if t =~ /^[a-zA-Z][\w\_\-]+[a-zA-Z]$/
|
226
|
+
return true
|
227
|
+
end
|
228
|
+
false
|
229
|
+
end
|
230
|
+
|
231
|
+
# lowercases and removes a trailing period (if one exists) from a domain name
|
232
|
+
def normalize_domain(domain)
|
233
|
+
return domain.downcase.gsub(/\.$/,'')
|
234
|
+
end
|
235
|
+
|
236
|
+
# helper function to perform an HTTP GET against the web API
|
237
|
+
def get(api, query)
|
238
|
+
params = { 'api_key' => @apikey, 'query' => query }
|
239
|
+
url2json(:GET, "#{@endpoint}#{api}", params)
|
240
|
+
end
|
241
|
+
|
242
|
+
# helper function to perform an HTTP POST against the web API
|
243
|
+
def post(api, query, set)
|
244
|
+
params = { 'api_key' => @apikey, 'query' => query, api => set }
|
245
|
+
url2json(:POST, "#{@endpoint}#{api}", params)
|
246
|
+
end
|
247
|
+
|
248
|
+
# helper function to perform an HTTP POST against the web API, but sets the parameter to 'tag' instead of the api name
|
249
|
+
def post_tag(api, query, set)
|
250
|
+
params = { 'api_key' => @apikey, 'query' => query, 'tag' => set }
|
251
|
+
url2json(:POST, "#{@endpoint}#{api}", params)
|
252
|
+
end
|
253
|
+
|
254
|
+
# main helper function to perform HTTP interactions with the web API.
|
255
|
+
def url2json(method, url, params)
|
256
|
+
if method == :GET
|
257
|
+
url << "?" + params.map{|k,v| "#{k}=#{v}"}.join("&")
|
258
|
+
end
|
259
|
+
url = URI.parse url
|
260
|
+
http = Net::HTTP.new(url.host, url.port)
|
261
|
+
http.use_ssl = (url.scheme == 'https')
|
262
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
263
|
+
http.verify_depth = 5
|
264
|
+
request = nil
|
265
|
+
if method == :GET
|
266
|
+
request = Net::HTTP::Get.new(url.request_uri)
|
267
|
+
else
|
268
|
+
request = Net::HTTP::Post.new(url.request_uri)
|
269
|
+
request.set_form_data(params)
|
270
|
+
end
|
271
|
+
request.add_field("User-Agent", "Ruby/#{RUBY_VERSION} passivetotal rubygem v#{PassiveTotal::VERSION}")
|
272
|
+
t1 = Time.now
|
273
|
+
response = http.request(request)
|
274
|
+
delta = (Time.now - t1).to_f
|
275
|
+
data = JSON.parse(response.body)
|
276
|
+
return data
|
277
|
+
end
|
278
|
+
|
279
|
+
# tests an item to see if it matches a valid type
|
280
|
+
def is_valid?(types, item)
|
281
|
+
types.each do |type|
|
282
|
+
if type == :ipv4
|
283
|
+
return true if ipv4?(item)
|
284
|
+
elsif type == :domain
|
285
|
+
return true if domain?(item)
|
286
|
+
elsif type == :hash
|
287
|
+
return true if hash?(item)
|
288
|
+
elsif type == :classification
|
289
|
+
return true if classification?(item)
|
290
|
+
elsif type == :tag
|
291
|
+
return true if tag?(item)
|
292
|
+
elsif type == :bool
|
293
|
+
return true if bool?(item)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
return false
|
297
|
+
end
|
298
|
+
|
299
|
+
# tests an item to see if it matches a valid type and raises an ArgumentError if invalid
|
300
|
+
def is_valid_with_error(methname, types, item)
|
301
|
+
valid = is_valid?(types, item)
|
302
|
+
unless valid
|
303
|
+
raise ArgumentError.new("#{methname} requires arguments of type: #{types.join(",")}")
|
304
|
+
end
|
305
|
+
valid
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'getoptlong'
|
2
|
+
require 'passivetotal/api'
|
3
|
+
|
4
|
+
module PassiveTotal # :nodoc:
|
5
|
+
# Handles all the command-line parsing and dispatching queries to the PassiveTotal::API instance
|
6
|
+
# CLInterface is aliased by CLI
|
7
|
+
class CLInterface
|
8
|
+
# parses the command line and yields an options hash
|
9
|
+
# === Default Options
|
10
|
+
# options = {
|
11
|
+
# :method => :usage,
|
12
|
+
# :query => nil,
|
13
|
+
# :set => nil,
|
14
|
+
# :debug => false,
|
15
|
+
# :apikey => ENV['PASSIVETOTAL_APIKEY']
|
16
|
+
# }
|
17
|
+
def self.parse_command_line(args)
|
18
|
+
origARGV = ARGV.dup
|
19
|
+
ARGV.replace(args)
|
20
|
+
opts = GetoptLong.new(
|
21
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
22
|
+
[ '--debug', '-v', GetoptLong::NO_ARGUMENT ],
|
23
|
+
[ '--apikey', '-k', GetoptLong::REQUIRED_ARGUMENT ],
|
24
|
+
[ '--metadata', '-m', GetoptLong::REQUIRED_ARGUMENT ],
|
25
|
+
[ '--passive', '-p', GetoptLong::REQUIRED_ARGUMENT ],
|
26
|
+
[ '--subdomains', '-s', GetoptLong::REQUIRED_ARGUMENT ],
|
27
|
+
[ '--classification', '-c', GetoptLong::REQUIRED_ARGUMENT ],
|
28
|
+
[ '--tags', '-t', GetoptLong::REQUIRED_ARGUMENT ],
|
29
|
+
[ '--sinkhole', '-x', GetoptLong::REQUIRED_ARGUMENT ],
|
30
|
+
[ '--evercompromised', '-e', GetoptLong::REQUIRED_ARGUMENT ],
|
31
|
+
[ '--dynamic', '-d', GetoptLong::REQUIRED_ARGUMENT ],
|
32
|
+
[ '--watching', '-w', GetoptLong::REQUIRED_ARGUMENT ],
|
33
|
+
[ '--sslcertificate', '-l', GetoptLong::REQUIRED_ARGUMENT ],
|
34
|
+
[ '--set', '-i', GetoptLong::REQUIRED_ARGUMENT ]
|
35
|
+
)
|
36
|
+
|
37
|
+
options = {
|
38
|
+
:method => :usage,
|
39
|
+
:query => nil,
|
40
|
+
:set => nil,
|
41
|
+
:debug => false,
|
42
|
+
:apikey => ENV['PASSIVETOTAL_APIKEY']
|
43
|
+
}
|
44
|
+
|
45
|
+
opts.each do |opt, arg|
|
46
|
+
case opt
|
47
|
+
when '--help'
|
48
|
+
options[:method] = :usage
|
49
|
+
when '--debug'
|
50
|
+
options[:debug] = true
|
51
|
+
when '--apikey'
|
52
|
+
options[:apikey] = arg
|
53
|
+
when '--metadata'
|
54
|
+
options[:method] = :metadata
|
55
|
+
options[:query] = arg
|
56
|
+
when '--passive'
|
57
|
+
options[:method] = :passive
|
58
|
+
options[:query] = arg
|
59
|
+
when '--subdomains'
|
60
|
+
options[:method] = :subdomains
|
61
|
+
options[:query] = arg
|
62
|
+
when '--classification'
|
63
|
+
options[:method] = :classification
|
64
|
+
options[:query] = arg
|
65
|
+
when '--tags'
|
66
|
+
options[:method] = :tags
|
67
|
+
options[:query] = arg
|
68
|
+
when '--sinkhole'
|
69
|
+
options[:method] = :sinkhole
|
70
|
+
options[:query] = arg
|
71
|
+
when '--evercompromised'
|
72
|
+
options[:method] = :ever_compromised
|
73
|
+
options[:query] = arg
|
74
|
+
when '--dynamic'
|
75
|
+
options[:method] = :dynamic
|
76
|
+
options[:query] = arg
|
77
|
+
when '--watching'
|
78
|
+
options[:method] = :watching
|
79
|
+
options[:query] = arg
|
80
|
+
when '--sslcertificate'
|
81
|
+
options[:method] = :ssl_certificate
|
82
|
+
options[:query] = arg
|
83
|
+
when '--set'
|
84
|
+
options[:set] = arg.dup
|
85
|
+
else
|
86
|
+
options[:method] = :usage
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
if options[:method] == :tags and options[:set]
|
91
|
+
if options[:set] =~ /^\-/
|
92
|
+
options[:set].gsub!(/^\-/,'')
|
93
|
+
options[:method] = :remove_tag
|
94
|
+
else
|
95
|
+
options[:method] = :add_tag
|
96
|
+
end
|
97
|
+
end
|
98
|
+
args = ARGV.dup
|
99
|
+
ARGV.replace(origARGV)
|
100
|
+
|
101
|
+
if options[:debug]
|
102
|
+
$stderr.puts "PassiveTotal CLI Options"
|
103
|
+
$stderr.puts " apikey: #{options[:apikey]}"
|
104
|
+
$stderr.puts " debug: #{options[:debug]}"
|
105
|
+
$stderr.puts " method: #{options[:method]}"
|
106
|
+
$stderr.puts " query: #{options[:query]}"
|
107
|
+
$stderr.puts " set: #{options[:set]}"
|
108
|
+
end
|
109
|
+
|
110
|
+
return options
|
111
|
+
end
|
112
|
+
|
113
|
+
# returns a string containing the usage information
|
114
|
+
def self.usage
|
115
|
+
help_text = "Usage: #{$0} [-h] [-v] [-k <apikey>] [[-m|-p|-c|-t|-e|-w] <ip or dom>] [[-s|-d] <dom>] [-x <ip>] [-l <ip or hash>] [-i <value>]\n"
|
116
|
+
help_text << "-h Help\n"
|
117
|
+
help_text << "-v Verbose output\n"
|
118
|
+
help_text << "-k <apikey> Sets the APIKEY, defaults to the environment variable PASSIVETOTAL_APIKEY\n"
|
119
|
+
help_text << "ACTIONS (You have to select one, last one wins)"
|
120
|
+
help_text << " -m <ip or dom> Queries metadata for given IP or domain\n"
|
121
|
+
help_text << " -p <ip or dom> Queries passive DNS data for given IP or domain\n"
|
122
|
+
help_text << " -c <ip or dom> Queries (or sets) the classification for a given IP or domain\n"
|
123
|
+
help_text << " -t <ip or dom> Queries (adds or removes) the tags associated with a given IP or domain\n"
|
124
|
+
help_text << " * To remove a tag, prepend a dash, '-' to the tag name when using the -i option\n"
|
125
|
+
help_text << " -e <ip or dom> Queries (or sets) the ever compromised flag on a given IP or domain\n"
|
126
|
+
help_text << " -w <ip or dom> Queries (or sets) the watched flag on a given IP or domain\n"
|
127
|
+
help_text << " -s <dom> Queries the subdomains for a given domain\n"
|
128
|
+
help_text << " -d <dom> Queries (or sets) if a domain is a dynamic DNS domain\n"
|
129
|
+
help_text << " -x <ip> Queries (or sets) if a given IP is a sinkhole\n"
|
130
|
+
help_text << " -l <ip or hash> Queries for SSL Certificates/IP addresses associated with a given IP or SHA-1 hash\n"
|
131
|
+
help_text << "SETTING VALUES"
|
132
|
+
help_text << " -i <value> Sets the value, used in conjuntion with -c, -t, -e, -w, -d, or -x\n"
|
133
|
+
help_text << " Valid values for -i depend on what it's used with:\n"
|
134
|
+
help_text << " -c : targeted, crime, multiple, benign\n"
|
135
|
+
help_text << " -t : <a tag name consisting of characters: [a-zA-Z_]>\n"
|
136
|
+
help_text << " -e, -w, -d, -x: true, false\n"
|
137
|
+
help_text
|
138
|
+
end
|
139
|
+
|
140
|
+
# main method, takes command-line arguments and performs the desired queries and outputs
|
141
|
+
def self.run(args)
|
142
|
+
options = parse_command_line(args)
|
143
|
+
return usage() if options[:method] == :usage
|
144
|
+
pt = PassiveTotal::API.new(options[:apikey])
|
145
|
+
if pt.respond_to?(options[:method])
|
146
|
+
if options[:set]
|
147
|
+
data = pt.send(options[:method], options[:query], options[:set])
|
148
|
+
else
|
149
|
+
data = pt.send(options[:method], options[:query])
|
150
|
+
end
|
151
|
+
return JSON.pretty_generate(data['results'])
|
152
|
+
end
|
153
|
+
return ''
|
154
|
+
end
|
155
|
+
end
|
156
|
+
# Alias for the CLInterface class
|
157
|
+
CLI = PassiveTotal::CLInterface
|
158
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'passivetotal/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "passivetotal"
|
8
|
+
spec.version = PassiveTotal::VERSION
|
9
|
+
spec.authors = ["chrislee35"]
|
10
|
+
spec.email = ["rubygems@chrislee.dhs.org"]
|
11
|
+
|
12
|
+
spec.summary = %q{Wrapper library for PassiveTotal.org's W eb API}
|
13
|
+
spec.description = %q{PassiveTotal offers an extensive API for users of the platform that maps most major actions available in the web application to a corresponding call. There are two flavors of the API available for use, stable and current. In order to use the stable API, add the version indicator (vX) into the URL as documented below. If you would rather use the current API which includes new changes and experiments, replace the version indicator with "current".}
|
14
|
+
spec.homepage = "https://github.com/chrislee35/passivetotal"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "bin"
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency "json", "~> 1.8"
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "minitest"
|
26
|
+
end
|
data/utils/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "passivetotal"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/utils/setup
ADDED
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: passivetotal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- chrislee35
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.10'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: PassiveTotal offers an extensive API for users of the platform that maps
|
70
|
+
most major actions available in the web application to a corresponding call. There
|
71
|
+
are two flavors of the API available for use, stable and current. In order to use
|
72
|
+
the stable API, add the version indicator (vX) into the URL as documented below.
|
73
|
+
If you would rather use the current API which includes new changes and experiments,
|
74
|
+
replace the version indicator with "current".
|
75
|
+
email:
|
76
|
+
- rubygems@chrislee.dhs.org
|
77
|
+
executables:
|
78
|
+
- passivetotal
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files: []
|
81
|
+
files:
|
82
|
+
- ".gitignore"
|
83
|
+
- ".travis.yml"
|
84
|
+
- Gemfile
|
85
|
+
- LICENSE.txt
|
86
|
+
- README.md
|
87
|
+
- Rakefile
|
88
|
+
- bin/passivetotal
|
89
|
+
- lib/passivetotal.rb
|
90
|
+
- lib/passivetotal/api.rb
|
91
|
+
- lib/passivetotal/cli.rb
|
92
|
+
- lib/passivetotal/version.rb
|
93
|
+
- passivetotal.gemspec
|
94
|
+
- utils/console
|
95
|
+
- utils/setup
|
96
|
+
homepage: https://github.com/chrislee35/passivetotal
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.4.6
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Wrapper library for PassiveTotal.org's W eb API
|
120
|
+
test_files: []
|