QQ乐园首页-提供最新QQ技术网以及QQ业务免费开通乐园网!

搜索

1款结构简捷的Github数据泄漏爬虫

2017-11-11 00:36| 发布者: Hide| 查看: 87| 评论: 0

摘要: 0×01.媒介Github作为一个代码托管平台,有着海量的开源代码和很多开发者。在代码上传时,有些开发者短缺网安认识,会在不经意间泄漏自己的暗码或者密钥。本文以这里为切入点,先容一个检索代码信息的小爬虫和在写爬 ...

0×01.媒介
Github作为一个代码托管平台,有着海量的开源代码和很多开发者。在代码上传时,有些开发者短缺网安认识,会在不经意间泄漏自己的暗码或者密钥。本文以这里为切入点,先容一个检索代码信息的小爬虫和在写爬虫时的一些奇技淫巧。
0×02.github信息泄漏
正如媒介所述,短缺网安认识的开发者会形成这个成绩。不止web门路下的.git目次会泄漏信息,在托管的开源代码中也会发生信息泄漏。例子很多,好比php衔接数据库的配置文件泄漏,那末能够数据库帐号暗码都泄漏了,任何人都能够拜访这个数据库。再好比通向内网的帐号暗码,管理员帐号暗码乃至ssh密钥。
api,即应用程序编程接口。家喻户晓,http是无状况协定,为了将用户辨别开引进了cookie机制。有很多厂商,供给了api这个接供词用户调取营业,为了辨别用户引进了token,好比'https://example.com/get?info=xxx&token=xxx'
而我比拟爱好做的事便是,在github上找api的密钥。因为比拟与帐号暗码,这个不只泄漏的更多,而且也更难以留意觉察,而且咱们挪用便利。好比查问whois信息,子域名检测等等,很多网安厂商供给了api接口,以是假如你没有密钥,无妨尝尝这个github信息泄漏的办法。
shodan能够很多网安从业者都晓得,这是一个很强大的搜刮引擎。下文我会以爬取github上的shodan api密钥为例子,写一个简略的小爬虫。
0×03.github搜刮成果爬取
1.shodan api格局
起首拜访https://developer.shodan.io/api,这是shodan的api文档,咱们能够看到api哀求格局为https://api.shodan.io/shodan/host/{ip}?key={YOUR_API_KEY}。以后咱们就能够在github上搜刮”https://api.shodan.io/shodan/host/ key=”来看看。
成果如图:




能够看到曾经有人不小心泄漏自己的密钥了,固然另有很多人没有。
2.github信息网络
固然github有供给api,然则对代码检索功效有限制,以是咱们这里不应用api。
起首停止搜刮咱们必要有一个登录状况,人人能够注册一个小号,或者是应用大号,这个不要紧的。
登录状况咱们能够应用cookies,也能够间接登录,咱们这里说间接登录。
起首F12抓包能够看到全部登录流程,即拜访github.com/login,以后将表单的值传递给github.com/session。全部流程异常清楚。
代码以下:
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0',
'Referer': 'https://github.com/',
'Host': 'github.com',
'Upgrade-Insecure-Requests': '1',
}
payload = {'commit': 'Sign in', 'login': 'xxxxxx@xxxx.xxx',  'password': 'xxxxxx'}
r = requests.get("https://github.com/login", headers=headers)
_cookies = r.cookies
r = requests.post("https://github.com/session", headers=headers, data=payload,  cookies=_cookies)
如上可不能够呢?细心阐发全部流程,实际上,表单值中另有一个authenticity_token,咱们要先抓取到这个值,而后传递给表单。
抓取函数以下:
from lxml import etree
def get_token(text):
#
    html = etree.HTML(text)
    t = html.xpath("//input[@name='authenticity_token']")
    try:
        token = t[0].get('value')
    except IndexError:
        print("[+] Error: can't get login token, exit...")
        os.exit()
    except Exception as e:
        print(e)
        os.exit()
    #print(token)
    return token
payload['authenticity_token'] = get_token(r.content)
如今咱们代码另有甚么毛病呢,我感到便是对cookies的处置不敷优雅。requests有一个奇异的类requests.session(),能够为每次哀求保留cookies,并应用于下次哀求。
在民间文档咱们能够找到http://www.python-requests.org/en/master/user/advanced/#session-objects。




这里咱们能够应用requests.session对代码停止优化,即:
import requests
session = requests.Session()
r = session.get("https://github.com/login", headers=headers)
payload['authenticity_token'] = get_token(r.content)
r = session.post("https://github.com/session", headers=headers, data=payload)
得到登录状况后,咱们就能够停止搜刮,以后列举出信息。
这里采纳lxml停止信息提取,xpath很简略,不多说,代码以下:
from lxml import etree
words = "https://api.shodan.io/shodan/host/ key="
url = ("https://github.com/search?p=1&q=%s&type=Code" % words)
r = session.get(url, headers=headers)
html = etree.HTML(r.text)
block = html.xpath("//div[@class='code-list-item col-12 py-4 code-list-item-public ']")
print("[+] Info: get item: %i" % len(block))
codes = html.xpath("//div[@class='code-list-item col-12 py-4 code-list-item-public ']/div[@class='file-box blob-wrapper']/table[@class='highlight']/tr/td[@class='blob-code blob-code-inner']")
nums = html.xpath("//div[@class='code-list-item col-12 py-4 code-list-item-public ']/div[@class='file-box blob-wrapper']/table[@class='highlight']/tr/td[@class='blob-num']/a")
if len(codes) == len(nums):
    print("[+] Info: start get data, waiting")
    lines = []
    strs = None
    for i in range(len(nums)):
        #print(etree.tostring(codes[i], method='text'))
    try:
        text = etree.tostring(codes[i], method='text')
    except UnicodeEncodeError:
        print("[+] UnicodeEncodeError of a result, jump...")
        continue
    if nums[i].text == '1':
        if strs is not None:
            lines.append(strs)
            strs = text
        else:
            strs = "%s \\n %s" % (strs, text)
            lines.append(strs)
    else:
        print("[+] Error: wrong number get for codes lines, exit")
print("info : %s" % lines)
print("total num of info get: %i" % len(lines))
接上去是正则部门,信息抓取上去后,怎样筛选出咱们必要的信息呢?靠的便是这里。
因为咱们晓得shodan api以http的情势,把shodan的token带入了参数中。简略的以get办法为例,能够是?key=xxx&host=xxx、?hosy=xxx&key=xxx&ip=xxx或者?host=xxx&key=xxx等等情势,以是咱们结构正则为 key=的情势,而后测验考试婚配key的末端,能够是'、"、&。
代码以下:
import re
pattern = re.compile('key=(.*)[&|"|\']')
for a in lines:
    strs = re.findall(pattern, str(a))
    if len(strs) > 0:
        #print(strs[0].split('"')[0])
        results = strs[0].split('"')[0]
        results = results.split('&')[0]
        results = results.split('\'')[0]
        if results == '':
            continue
        print(results)
用一个简略的多线程,多爬取几页能够看到输入:
D32FBKHYYqETSf4bIdmurM7xoZA74FnL
E48kKXIaCpuKq4nsTJCglvd9o4y8oBni
${SHODAN_API_KEY}
AR7LzKvBGZNaXlgkYCg4Z9y3x5lEO352
%s
${PINCH.USERDEFINED.api_key.value}
{ShodanAPIKey}
{YOUR_API_KEY}
%s
$SHODAN_API_KEY
MFuS0RPXqInMILeWWPFktPp2BOHUZpzF
#{SHODAN_API_KEY}
D32FBKHYYqETSf4bIdmurM7xoZA74FnL
$SHODAN_API_KEY
MFuS0RPXqInMILeWWPFktPp2BOHUZpzF
#{SHODAN_API_KEY}
${PINCH.USERDEFINED.api_key.value}
E48kKXIaCpuKq4nsTJCglvd9o4y8oBni
{YOUR_API_KEY}
%s
${SHODAN_API_KEY}
AR7LzKvBGZNaXlgkYCg4Z9y3x5lEO352
固然曾经有了输入,然则留意到并非一切输入都相符请求,有的乃至只是一个变量名。
实在到这里曾经停止了,shodan api长度为32,只需验证长度就能够得到密钥了。然则本着千锤百炼的精力,咱们将会编写正则表达式,进一步的得到信息。
从输入能够看到,除输入token,另有%s和变量称号两种情势。认识python的人能够晓得,%s是python中的格局化输入。
咱们起首去除特殊符号data = re.findall(pattern1, results)[0],以后断定字符串范例:
if data == 's':
    print("python")
elif len(data) 32:
    print("value")
else:
    print(data)
这里咱们顺遂辨别开了python的输入、值为token的变量和token。
下一步咱们测验考试得到变量的值,即token。咱们假定变量的值就在搜刮成果中,即key=value的情势。
因为上一步中曾经得到了变量称号data,以是构建正则以下:
pattern0 = re.compile("%s[=|:](.*)[\"|']" % data[:6])
results = re.findall(pattern0, a.replace(' ',''))
if len(results) > 0:
    results = results[0].split('\'')[0]
    print(results.split('"'))
咱们能够得到输入好比['sys.argv[1]\\n\\n']。
而后是对python格局化输入%s的剖析。平日%s格局化输入为print("%s" % strs)或者print("%s,%s" % (strs, strs))的情势。以是构建正则以下:
pattern2 = re.compile('%\([\w|\.|,]+')
results = re.findall(pattern2, a.replace(' ',''))
lists = []
for i in results:
    i = i.replace('%(', '')
    i = i.split(',')
    lists.extend(i)
    lists = set(lists)
这里咱们起首提取变量称号strs,以后做了去重操纵。
既然得到了变量名词,咱们能够模仿上一步,得到变量的值。
再次运转成果以下:
['//api.bintray.com/packages/fooock/maven/jShodan/images/download.svg)](https://bintray.com/fooock/maven/jShodan/_latestVersion)[![AndroidArsenal](https://img.shields.io/badge/Android%20Arsenal-jShodan-brightgreen.svg?style=flat)]( https://android-arsenal.com/details/1/5312)\\n']
D32FBKHYYqETSf4bIdmurM7xoZA74FnL
D32FBKHYYqETSf4bIdmurM7xoZA74FnL
E48kKXIaCpuKq4nsTJCglvd9o4y8oBni
AR7LzKvBGZNaXlgkYCg4Z9y3x5lEO352
['//developer.shodan.io](https://developer.shodan.io)\\n']
MFuS0RPXqInMILeWWPFktPp2BOHUZpzF
['//api.shodan.io/shodan/host/search?key=%s&query=hostname:%s&facets={facets}', '%(\\n']
MM72AkzHXdHpC8iP65VVEEVrJjp7zkgd
['OPTIONAL)],\\n\\n']
0fTS2YJPZAOSQHnC7kSEI06LrTg7pPcV
0×04.爬虫技能
1.调试爬虫
偶然咱们写完爬虫后,会发明成果并非咱们想要的,咱们就想晓得中央出了甚么成绩。
最直观的,间接输入代码print(req.content),或者繁杂一点,输入成html文件:
def see(text):
    with open("./t.html", "w") as f:
        f.write(text)
see(req.content)
人人能够晓得,咱们会用burp suite、fiddle来停止挪动端的抓包阐发。异样的,在这里,咱们也能够通过署理,对爬虫停止阐发。这里我应用的是burp suite。





咱们不只能够及时阐发哀求,也能够在history里阐发哀求。
这里以requests为例,咱们能够应用署理设置,民间文档如图:






proxies = {
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080',
}
r = session.get("https://github.com/login", headers=headers, proxies=proxies)
然则人人应当晓得,在浏览器中应用burp suite对https停止阐发的时刻,必要导入证书。因为https会对证书停止验证,而burp suite不属于可托证书,以是必要导入。然则这里咱们怎样导入证书呢?
很简略,只必要简略的加一个参数verify便可。这个verify的意思为,纰谬证书停止验证。
r = session.get("https://github.com/login", headers=headers, verify=False, proxies=proxies)
2.保留状况
为了不必要每次都要登录,咱们能够保留cookie到文件,下次间接读取cookie就好了。代码以下:
# 从文件读入cookie
with open('./cookies.txt', 'rb') as f:
    cookies = requests.utils.cookiejar_from_dict(pickle.load(f))
session.cookies=cookies
# 保留cookie
with open('./cookies.txt', 'wb') as f:
    pickle.dump(requests.utils.dict_from_cookiejar(session.cookies), f)
0×05.总结
此次只因此shodan api为例子,提示人人留意github信息泄漏,也给想要爬取github敏感信息的人抛个砖。不只是shodan api,github上有更多的api期待你去发掘。只必要改改正则,调试一下,你也有了自己的api爬取爬虫。
附录
代码以下(也能够拜访这个私密gist):
#coding:utf-8
import requests
import re
from lxml import etree
import os
import io
import pickle
import threading
import warnings
warnings.filterwarnings('ignore')
session = requests.Session()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0' ,
'Referer': 'https://github.com/' ,
'Host': 'github.com',
'Upgrade-Insecure-Requests': '1',
}
payload = {'commit': 'Sign in' , 'login': 'xxxxx@xxx.xxx', 'password' : 'xxxxxx'}
proxies = {
'http': 'http://127.0.0.1:8080' ,
'https': 'http://127.0.0.1:8080' ,
}
def see(text):
with open("./t.html" , "w") as f:
f.write(text)
def get_token(text):
#
html = etree.HTML(text)
t = html.xpath("//input[@name='authenticity_token']")
try:
token = t[0].get('value' )
except IndexError:
print("[+] Error: can't get login token, exit...")
os.exit()
except Exception as e:
print(e)
os.exit()
#print(token)
return token
def get_cookie(session):
if not os.path.exists("./cookies.txt" ):
r = session.get("https://github.com/login" , headers=headers)#, verify=False, proxies=proxies)
payload['authenticity_token'] = get_token(r.content)
r = session.post("https://github.com/session" , headers=headers, data= payload)#, verify=False, proxies=proxies)
#print(r.cookies.get_dict())
#see(r.text)
else:
with open('./cookies.txt' , 'rb') as f:
try:
cookies = requests.utils.cookiejar_from_dict(pickle.load(f))
except TypeError:
os.remove("./cookies.txt")
return get_cookie(session)
session.cookies=cookies
return session
def search(url, session):
r = session.get(url, headers=headers) #, verify=False, proxies=proxies)
html = etree.HTML(r.text)
block = html.xpath("//div[@class='code-list-item col-12 py-4 code-list-item-public ']" )
#print("[+] Info: get item: %i" % len(block))
codes = html.xpath("//div[@class='code-list-item col-12 py-4 code-list-item-public ']/div[@class='file-box blob-wrapper']/table[@class='highlight']/tr/td[@class='blob-code blob-code-inner']" )
nums = html.xpath("//div[@class='code-list-item col-12 py-4 code-list-item-public ']/div[@class='file-box blob-wrapper']/table[@class='highlight']/tr/td[@class='blob-num']/a" )
if len(codes) == len(nums):
lines = []
strs = None
for i in range (len(nums)):
#print(etree.tostring(codes[i], method='text'))
try:
text = etree.tostring(codes[i], method= 'text')
except UnicodeEncodeError:
#print("UnicodeEncodeError")
continue
if nums[i].text == '1' :
if strs is not None:
lines.append(strs)
strs = text
else:
strs = "%s \n %s" % (strs, text)
lines.append(strs)
else:
print("[+] Error: wrong number get for codes lines, exit")
pattern = re.compile('key=(.*)[&|"|\']')
pattern1 = re.compile("\w+")
pattern2 = re.compile('%([\w|.|,]+')
for a in lines:
#a = a.replace(' ','')
strs = re.findall(pattern, str(a))
if len(strs) > 0:
results = strs[0].split('"' )[0]
results = results.split('&')[ 0]
results = results.split('\'')[ 0]
if results == '' :
continue
try:
data = re.findall(pattern1, results)[0]
except IndexError:
print(results)
continue
if data == 's' :
resulresults = re.findall(pattern2, a.replace(' ', ''))
lists = []
for i in results:
i = i.replace('%(', '' )
i = i.split(',')
lists.extend(i)
lists = set(lists)
for i in lists:
pattern0 = re.compile("%s=|:[\"|']" % i[:6])
results = re.findall(pattern0, a.replace(' ', ''))
if len(results) > 0:
results = results[0].split('\'' )[0]
print(results.split('"'))
#print(a)
elif len(data) 32:
pattern0 = re.compile("%s=|:[\"|']" % data[:6])
results = re.findall(pattern0, a.replace(' ', ''))
if len(results) > 0:
results = results[0].split('\'' )[0]
print(results.split('"'))
#print(a)
else:
print(data)
words = "https://api.shodan.io/shodan/host/ key="
session = get_cookie(session)
threads = []
for i in range( 1, 21):
url = "https://github.com/search?p= %i&q=%s&type=Code" % (i, words)
t=threading.Thread(target = search, args = (url, session))
t.start()
threads.append(t)
for t in threads:
t.join()
threads = []
for i in range( 21, 41):
url = "https://github.com/search?p= %i&q=%s&type=Code" % (i, words)
t=threading.Thread(target = search, args = (url, session))
t.start()
threads.append(t)
for t in threads:
t.join()
with open('./cookies.txt' , 'wb') as f:
pickle.dump(requests.utils.dict_from_cookiejar(session.cookies), f)

http://www.qqhello.net/
普及network security意识 
不要对network security一无所知


鲜花

握手

雷人

路过

鸡蛋
返回顶部