渗透测试之记一次红包激励模块逻辑漏洞测试总结

渗透测试之红包激励模块逻辑漏洞测试总结

下图是本次App逻辑漏洞测试个人总结的指导原则和测试方法,指导原则后面对应的是发现的漏洞,有些漏洞描述做了脱敏泛化处理,根据这些漏洞基本可以反推出测试用例。这些是本人认为App逻辑漏洞测试应该重点关注的,可能不全,如有补充欢迎探讨(其他sql注入、XSS等其他类型漏洞不是本次测试的重点,测试的对象App,另外看了后端代码都是用的orm框架一般也没有什么问题)。
渗透测试之记一次红包激励模块逻辑漏洞测试总结_第1张图片

背景

公司不同App产品线的红包激励模块曾好几次被破解,出现过几次针对我司产品的autojs破解协议apk,本人作为风控部门专业做逆向的技术人员,曾逆向分析过这些黑产app,大部分都是抓包获取用户token后直接发包获取奖励,绕过看广告、做任务等环节。
而本次任务的主要目的就是主动出击,对公司核心产品的红包激励模块进行逻辑漏洞测试,看是否有容易被薅羊毛的的逻辑漏洞,对发现的漏洞督促业务线进行整改。虽然本人一直做的逆向方向的,但也长期关注安全相关的其他领域,对渗透测试的方法和工具等也是耳濡目染。本文主要是做本人本次所做渗透的测试简要总结。

渗透测试要点总结

渗透测试和代码审计相辅相成
通常需要编写burpsuite插件实现自动sign计算,这样在修改参数重放或并发测试时不需要关心sign计算问题,再评估完sign算法保护强度后,可以直接看代码算法实现,而不需要完全逆向还原可以提高测试效率。
很多漏洞,如并发漏洞等是可以通过代码审计一眼看出来的,不需要完全黑盒测试,这也可以极大提高测试效率。当然也有由于对代码逻辑不熟或代码逻辑过于复杂,导致看代码还不是直接黑盒测试来的快得情况。个人觉得两者是相辅相成的。
竞争/并发漏洞
服务端对客户端请求都是并发处理的,而且很多服务端还有负载均衡多节点处理,这样当对数据库某字段进行加减操作(如红包发放)如果没有加锁就会导致竞争/并发漏洞,分布式锁一般用数据库锁或redis锁。
做好提现最后关卡审核
本次测试的一个重点就是看能否完全脱离app直接通过发包就可以完成提现的,经验证在有提现微信账号openid的前提下是可以全程脱离APP通过发包实现提现1元到账的。但微信的openid是跟app绑定的,不同app对应的openid不同,需要获取微信的openid,需要调⽤微信授权登录接⼝来获取,并且调⽤时会校验调⽤⽅app的签名,若app的签名与后台配置的签名不⼀致,⽆法成功调⽤,试过各种签名欺骗未生效,估计不是在app内校验签名的而是在微信里校验的,⽬前openid是抓包获取的。
想象一下如果可以完全脱离app就可以全程发包获取奖励并提现(如支付号手机号直接提现),那么即使限制了每个账号的提现次数那也是很恐怖的。比如开发一个在线的薅羊毛工具,然后到处传播,每个人只要扫个码或者填个手机号就可以提现一元,传播量大也很恐怖。
解决办法就是事先在app内做好各个用户行为的埋点数据和用户环境检测数据等,然后在提现重要关卡验证这些数据是否正常,不正常就拒绝提现。

burpsuite脚本

关于如何使用burpsuite来进行测试,看官方帮助文档或网上资料基本就能学会这边就不展开细说,附上处理过burpsuite插件脚本供参考。

# -*- coding: utf-8 -*-
from burp import IBurpExtender
from burp import IHttpListener
from burp import IRequestInfo
from burp import IParameter
from burp import IBurpExtenderCallbacks
from java.io import PrintWriter
import base64
from hashlib import md5
import time
HOST_FROM = "yy.xx.com"
# https://portswigger.net/burp/extender/api/index.html
class BurpExtender(IBurpExtender, IHttpListener):

    #
    # implement IBurpExtender
    #
    
    def    registerExtenderCallbacks(self, callbacks):
        # obtain an extension helpers object
        self._helpers = callbacks.getHelpers()
        self._stdout = PrintWriter(callbacks.getStdout(), True)
        
        # set our extension name
        callbacks.setExtensionName("zz resign plugin")
        
        # register ourselves as an HTTP listener
        callbacks.registerHttpListener(self)

    #
    # implement IHttpListener
    #
    
    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        # only process requests
        if not messageIsRequest:
            return

        # get the HTTP service for the request
        httpService = messageInfo.getHttpService()
        
        # only process the host that is HOST_FROM
        if (HOST_FROM != httpService.getHost()):
            return

        # self._stdout.println(toolFlag)
        # self._stdout.println(IBurpExtenderCallbacks.TOOL_REPEATER)
        if (IBurpExtenderCallbacks.TOOL_REPEATER != toolFlag) or (IBurpExtenderCallbacks.TOOL_INTRUDER != toolFlag):
            return

        requestInfo = self._helpers.analyzeRequest(messageInfo)
        path = requestInfo.getUrl().getPath()
        self._stdout.println("path=%s" % path)
        if not path.startswith("/xx/"):
            return
        self.resign(messageInfo, check_sign=False)

    def resign(self, messageInfo, check_sign=False):

        requestInfo = self._helpers.analyzeRequest(messageInfo)
        method = requestInfo.getMethod()
        # self._stdout.println("method=%s" % method)

        path = requestInfo.getUrl().getPath()
        # self._stdout.println("path=%s" % path)
  
        parameters = requestInfo.getParameters()
        body_offset = requestInfo.getBodyOffset()
        url_param_dist = {}
        for param in parameters:
            if IParameter.PARAM_URL == param.getType():
                k = param.getName()
                v = param.getValue()
                v = self._helpers.urlDecode(v) # 需要转化一下有些%号没有decode,签名算法是错误的
                self._stdout.println("%s=%s" % (k, v))
                # ...
        
        request = messageInfo.getRequest()
        body = request[body_offset:]
        body = self._helpers.bytesToString(body)
        self._stdout.println("body=%s" % body)

        s = '{}-{}'.format(method, path)
        # params
        old_sign = ""
        new_time_sign = None
        new_ts = str(int(time.time()))
        if not check_sign:
            url_param_dist["_ts"] = new_ts #更新时间戳
            if "XX" in url_param_dist:
                s = str(url_param_dist["XX"]) + time.strftime('%Y%m%d')
                new_time_sign = md5(s.encode()).hexdigest()
                url_param_dist["sign"] = new_time_sign #更新sign
        arguments = url_param_dist
        keys = list(arguments.keys())
        keys.sort()
        for k in keys:
            if k == '_sign':
                old_sign = arguments[k]
                continue
            val = arguments[k]
            if type(val) == str or type(val) == unicode or type(val) == int:
                s += '&{}={}'.format(k, val)
            elif type(val) == list:
                for v in val:
                    s += '&{}={}'.format(k, v)
            else:
                self._stdout.println('params unknown type:{}, key:{}'.format(type(val), k))
                return

        # json body
        s += '&json={}'.format(body or '')

        digest = md5(s.encode()).hexdigest()
        new_sign = base64.urlsafe_b64encode(digest)[:-2]
        # self._stdout.println('new_sign:{}, old_sign:{}'.format(new_sign, old_sign))
        request = self._helpers.urlDecode(request)
        if not check_sign:
            new_request = self._helpers.updateParameter(request, self._helpers.buildParameter("ts", new_ts, IParameter.PARAM_URL))
            new_request = self._helpers.updateParameter(new_request, self._helpers.buildParameter("_sign", new_sign, IParameter.PARAM_URL))
            if new_time_sign:
                new_request = self._helpers.updateParameter(new_request, self._helpers.buildParameter("sign", new_time_sign, IParameter.PARAM_URL))
            # self._stdout.println(self._helpers.bytesToString(new_request))
            messageInfo.setRequest(new_request)
        else:
            if new_sign != old_sign:
                self._stdout.println("error! new_sign != old_sign")
            else:
                self._stdout.println("success! new_sign == old_sign")

        # messageInfo.setHttpService(self._helpers.buildHttpService(HOST_TO,
        #     httpService.getPort(), httpService.getProtocol()))

你可能感兴趣的:(渗透测试安全性测试安全漏洞)