0x01 测试内容
测试系统是否存在设计缺陷、逻辑错误漏洞,如支付漏洞、业务流程绕过、业务功能缺陷、短信轰炸、任意用户密码重置等。
0x02 漏洞原理
什么是设计缺陷/逻辑漏洞?
设计缺陷/逻辑漏洞是指由于程序逻辑不严谨或逻辑太复杂,导致一些逻辑分支不能正常处理或处理错误。攻击者利用业务/功能上的设计缺陷,获取敏感信息或破坏业务的完整性。
设计缺陷/逻辑漏洞产生的主要原因
一个系统功能太多后,程序开发人员难以思考全面,只考虑了常规的操作流程(比如,在A情况,会出现B,那么执行C即可),对某些地方可能有遗漏,或者未能正确处理,从而导致逻辑漏洞。逻辑漏洞也可以说是程序开发人员的思路错误、程序开发人员的逻辑存在错误。
一般常出现在什么场景?
用户注册、用户登录、验证码获取、权限鉴定、交易支付、修改个人资料等。
为什么逻辑漏洞不好防范?
设计缺陷/逻辑漏洞是非常隐蔽的,它不像SQL注入、XSS、文件上传、命令执行等,有鲜明的表示,自动化、半自动化扫描器可以定义一系列规则识别出这些漏洞,而逻辑漏洞一般出现在功能、业务流程中,每个漏洞的出现都有一定的独特性,很难复制或者通过规则脚本、漏扫工具精准识别。攻击者利用代码逻辑上的缺陷,利用合法流量进行攻击,也没有防御标准。
0x03 代码示例
damiCMS5.4支付逻辑漏洞。damiCMS版本存在订单支付逻辑漏洞,提交订单后可将商品的数量修改为负数,然后选择站内支付,可以成功提交订单。
lib/Action/MemberAction的dobuy方法,dobuy函数内部对提交的数据过滤不严格,接着调用第51行 $total_fee += (intval($_POST['qty'][$i]) * $price) * 1代码。
intval()函数的作用是获取变量的整数值,这里的qty代表数量,后续代码没有对qty进行过滤,如果其值为负出,那系统最后计算得到的金额就为负,导致漏洞的产生。
//订单处理 function dobuy(){ self::is_login(); if (!$_POST) { exit(); } if (!is_array($_POST['id'])) { $this->error('您的购物为空!'); exit(); } if ($_POST['realname'] == '' || $_POST['tel'] == '') { $this->error('收货人信息为空!'); exit(); } $trade_type = (int)$_POST['trade_type']; $iscart = (int)$_POST['iscart']; $group_trade_no = "GB" . time() . "-" . $_SESSION['dami_uid']; if ($iscart == 1) { import('@.ORG.Cart'); $cart = new Cart(); $cart->destroy(); } //过滤可能出现的xss $_POST = array_map('remove_xss', $_POST); $trade = M('member_trade'); if (C('TOKEN_ON') && !$trade->autoCheckToken($_POST)) { $this->error(L('_TOKEN_ERROR_')); }//防止乱提交表单 //循环出购物车 写进数据库 if ($trade_type == 1) { //...... } else if ($trade_type == 2) { //...... //接着会执行这段逻辑 } else if ($trade_type == 3) { $title = ''; $total_fee = 0; $total_num = 0; for ($i = 0; $i < count($_POST['id']); $i++) { $price = (float)M('article')->where('aid=' . intval($_POST['id'][$i]))->getField('price'); //过滤非数字字符串 if (!is_numeric($_POST['id'][$i]) || !is_numeric($_POST['price'][$i]) || !is_numeric($_POST['qty'][$i])) { continue; } //这行代码是漏洞的触发点 $total_fee += (intval($_POST['qty'][$i]) * $price) * 1; } $have_money = M('member')->where('id=' . $_SESSION['dami_uid'])->getField('money'); if ($have_money < $total_fee) { $this->assign('jumpUrl', U('Member/chongzhi')); $this->error('您的余额不足,请充值!'); exit(); } for ($i = 0; $i < count($_POST['id']); $i++) { //这里还过滤了一次非数字字符串 if (!is_numeric($_POST['id'][$i]) || !is_numeric($_POST['price'][$i]) || !is_numeric($_POST['qty'][$i])) { continue; } $data['gid'] = $_POST['id'][$i]; $data['uid'] = $_SESSION['dami_uid']; $data['price'] = (float)M('article')->where('aid=' . $data['gid'])->getField('price');//必须 $data['province'] = $_POST['province']; $data['city'] = $_POST['city']; $data['area'] = $_POST['area']; $data['sh_name'] = $_POST['realname']; $data['sh_tel'] = $_POST['tel']; $data['address'] = $_POST['address']; $data['group_trade_no'] = $group_trade_no; $data['out_trade_no'] = "DB" . time() . "-" . $_SESSION['dami_uid']; $data['servial'] = $_POST['gtype'][$i]; $data['status'] = 1;//已付款等待发货 $data['trade_type'] = 3; $data['addtime'] = time(); $data['num'] = (int)$_POST['qty'][$i]; $total_num += $data['num']; $trade->add($data); if (strlen($title) < 400) { $title .= $_POST['name'][$i] . "&nbsp;&nbsp;数量:" . $data['num'] . ' 单价:' . $data['price'] . '<br>'; } } //扣款 M('member')->setDec('money', 'id=' . $_SESSION['dami_uid'], $total_fee); if (intval(C('MAIL_TRADE')) == 1) { $config = F('basic', '', './Web/Conf/'); $user_name = $config[sitetitle] . '管理员'; $subject = $config[sitetitle] . '订单提醒'; $bodyurl = '下单时间:' . date('Y-m-d H:i:s', time()) . '<br>会员编号:' . $_SESSION['dami_uid'] . '<br>姓名:' . $_POST['realname'] . '<br>订单号:' . $group_trade_no . '<br>付款方式:站内扣款<br>订购物件:<br>' . $title . '<br>总数量:' . $total_num . '<br>总金额:' . $total_fee . '元'; $sendto_email = C('MAIL_TOADMIN'); $email_port = C('MAIL_PORT'); send_mail($sendto_email, $user_name, $subject, $bodyurl, $email_port); } $this->assign('group_trade_no', $group_trade_no); $this->display('buysuccess'); } else { $this->error('交易方式不确定!'); exit(); } }
0x04 测试过程
测试案例1
短信轰炸
打开APP,点击【手机验证码登录】,输入手机号码,截取数据包。
重放数据包,可无限制发送短信,造成短信轰炸漏洞。
测试案例2
任意用户密码重置
打开APP,点击【忘记密码】,输入手机号、密码,点击【获取验证码】后截取数据包。
在返回包中获取明文验证码。
输入验证码,提交。密码重置成功。
测试案例3
CmsEasy7.6.3.2订单金额任意修改漏洞。攻击者利用业务逻辑层的应用安全问题,在提交订单时抓取数据包并修改,以及对订单的数量进行任意修改。
进入网站,登录用户,查看账户余额为100元。
点击精选产品模块,任意选一款商品,数量随便选,点击购买,抓取请求包。
将请求包中thisnum的值改为负数,放包。
页面自动跳转至支付页面,订购总价变为负数。
填写联系方式,选择余额支付,点击购买,提示购买成功。
再进入个人中心,显示订单完成,账户余额变为4150。
漏洞原理
用户在添加购物车时,系统未添加数量做验证和处理,直接保存到了数据库中,而结算金额时,系统依赖数据库中的商品数量进行计算时,也没有对金额和数量做处理,导致了”越买越多“的情况出现。
修复建议
- 升级到7.6.3.2以上版本;
- 订单多重验证;
- 数额巨大时采用人工验证
0x05 风险分析
逻辑漏洞的危害巨大,根据不同的场景所产生的效果也不同,比如
- 任意密码重置;
- 越权访问;
- 交易支付商品数量修改;
- 短信轰炸;
- 0元购等。
0x06加固建议
交易支付
- 对支付流程的每个环节进行校验,并防止环节被跳过;
- 用户确认购买后,立即在后端验证商品价格(商品单价、商品数量、折扣优惠)、订单价格和到账金额;
- 对一些优惠券、折扣券的使用方式进行测试;
- 查看订单时要通过session校验查看订单者的身份,防止越权;
- 通过session判断用户身份,不要轻易相信用户传过来的ID,如果想要通过ID进行判断,也要与session进行对比;
- 处理订单业务时使用“锁”的方式,保证业务的原子性。
- 对于业务流程有多步的情况,首先判断该步的请求是不是由上一步发起的,如果不是则返回错误或者是页面失效;
- 对敏感信息如身份 ID 等进行加密处理,并要在服务器端进行二次校验
业务接口调用
- 对生成订单环节进行验证码测试;
- 每个订单使用唯一的 token ,提交一次 Token 失效;
- 使用 token 校验,在 URL 中设置 Token 字段,只有 token 验证通过后才能使用接口,并且使用一次后 token 失效;
- 使用接口时,后端对登录状态进行验证,如果登陆成功则返回数据,如果失败则返回错误。
安全步骤
- 每一个应用程序都需要使用事务数据流和访问控制矩阵来描述业务逻辑;
- 在设计业务逻辑时,将它设计为防止业务逻辑滥用的。使用过程验证和控制假设应用程序业务逻辑可能被滥用的一些情况;
- 使用应用程序威胁建模来识别业务逻辑中存在设计缺陷的地方;
- 对OWASP/WASC/SACS-25-CWE中描述的业务逻辑漏洞进行测试;
- 对业务逻辑的滥用建立确定的测试用例;
- 分析风险并应用对策来减轻业务逻辑攻击的可能性和影响;
参考:
http://www.bryh.cn/a/110390.html
https://cloud.tencent.com/developer/article/2032517?from=15425