SRC 视角下:渗透测试中的逻辑漏洞思路博弈
原文链接:https://forum.butian.net/share/4441
最近挖到的中高危漏洞,既没靠 `0day` 这种 "王炸",也没搞复杂利用链 "炫技",纯靠瞪大眼睛当 "人肉扫描器",连标点符号都不放过地逐行比对参数和响应。直到某个昏昏欲睡的下午,随手改了个藏在 `JSON` 数据深处的小参数,系统突然像短路反馈了全新的信息,反常的响应直接暴露未授权访问的 "马脚"。当时激动得差点把咖啡泼到键盘上,看着满地咖啡渍才顿悟 —— 原来倒掉的咖啡,比直接喝咖啡提神一百倍
渗透测试人员堪称代码世界的 "超级侦探",手握 Burp Suite 这把 "神奇放大镜",进入甲方的资产海洋遨游,在其中对着页面疯狂改参数、发请求,却总被系统用平淡入手的响应打发,如同在广阔的太平洋掷入一枚石子,不泛起一丝涟漪; 要么直接拦截请求让人气的砸电脑。开局登录框更是 "经典打卡圣地",测 API 接口这边要扮成研究正常逻辑的好学生,那边得秒变设计注入 Payload 的 脑洞大师,还得时刻提防 WAF悄无声息送你一张404飞机票✈️, 漏洞挖掘本就是逆天而行,挖不到才是正常的
最近挖到的中高危漏洞,既没靠 0day 这种 "王炸",也没搞复杂利用链 "炫技",纯靠瞪大眼睛当 "人肉扫描器",连标点符号都不放过地逐行比对参数和响应。直到某个昏昏欲睡的下午,随手改了个藏在 JSON 数据深处的小参数,系统突然像短路反馈了全新的信息,反常的响应直接暴露未授权访问的 "马脚"。当时激动得差点把咖啡泼到键盘上,看着满地咖啡渍才顿悟 —— 原来倒掉的咖啡,比直接喝咖啡提神一百倍
无限抽奖币
新的功能点往往被常规功能点所裹挟,黑盒测试的我们只能点点点,当坚持到隐秘功能点出现的那一刻,漏洞已经是呼之欲出了
微信搜索厂商资产找到一处不起眼的资产,20币一次并且可得实物
初始币是100当我小心翼翼的尝试投币点进行一些,自定投币数量,修改返回包控制所得物品,并发签到等操作时,结果洞还没挖到我的游戏币就已经快测没了,真的是天塌了
当时已经非常晚了到了12点,当我最后一次爪子进行抓娃娃的时候,修改请求包发现还是一无所获,悬着的心最终还是死了,已经准备win+x+u u 光速下播,但游戏币消失的时候又出现了新的功能点,这让我死去的心又再次燃烧起来
分享赚币,点击后BP记录接口,在历史记录逐个对数据包进行查看,最终凭借经验锁定/app/point/doll/share接口就是关键的数值包,它的值所控制的就是所得金币数量
points=15
思路都理清了那还说啥,直接重发狠狠的输入最大数值
不出意外成功,爽吃一中,又可以多买几个馒头恰了,这次渗透告诉我,不测试完所有功能点就绝对不轻言放弃,不要自己跟自己说丧气话,因为大多数人在一个站比如测试越权发现有鉴权,就下定义觉得这个站已经没有越权了,这是万万不行的,作为白帽子最后是把所有思路穷尽才算是一次完整的渗透测试,细心再细心,虽然这样很枯燥但是长此以往相信肯定会有所收获
可控所得优惠卷
功能点购入某些物品系统会赠送一部分优惠卷,但大多是一些小额优惠卷,除非购买昂贵的服务才会赠送价格不菲的优惠卷,利用低级服务所得优惠卷,通过手法找到高等级优惠卷对应token,替换后造成刷优惠卷效果
站点特征较明显,省略一些前期发现步骤,快进到数据包分析,选择一个商品服务进行下单,BP记录所有流量,通过逐个分析定位到此接口为关键的创建订单包,将其测试发送到重发起等待进一步测试
https://xxxxx/restapi/soa2/19691/orderCreate?_fxpcqlnired
进行小程序看先前创建的订单,看有没有可以操作的业务,观察到订单创建后成功支付会相对应的赠送优惠卷
都是一些不起眼的小福利卷,跟QQ空间兰博基尼5元代金券不能说没有区别只能说一模一样
将创建订单接口发送到重发器后,观察数据包,这种涉及多个流程的业务数据包一般都很多参数,有的是关键参数,有的是多余参数用于迷惑,通过"人肉扫描器"一段段观察,定位到data 数据体记录了优惠卷对应的token
通过一系列手法,找到了其他优惠卷的token,然后对先前的三处优惠卷token进行替换.并且全是一模一样的,意味着我可以通过低级服务自定义所得优惠卷,并且叠加
当前替换后再次创建新订单,先前的三张低级优惠卷已经被替换为了三张豪车打车减免卷,并且只需要我知道任意卷的token,就可以得到对应券,还可以叠加字段进行复用
普通的CSRF
如今各种逻辑漏洞,云攻防,Top10 为主流的今天,最基础的CSRF往往是同其他漏洞结合利用,但忽略了单个CSRF所能造成的影响,我理解的是只要功能涉及到主观操作场景,如修改信息,增加收货地址,发送邮件...等等需要主观意愿才能进行,并且请求中并没有发现token或其他校验字段均可以尝试CSRF
正常用户修改绑定的邮箱需要输入交易密码才可以修改,但是功能点的个人信息接口可以直接修改绑定的邮箱,制作post请求的csrf数据包发送受害者点击, 就会自动修改绑定邮箱及个人信息为我们所设置的
为不影响业务,实名认证后准备两个小号相互测试
对个人信息进行修改,BP记录所有的流量,此接口为更新信息
/fund/apl/postUserinfo
观察更新个人信息功能第一是携带了邮箱更新,如上所示,正常我们单独修改关键的邮箱是需要交易密码的, 因为站点是一处涉及基金交易,但是普通的修改个人信息却是可以直接修改邮箱,跳过交易密码,第二是请求包没有token等其他涉及状态码字段,那么是不是可以尝试一下CSRF呢
将更新POST请求右键制作为CSRFPOC 把生成的代码拷贝一下随意改一下信息为自定义的,然后本地作为html文件
poc.html 用当前小号登录的浏览器去打开,点击后返回修改成功
刷新站点,当前的个人信息包括邮箱已经是我们所设置的,CSRF 攻击成功
网站泄露导致接管
写多了企业SRC报告,现如今刚刚初入社会投身于工作当中,下班后再去花精力挖掘SRC可能是越来越少,还是怀念大学的时光没有压力,别管挖没挖到洞,你只管去挖剩下的交给时间,现在如果下班没有产出其实还是挺浪费时间的,因为本来是可以利用这个时间再去学习一些新知识,提高自己技术....分享一个在近期在公司渗透测试一个单位的过程,最终也是拿下网站超级管理员
企业SRC大多目标较少,且会给出具体的域名范围,工作渗透项目因为是会有地区网信办授权,所以给我某一地区庞大的单位,从中进行渗透,海量的资产梳理不是一件简单的事情
测绘资产处理
众多单位名有些是存在备案网址的,这些才是有效资产可以进行渗透,那么无备案的单位如果排除呢?测绘网站hunter可以很好的做到这一点,它支持企业备案名称进行查询,这是其他fofa 360 都没有的,所有在攻防项目还是企业渗透开局海量单位名hunter是最佳的选择,唯一缺点这个打法比较吃积分
icp.name="xxx"
既然知道hunter可以根据备案名查询,那么写一个批量脚本,填入API进行调用语法即可
import requests import base64 import time import json import pandas as pd # 鹰图平台的 API 地址 BASE_URL = "https://hunter.qianxin.com" SEARCH_ENDPOINT = "/openApi/search" # 你的 API Key(从鹰图平台获取) API_KEY = ""# 替换为实际的 API Key # 字段翻译映射表 FIELD_TRANSLATION = { "code": "状态码", "message": "错误信息", "data": "数据部分", "total": "资产总数", "time": "时间", "arr": "资产列表", "is_risk": "风险资产", "url": "URL地址", "ip": "IP地址", "port": "端口号", "web_title": "网站标题", "domain": "域名", "is_risk_protocol": "高危协议", "protocol": "协议类型", "base_protocol": "通讯协议", "status_code": "网站状态码", "component": "应用组件/版本", "os": "操作系统", "company": "备案单位", "number": "备案号", "icp_exception": "备案异常", "country": "国家", "province": "省份", "city": "市区", "ip_tag": "IP标签", "asset_tag": "资产标签", "vul_list": "历史漏洞", "updated_at": "探查时间", "is_web": "Web资产", "name": "组件名称", "version": "组件版本", "as_org": "注册机构", "isp": "运营商信息", "banner": "服务标识信息", "header": "HTTP响应头", "sha256": "证书签名哈希算法(SHA256)", "consume_quota": "消耗积分", "rest_quota": "今日剩余积分" } # 从 txt 文件读取公司名称列表 def load_company_names(file_path): try: with open(file_path, 'r', encoding='utf-8') as f: companies = [line.strip() for line in f.readlines() if line.strip()] return companies except FileNotFoundError: print(f"错误:文件 {file_path} 不存在") return [] except Exceptionas e: print(f"读取文件时出错: {e}") return [] # 公司名称文件路径 COMPANY_FILE = "dz.txt" # 加载公司名称列表 company_names = load_company_names(COMPANY_FILE) if not company_names: print("没有可用的公司名称,请检查 gs.txt 文件内容") exit() # 请求参数 page_size = 10# 每页资产条数 # 存储所有检索结果 results = [] no_data_companies = [] for company_name in company_names: print(f"\n正在查询公司: {company_name}") page = 1 total_pages = 1 has_data = False while page <= total_pages: search_query = f'icp.name="{company_name}"' search_encoded = base64.urlsafe_b64encode(search_query.encode("utf-8")).decode("utf-8") params = { "api-key": API_KEY, "search": search_encoded, "page": page, "page_size": page_size } try: response = requests.get( f"{BASE_URL}{SEARCH_ENDPOINT}", params=params, timeout=15 ) response.raise_for_status() data = response.json() # 转换状态码为是/否 if"code" in data: data["status"] = "是"if data["code"] == 200else"否" if data.get("code") == 429: print(f"公司 {company_name} 请求过多,等待15秒后重试...") time.sleep(15) continue if data.get("code") != 200: print(f"公司 {company_name} 检索失败: {data.get('message')}") break total = data.get("data", {}).get("total", 0) total_pages = (total + page_size - 1) // page_size if data.get("data", {}).get("arr"): has_data = True # 移除banner字段 for item in data["data"]["arr"]: if"banner" in item: del item["banner"] results.append({ "company_name": company_name, "page": page, "data": data }) print(f"公司 {company_name} 第 {page}/{total_pages} 页检索完成,共 {total} 条结果") page += 1 time.sleep(1.5) except requests.exceptions.RequestException as e: print(f"公司 {company_name} 请求失败: {e}") break if not has_data: no_data_companies.append(company_name) # 统计结果 total_companies = len(company_names) no_data_count = len(no_data_companies) print(f"\n总共查询了 {total_companies} 个公司,其中 {no_data_count} 个未查询到数据。") # 保存原始JSON结果 timestamp = time.strftime("%Y%m%d_%H%M%S") filename = f"search_results_{timestamp}.json" with open(filename, "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=4) # 保存无数据公司名单 no_data_filename = f"no_data_companies_{timestamp}.txt" with open(no_data_filename, "w", encoding="utf-8") as f: f.write("\n".join(no_data_companies)) # 准备Excel数据 excel_data = [] for result in results: company_name = result["company_name"] status = result["data"].get("status", "否") # 获取状态码转换结果 for item in result["data"]["data"]["arr"]: row = {"公司名称": company_name, "请求状态": status} # 添加所有字段到行数据 for field, translation in FIELD_TRANSLATION.items(): if field in item: row[translation] = item[field] elif field in result["data"]: row[translation] = result["data"][field] excel_data.append(row) # 创建DataFrame df = pd.DataFrame(excel_data) # 保存为Excel文件 excel_filename = f"search_results_{timestamp}.xlsx" df.to_excel(excel_filename, index=False) print(f"\n所有公司检索完成,结果已保存到 {filename}") print(f"查询不到的公司名称已保存到 {no_data_filename}") print(f"Excel表格已保存到 {excel_filename}")
填入公司名在dz.txt 结束好后会生成json 格式文件和xlsh 文件
通过这个步骤收集到的域名资产我一般是保留200状态码,之后便会开始nuclei和afrog同步进行扫描,收取漏洞标准没有限制所以在手工测试前,配置好的自动化工具就会帮我们找好一些swagger泄露 ,目录遍历,以及一些CVE 和高危的端口服务,可以再进行弱口令的爆破等等
渗透实战
当资产在被扫描器轮番测试时,这个时间就开始逐一对资产进行手工测试,开局一个首页,看起来确实没什么功能点,现在可以选择的测试是目录扫描和接口未授权
打开我的dirsearch为了应对不同的目标架构还有需求,需要在多个不同的命令行参数间转换,各位白帽师傅可以让ai 辅助写一个bat 脚本去调用工具不同的参数从而在单个目标 多个目标,递归目标间迅速切换
相信大家都发现了出现了一个Admin目录大概率是网站管理后台
http://xxxxx.com/Admin/
提示超时,那么应该是进行鉴权 了,状态码301调整到了网站管理登录页面
登录框先尝试了简单的弱口令admin admin 几个经典账号密码,均失败,注意到网站标题是xx网络,这个时候我在想这个会不会是个框架呢,遂寻找是否有相关文章
查询到了对应的服务商企业,但却十分小众,并没有公开的漏洞,开始测试接口看着能不能信息泄露,但是接口同样少的可怜,此时常规思路已经穷尽,站点还是没有拿下,哈基W 还是办不到吗。。。
此时只剩下两个选择,继续尝试突破登录框,虽然有验证码但是开启验证码插件还是可以继续爆破的不过完全是听天由命,二是继续在目录处做操作,既然一开始就可以爆破出登录框,证明站点防护并不是特别好,第一层目录就是admin
回到第一步查看dirsearch 结果,我擦看我发现了什么,朴实无华的password.txt 不会吧不会把 不会真有人把密码写进目录然后没有删掉吧,开什么玩笑啊 居然这么简单吗
一气呵成,拿下后台,点到为止
总结
没有华而不实的屠龙术只有重复99个数据包,仅有第100个数据包产生了惊喜,唯有点滴的付出,你坚持的东西总有一天会反过来拥抱你; 之前文章觉得过于死板加入一些诙谐幽默的叙事方法,让各位读者阅读我的文章不像是一本正经的聊技术,更是像一位朋友一般分享着我的挖掘经历以及心态转换过程,看的开心又能学到技术;欢迎学习交流,往后会在社区投稿更多实战案例。
黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!
如侵权请私聊我们删文
END