常用工具及插件 findsomething浏览器插件、packerfuzzer和JSFinder之类的工具
Webpack情形下提取js及接口 有时候findsomething会看不到接口,wap显示是webpack打包,使用packerfuzzer工具
1 https://github.com/rtcatc/Packer-Fuzzer
这里packerfuzzer需要nodejs环境,并且运行的时候如果报错
1 ImportError: cannot import name 'OxmlElement' from 'docx.oxml.xmlchemy' (C:\Users\Radish\AppData\Local\Programs\Python\Python38\lib\site-packages\docx\oxml\xmlchemy.py)
需要指定安装
1 pip install python-docx==0.8.11
packerfuzzer会把网站的js都下载下来,然后可以处理js文件,提取其中的uri
1 python PackerFuzzer.py -u url -f 1
正则表达式匹配字符串就行,也就是匹配被单引号或者双引号包裹起来的字符串,如果字符串被/分割,则将其提取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import jsonimport reimport requestsimport sysimport osfileurl=sys.argv[1 ] filemkdir=fileurl.split('_' )[0 ] if not os.path.exists(filemkdir): os.makedirs(filemkdir) paths=[] for dirpath, dirnames, filenames in os.walk('./' +filemkdir): for file in filenames: with open ("./" +filemkdir+"/" +file,"r" ,encoding='gb18030' , errors='ignore' ) as f2: try : line=f2.readlines() for line in line: line=line.strip('\n' ).strip('\t' ) p = re.findall('''(['"]\/[^][^>< \)\(\{\}]*?['"])''' ,line) if p != None : for path in p: path=path.replace(':"' ,"" ).replace('"' ,"" ) paths.append(file+"---" +path) except Exception as e: print (e) for var in sorted (set (paths)): with open (fileurl+'_path.txt' ,"a+" ,encoding='gb18030' , errors='ignore' ) as paths: paths.write(var+'\n' )
但是packetfuzzer并不是万能的,有时候会下载下来一些.db文件,需要结合数据库工具来查看
findsomething和Unexpected.information这类插件也需要结合着用,有些网站直接查看其前端并没有找到任何和app.js(app.xxxxxxxx.js这种格式) index.js main.js等比较经典的和webpack相关的文件,然而查看burp的history,却发现该网站加载了app.js,并且unexpected information插件成功在其中匹配到了各种信息
目录FUZZ结合接口测试 第一,提取了形如http://test.test.com/api/test/admin/delete 这种接口,那么就有理由测试其他的添加、修改等功能的接口
1 2 3 4 5 6 7 8 查询(获取信息) search list select query get find 删除(删除某个数据) del Delete 编辑(更新某个信息) Update Up edit Change 添加(增加某个信息) add create new
第二,如果提取了形如http://test.test.com/api/test/user/add 这种接口,那么就有理由测试/admin/add这种接口
1 2 3 http://test.hack.com/api/test/FUZZ/add 或者 http://test.hack.com/api/test/adminEdit
多观察接口,推测其功能,然后根据功能去FUZZ,毕竟要实现一个web功能,基本都要有对应的增删改查接口
推荐FFUF这款工具
从接口提取到定位后端地址 第一种方式简单来说就是猜,简单来说你一个网站,页面上肯定有很多功能,以登录场景为例,肯定是有一个登录的功能嘛。那我们可以抓取登录的数据包,观察其接口的uri规则,而且在你访问某个网站的时候,很多时候会先访问一些接口来完成初始化,或者获取你的某些状态,在没有登录点的情况下,通过这些接口也是可以的。
比方说我们调用http://test.com/#/login的登录点,这时候抓包访问到了/vsk/virsical-auth/oauth/token这个uri
这里我们引入一个“初级目录”的概念,打引号是因为绝大多数情况下这并不是真正的目录。
什么意思呢?我们以Spring开发为例,对SpringSSM比较熟悉的小伙伴应该知道,我们可以在配置文件里添加一个配置项:
1 server.servlet.context-path
比方说
1 server.servlet.context-path=/vsk
这样,你的web应用的所有uri前面都要加一层/test1,比方说我给登录的动作配置一个RequestMapping,为
1 @RequestMapping(“/virsical-auth/oauth/token”);
那么如果我想通过WEB访问这个动作,应该访问的uri应该是:
1 /vsk/virsical-auth/oauth/token
通过上面这个例子你想到了什么?没错,我们上面抓到的那个数据包,有可能是以/vsk作为初始路径。然后我们通过前文的提取接口相关的技术找到的接口uri可能是/virsical-auth/oauth/token,这样我们就知道怎么拼接了,就可以得到/vsk/virsical-auth/oauth/token。
当然实战中绝大部分都是黑盒的情况,你通过现有功能看到这么一个uri,其实也不一定能百分百判断到底哪个是初级uri,比方说上述情况。有可能是/vsk作为初级uri,亦可能是/vsk/virsical-auth/作为初级uri,这个需要靠经验来判断。如果你怕判断有误,最好的办法就是多拼接几次,比如:
1 2 3 http://test.com/vsk/ <---接口依次拼接在这后面 http://test.com/vsk/virsical-auth/ <---接口依次拼接在这后面 http://test.com/vsk/virsical-auth/oauth/ <---接口依次拼接在这后面
以此类推,不过一般比较少出现这种情况。上述是“初级目录”的情况,还有一种情况则是前后端分离。这个也很好理解。比方说你访问的网站是:
http://admin.test.com/#/login
然而负责提供服务的后端地址可能是:
1 2 http://api.admin.test.com/api/admin/login http://admin.test.com:8080/api/admin/login
这些抓包都很容易看出来
但是还有一些情况。
情况一:某个后台,访问即重定向到SSO登录,能加载出JS,但是在这个过程中不会访问多余的接口,也没有登录之类的现成功能点给我们调用。这样我们没法通过现有功能去获取到接口uri,这对我们判断“初级路径”就会造成很大阻碍,更何况如果目标采用前后端分离,我们都不知道后端在哪里,又该怎么拼接接口测试呢?
情况二:还是某个后台,假如开发的思维非常严谨,给每个Controller都设置了一个RequestMapping,比方说登录相关功能可能放在/login/这个uri下,普通用户的功能放在/user/,管理员的功能放在/admin/。也就是说,我们至少有可能存在三种“初级路径”,在这种情况下,即使我们有一个登录的现有功能,我们也只是获取到了/login/这个初级路径,而我们提取的接口uri肯定绝大部分都是分布在/user/和/admin/两个路径下,在这种情况下,实际上也相当于我们忽略掉了绝大部分的接口。
那么这又该如何破局呢?其实也非常ez,因为他这些后端地址包括“初级路径”,肯定不是无中生有搞出来的,这些东西肯定都在JS里。比方说上面的那个多个初级路径的情况,在JS中大概率存放了一些地址:
1 2 3 http://api.test.com/login/ http://api.test.com/user/ http://api.test.com/admin/
当用户访问对应功能,比方说普通用户修改自己的密码,前端会取出:
然后和对应的地址做拼接:
1 http://api.test.com/user/userApi/changePwd
然后发起一次请求。
因此我们上述提取接口,实际上就是提取了所有类似/userApi/changePwd这样的uri。我们要找后端也很简单,写个正则专门匹配http://或https://开头的字符串即可(Unexpected information和findsomething也可以实现一部分这样的功能)
提取出来之后,后续要做的就是把后端字典和接口字典拼接起来
1 2 3 4 后端字典 接口字典 http://api1.test.com/ /user/getinfo http://api2.test.com/api/admin/ /user/register http://api2.test.com/api/ /manager/changeSetting
把列表丢到HTTPX之类的工具跑一遍
FUZZ初级目录 有些开发在写后端的时候,喜欢加一些用于测试的接口和页面,这些接口肯定不会放在js里,并且其uri和js中的正常接口可能截然不同。反正大部分情况下初级路径是不同的。因此有时候习惯性对后端地址FUZZ一下初级路径,会有很大收获。
当然这里我们其实还可以拓展,那就是既然每一级目录下都可能有一些新东西,为什么我们不可以写一个插件去递归测试呢?比如java中常见的swaggerui、api-docs接口文档,再比如actuator端点泄露,druid未授权等问题,关于这块其实早就有很多很好用的插件了,例如Tsojanscan、APIKIT等等
从js中搜寻凭据信息 首先我们很容易想到一些要素,比如手机号、邮箱、身份证,因为很多提取JS并分析的场景都是那种后台登录点,提取这些信息,我们可能可以找到潜藏的用户名,或者我们可以通过这些信息去进行密码重置等操作,再或者结合裤子进一步查找信息。在部分场景下,甚至只能通过邮箱或手机号作为用户名登录。因此搜集这些实际上是为了后续的密码喷洒、爆破相关攻击路线服务的。给出提取正则(python)如下:
1 2 3 4 5 6 #邮箱匹配 matches = re.findall(r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}', line) #手机号匹配 matches = re.findall(r'(?<!\d)(13\d{9}|14[579]\d{8}|15[^4\D]\d{8}|166\d{8}|17[^49\D]\d{8}|18\d{9}|19[189]\d{8})(?!\d)', line) #身份证匹配 matches = re.findall(r'\b\d{17}[\dXx]|\b\d{14}\d{1}|\b\d{17}[\dXx]', line)
顺这个思路想,我们在密码喷洒、爆破流程中常见验证码以及一些IP限制,在部分情况下,XFF伪造一个内部IP会是绕过方法,如果目标的鉴权机制检测到特定内部IP就直接放行,那就更爽了,因此我们还可以写个正则匹配IP:
1 2 #ip匹配 matches = re.findall(r'\d+\.\d+\.\d+\.\d+', line)
继续思考,既然能找到账号,我们有没有可能找到密码呢?嗯,这个难度就大了,一般只能通过js中变量的赋值来找到蛛丝马迹,我们可以用正则匹配
这样的关键字,从而找到一些密码的蛛丝马迹,通过这种思路,其实也可以去找AK/SK,两个相关的正则如下:
1 2 3 4 matches = re.findall(r'(?:^|_)((?:username|password|key|auv)_)\s*[:=><]*\s*["\']([^"\']+)["\']' , line) matches = re.findall(r'(?i)((access_key|username|user|jwtkey|jwt_key|AESKEY|AES_KEY|appsecret|app_secret|access_token|password|admin_pass|admin_user|algolia_admin_key|algolia_api_key|alias_pass|alicloud_access_key|amazon_secret_access_key|amazonaws|ansible_vault_password|phone|aos_key|api_key|api_key_secret|api_key_sid|api_secret|api\.googlemaps\s+AIza|apidocs|apikey|apiSecret|app_debug|app_id|app_key|app_log_level|app_secret|appkey|appkeysecret|application_key|appspot|auth_token|authorizationToken|authsecret|aws_access|aws_access_key_id|aws_bucket|aws_key|aws_secret|aws_secret_key|aws_token|AWSSecretKey|b2_app_key|bashrc\ password|bintray_apikey|bintray_gpg_password|bintray_key|bintraykey|bluemix_api_key|bluemix_pass|browserstack_access_key|bucket_password|bucketeer_aws_access_key_id|bucketeer_aws_secret_access_key|built_branch_deploy_key|bx_password|cache_driver|cache_s3_secret_key|cattle_access_key|cattle_secret_key|certificate_password|ci_deploy_password|client_secret|client_zpk_secret_key|clojars_password|cloud_api_key|cloud_watch_aws_access_key|cloudant_password|cloudflare_api_key|cloudflare_auth_key|cloudinary_api_secret|cloudinary_name|codecov_token|config|conn\.login|connectionstring|consumer_key|consumer_secret|credentials|cypress_record_key|database_password|database_schema_test|datadog_api_key|datadog_app_key|db_password|db_server|db_username|dbpasswd|dbpassword|dbuser|deploy_password|digitalocean_ssh_key_body|digitalocean_ssh_key_ids|docker_hub_password|docker_key|docker_pass|docker_passwd|docker_password|dockerhub_password|dockerhubpassword|dot-files|dotfiles|droplet_travis_password|dynamoaccesskeyid|dynamosecretaccesskey|elastica_host|elastica_port|elasticsearch_password|encryption_key|encryption_password|env\.heroku_api_key|env\.sonatype_password|eureka\.awssecretkey)\s*[:=><]{1,2}\s*[\"\']{0,1}([0-9a-zA-Z\-_=+/]{8,64})[\"\']{0,1})' , line)
如果你有其它想匹配的关键字,自己往上述正则中添加即可。
前面我们提了一嘴AK/SK,从JS中找这玩意真是典中典了,尤其是那些把文件传储存桶里的功能点,要尤其注意其是否把AK/SK硬编码在前端了。SK一般是无特征无规律可循的,不过AK是有规律的,AK的前四位一般是固定的,不同的云厂商则不同,详情可以参考这篇文章:
1 https://wiki.teamssix.com/cloudservice/more/
依据这篇文章,我们实际上可以写出一些提取AK的正则,并且SK一般都会和AK写在一起,因此只要在JS中定位到AK,那么离找到SK也不远了,正则如下:
1 2 3 4 5 6 7 8 9 10 11 12 matches = re.findall(r'''(['"]\s*(?:GOOG[\w\W]{10,30}|AZ[A-Za-z0-9]{34,40}|AKID[A-Za-z0-9]{13,20}|AKIA[A-Za-z0-9]{16}|IBM[A-Za-z0-9]{10,40}|OCID[A-Za-z0-9]{10,40}|LTAI[A-Za-z0-9]{12,20}|AK[\w\W]{10,62}|AK[A-Za-z0-9]{10,40}|AK[A-Za-z0-9]{10,40}|UC[A-Za-z0-9]{10,40}|QY[A-Za-z0-9]{10,40}|KS3[A-Za-z0-9]{10,40}|LTC[A-Za-z0-9]{10,60}|YD[A-Za-z0-9]{10,60}|CTC[A-Za-z0-9]{10,60}|YYT[A-Za-z0-9]{10,60}|YY[A-Za-z0-9]{10,40}|CI[A-Za-z0-9]{10,40}|gcore[A-Za-z0-9]{10,30})\s*['"])''' , line) matches = re.findall(r'\bAIza[0-9A-Za-z_\-]{35}\b' , line) matches = re.findall(r'\bAKLT[a-zA-Z0-9-_]{16,28}\b' , line) matches = re.findall(r'\b(?:AKLT|AKTP)[a-zA-Z0-9]{35,50}\b' , line) matches = re.findall(r'["' '](?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}["' ']' , line) matches = re.findall(r'\bJDC_[0-9A-Z]{25,40}\b' , line)
再接下来,我们还能想到什么呢?前面我们提到了一些明文的鉴权因素,比如用户名和密码。然而实际情况下我们经常遇到别的一些鉴权因素,比如JWT,再比如形如”Basic 一串base64编码”这样格式的鉴权凭据,常见于请求头中。开发者有没有可能在测试前端的时候,在前端放了一些测试用户的鉴权凭据,上线后忘记删除呢?当然也是有可能的
这里也给出几个相关正则:
1 2 3 4 5 6 7 8 9 10 matches = re.findall(r'eyJ[A-Za-z0-9_/+\-]{10,}={0,2}\.[A-Za-z0-9_/+\-\\]{15,}={0,2}\.[A-Za-z0-9_/+\-\\]{10,}={0,2}' , line) matches = re.findall(r'-----\s*?BEGIN[ A-Z0-9_-]*?PRIVATE KEY\s*?-----[a-zA-Z0-9\/\n\r=+]*-----\s*?END[ A-Z0-9_-]*? PRIVATE KEY\s*?-----' , line) matches = re.findall(r'["' '\[]*[Aa]uthorization["' '\]]*\s*[:=]\s*[' '"]?\b(?:[Tt]oken\s+)?[a-zA-Z0-9\-_+/]{20,500}[' '"]?' , line) matches = re.findall(r'\b[Bb]asic\s+[A-Za-z0-9+/]{18,}={0,2}\b' , line) matches = re.findall(r'\b[Bb]earer\s+[a-zA-Z0-9\-=._+/\\]{20,500}\b' , line)
然后说到上述的Token和key,还有一种情况相信各位师傅也经常见到,那就是硬编码在JS里的钉钉、飞书、企微、微信小程序/公众号之类的key和token,能搞到这些东西并且能利用也大概率能搞个高危,下面也给出部分规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 matches = re.findall(r'\bhttps://hooks.slack.com/services/[a-zA-Z0-9\-_]{6,12}/[a-zA-Z0-9\-_]{6,12}/[a-zA-Z0-9\-_]{15,24}\b' , line) matches = re.findall(r'\bhttps://open.feishu.cn/open-apis/bot/v2/hook/[a-z0-9\-]{25,50}\b' , line) matches = re.findall(r'\bhttps://oapi.dingtalk.com/robot/send\?access_token=[a-z0-9]{50,80}\b' , line) matches = re.findall(r'\bhttps://qyapi.weixin.qq.com/cgi-bin/webhook/send\?key=[a-zA-Z0-9\-]{25,50}\b' , line) matches = re.findall(r'["' '](gh_[a-z0-9]{11,13})["' ']' , line) matches = re.findall(r'["' '](ww[a-z0-9]{15,18})["' ']' , line) matches = re.findall(r'["' '](wx[a-z0-9]{15,18})["' ']' , line) matches = re.findall(r'\bAPID[a-zA-Z0-9]{32,42}\b' , line)
除此之外还有一些乱七八糟的应用的Token:
1 2 3 4 5 6 7 8 9 10 11 12 matches = re.findall(r'\b(?:VUE|APP|REACT)_[A-Z_0-9]{1,15}_(?:KEY|PASS|PASSWORD|TOKEN|APIKEY)[\'"]*[:=]"(?:[A-Za-z0-9_\-]{15,50}|[a-z0-9/+]{50,100}==?)"' , line) matches = re.findall(r'\bglsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}\b' , line) matches = re.findall(r'\bglc_[A-Za-z0-9\-_+/]{32,200}={0,2}\b' , line) matches = re.findall(r'\beyJrIjoi[a-zA-Z0-9\-_+/]{50,100}={0,2}\b' , line) matches = re.findall(r'\b((?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255})\b' , line) matches = re.findall(r'\b(glpat-[a-zA-Z0-9\-=_]{20,22})\b' , line)
把上面的所有正则综合起来,就能得到一个比较好用的敏感信息提取脚本了
提取注释和汉字 从JS和HTML中提取注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 match = re.search(r'\*/(.*)', line) matches = re.findall(r'//[^\n]*', line) match = re.search(r'/\*(.*)', line) matches = re.findall(r'<!--(?:.|\n)*?-->', line) https://github.com/rtcatc/Packer-Fuzzer https://github.com/momosecurity/FindSomething https://github.com/ScriptKid-Beta/Unexpected_information https://github.com/gh0stkey/HaE https://github.com/Tsojan/TsojanScan https://github.com/API-Security/APIKit
定位Next.js网站所有接口的方法 直接控制台输入
1 console.log(__BUILD_MANIFEST.sortedPages)
或者firefox新建一个标签页,网址填
1 javascript:console.log(__BUILD_MANIFEST.sortedPages.join('\n'));
遇到Next.js网站时,点击这个标签就能在控制台看到所有路径