passivetotal 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
4
+ before_install: gem install bundler -v 1.10.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in passivetotal.gemspec
4
+ gemspec
@@ -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.
@@ -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
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'passivetotal/cli'
3
+ puts PassiveTotal::CLI.run(ARGV)
@@ -0,0 +1,6 @@
1
+ require "passivetotal/version"
2
+ require "passivetotal/api"
3
+
4
+ module PassiveTotal
5
+ # Your code goes here...
6
+ end
@@ -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,3 @@
1
+ module PassiveTotal
2
+ VERSION = "0.1.0"
3
+ 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
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
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: []