本文不探讨P2P行业现状,只是借此介绍下接口渗透以及中间人攻击原理
前几年我试着把一些资金投到P2P产品里,想要赚取比余额宝多一些的利润,当时选择的某个大平台下的一个P2P子品牌。到了去年这个时候,监管政策不断收紧,隐隐约约觉得原先的平台不稳妥,但同时又觉得P2P产品的合理性是有的,监管层面应该会给头部的大平台留一些生存空间。自以为是的把资金切换到了头部的并且都已上市的平台上,好在我还懂不能把鸡蛋都放一个篮子里的道理:没把所有的钱都投到P2P,还把投入到P2P的钱分散到了两个平台上。
不过平台暴雷这个事情还是被我赶上了↓↓↓
我惊愕平台单方面擅自修改了用户协议,但愤怒归愤怒,我需要快速理解现在的还款逻辑,方便我收集证据、维权:
比如你在去年3月1日投标给这个网贷平台,期限一年,本金1万。按理能在今年的2月29日收到本金一万,以及利息若干。P2P平台实际运作时,为了分摊风险,不会把你的钱只贷款给一个人,它会把你的钱打散,分到多个散标之中,放款给借款人。
该平台修改协议后,只有当你投的散标回款后你才可能收到回款,而且这些散标不是一年前的,有可能是半年前的,有可能最近才发,而且整个回款周期最长要三年。
惨,我为了图那点利息,资金被平台占用四年之久,而且连一年的利息都不给你了,只还本金。
打开APP看到的惨状:
不考虑之后跟平台怎么撕逼维权这些事,面对这么多散标,并且已经无法再相信平台的情况,有几个问题亟待解决:
- 如何确认被拆成这么多散标后,我的资金总额是完整的?
- 每天都可能有散标回款,少则一分,多则几十、几百,如何进行对账?
接口嗅探
我的想法是从接口层面拿结构化的数据出来,累加之后判断散标总额是否正确,之后每次回款都进行对账,不能遗漏一笔标的。
上面的方法是建立于该平台APP未进行双向证书验证(大部分APP都没有该防护),并且接口设计上未做严格的数字签名,能够被外部用户手动构造合法请求的基础之上。动手试试看吧。
我这边使用的mitmproxy
,也可以使用charles
,完成iOS的网络配置后,我试着打开APP做一些操作。
接口请求在mitmweb上齐刷刷的出现的,果然,这类APP并没有双向证书校验,通讯报文一目了然,我也很快定位到我需要的一个接口上:/api/v2/v3/user/repay/list/BIDDING
接口分析
先分析下这个接口的query params,比较简单:
holdStatus
: 散标持有状态吧,0应该表示为回款的page
&size
: 分页参数,page从1开始递增
再来看下请求头参数,这里就有一大堆了:
Device-Mac
: 猜测是手机硬件信息,但是应该可以随便传Cookie
:这个没什么可说的,原样复制就行vendorId
: 估计是表示手机类型,苹果或者安卓ScreenRes
: 不清楚Channel
: 不清楚,但不重要Keys-Identity
:看着很重要的一个值,但是固定不变,抄就行了Authorization
: 最担心的一个参数,不过居然也是固定不变,障碍扫清了safeimei
、idfa
: 不清楚什么含义,但是值是一致的,也是固定不变的,抄
再来看下响应报文,看看怎么提取我要的数据:
{
"d": {
"repayments": [
{
"dateDesc": "2020年02月29日",
"items": [
{
"amount": "0.01",
...
},
{
"amount": "0.01",
...
}
},
{
"dateDesc": "2020年03月01日",
"items": [
{
"amount": "0.01",
...
}
],
}
],
"xplanContinuedGoodsNo": "",
"xplanContinuedRate": "0"
},
"m": "",
"r": 1
}
- 回款数据都在
d.repayments
节点下 - 回款记录按天来切割,并存放于
..items
节点下,是个数组 amount
即为回款金额,是字符串类型
于是我快速写了个Python脚本来统计我的资金:
import requests
class WDWClient:
headers = {
"Device-Mac": "xxx",
"Referer": "https://mobilegt.weidai.com.cn/api",
"Cookie": "xxx",
"User-Agent": "xxx",
"vendorId": "apple",
"ScreenRes": "2",
"Channel": "AppStore",
"Keys-Identity": "xxx",
"Device-ID": "xxx",
"Device-Version": "xxx",
"Authorization": "xxx",
"safeimei": "xxx",
"idfa": "xxx",
"Device": "1",
"Trace-ID": "xxx",
}
def __init__(self):
self.client = requests.Session()
self.client.headers.update(self.headers)
self.base_url = "https://mobilegt.weidai.com.cn"
def get_bidding(self, hold_status=0, page=1, size=20):
"""获取散标"""
return self.client.get(self.base_url + "/api/v2/v3/user/repay/list/BIDDING",
params={"holdStatus": hold_status, "page": page, "size": size}).json()
def get_repayments(res: dict) -> list:
items = []
repayments = res.get("d", {}).get("repayments", [])
for day in repayments:
if day.get("items", None):
items.extend(day['items'])
return items
wdw = WDWClient()
amount = 0
count = 0
page = 1
size = 20
while 1:
res = wdw.get_bidding(hold_status=0, page=page, size=size)
items = get_repayments(res)
amount += sum([float(item["amount"]) for item in items])
c = len(items)
count += c
if c < size:
break
page += 1
print(count)
print(amount)
到这里我就能统计出散标的资金总额了,暂时是没有差错,之后会再利用它的接口来实现对账能力,这是后话了。
整个接口渗透的过程非常简单,这其实也暴露出该平台技术人员在安全这块掉以轻心了:
- 没有双向认证的https并不会提升什么安全性
- 没有数字签名,用户侧很容易构造接口请求,又天然的绕开了前端校验
- 没有控制接口调用频次
而对于“我”这个角色,不要去尝试越权、资金修改调用等,即便它有可能做好了完备的校验:狗咬你一口,你不需要咬回去
中间人攻击
到这里,我还是想尝试讲下中间人攻击的原理。
首先看一张https过程的图:
我把关键点罗列下:
- 服务端的证书分成公钥、私钥两部分,顾名思义,公钥是可以公开的,私钥不准别人看的
- client接入时,服务端会把公钥下发给client
- client会验证公钥的有效性,注意:只是验证,如果无效、非法呢?接受还是拒绝?这时由client来决定的,没有强制要求
中间人攻击形成的原理就是上面第三点,看下图:
在原有的请求链路加入一个中间人(Proxy),此时的通讯过程就会变成这样:
- User向MITM建立请求,然后MITM向Service建立请求
- Service的公钥不再直接给到User,而是MITM,MITM此时把Service公钥替换成自己证书的公钥,并把自己的公钥下发给User
- User以为跟Service协商出了一个对称性密钥,实际却是跟MITM协商的,此时User跟MIMT通讯完成透明化
- MITM也跟Service协商了对称性密钥,完成了通讯的透明化
- User跟Serivce之间的通讯完全被劫持,MITM可以做任何事
要防范中间人攻击有几种做法:
- 双向认证
- 拒绝无效、非权威结构颁发的证书
对于第二点,思维严谨的同学可能会提出假设:如果MITM用的证书是可信的呢?如果能想到这一点,接受我由衷地称赞吧!👍👍👍
贴一张图,不多展开了